From 69ca428a4f393122d3cd2bf257c7b7252f6d3df9 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 6 Feb 2025 16:02:00 +0100 Subject: [PATCH 01/33] Create basic section heading --- src/data/lovelace.ts | 1 + src/data/lovelace/config/section.ts | 2 + .../lovelace/badges/hui-section-badges.ts | 260 ++++++++++++++++++ src/panels/lovelace/badges/hui-view-badges.ts | 51 +++- .../lovelace/cards/hui-markdown-card.ts | 6 + src/panels/lovelace/cards/types.ts | 1 + .../create-element/create-section-element.ts | 3 +- .../badge-editor/hui-dialog-create-badge.ts | 2 +- .../badge-editor/hui-dialog-suggest-badge.ts | 6 +- .../card-editor/hui-dialog-create-card.ts | 2 +- src/panels/lovelace/editor/lovelace-path.ts | 5 +- .../lovelace/sections/hui-heading-section.ts | 235 ++++++++++++++++ src/panels/lovelace/sections/hui-section.ts | 75 ++++- 13 files changed, 624 insertions(+), 25 deletions(-) create mode 100644 src/panels/lovelace/badges/hui-section-badges.ts create mode 100644 src/panels/lovelace/sections/hui-heading-section.ts diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts index 98ed39e912fe..cd6b231dd6d8 100644 --- a/src/data/lovelace.ts +++ b/src/data/lovelace.ts @@ -33,6 +33,7 @@ export interface LovelaceSectionElement extends HTMLElement { viewIndex?: number; index?: number; cards?: HuiCard[]; + badges?: HuiBadge[]; isStrategy: boolean; importOnly?: boolean; setConfig(config: LovelaceSectionConfig): void; diff --git a/src/data/lovelace/config/section.ts b/src/data/lovelace/config/section.ts index ad2c66e2aa50..f8a050d7fe80 100644 --- a/src/data/lovelace/config/section.ts +++ b/src/data/lovelace/config/section.ts @@ -1,4 +1,5 @@ import type { Condition } from "../../../panels/lovelace/common/validate-condition"; +import type { LovelaceBadgeConfig } from "./badge"; import type { LovelaceCardConfig } from "./card"; import type { LovelaceStrategyConfig } from "./strategy"; @@ -15,6 +16,7 @@ export interface LovelaceBaseSectionConfig { export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig { type?: string; cards?: LovelaceCardConfig[]; + badges?: (string | Partial)[]; // Badge can be just an entity_id or without type } export interface LovelaceStrategySectionConfig diff --git a/src/panels/lovelace/badges/hui-section-badges.ts b/src/panels/lovelace/badges/hui-section-badges.ts new file mode 100644 index 000000000000..d5ffc466fcd6 --- /dev/null +++ b/src/panels/lovelace/badges/hui-section-badges.ts @@ -0,0 +1,260 @@ +import { mdiPlus } from "@mdi/js"; +import type { PropertyValues } from "lit"; +import { css, html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import { repeat } from "lit/directives/repeat"; +import { fireEvent } from "../../../common/dom/fire_event"; +import "../../../components/ha-ripple"; +import "../../../components/ha-sortable"; +import type { HaSortableOptions } from "../../../components/ha-sortable"; +import "../../../components/ha-svg-icon"; +import type { HomeAssistant } from "../../../types"; +import "../components/hui-badge-edit-mode"; +import { moveBadge } from "../editor/config-util"; +import type { Lovelace } from "../types"; +import type { HuiBadge } from "./hui-badge"; +import type { LovelaceCardPath } from "../editor/lovelace-path"; + +const BADGE_SORTABLE_OPTIONS: HaSortableOptions = { + delay: 100, + delayOnTouchOnly: true, + direction: "horizontal", + invertedSwapThreshold: 0.7, +} as HaSortableOptions; + +@customElement("hui-section-badges") +export class HuiSectionBadges extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public lovelace!: Lovelace; + + @property({ attribute: false }) public badges: HuiBadge[] = []; + + @property({ attribute: false }) public viewIndex!: number; + + @property({ attribute: false }) public sectionIndex!: number; + + @property({ type: Boolean, attribute: "show-add-label" }) + public showAddLabel!: boolean; + + @state() _dragging = false; + + private _badgeConfigKeys = new WeakMap(); + + private _checkAllHidden() { + const allHidden = + !this.lovelace.editMode && this.badges.every((section) => section.hidden); + this.toggleAttribute("hidden", allHidden); + } + + private _badgeVisibilityChanged = () => { + this._checkAllHidden(); + }; + + connectedCallback(): void { + super.connectedCallback(); + this.addEventListener( + "badge-visibility-changed", + this._badgeVisibilityChanged + ); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + this.removeEventListener( + "badge-visibility-changed", + this._badgeVisibilityChanged + ); + } + + willUpdate(changedProperties: PropertyValues): void { + if (changedProperties.has("badges") || changedProperties.has("lovelace")) { + this._checkAllHidden(); + } + } + + private _getBadgeKey(badge: HuiBadge) { + if (!this._badgeConfigKeys.has(badge)) { + this._badgeConfigKeys.set(badge, Math.random().toString()); + } + return this._badgeConfigKeys.get(badge)!; + } + + private _badgeMoved(ev) { + ev.stopPropagation(); + const { oldIndex, newIndex } = ev.detail; + const newConfig = moveBadge( + this.lovelace!.config, + [this.viewIndex!, this.sectionIndex!, oldIndex], + [this.viewIndex!, this.sectionIndex!, newIndex] + ); + this.lovelace!.saveConfig(newConfig); + } + + private _badgeAdded(ev) { + ev.stopPropagation(); + const { index, data } = ev.detail; + const oldPath = data as LovelaceCardPath; + const newPath = [ + this.viewIndex!, + this.sectionIndex!, + index, + ] as LovelaceCardPath; + const newConfig = moveBadge(this.lovelace!.config, oldPath, newPath); + this.lovelace!.saveConfig(newConfig); + } + + private _badgeRemoved(ev) { + ev.stopPropagation(); + // Do nothing, it's handle by the "card-added" event from the new parent. + } + + private _dragStart() { + this._dragging = true; + } + + private _dragEnd() { + this._dragging = false; + } + + private _addBadge() { + fireEvent(this, "ll-create-badge"); + } + + render() { + if (!this.lovelace) return nothing; + + const editMode = this.lovelace.editMode; + + const badges = this.badges; + + return html` + ${badges?.length > 0 || editMode + ? html` + +
+ ${repeat( + badges, + (badge) => this._getBadgeKey(badge), + (badge, idx) => { + const badgePath = [ + this.viewIndex, + this.sectionIndex, + idx, + ] as LovelaceCardPath; + return html` + ${editMode + ? html` + + ${badge} + + ` + : badge} + `; + } + )} + ${editMode + ? html` + + ` + : nothing} +
+
+ ` + : nothing} + `; + } + + static styles = css` + :host([hidden]) { + display: none !important; + } + + .badges { + display: flex; + align-items: flex-start; + flex-wrap: wrap; + justify-content: var(--badges-aligmnent, center); + gap: 8px; + margin: 0; + } + + hui-badge-edit-mode { + display: block; + position: relative; + min-width: 36px; + min-height: 36px; + } + + .add { + position: relative; + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + height: 36px; + padding: 6px 20px 6px 20px; + box-sizing: border-box; + width: auto; + border-radius: 18px; + background-color: transparent; + border-width: 2px; + border-style: dashed; + border-color: var(--primary-color); + --mdc-icon-size: 18px; + cursor: pointer; + font-size: 14px; + order: 1; + color: var(--primary-text-color); + --ha-ripple-color: var(--primary-color); + --ha-ripple-hover-opacity: 0.04; + --ha-ripple-pressed-opacity: 0.12; + } + .add:focus { + border-style: solid; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-section-badges": HuiSectionBadges; + } +} diff --git a/src/panels/lovelace/badges/hui-view-badges.ts b/src/panels/lovelace/badges/hui-view-badges.ts index 8308c5577627..9433b62a3c19 100644 --- a/src/panels/lovelace/badges/hui-view-badges.ts +++ b/src/panels/lovelace/badges/hui-view-badges.ts @@ -12,6 +12,7 @@ import "../../../components/ha-svg-icon"; import type { HomeAssistant } from "../../../types"; import "../components/hui-badge-edit-mode"; import { moveBadge } from "../editor/config-util"; +import type { LovelaceCardPath } from "../editor/lovelace-path"; import type { Lovelace } from "../types"; import type { HuiBadge } from "./hui-badge"; @@ -89,6 +90,20 @@ export class HuiViewBadges extends LitElement { this.lovelace!.saveConfig(newConfig); } + private _badgeAdded(ev) { + ev.stopPropagation(); + const { index, data } = ev.detail; + const oldPath = data as LovelaceCardPath; + const newPath = [this.viewIndex!, index] as LovelaceCardPath; + const newConfig = moveBadge(this.lovelace!.config, oldPath, newPath); + this.lovelace!.saveConfig(newConfig); + } + + private _badgeRemoved(ev) { + ev.stopPropagation(); + // Do nothing, it's handle by the "card-added" event from the new parent. + } + private _dragStart() { this._dragging = true; } @@ -114,6 +129,8 @@ export class HuiViewBadges extends LitElement { this._getBadgeKey(badge), - (badge, idx) => html` - ${editMode - ? html` - - ${badge} - - ` - : badge} - ` + (badge, idx) => { + const badgePath = [this.viewIndex, idx] as LovelaceCardPath; + return html` + ${editMode + ? html` + + ${badge} + + ` + : badge} + `; + } )} ${editMode ? html` diff --git a/src/panels/lovelace/cards/hui-markdown-card.ts b/src/panels/lovelace/cards/hui-markdown-card.ts index b1f2d71ca9f9..d3b13dfdda25 100644 --- a/src/panels/lovelace/cards/hui-markdown-card.ts +++ b/src/panels/lovelace/cards/hui-markdown-card.ts @@ -232,6 +232,12 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard { ha-card { height: 100%; } + ha-card.no-border { + border: none; + box-shadow: none; + background: none; + padding: 0; + } ha-alert { margin-bottom: 8px; } diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 9771067a18de..356320f4be11 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -342,6 +342,7 @@ export interface MarkdownCardConfig extends LovelaceCardConfig { entity_ids?: string | string[]; theme?: string; show_empty?: boolean; + no_border?: boolean; } export interface MediaControlCardConfig extends LovelaceCardConfig { diff --git a/src/panels/lovelace/create-element/create-section-element.ts b/src/panels/lovelace/create-element/create-section-element.ts index bdfaca27fd59..e9f04d704277 100644 --- a/src/panels/lovelace/create-element/create-section-element.ts +++ b/src/panels/lovelace/create-element/create-section-element.ts @@ -2,9 +2,10 @@ import type { LovelaceSectionElement } from "../../../data/lovelace"; import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; import type { HuiErrorCard } from "../cards/hui-error-card"; import "../sections/hui-grid-section"; +import "../sections/hui-heading-section"; import { createLovelaceElement } from "./create-element-base"; -const ALWAYS_LOADED_LAYOUTS = new Set(["grid"]); +const ALWAYS_LOADED_LAYOUTS = new Set(["grid", "heading"]); const LAZY_LOAD_LAYOUTS = {}; diff --git a/src/panels/lovelace/editor/badge-editor/hui-dialog-create-badge.ts b/src/panels/lovelace/editor/badge-editor/hui-dialog-create-badge.ts index 58d37b40ed73..bd7a713f4ca3 100644 --- a/src/panels/lovelace/editor/badge-editor/hui-dialog-create-badge.ts +++ b/src/panels/lovelace/editor/badge-editor/hui-dialog-create-badge.ts @@ -260,7 +260,7 @@ export class HuiCreateDialogBadge showSuggestBadgeDialog(this, { lovelaceConfig: this._params!.lovelaceConfig, saveConfig: this._params!.saveConfig, - path: this._params!.path as [number], + path: this._params!.path, entities: this._selectedEntities, badgeConfig, }); diff --git a/src/panels/lovelace/editor/badge-editor/hui-dialog-suggest-badge.ts b/src/panels/lovelace/editor/badge-editor/hui-dialog-suggest-badge.ts index b25193385257..e6bc94f02f87 100644 --- a/src/panels/lovelace/editor/badge-editor/hui-dialog-suggest-badge.ts +++ b/src/panels/lovelace/editor/badge-editor/hui-dialog-suggest-badge.ts @@ -168,9 +168,13 @@ export class HuiDialogSuggestBadge extends LitElement { config: LovelaceConfig, path: LovelaceContainerPath ): LovelaceConfig { - const { viewIndex } = parseLovelaceContainerPath(path); + const { viewIndex, sectionIndex } = parseLovelaceContainerPath(path); const newBadges = this._badgeConfig!; + + if (sectionIndex != null) { + return addBadges(config, [viewIndex, sectionIndex], newBadges); + } return addBadges(config, [viewIndex], newBadges); } diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts index 403bc7c31ee0..fa7e1360756a 100644 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts @@ -320,7 +320,7 @@ export class HuiCreateDialogCard showSuggestCardDialog(this, { lovelaceConfig: this._params!.lovelaceConfig, saveConfig: this._params!.saveConfig, - path: this._params!.path as [number], + path: this._params!.path, entities: this._selectedEntities, cardConfig, sectionConfig, diff --git a/src/panels/lovelace/editor/lovelace-path.ts b/src/panels/lovelace/editor/lovelace-path.ts index 259a2ce54ef9..c3a84e2f1682 100644 --- a/src/panels/lovelace/editor/lovelace-path.ts +++ b/src/panels/lovelace/editor/lovelace-path.ts @@ -205,8 +205,5 @@ export const findLovelaceItems = ( if (isStrategySection(section)) { throw new Error("Can not find cards in a strategy section"); } - if (key === "cards") { - return section[key as "cards"] as LovelaceItemKeys[T] | undefined; - } - throw new Error(`${key} is not supported in section`); + return section[key] as LovelaceItemKeys[T] | undefined; }; diff --git a/src/panels/lovelace/sections/hui-heading-section.ts b/src/panels/lovelace/sections/hui-heading-section.ts new file mode 100644 index 000000000000..2b98fdd27fa4 --- /dev/null +++ b/src/panels/lovelace/sections/hui-heading-section.ts @@ -0,0 +1,235 @@ +import { mdiPlus } from "@mdi/js"; +import type { CSSResultGroup } from "lit"; +import { LitElement, css, html, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import "../../../components/ha-ripple"; +import type { LovelaceSectionElement } from "../../../data/lovelace"; +import type { LovelaceCardConfig } from "../../../data/lovelace/config/card"; +import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; +import { haStyle } from "../../../resources/styles"; +import type { HomeAssistant } from "../../../types"; +import type { HuiBadge } from "../badges/hui-badge"; +import "../badges/hui-section-badges"; +import type { HuiCard } from "../cards/hui-card"; +import "../components/hui-card-edit-mode"; +import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"; +import type { Lovelace } from "../types"; + +@customElement("hui-heading-section") +export class HeadingSection + extends LitElement + implements LovelaceSectionElement +{ + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public lovelace?: Lovelace; + + @property({ type: Number }) public index?: number; + + @property({ attribute: false, type: Number }) public viewIndex?: number; + + @property({ attribute: false }) public isStrategy = false; + + @property({ attribute: false }) public cards: HuiCard[] = []; + + @property({ attribute: false }) public badges: HuiBadge[] = []; + + @state() _config?: LovelaceSectionConfig; + + @state() _dragging = false; + + public setConfig(config: LovelaceSectionConfig): void { + this._config = config; + } + + render() { + if (!this.cards || !this._config) return nothing; + + const editMode = Boolean(this.lovelace?.editMode && !this.isStrategy); + + const card = this.cards[0]; + + const cardPath = [this.viewIndex!, this.index!, 0] as [ + number, + number, + number, + ]; + return html` +
+ ${card + ? html`
+ ${editMode + ? html` + + ${card} + + ` + : card} +
` + : editMode + ? html` + + ` + : nothing} + ${this.lovelace + ? html` +
+ +
+ ` + : nothing} +
+ `; + } + + private _addCard() { + const cardConfig: LovelaceCardConfig = { + type: "markdown", + no_border: true, + content: + "# Hello {{ user }}\nToday is going to be warm and humid outside. Home Assistant will adjust the temperature throughout the day while you and your family is at home. ✨", + }; + showEditCardDialog(this, { + lovelaceConfig: this.lovelace!.config, + saveConfig: (config) => this.lovelace!.saveConfig(config), + cardConfig, + path: [this.viewIndex!, this.index!], + }); + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + css` + :host { + --base-column-count: 12; + --row-gap: var(--ha-section-grid-row-gap, 8px); + --column-gap: var(--ha-section-grid-column-gap, 8px); + --row-height: var(--ha-section-grid-row-height, 56px); + display: flex; + flex-direction: column; + gap: var(--row-gap); + } + .container { + position: relative; + display: flex; + flex-direction: row; + gap: var(--column-gap); + } + + .content { + position: relative; + flex: 1; + display: flex; + } + + .content > * { + width: 100%; + height: 100%; + } + + .badges { + position: relative; + flex: 1; + display: flex; + } + + hui-section-badges { + --badges-aligmnent: flex-end; + width: 100%; + height: 100%; + } + + @media (max-width: 600px) { + .container { + flex-direction: column-reverse; + hui-section-badges { + --badges-aligmnent: center; + } + } + } + + .container.edit-mode { + padding: 8px; + border-radius: var(--ha-card-border-radius, 12px); + border: 2px dashed var(--divider-color); + min-height: var(--row-height); + } + + .card { + border-radius: var(--ha-card-border-radius, 12px); + position: relative; + grid-row: span var(--row-size, 1); + grid-column: span min(var(--column-size, 1), var(--grid-column-count)); + } + + .container.edit-mode .card { + min-height: calc((var(--row-height) - var(--row-gap)) / 2); + } + + .card:has(> *) { + display: block; + } + + .card:has(> *[hidden]) { + display: none; + } + + .add { + position: relative; + outline: none; + background: none; + cursor: pointer; + border-radius: var(--ha-card-border-radius, 12px); + border: 2px dashed var(--primary-color); + height: var(--row-height); + --ha-ripple-color: var(--primary-color); + --ha-ripple-hover-opacity: 0.04; + --ha-ripple-pressed-opacity: 0.12; + display: flex; + align-items: center; + justify-content: center; + } + + .add:focus { + border-style: solid; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-heading-section": HeadingSection; + } +} diff --git a/src/panels/lovelace/sections/hui-section.ts b/src/panels/lovelace/sections/hui-section.ts index 5155e996ea27..066329a890dc 100644 --- a/src/panels/lovelace/sections/hui-section.ts +++ b/src/panels/lovelace/sections/hui-section.ts @@ -5,6 +5,10 @@ import { fireEvent } from "../../../common/dom/fire_event"; import type { MediaQueriesListener } from "../../../common/dom/media_query"; import "../../../components/ha-svg-icon"; import type { LovelaceSectionElement } from "../../../data/lovelace"; +import { + ensureBadgeConfig, + type LovelaceBadgeConfig, +} from "../../../data/lovelace/config/badge"; import type { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import type { LovelaceSectionConfig, @@ -12,6 +16,7 @@ import type { } from "../../../data/lovelace/config/section"; import { isStrategySection } from "../../../data/lovelace/config/section"; import type { HomeAssistant } from "../../../types"; +import type { HuiBadge } from "../badges/hui-badge"; import "../cards/hui-card"; import type { HuiCard } from "../cards/hui-card"; import { @@ -27,6 +32,9 @@ import { parseLovelaceCardPath } from "../editor/lovelace-path"; import { generateLovelaceSectionStrategy } from "../strategies/get-strategy"; import type { Lovelace } from "../types"; import { DEFAULT_SECTION_LAYOUT } from "./const"; +import { performDeleteBadge } from "../editor/delete-badge"; +import { showEditBadgeDialog } from "../editor/badge-editor/show-edit-badge-dialog"; +import { showCreateBadgeDialog } from "../editor/badge-editor/show-create-badge-dialog"; declare global { interface HASSDomEvents { @@ -53,6 +61,8 @@ export class HuiSection extends ReactiveElement { @state() private _cards: HuiCard[] = []; + @state() private _badges: HuiBadge[] = []; + private _layoutElementType?: string; private _layoutElement?: LovelaceSectionElement; @@ -72,6 +82,19 @@ export class HuiSection extends ReactiveElement { return element; } + public createBadgeElement(badgeConfig: LovelaceBadgeConfig) { + const element = document.createElement("hui-badge"); + element.hass = this.hass; + element.preview = this.preview; + element.config = badgeConfig; + element.addEventListener("badge-updated", (ev: Event) => { + ev.stopPropagation(); + this._badges = [...this._badges]; + }); + element.load(); + return element; + } + protected createRenderRoot() { return this; } @@ -85,7 +108,7 @@ export class HuiSection extends ReactiveElement { - config changed to section with same layout element - config changed to section with different layout element - forwarded properties hass/narrow/lovelace/cards change - - cards change if one is rebuild when it was loaded later + - cards/badges change if one is rebuild when it was loaded later - lovelace changes if edit mode is enabled or config has changed */ @@ -121,6 +144,9 @@ export class HuiSection extends ReactiveElement { this._cards.forEach((element) => { element.hass = this.hass; }); + this._badges.forEach((badge) => { + badge.hass = this.hass; + }); this._layoutElement.hass = this.hass; } if (changedProperties.has("lovelace")) { @@ -131,6 +157,9 @@ export class HuiSection extends ReactiveElement { this._cards.forEach((element) => { element.preview = this.preview; }); + this._badges.forEach((element) => { + element.preview = this.preview; + }); } if (changedProperties.has("importOnly")) { this._layoutElement.importOnly = this.importOnly; @@ -138,6 +167,9 @@ export class HuiSection extends ReactiveElement { if (changedProperties.has("_cards")) { this._layoutElement.cards = this._cards; } + if (changedProperties.has("_badges")) { + this._layoutElement.badges = this._badges; + } if (changedProperties.has("hass") || changedProperties.has("preview")) { this._updateElement(); } @@ -195,7 +227,7 @@ export class HuiSection extends ReactiveElement { addLayoutElement = true; this._createLayoutElement(sectionConfig); } - + this._createBadges(sectionConfig); this._createCards(sectionConfig); this._layoutElement!.isStrategy = isStrategy; this._layoutElement!.hass = this.hass; @@ -203,6 +235,7 @@ export class HuiSection extends ReactiveElement { this._layoutElement!.index = this.index; this._layoutElement!.viewIndex = this.viewIndex; this._layoutElement!.cards = this._cards; + this._layoutElement!.badges = this._badges; if (addLayoutElement) { while (this.lastChild) { @@ -278,6 +311,44 @@ export class HuiSection extends ReactiveElement { if (!this.lovelace) return; performDeleteCard(this.hass, this.lovelace, ev.detail); }); + this._layoutElement.addEventListener("ll-create-badge", async (ev) => { + ev.stopPropagation(); + if (!this.lovelace) return; + showCreateBadgeDialog(this, { + lovelaceConfig: this.lovelace.config, + saveConfig: this.lovelace.saveConfig, + path: [this.viewIndex, this.index], + }); + }); + this._layoutElement.addEventListener("ll-edit-badge", (ev) => { + ev.stopPropagation(); + if (!this.lovelace) return; + const { cardIndex } = parseLovelaceCardPath(ev.detail.path); + showEditBadgeDialog(this, { + lovelaceConfig: this.lovelace.config, + saveConfig: this.lovelace.saveConfig, + path: [this.viewIndex, this.index], + badgeIndex: cardIndex, + }); + }); + this._layoutElement.addEventListener("ll-delete-badge", async (ev) => { + ev.stopPropagation(); + if (!this.lovelace) return; + performDeleteBadge(this.hass, this.lovelace, ev.detail); + }); + } + + private _createBadges(config: LovelaceSectionConfig): void { + if (!config || !config.badges || !Array.isArray(config.badges)) { + this._badges = []; + return; + } + + this._badges = config.badges.map((badge) => { + const badgeConfig = ensureBadgeConfig(badge); + const element = this.createBadgeElement(badgeConfig); + return element; + }); } private _createCards(config: LovelaceSectionConfig): void { From 1508b518d37aacbd4d50f6123449df78e3f349a4 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 6 Feb 2025 17:37:47 +0100 Subject: [PATCH 02/33] Update badge position --- .../lovelace/badges/hui-section-badges.ts | 2 +- .../lovelace/cards/hui-markdown-card.ts | 3 + .../lovelace/sections/hui-heading-section.ts | 57 ++++++++++--------- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/panels/lovelace/badges/hui-section-badges.ts b/src/panels/lovelace/badges/hui-section-badges.ts index d5ffc466fcd6..cba67f0ae48e 100644 --- a/src/panels/lovelace/badges/hui-section-badges.ts +++ b/src/panels/lovelace/badges/hui-section-badges.ts @@ -210,7 +210,7 @@ export class HuiSectionBadges extends LitElement { .badges { display: flex; align-items: flex-start; - flex-wrap: wrap; + flex-wrap: var(--badges-wrap, wrap); justify-content: var(--badges-aligmnent, center); gap: 8px; margin: 0; diff --git a/src/panels/lovelace/cards/hui-markdown-card.ts b/src/panels/lovelace/cards/hui-markdown-card.ts index d3b13dfdda25..a48bbeff2589 100644 --- a/src/panels/lovelace/cards/hui-markdown-card.ts +++ b/src/panels/lovelace/cards/hui-markdown-card.ts @@ -238,6 +238,9 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard { background: none; padding: 0; } + ha-card.no-border ha-markdown { + padding: 0; + } ha-alert { margin-bottom: 8px; } diff --git a/src/panels/lovelace/sections/hui-heading-section.ts b/src/panels/lovelace/sections/hui-heading-section.ts index 2b98fdd27fa4..e2c506db8d12 100644 --- a/src/panels/lovelace/sections/hui-heading-section.ts +++ b/src/panels/lovelace/sections/hui-heading-section.ts @@ -62,21 +62,23 @@ export class HeadingSection })}" > ${card - ? html`
- ${editMode - ? html` - - ${card} - - ` - : card} -
` + ? html` +
+ ${editMode + ? html` + + ${card} + + ` + : card} +
+ ` : editMode ? html` + ` + : nothing} ${repeat( sections, (section) => this._getSectionKey(section), (section, idx) => { - const sectionConfig = this._config?.sections?.[idx]; + const isMoveable = this._isMoveableSection(section.config); + const isFullSize = this._isFullSizeSection(section.config); + const columnSpan = Math.min( - sectionConfig?.column_span || 1, + isFullSize + ? totalSectionCount + : section.config.column_span || 1, maxColumnCount ); - const rowSpan = sectionConfig?.row_span || 1; + const rowSpan = section.config.row_span || 1; return html`
- + ${isMoveable + ? html` + + ` + : nothing} Date: Thu, 13 Feb 2025 18:44:32 +0100 Subject: [PATCH 06/33] Add basic editor for heading section --- src/data/lovelace/config/section.ts | 3 + .../hui-section-settings-editor.ts | 64 ++++++++++++++++--- src/translations/en.json | 5 +- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/data/lovelace/config/section.ts b/src/data/lovelace/config/section.ts index f8a050d7fe80..96eda083e8e3 100644 --- a/src/data/lovelace/config/section.ts +++ b/src/data/lovelace/config/section.ts @@ -11,6 +11,9 @@ export interface LovelaceBaseSectionConfig { * @deprecated Use heading card instead. */ title?: string; + // Only used for section view, it should move to a section view config type when the views will have dedicated editor. + layout?: "start" | "center" | "responsive"; + top_margin?: boolean; } export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig { diff --git a/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts b/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts index 4bf53b4f1e95..1e4f16564c0d 100644 --- a/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts +++ b/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts @@ -7,13 +7,17 @@ import type { SchemaUnion, } from "../../../../components/ha-form/types"; import "../../../../components/ha-form/ha-form"; -import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section"; +import { + isStrategySection, + type LovelaceSectionRawConfig, +} from "../../../../data/lovelace/config/section"; import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; import type { HomeAssistant } from "../../../../types"; -interface SettingsData { - column_span?: number; -} +type SettingsData = Pick< + LovelaceSectionRawConfig, + "layout" | "top_margin" | "column_span" +>; @customElement("hui-section-settings-editor") export class HuiDialogEditSection extends LitElement { @@ -23,6 +27,34 @@ export class HuiDialogEditSection extends LitElement { @property({ attribute: false }) public viewConfig!: LovelaceViewConfig; + private _headingSchema = memoizeOne( + () => + [ + { + name: "layout", + selector: { + select: { + options: [ + { + value: "responsive", + label: "Responsive (Stacked on mobile)", + }, + { + value: "start", + label: "Left aligned (Always stacked)", + }, + { + value: "center", + label: "Centered aligned (Always stacked)", + }, + ], + }, + }, + }, + { name: "top_margin", selector: { boolean: {} } }, + ] as const satisfies HaFormSchema[] + ); + private _schema = memoizeOne( (maxColumns: number) => [ @@ -39,12 +71,28 @@ export class HuiDialogEditSection extends LitElement { ] as const satisfies HaFormSchema[] ); - render() { - const data: SettingsData = { + private _getData(): SettingsData { + if (!isStrategySection(this.config) && this.config.type === "heading") { + return { + layout: this.config.layout || "responsive", + top_margin: this.config.top_margin || false, + }; + } + return { column_span: this.config.column_span || 1, }; + } - const schema = this._schema(this.viewConfig.max_columns || 4); + private _getSchema(): HaFormSchema[] { + if (!isStrategySection(this.config) && this.config.type === "heading") { + return this._headingSchema(); + } + return this._schema(this.viewConfig.max_columns || 4); + } + + render() { + const data = this._getData(); + const schema = this._getSchema(); return html` Date: Fri, 14 Feb 2025 14:22:15 +0100 Subject: [PATCH 07/33] Add badges position option --- src/data/lovelace/config/section.ts | 1 + .../hui-section-settings-editor.ts | 20 ++++++++++++++++++- src/translations/en.json | 1 + 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/data/lovelace/config/section.ts b/src/data/lovelace/config/section.ts index 96eda083e8e3..5ed58b53e7cd 100644 --- a/src/data/lovelace/config/section.ts +++ b/src/data/lovelace/config/section.ts @@ -13,6 +13,7 @@ export interface LovelaceBaseSectionConfig { title?: string; // Only used for section view, it should move to a section view config type when the views will have dedicated editor. layout?: "start" | "center" | "responsive"; + badges_position?: "bottom" | "top"; top_margin?: boolean; } diff --git a/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts b/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts index 1e4f16564c0d..a32b23ac85ea 100644 --- a/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts +++ b/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts @@ -16,7 +16,7 @@ import type { HomeAssistant } from "../../../../types"; type SettingsData = Pick< LovelaceSectionRawConfig, - "layout" | "top_margin" | "column_span" + "layout" | "top_margin" | "badges_position" | "column_span" >; @customElement("hui-section-settings-editor") @@ -51,6 +51,23 @@ export class HuiDialogEditSection extends LitElement { }, }, }, + { + name: "badges_position", + selector: { + select: { + options: [ + { + value: "bottom", + label: "Bottom", + }, + { + value: "top", + label: "Top", + }, + ], + }, + }, + }, { name: "top_margin", selector: { boolean: {} } }, ] as const satisfies HaFormSchema[] ); @@ -76,6 +93,7 @@ export class HuiDialogEditSection extends LitElement { return { layout: this.config.layout || "responsive", top_margin: this.config.top_margin || false, + badges_position: this.config.badges_position || "bottom", }; } return { diff --git a/src/translations/en.json b/src/translations/en.json index aa755d77081a..42e75e0c224a 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6680,6 +6680,7 @@ "column_span": "Width", "column_span_helper": "Larger sections will be made smaller to fit the display. (e.g. on mobile devices)", "layout": "Layout", + "badges_position": "Badges position", "top_margin": "Add extra space at the top for background", "top_margin_helper": "Make the content more touch friendly" }, From f1e922ffc0dea8e5830cd9a3b719bac5309c9eeb Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 14 Feb 2025 16:15:19 +0100 Subject: [PATCH 08/33] Add layout and badge position --- .../lovelace/sections/hui-heading-section.ts | 90 ++++++++++++++----- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/src/panels/lovelace/sections/hui-heading-section.ts b/src/panels/lovelace/sections/hui-heading-section.ts index 67c2fe2bd5e0..e5ed72809113 100644 --- a/src/panels/lovelace/sections/hui-heading-section.ts +++ b/src/panels/lovelace/sections/hui-heading-section.ts @@ -55,10 +55,18 @@ export class HeadingSection number, number, ]; + + const layout = this._config.layout ?? "responsive"; + const badgesPosition = this._config.badges_position ?? "bottom"; + return html`
${card @@ -99,7 +107,7 @@ export class HeadingSection : nothing} ${this.lovelace ? html` -
+
* { @@ -165,23 +181,58 @@ export class HeadingSection hui-section-badges { width: 100%; - --badges-aligmnent: flex-end; - --badges-wrap: wrap; display: flex; flex-direction: column; justify-content: flex-end; + --badges-aligmnent: flex-start; + } + + /* Center layout */ + .container.center { + align-items: center; } - @media (max-width: 800px) { - .container { - flex-direction: column; + .container.center .content { + text-align: center; + } + + .container.center hui-section-badges { + --badges-aligmnent: center; + } + + /* Badges top */ + .container.badges-top { + flex-direction: column-reverse; + } + + .container.badges-top:not(.top-margin) .content { + margin: 0; + } + + @media (min-width: 768px) { + .container.responsive { + flex-direction: row; } - hui-section-badges { - --badges-aligmnent: center; - --badges-wrap: wrap; + .container.responsive hui-section-badges { + --badges-aligmnent: flex-end; + } + .container.responsive.badges-top hui-section-badges { + justify-content: flex-start; + } + /* Align badges to heading if it is set */ + .container.responsive.badges-top .content, + .container.responsive.badges-top.has-heading hui-section-badges { + margin-top: var(--content-margin); } } + .container.responsive .container .card { + border-radius: var(--ha-card-border-radius, 12px); + position: relative; + grid-row: span var(--row-size, 1); + grid-column: span min(var(--column-size, 1), var(--grid-column-count)); + } + .container.edit-mode { padding: 8px; border-radius: var(--ha-card-border-radius, 12px); @@ -189,13 +240,6 @@ export class HeadingSection min-height: var(--row-height); } - .card { - border-radius: var(--ha-card-border-radius, 12px); - position: relative; - grid-row: span var(--row-size, 1); - grid-column: span min(var(--column-size, 1), var(--grid-column-count)); - } - .container.edit-mode .card { min-height: calc((var(--row-height) - var(--row-gap)) / 2); } From 61c6ae2ffa1ed2349866900b4778119e82ee0a1e Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 17 Feb 2025 11:36:54 +0100 Subject: [PATCH 09/33] Improve button --- .../lovelace/sections/hui-heading-section.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/panels/lovelace/sections/hui-heading-section.ts b/src/panels/lovelace/sections/hui-heading-section.ts index e5ed72809113..8ebadad0dfc6 100644 --- a/src/panels/lovelace/sections/hui-heading-section.ts +++ b/src/panels/lovelace/sections/hui-heading-section.ts @@ -102,6 +102,7 @@ export class HeadingSection > + Add title ` : nothing} @@ -226,13 +227,6 @@ export class HeadingSection } } - .container.responsive .container .card { - border-radius: var(--ha-card-border-radius, 12px); - position: relative; - grid-row: span var(--row-size, 1); - grid-column: span min(var(--column-size, 1), var(--grid-column-count)); - } - .container.edit-mode { padding: 8px; border-radius: var(--ha-card-border-radius, 12px); @@ -254,18 +248,26 @@ export class HeadingSection .add { position: relative; + display: flex; + align-items: center; + justify-content: center; + flex-direction: row; outline: none; background: none; cursor: pointer; border-radius: var(--ha-card-border-radius, 12px); border: 2px dashed var(--primary-color); - height: var(--row-height); + height: 36px; + gap: 8px; + padding: 0 10px; --ha-ripple-color: var(--primary-color); --ha-ripple-hover-opacity: 0.04; --ha-ripple-pressed-opacity: 0.12; display: flex; align-items: center; justify-content: center; + font-weight: 400; + line-height: 20px; } .add:focus { From 37494e975caeae6601b5187d74ee83b0ead7585f Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 17 Feb 2025 11:57:41 +0100 Subject: [PATCH 10/33] Use layout --- .../lovelace/sections/hui-heading-section.ts | 159 ++++++++---------- 1 file changed, 73 insertions(+), 86 deletions(-) diff --git a/src/panels/lovelace/sections/hui-heading-section.ts b/src/panels/lovelace/sections/hui-heading-section.ts index 8ebadad0dfc6..d24813395c7b 100644 --- a/src/panels/lovelace/sections/hui-heading-section.ts +++ b/src/panels/lovelace/sections/hui-heading-section.ts @@ -63,62 +63,66 @@ export class HeadingSection
- ${card - ? html` -
- ${editMode - ? html` - - ${card} - - ` - : card} -
- ` - : editMode +
+ ${card ? html` - +
+ ${editMode + ? html` + + ${card} + + ` + : card} +
+ ` + : editMode + ? html` + + ` + : nothing} + ${this.lovelace + ? html` +
+ +
` : nothing} - ${this.lovelace - ? html` -
- -
- ` - : nothing} +
`; } @@ -144,21 +148,17 @@ export class HeadingSection css` .container { position: relative; + } + .layout { + position: relative; display: flex; flex-direction: column; gap: 24px; + margin-top: 24px; } - .container { - --content-margin: 24px; - } - - .container.top-margin { - --content-margin: 80px; - } - - .container .content { - margin-top: var(--content-margin); + .layout.top-margin { + margin-top: 80px; } .content { @@ -189,53 +189,40 @@ export class HeadingSection } /* Center layout */ - .container.center { + .layout { + align-items: flex-start; + } + + .layout.center { align-items: center; } - .container.center .content { + .layout.center .content { text-align: center; } - .container.center hui-section-badges { + .layout.center hui-section-badges { --badges-aligmnent: center; } - /* Badges top */ - .container.badges-top { - flex-direction: column-reverse; - } - - .container.badges-top:not(.top-margin) .content { - margin: 0; - } - @media (min-width: 768px) { - .container.responsive { + .layout.responsive { flex-direction: row; + align-items: flex-end; } - .container.responsive hui-section-badges { + .layout.responsive hui-section-badges { --badges-aligmnent: flex-end; } - .container.responsive.badges-top hui-section-badges { - justify-content: flex-start; - } - /* Align badges to heading if it is set */ - .container.responsive.badges-top .content, - .container.responsive.badges-top.has-heading hui-section-badges { - margin-top: var(--content-margin); - } } .container.edit-mode { padding: 8px; border-radius: var(--ha-card-border-radius, 12px); border: 2px dashed var(--divider-color); - min-height: var(--row-height); } - .container.edit-mode .card { - min-height: calc((var(--row-height) - var(--row-gap)) / 2); + .container.edit-mode .content { + min-height: 36px; } .card:has(> *) { From 9f203089e07da14b971a05d40bd3c6cf66c1a7b1 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 17 Feb 2025 12:19:28 +0100 Subject: [PATCH 11/33] Simplify edit mode --- .../lovelace/sections/hui-heading-section.ts | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/panels/lovelace/sections/hui-heading-section.ts b/src/panels/lovelace/sections/hui-heading-section.ts index d24813395c7b..bc687876311c 100644 --- a/src/panels/lovelace/sections/hui-heading-section.ts +++ b/src/panels/lovelace/sections/hui-heading-section.ts @@ -59,17 +59,16 @@ export class HeadingSection const layout = this._config.layout ?? "responsive"; const badgesPosition = this._config.badges_position ?? "bottom"; + const hasHeading = card !== undefined; + return html` -
+
${card @@ -109,7 +108,7 @@ export class HeadingSection ` : nothing} - ${this.lovelace + ${this.lovelace && (editMode || this.badges.length > 0) ? html`
Date: Mon, 17 Feb 2025 12:53:45 +0100 Subject: [PATCH 12/33] Fix CSS --- .../lovelace/sections/hui-heading-section.ts | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/panels/lovelace/sections/hui-heading-section.ts b/src/panels/lovelace/sections/hui-heading-section.ts index bc687876311c..0959bebd27d1 100644 --- a/src/panels/lovelace/sections/hui-heading-section.ts +++ b/src/panels/lovelace/sections/hui-heading-section.ts @@ -73,7 +73,7 @@ export class HeadingSection > ${card ? html` -
+
${editMode ? html` * { + .heading > * { width: 100%; height: 100%; } @@ -190,7 +191,7 @@ export class HeadingSection --badges-aligmnent: flex-start; } - /* Center layout */ + /* Layout */ .layout { align-items: flex-start; } @@ -199,7 +200,7 @@ export class HeadingSection align-items: center; } - .layout.center .content { + .layout.center .heading { text-align: center; } @@ -227,14 +228,6 @@ export class HeadingSection min-height: 36px; } - .card:has(> *) { - display: block; - } - - .card:has(> *[hidden]) { - display: none; - } - .add { position: relative; display: flex; From ca51ed8c3bdf6ecbf56334cd210218e76e4bf238 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 17 Feb 2025 13:03:27 +0100 Subject: [PATCH 13/33] Fix add heading button --- .../lovelace/sections/hui-heading-section.ts | 60 ++++++++----------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/src/panels/lovelace/sections/hui-heading-section.ts b/src/panels/lovelace/sections/hui-heading-section.ts index 0959bebd27d1..905502ff4b15 100644 --- a/src/panels/lovelace/sections/hui-heading-section.ts +++ b/src/panels/lovelace/sections/hui-heading-section.ts @@ -71,43 +71,34 @@ export class HeadingSection "has-heading": hasHeading, })}" > - ${card + ${card || editMode ? html`
${editMode - ? html` - - ${card} - - ` + ? card + ? html` + + ${card} + + ` + : html` + + ` : card}
` - : editMode - ? html` - - ` - : nothing} + : nothing} ${this.lovelace && (editMode || this.badges.length > 0) ? html`
@@ -202,6 +193,9 @@ export class HeadingSection .layout.center .heading { text-align: center; + display: flex; + flex-direction: column; + align-items: center; } .layout.center hui-section-badges { @@ -240,9 +234,6 @@ export class HeadingSection border-radius: var(--ha-card-border-radius, 12px); border: 2px dashed var(--primary-color); min-height: 36px; - flex: 1; - width: 100%; - max-width: 700px; gap: 8px; padding: 0 10px; --ha-ripple-color: var(--primary-color); @@ -253,6 +244,7 @@ export class HeadingSection justify-content: center; font-weight: 400; line-height: 20px; + width: auto; } .add:focus { From eb6ecf6cd7a20a35732cc5e0217117870da54a61 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 17 Feb 2025 13:05:37 +0100 Subject: [PATCH 14/33] Improve badges --- src/panels/lovelace/sections/hui-heading-section.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/lovelace/sections/hui-heading-section.ts b/src/panels/lovelace/sections/hui-heading-section.ts index 905502ff4b15..79aec9429e30 100644 --- a/src/panels/lovelace/sections/hui-heading-section.ts +++ b/src/panels/lovelace/sections/hui-heading-section.ts @@ -108,6 +108,7 @@ export class HeadingSection .lovelace=${this.lovelace!} .sectionIndex=${this.index!} .viewIndex=${this.viewIndex!} + .showAddLabel=${this.badges.length === 0} >
` From 9185b083311e80571f085db671a0b05e4888e6ce Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 17 Feb 2025 15:23:43 +0100 Subject: [PATCH 15/33] Rename to extra space --- src/data/lovelace/config/section.ts | 2 +- .../section-editor/hui-section-settings-editor.ts | 6 +++--- .../lovelace/sections/hui-heading-section.ts | 14 +++++++++----- src/translations/en.json | 4 ++-- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/data/lovelace/config/section.ts b/src/data/lovelace/config/section.ts index 5ed58b53e7cd..75a24ea9cf87 100644 --- a/src/data/lovelace/config/section.ts +++ b/src/data/lovelace/config/section.ts @@ -14,7 +14,7 @@ export interface LovelaceBaseSectionConfig { // Only used for section view, it should move to a section view config type when the views will have dedicated editor. layout?: "start" | "center" | "responsive"; badges_position?: "bottom" | "top"; - top_margin?: boolean; + extra_space?: boolean; } export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig { diff --git a/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts b/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts index a32b23ac85ea..29979151e678 100644 --- a/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts +++ b/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts @@ -16,7 +16,7 @@ import type { HomeAssistant } from "../../../../types"; type SettingsData = Pick< LovelaceSectionRawConfig, - "layout" | "top_margin" | "badges_position" | "column_span" + "layout" | "extra_space" | "badges_position" | "column_span" >; @customElement("hui-section-settings-editor") @@ -68,7 +68,7 @@ export class HuiDialogEditSection extends LitElement { }, }, }, - { name: "top_margin", selector: { boolean: {} } }, + { name: "extra_space", selector: { boolean: {} } }, ] as const satisfies HaFormSchema[] ); @@ -92,7 +92,7 @@ export class HuiDialogEditSection extends LitElement { if (!isStrategySection(this.config) && this.config.type === "heading") { return { layout: this.config.layout || "responsive", - top_margin: this.config.top_margin || false, + extra_space: this.config.extra_space || false, badges_position: this.config.badges_position || "bottom", }; } diff --git a/src/panels/lovelace/sections/hui-heading-section.ts b/src/panels/lovelace/sections/hui-heading-section.ts index 79aec9429e30..57e37954038a 100644 --- a/src/panels/lovelace/sections/hui-heading-section.ts +++ b/src/panels/lovelace/sections/hui-heading-section.ts @@ -60,15 +60,17 @@ export class HeadingSection const badgesPosition = this._config.badges_position ?? "bottom"; const hasHeading = card !== undefined; + const hasBadges = this.badges.length > 0; return html`
${card || editMode @@ -146,14 +148,16 @@ export class HeadingSection display: flex; flex-direction: column; gap: 24px 8px; + --spacing: 24px; } - .layout.has-heading { - margin-top: 24px; + .layout.has-heading, + .layout.extra-space { + margin-top: var(--spacing); } - .layout.top-margin { - margin-top: 80px; + .layout.extra-space { + --spacing: 80px; } .heading { diff --git a/src/translations/en.json b/src/translations/en.json index 42e75e0c224a..cf51c599632d 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6681,8 +6681,8 @@ "column_span_helper": "Larger sections will be made smaller to fit the display. (e.g. on mobile devices)", "layout": "Layout", "badges_position": "Badges position", - "top_margin": "Add extra space at the top for background", - "top_margin_helper": "Make the content more touch friendly" + "extra_space": "Add extra space for background", + "extra_space_helper": "Make the content more touch friendly" }, "visibility": { "explanation": "The section will be shown when ALL conditions below are fulfilled. If no conditions are set, the section will always be shown." From 236fdc3293c2ac3ba70374144aecbd24a556bd8d Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 17 Feb 2025 15:55:23 +0100 Subject: [PATCH 16/33] Fix add top badge position --- .../lovelace/sections/hui-heading-section.ts | 20 +++++++++++++++++++ src/translations/en.json | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/panels/lovelace/sections/hui-heading-section.ts b/src/panels/lovelace/sections/hui-heading-section.ts index 57e37954038a..c6d2e43a391a 100644 --- a/src/panels/lovelace/sections/hui-heading-section.ts +++ b/src/panels/lovelace/sections/hui-heading-section.ts @@ -217,6 +217,26 @@ export class HeadingSection } } + .layout.badges-top { + flex-direction: column-reverse; + } + + .layout.badges-top.has-badges { + margin-top: 0; + } + + .layout.badges-top.extra-space { + margin-top: var(--spacing); + } + + @media (min-width: 768px) { + .layout.responsive.badges-top.has-heading { + flex-direction: row; + align-items: flex-start; + margin-top: var(--spacing); + } + } + .container.edit-mode { padding: 8px; border-radius: var(--ha-card-border-radius, 12px); diff --git a/src/translations/en.json b/src/translations/en.json index cf51c599632d..0e5f2ac8315a 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6681,7 +6681,7 @@ "column_span_helper": "Larger sections will be made smaller to fit the display. (e.g. on mobile devices)", "layout": "Layout", "badges_position": "Badges position", - "extra_space": "Add extra space for background", + "extra_space": "Add extra space at the top for background", "extra_space_helper": "Make the content more touch friendly" }, "visibility": { From 118460010b33badb5e4b2c05355e6bafb0401f05 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 17 Feb 2025 16:49:24 +0100 Subject: [PATCH 17/33] Add migration --- .../lovelace/common/check-lovelace-config.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/panels/lovelace/common/check-lovelace-config.ts b/src/panels/lovelace/common/check-lovelace-config.ts index 5a79916bda2c..adcd7106ebb9 100644 --- a/src/panels/lovelace/common/check-lovelace-config.ts +++ b/src/panels/lovelace/common/check-lovelace-config.ts @@ -1,9 +1,14 @@ -import type { LovelaceSectionRawConfig } from "../../../data/lovelace/config/section"; +import type { + LovelaceSectionConfig, + LovelaceSectionRawConfig, +} from "../../../data/lovelace/config/section"; import { isStrategySection } from "../../../data/lovelace/config/section"; import type { LovelaceRawConfig } from "../../../data/lovelace/config/types"; import { isStrategyDashboard } from "../../../data/lovelace/config/types"; import type { LovelaceViewRawConfig } from "../../../data/lovelace/config/view"; import { isStrategyView } from "../../../data/lovelace/config/view"; +import { SECTIONS_VIEW_LAYOUT } from "../views/const"; +import { getViewType } from "../views/get-view-type"; export const checkLovelaceConfig = ( config: LovelaceRawConfig @@ -40,6 +45,19 @@ export const checkViewConfig = ( updatedView.sections = updatedView.sections!.map(checkSectionConfig); } + const type = getViewType(updatedView); + + if (type === SECTIONS_VIEW_LAYOUT && updatedView.badges) { + const headingSection: LovelaceSectionConfig = { + type: "heading", + badges: updatedView.badges || [], + layout: "center", + }; + const existingSections = updatedView.sections || []; + updatedView.sections = [headingSection, ...existingSections]; + delete updatedView.badges; + } + return updatedView; }; From bfd86e6029ea4c3d1f34a687b7b52055279c105e Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 17 Feb 2025 16:56:57 +0100 Subject: [PATCH 18/33] Fix delete section confirmation --- src/panels/lovelace/views/hui-sections-view.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/panels/lovelace/views/hui-sections-view.ts b/src/panels/lovelace/views/hui-sections-view.ts index d784aebb1331..560d579f8777 100644 --- a/src/panels/lovelace/views/hui-sections-view.ts +++ b/src/panels/lovelace/views/hui-sections-view.ts @@ -459,8 +459,9 @@ export class SectionsView extends LitElement implements LovelaceViewElement { const section = findLovelaceContainer(this.lovelace!.config, path); const cardCount = "cards" in section && section.cards?.length; + const badgeCount = "badges" in section && section.badges?.length; - if (cardCount) { + if (cardCount || badgeCount) { const confirm = await showConfirmationDialog(this, { title: this.hass.localize( "ui.panel.lovelace.editor.delete_section.title" From c9effc5fae6150207058d28291420b4e080e4bff Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 17 Feb 2025 16:59:08 +0100 Subject: [PATCH 19/33] Add translations --- src/panels/lovelace/sections/hui-heading-section.ts | 4 +++- src/translations/en.json | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/panels/lovelace/sections/hui-heading-section.ts b/src/panels/lovelace/sections/hui-heading-section.ts index c6d2e43a391a..93cd9cfc809d 100644 --- a/src/panels/lovelace/sections/hui-heading-section.ts +++ b/src/panels/lovelace/sections/hui-heading-section.ts @@ -94,7 +94,9 @@ export class HeadingSection ` : card} diff --git a/src/translations/en.json b/src/translations/en.json index 0e5f2ac8315a..87308c5beb5d 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6657,6 +6657,7 @@ "section": { "add_badge": "Add badge", "add_card": "[%key:ui::panel::lovelace::editor::edit_card::add%]", + "add_title": "Add title", "drop_card_create_section": "Drop card here to create a new section", "create_section": "Create section", "default_section_title": "New section", From cfe55bc9a738da064c8e298ec665cec1435e3773 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 17 Feb 2025 17:01:42 +0100 Subject: [PATCH 20/33] Update comment --- src/data/lovelace/config/section.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/lovelace/config/section.ts b/src/data/lovelace/config/section.ts index 75a24ea9cf87..beb21d1d9a66 100644 --- a/src/data/lovelace/config/section.ts +++ b/src/data/lovelace/config/section.ts @@ -11,7 +11,7 @@ export interface LovelaceBaseSectionConfig { * @deprecated Use heading card instead. */ title?: string; - // Only used for section view, it should move to a section view config type when the views will have dedicated editor. + // Only used for heading section, it should move to a section view config type when the sections will have dedicated editor. layout?: "start" | "center" | "responsive"; badges_position?: "bottom" | "top"; extra_space?: boolean; From 71b6027b41b70748f04558547dbe2ebdc9a8efa0 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 19 Feb 2025 17:55:45 +0100 Subject: [PATCH 21/33] Add header config to view --- src/data/lovelace/config/view.ts | 8 + src/panels/lovelace/badges/hui-view-badges.ts | 4 +- src/panels/lovelace/badges/hui-view-header.ts | 310 ++++++++++++++++++ .../lovelace/common/check-lovelace-config.ts | 20 +- .../lovelace/views/hui-sections-view.ts | 26 ++ 5 files changed, 347 insertions(+), 21 deletions(-) create mode 100644 src/panels/lovelace/badges/hui-view-header.ts diff --git a/src/data/lovelace/config/view.ts b/src/data/lovelace/config/view.ts index 915c5545cbf6..ffd8adaef49d 100644 --- a/src/data/lovelace/config/view.ts +++ b/src/data/lovelace/config/view.ts @@ -25,6 +25,13 @@ export interface LovelaceViewBackgroundConfig { attachment?: "scroll" | "fixed"; } +export interface LovelaceViewHeaderConfig { + card?: LovelaceCardConfig; + layout?: "start" | "center" | "responsive"; + badges_position?: "bottom" | "top"; + extra_space?: boolean; +} + export interface LovelaceBaseViewConfig { index?: number; title?: string; @@ -46,6 +53,7 @@ export interface LovelaceViewConfig extends LovelaceBaseViewConfig { badges?: (string | Partial)[]; // Badge can be just an entity_id or without type cards?: LovelaceCardConfig[]; sections?: LovelaceSectionRawConfig[]; + header?: LovelaceViewHeaderConfig; } export interface LovelaceStrategyViewConfig extends LovelaceBaseViewConfig { diff --git a/src/panels/lovelace/badges/hui-view-badges.ts b/src/panels/lovelace/badges/hui-view-badges.ts index 9433b62a3c19..c4fe52e8588f 100644 --- a/src/panels/lovelace/badges/hui-view-badges.ts +++ b/src/panels/lovelace/badges/hui-view-badges.ts @@ -200,8 +200,8 @@ export class HuiViewBadges extends LitElement { .badges { display: flex; align-items: flex-start; - flex-wrap: wrap; - justify-content: center; + flex-wrap: var(--badges-wrap, wrap); + justify-content: var(--badges-aligmnent, center); gap: 8px; margin: 0; } diff --git a/src/panels/lovelace/badges/hui-view-header.ts b/src/panels/lovelace/badges/hui-view-header.ts new file mode 100644 index 000000000000..52d7e88673c8 --- /dev/null +++ b/src/panels/lovelace/badges/hui-view-header.ts @@ -0,0 +1,310 @@ +import { mdiPlus } from "@mdi/js"; +import type { PropertyValues } from "lit"; +import { css, html, LitElement, nothing } from "lit"; +import { customElement, property } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import "../../../components/ha-ripple"; +import "../../../components/ha-sortable"; +import "../../../components/ha-svg-icon"; +import type { LovelaceCardConfig } from "../../../data/lovelace/config/card"; +import type { LovelaceViewHeaderConfig } from "../../../data/lovelace/config/view"; +import type { HomeAssistant } from "../../../types"; +import type { HuiCard } from "../cards/hui-card"; +import "../components/hui-badge-edit-mode"; +import type { Lovelace } from "../types"; +import type { HuiBadge } from "./hui-badge"; +import "./hui-view-badges"; + +@customElement("hui-view-header") +export class HuiViewHeader extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public lovelace!: Lovelace; + + @property({ attribute: false }) public card?: HuiCard; + + @property({ attribute: false }) public badges: HuiBadge[] = []; + + @property({ attribute: false }) public config?: LovelaceViewHeaderConfig; + + @property({ attribute: false }) public viewIndex!: number; + + private _checkAllHidden() { + const allHidden = + !this.lovelace.editMode && this.badges.every((badges) => badges.hidden); + this.toggleAttribute("hidden", allHidden); + } + + private _badgeVisibilityChanged = () => { + this._checkAllHidden(); + }; + + connectedCallback(): void { + super.connectedCallback(); + this.addEventListener( + "badge-visibility-changed", + this._badgeVisibilityChanged + ); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + this.removeEventListener( + "badge-visibility-changed", + this._badgeVisibilityChanged + ); + } + + willUpdate(changedProperties: PropertyValues): void { + if (changedProperties.has("badges") || changedProperties.has("lovelace")) { + this._checkAllHidden(); + } + + if (changedProperties.has("config")) { + if (this.config?.card) { + this.card = this._createCardElement(this.config.card); + } else { + this.card = undefined; + } + return; + } + + if (this.card) { + if (changedProperties.has("hass")) { + this.card.hass = this.hass; + } + if (changedProperties.has("lovelace")) { + this.card.preview = this.lovelace.editMode; + } + } + } + + private _createCardElement(cardConfig: LovelaceCardConfig) { + const element = document.createElement("hui-card"); + element.hass = this.hass; + element.preview = this.lovelace.editMode; + element.config = cardConfig; + element.load(); + return element; + } + + private _addCard() { + const cardConfig: LovelaceCardConfig = { + type: "markdown", + no_border: true, + content: + "# Hello {{ user }}\nToday is going to be warm and humid outside. Home Assistant will adjust the temperature throughout the day while you and your family is at home. ✨", + }; + // eslint-disable-next-line + console.log("Add card", cardConfig); + } + + render() { + if (!this.lovelace) return nothing; + + const editMode = Boolean(this.lovelace?.editMode); + + const card = this.card; + + const layout = this.config?.layout ?? "responsive"; + const badgesPosition = this.config?.badges_position ?? "bottom"; + + const hasHeading = card !== undefined; + const hasBadges = this.badges.length > 0; + + return html` +
+
+ ${card || editMode + ? html` +
+ ${editMode + ? card + ? html`${card}` + : html` + + ` + : card} +
+ ` + : nothing} + ${this.lovelace && (editMode || this.badges.length > 0) + ? html` +
+ +
+ ` + : nothing} +
+
+ `; + } + + static styles = css` + :host([hidden]) { + display: none !important; + } + .container { + position: relative; + } + + .layout { + position: relative; + display: flex; + flex-direction: column; + gap: 24px 8px; + --spacing: 24px; + } + + .layout.has-heading, + .layout.extra-space { + margin-top: var(--spacing); + } + + .layout.extra-space { + --spacing: 80px; + } + + .heading { + position: relative; + flex: 1; + width: 100%; + max-width: 700px; + display: flex; + } + + .heading > * { + width: 100%; + height: 100%; + } + + .badges { + position: relative; + flex: 1; + display: flex; + } + + hui-view-badges { + width: 100%; + display: flex; + flex-direction: column; + justify-content: flex-end; + --badges-aligmnent: flex-start; + } + + /* Layout */ + .layout { + align-items: flex-start; + } + + .layout.center { + align-items: center; + } + + .layout.center .heading { + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + } + + .layout.center hui-view-badges { + --badges-aligmnent: center; + } + + @media (min-width: 768px) { + .layout.responsive.has-heading { + flex-direction: row; + align-items: flex-end; + } + .layout.responsive.has-heading hui-view-badges { + --badges-aligmnent: flex-end; + } + } + + .layout.badges-top { + flex-direction: column-reverse; + } + + .layout.badges-top.has-badges { + margin-top: 0; + } + + .layout.badges-top.extra-space { + margin-top: var(--spacing); + } + + @media (min-width: 768px) { + .layout.responsive.badges-top.has-heading { + flex-direction: row; + align-items: flex-start; + margin-top: var(--spacing); + } + } + + .container.edit-mode { + padding: 8px; + border-radius: var(--ha-card-border-radius, 12px); + border: 2px dashed var(--divider-color); + } + + .container.edit-mode .content { + min-height: 36px; + } + + .add { + position: relative; + display: flex; + align-items: center; + justify-content: center; + flex-direction: row; + outline: none; + background: none; + cursor: pointer; + border-radius: var(--ha-card-border-radius, 12px); + border: 2px dashed var(--primary-color); + min-height: 36px; + gap: 8px; + padding: 0 10px; + --ha-ripple-color: var(--primary-color); + --ha-ripple-hover-opacity: 0.04; + --ha-ripple-pressed-opacity: 0.12; + display: flex; + align-items: center; + justify-content: center; + font-weight: 400; + line-height: 20px; + width: auto; + } + + .add:focus { + border-style: solid; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-view-header": HuiViewHeader; + } +} diff --git a/src/panels/lovelace/common/check-lovelace-config.ts b/src/panels/lovelace/common/check-lovelace-config.ts index adcd7106ebb9..5a79916bda2c 100644 --- a/src/panels/lovelace/common/check-lovelace-config.ts +++ b/src/panels/lovelace/common/check-lovelace-config.ts @@ -1,14 +1,9 @@ -import type { - LovelaceSectionConfig, - LovelaceSectionRawConfig, -} from "../../../data/lovelace/config/section"; +import type { LovelaceSectionRawConfig } from "../../../data/lovelace/config/section"; import { isStrategySection } from "../../../data/lovelace/config/section"; import type { LovelaceRawConfig } from "../../../data/lovelace/config/types"; import { isStrategyDashboard } from "../../../data/lovelace/config/types"; import type { LovelaceViewRawConfig } from "../../../data/lovelace/config/view"; import { isStrategyView } from "../../../data/lovelace/config/view"; -import { SECTIONS_VIEW_LAYOUT } from "../views/const"; -import { getViewType } from "../views/get-view-type"; export const checkLovelaceConfig = ( config: LovelaceRawConfig @@ -45,19 +40,6 @@ export const checkViewConfig = ( updatedView.sections = updatedView.sections!.map(checkSectionConfig); } - const type = getViewType(updatedView); - - if (type === SECTIONS_VIEW_LAYOUT && updatedView.badges) { - const headingSection: LovelaceSectionConfig = { - type: "heading", - badges: updatedView.badges || [], - layout: "center", - }; - const existingSections = updatedView.sections || []; - updatedView.sections = [headingSection, ...existingSections]; - delete updatedView.badges; - } - return updatedView; }; diff --git a/src/panels/lovelace/views/hui-sections-view.ts b/src/panels/lovelace/views/hui-sections-view.ts index 560d579f8777..48c2b6a849f0 100644 --- a/src/panels/lovelace/views/hui-sections-view.ts +++ b/src/panels/lovelace/views/hui-sections-view.ts @@ -28,6 +28,8 @@ import { import type { LovelaceViewConfig } from "../../../data/lovelace/config/view"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import type { HomeAssistant } from "../../../types"; +import { HuiBadge } from "../badges/hui-badge"; +import "../badges/hui-view-header"; import type { HuiCard } from "../cards/hui-card"; import type { MarkdownCardConfig } from "../cards/types"; import "../components/hui-badge-edit-mode"; @@ -67,6 +69,8 @@ export class SectionsView extends LitElement implements LovelaceViewElement { @property({ attribute: false }) public cards: HuiCard[] = []; + @property({ attribute: false }) public badges: HuiBadge[] = []; + @state() private _config?: LovelaceViewConfig; @state() private _sectionColumnCount = 0; @@ -188,6 +192,16 @@ export class SectionsView extends LitElement implements LovelaceViewElement { const maxColumnCount = this._columnsController.value ?? 1; return html` + Date: Wed, 19 Feb 2025 18:18:23 +0100 Subject: [PATCH 22/33] Add edit card overlay --- src/panels/lovelace/badges/hui-view-header.ts | 62 +++++++++++++++++-- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/src/panels/lovelace/badges/hui-view-header.ts b/src/panels/lovelace/badges/hui-view-header.ts index 52d7e88673c8..5ffb6028051b 100644 --- a/src/panels/lovelace/badges/hui-view-header.ts +++ b/src/panels/lovelace/badges/hui-view-header.ts @@ -7,10 +7,14 @@ import "../../../components/ha-ripple"; import "../../../components/ha-sortable"; import "../../../components/ha-svg-icon"; import type { LovelaceCardConfig } from "../../../data/lovelace/config/card"; -import type { LovelaceViewHeaderConfig } from "../../../data/lovelace/config/view"; +import type { + LovelaceViewConfig, + LovelaceViewHeaderConfig, +} from "../../../data/lovelace/config/view"; import type { HomeAssistant } from "../../../types"; import type { HuiCard } from "../cards/hui-card"; import "../components/hui-badge-edit-mode"; +import { replaceView } from "../editor/config-util"; import type { Lovelace } from "../types"; import type { HuiBadge } from "./hui-badge"; import "./hui-view-badges"; @@ -95,8 +99,40 @@ export class HuiViewHeader extends LitElement { content: "# Hello {{ user }}\nToday is going to be warm and humid outside. Home Assistant will adjust the temperature throughout the day while you and your family is at home. ✨", }; - // eslint-disable-next-line - console.log("Add card", cardConfig); + + // Todo: open edit card dialog + const newConfig = { ...this.config }; + newConfig.card = cardConfig; + this._saveHeaderConfig(newConfig); + } + + private _deleteCard(ev) { + ev.stopPropagation(); + const newConfig = { ...this.config }; + delete newConfig.card; + this._saveHeaderConfig(newConfig); + } + + private _editCard(ev) { + ev.stopPropagation(); + // Todo: open edit card dialog + } + + private _saveHeaderConfig(headerConfig: LovelaceViewHeaderConfig) { + const viewConfig = this.lovelace.config.views[ + this.viewIndex + ] as LovelaceViewConfig; + + const config = { ...viewConfig }; + config.header = headerConfig; + + const updatedConfig = replaceView( + this.hass, + this.lovelace.config, + this.viewIndex, + config + ); + this.lovelace.saveConfig(updatedConfig); } render() { @@ -106,7 +142,7 @@ export class HuiViewHeader extends LitElement { const card = this.card; - const layout = this.config?.layout ?? "responsive"; + const layout = this.config?.layout ?? "center"; const badgesPosition = this.config?.badges_position ?? "bottom"; const hasHeading = card !== undefined; @@ -128,7 +164,19 @@ export class HuiViewHeader extends LitElement {
${editMode ? card - ? html`${card}` + ? html` + + ${card} + + ` : html` - ` - : card} -
- ` - : nothing} - ${this.lovelace && (editMode || this.badges.length > 0) - ? html` -
- -
- ` - : nothing} -
-
- `; - } - - private _addCard() { - const cardConfig: LovelaceCardConfig = { - type: "markdown", - no_border: true, - content: - "# Hello {{ user }}\nToday is going to be warm and humid outside. Home Assistant will adjust the temperature throughout the day while you and your family is at home. ✨", - }; - showEditCardDialog(this, { - lovelaceConfig: this.lovelace!.config, - saveConfig: (config) => this.lovelace!.saveConfig(config), - cardConfig, - path: [this.viewIndex!, this.index!], - }); - } - - static get styles(): CSSResultGroup { - return [ - haStyle, - css` - .container { - position: relative; - } - - .layout { - position: relative; - display: flex; - flex-direction: column; - gap: 24px 8px; - --spacing: 24px; - } - - .layout.has-heading, - .layout.extra-space { - margin-top: var(--spacing); - } - - .layout.extra-space { - --spacing: 80px; - } - - .heading { - position: relative; - flex: 1; - width: 100%; - max-width: 700px; - display: flex; - } - - .heading > * { - width: 100%; - height: 100%; - } - - .badges { - position: relative; - flex: 1; - display: flex; - } - - hui-section-badges { - width: 100%; - display: flex; - flex-direction: column; - justify-content: flex-end; - --badges-aligmnent: flex-start; - } - - /* Layout */ - .layout { - align-items: flex-start; - } - - .layout.center { - align-items: center; - } - - .layout.center .heading { - text-align: center; - display: flex; - flex-direction: column; - align-items: center; - } - - .layout.center hui-section-badges { - --badges-aligmnent: center; - } - - @media (min-width: 768px) { - .layout.responsive.has-heading { - flex-direction: row; - align-items: flex-end; - } - .layout.responsive.has-heading hui-section-badges { - --badges-aligmnent: flex-end; - } - } - - .layout.badges-top { - flex-direction: column-reverse; - } - - .layout.badges-top.has-badges { - margin-top: 0; - } - - .layout.badges-top.extra-space { - margin-top: var(--spacing); - } - - @media (min-width: 768px) { - .layout.responsive.badges-top.has-heading { - flex-direction: row; - align-items: flex-start; - margin-top: var(--spacing); - } - } - - .container.edit-mode { - padding: 8px; - border-radius: var(--ha-card-border-radius, 12px); - border: 2px dashed var(--divider-color); - } - - .container.edit-mode .content { - min-height: 36px; - } - - .add { - position: relative; - display: flex; - align-items: center; - justify-content: center; - flex-direction: row; - outline: none; - background: none; - cursor: pointer; - border-radius: var(--ha-card-border-radius, 12px); - border: 2px dashed var(--primary-color); - min-height: 36px; - gap: 8px; - padding: 0 10px; - --ha-ripple-color: var(--primary-color); - --ha-ripple-hover-opacity: 0.04; - --ha-ripple-pressed-opacity: 0.12; - display: flex; - align-items: center; - justify-content: center; - font-weight: 400; - line-height: 20px; - width: auto; - } - - .add:focus { - border-style: solid; - } - `, - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "hui-heading-section": HeadingSection; - } -} diff --git a/src/panels/lovelace/sections/hui-section.ts b/src/panels/lovelace/sections/hui-section.ts index 066329a890dc..718e04167e5f 100644 --- a/src/panels/lovelace/sections/hui-section.ts +++ b/src/panels/lovelace/sections/hui-section.ts @@ -5,10 +5,7 @@ import { fireEvent } from "../../../common/dom/fire_event"; import type { MediaQueriesListener } from "../../../common/dom/media_query"; import "../../../components/ha-svg-icon"; import type { LovelaceSectionElement } from "../../../data/lovelace"; -import { - ensureBadgeConfig, - type LovelaceBadgeConfig, -} from "../../../data/lovelace/config/badge"; + import type { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import type { LovelaceSectionConfig, @@ -16,7 +13,6 @@ import type { } from "../../../data/lovelace/config/section"; import { isStrategySection } from "../../../data/lovelace/config/section"; import type { HomeAssistant } from "../../../types"; -import type { HuiBadge } from "../badges/hui-badge"; import "../cards/hui-card"; import type { HuiCard } from "../cards/hui-card"; import { @@ -32,9 +28,6 @@ import { parseLovelaceCardPath } from "../editor/lovelace-path"; import { generateLovelaceSectionStrategy } from "../strategies/get-strategy"; import type { Lovelace } from "../types"; import { DEFAULT_SECTION_LAYOUT } from "./const"; -import { performDeleteBadge } from "../editor/delete-badge"; -import { showEditBadgeDialog } from "../editor/badge-editor/show-edit-badge-dialog"; -import { showCreateBadgeDialog } from "../editor/badge-editor/show-create-badge-dialog"; declare global { interface HASSDomEvents { @@ -61,8 +54,6 @@ export class HuiSection extends ReactiveElement { @state() private _cards: HuiCard[] = []; - @state() private _badges: HuiBadge[] = []; - private _layoutElementType?: string; private _layoutElement?: LovelaceSectionElement; @@ -82,19 +73,6 @@ export class HuiSection extends ReactiveElement { return element; } - public createBadgeElement(badgeConfig: LovelaceBadgeConfig) { - const element = document.createElement("hui-badge"); - element.hass = this.hass; - element.preview = this.preview; - element.config = badgeConfig; - element.addEventListener("badge-updated", (ev: Event) => { - ev.stopPropagation(); - this._badges = [...this._badges]; - }); - element.load(); - return element; - } - protected createRenderRoot() { return this; } @@ -108,7 +86,7 @@ export class HuiSection extends ReactiveElement { - config changed to section with same layout element - config changed to section with different layout element - forwarded properties hass/narrow/lovelace/cards change - - cards/badges change if one is rebuild when it was loaded later + - cards change if one is rebuild when it was loaded later - lovelace changes if edit mode is enabled or config has changed */ @@ -144,9 +122,6 @@ export class HuiSection extends ReactiveElement { this._cards.forEach((element) => { element.hass = this.hass; }); - this._badges.forEach((badge) => { - badge.hass = this.hass; - }); this._layoutElement.hass = this.hass; } if (changedProperties.has("lovelace")) { @@ -157,9 +132,6 @@ export class HuiSection extends ReactiveElement { this._cards.forEach((element) => { element.preview = this.preview; }); - this._badges.forEach((element) => { - element.preview = this.preview; - }); } if (changedProperties.has("importOnly")) { this._layoutElement.importOnly = this.importOnly; @@ -167,9 +139,6 @@ export class HuiSection extends ReactiveElement { if (changedProperties.has("_cards")) { this._layoutElement.cards = this._cards; } - if (changedProperties.has("_badges")) { - this._layoutElement.badges = this._badges; - } if (changedProperties.has("hass") || changedProperties.has("preview")) { this._updateElement(); } @@ -227,7 +196,6 @@ export class HuiSection extends ReactiveElement { addLayoutElement = true; this._createLayoutElement(sectionConfig); } - this._createBadges(sectionConfig); this._createCards(sectionConfig); this._layoutElement!.isStrategy = isStrategy; this._layoutElement!.hass = this.hass; @@ -235,7 +203,6 @@ export class HuiSection extends ReactiveElement { this._layoutElement!.index = this.index; this._layoutElement!.viewIndex = this.viewIndex; this._layoutElement!.cards = this._cards; - this._layoutElement!.badges = this._badges; if (addLayoutElement) { while (this.lastChild) { @@ -311,44 +278,6 @@ export class HuiSection extends ReactiveElement { if (!this.lovelace) return; performDeleteCard(this.hass, this.lovelace, ev.detail); }); - this._layoutElement.addEventListener("ll-create-badge", async (ev) => { - ev.stopPropagation(); - if (!this.lovelace) return; - showCreateBadgeDialog(this, { - lovelaceConfig: this.lovelace.config, - saveConfig: this.lovelace.saveConfig, - path: [this.viewIndex, this.index], - }); - }); - this._layoutElement.addEventListener("ll-edit-badge", (ev) => { - ev.stopPropagation(); - if (!this.lovelace) return; - const { cardIndex } = parseLovelaceCardPath(ev.detail.path); - showEditBadgeDialog(this, { - lovelaceConfig: this.lovelace.config, - saveConfig: this.lovelace.saveConfig, - path: [this.viewIndex, this.index], - badgeIndex: cardIndex, - }); - }); - this._layoutElement.addEventListener("ll-delete-badge", async (ev) => { - ev.stopPropagation(); - if (!this.lovelace) return; - performDeleteBadge(this.hass, this.lovelace, ev.detail); - }); - } - - private _createBadges(config: LovelaceSectionConfig): void { - if (!config || !config.badges || !Array.isArray(config.badges)) { - this._badges = []; - return; - } - - this._badges = config.badges.map((badge) => { - const badgeConfig = ensureBadgeConfig(badge); - const element = this.createBadgeElement(badgeConfig); - return element; - }); } private _createCards(config: LovelaceSectionConfig): void { diff --git a/src/panels/lovelace/views/hui-sections-view.ts b/src/panels/lovelace/views/hui-sections-view.ts index 48c2b6a849f0..e148d10fe584 100644 --- a/src/panels/lovelace/views/hui-sections-view.ts +++ b/src/panels/lovelace/views/hui-sections-view.ts @@ -4,7 +4,6 @@ import { mdiDrag, mdiEyeOff, mdiPencil, - mdiPlus, mdiViewGridPlus, } from "@mdi/js"; import type { PropertyValues } from "lit"; @@ -21,22 +20,17 @@ import "../../../components/ha-sortable"; import "../../../components/ha-svg-icon"; import type { LovelaceViewElement } from "../../../data/lovelace"; import type { LovelaceCardConfig } from "../../../data/lovelace/config/card"; -import { - isStrategySection, - type LovelaceSectionConfig, -} from "../../../data/lovelace/config/section"; +import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; import type { LovelaceViewConfig } from "../../../data/lovelace/config/view"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import type { HomeAssistant } from "../../../types"; -import { HuiBadge } from "../badges/hui-badge"; +import type { HuiBadge } from "../badges/hui-badge"; import "../badges/hui-view-header"; import type { HuiCard } from "../cards/hui-card"; -import type { MarkdownCardConfig } from "../cards/types"; import "../components/hui-badge-edit-mode"; import { addSection, deleteSection, - insertSection, moveCard, moveSection, } from "../editor/config-util"; @@ -116,26 +110,9 @@ export class SectionsView extends LitElement implements LovelaceViewElement { return this._sectionConfigKeys.get(section)!; } - private _isFullSizeSection(section: LovelaceSectionConfig): boolean { - return this._isHeadingSection(section); - } - - private _isMoveableSection(section: LovelaceSectionConfig): boolean { - return !this._isHeadingSection(section); - } - - private _isHeadingSection(section: LovelaceSectionConfig): boolean { - if (isStrategySection(section)) { - return false; - } - return section.type === "heading"; - } - private _computeSectionsCount() { this._sectionColumnCount = this.sections - .filter( - (section) => !section.hidden && !this._isFullSizeSection(section.config) - ) + .filter((section) => !section.hidden) .map((section) => section.config.column_span ?? 1) .reduce((acc, val) => acc + val, 0); } @@ -166,21 +143,6 @@ export class SectionsView extends LitElement implements LovelaceViewElement { } } - private _addHeadingSection() { - const headingSection = this._defaultHeadingSection(); - const newConfig = insertSection( - this.lovelace!.config, - this.index!, - 0, - headingSection - ); - this.lovelace!.saveConfig(newConfig); - } - - private _hasHeadingSection(): boolean { - return this._config?.sections?.some(this._isHeadingSection) ?? false; - } - protected render() { if (!this.lovelace) return nothing; @@ -207,7 +169,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement { @item-moved=${this._sectionMoved} group="section" handle-selector=".handle" - draggable-selector=".moveable" + draggable-selector=".section" .rollback=${false} >
- ${editMode && !this._hasHeadingSection() - ? html` - - ` - : nothing} ${repeat( sections, (section) => this._getSectionKey(section), (section, idx) => { - const isMoveable = this._isMoveableSection(section.config); - const isFullSize = this._isFullSizeSection(section.config); - const columnSpan = Math.min( - isFullSize - ? totalSectionCount - : section.config.column_span || 1, + section.config.column_span || 1, maxColumnCount ); - const rowSpan = section.config.row_span || 1; return html`
- ${isMoveable - ? html` - - ` - : nothing} + Date: Wed, 19 Feb 2025 18:32:01 +0100 Subject: [PATCH 24/33] Remove section badges --- .../lovelace/badges/hui-section-badges.ts | 260 ------------------ 1 file changed, 260 deletions(-) delete mode 100644 src/panels/lovelace/badges/hui-section-badges.ts diff --git a/src/panels/lovelace/badges/hui-section-badges.ts b/src/panels/lovelace/badges/hui-section-badges.ts deleted file mode 100644 index cba67f0ae48e..000000000000 --- a/src/panels/lovelace/badges/hui-section-badges.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { mdiPlus } from "@mdi/js"; -import type { PropertyValues } from "lit"; -import { css, html, LitElement, nothing } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { classMap } from "lit/directives/class-map"; -import { repeat } from "lit/directives/repeat"; -import { fireEvent } from "../../../common/dom/fire_event"; -import "../../../components/ha-ripple"; -import "../../../components/ha-sortable"; -import type { HaSortableOptions } from "../../../components/ha-sortable"; -import "../../../components/ha-svg-icon"; -import type { HomeAssistant } from "../../../types"; -import "../components/hui-badge-edit-mode"; -import { moveBadge } from "../editor/config-util"; -import type { Lovelace } from "../types"; -import type { HuiBadge } from "./hui-badge"; -import type { LovelaceCardPath } from "../editor/lovelace-path"; - -const BADGE_SORTABLE_OPTIONS: HaSortableOptions = { - delay: 100, - delayOnTouchOnly: true, - direction: "horizontal", - invertedSwapThreshold: 0.7, -} as HaSortableOptions; - -@customElement("hui-section-badges") -export class HuiSectionBadges extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @property({ attribute: false }) public lovelace!: Lovelace; - - @property({ attribute: false }) public badges: HuiBadge[] = []; - - @property({ attribute: false }) public viewIndex!: number; - - @property({ attribute: false }) public sectionIndex!: number; - - @property({ type: Boolean, attribute: "show-add-label" }) - public showAddLabel!: boolean; - - @state() _dragging = false; - - private _badgeConfigKeys = new WeakMap(); - - private _checkAllHidden() { - const allHidden = - !this.lovelace.editMode && this.badges.every((section) => section.hidden); - this.toggleAttribute("hidden", allHidden); - } - - private _badgeVisibilityChanged = () => { - this._checkAllHidden(); - }; - - connectedCallback(): void { - super.connectedCallback(); - this.addEventListener( - "badge-visibility-changed", - this._badgeVisibilityChanged - ); - } - - disconnectedCallback(): void { - super.disconnectedCallback(); - this.removeEventListener( - "badge-visibility-changed", - this._badgeVisibilityChanged - ); - } - - willUpdate(changedProperties: PropertyValues): void { - if (changedProperties.has("badges") || changedProperties.has("lovelace")) { - this._checkAllHidden(); - } - } - - private _getBadgeKey(badge: HuiBadge) { - if (!this._badgeConfigKeys.has(badge)) { - this._badgeConfigKeys.set(badge, Math.random().toString()); - } - return this._badgeConfigKeys.get(badge)!; - } - - private _badgeMoved(ev) { - ev.stopPropagation(); - const { oldIndex, newIndex } = ev.detail; - const newConfig = moveBadge( - this.lovelace!.config, - [this.viewIndex!, this.sectionIndex!, oldIndex], - [this.viewIndex!, this.sectionIndex!, newIndex] - ); - this.lovelace!.saveConfig(newConfig); - } - - private _badgeAdded(ev) { - ev.stopPropagation(); - const { index, data } = ev.detail; - const oldPath = data as LovelaceCardPath; - const newPath = [ - this.viewIndex!, - this.sectionIndex!, - index, - ] as LovelaceCardPath; - const newConfig = moveBadge(this.lovelace!.config, oldPath, newPath); - this.lovelace!.saveConfig(newConfig); - } - - private _badgeRemoved(ev) { - ev.stopPropagation(); - // Do nothing, it's handle by the "card-added" event from the new parent. - } - - private _dragStart() { - this._dragging = true; - } - - private _dragEnd() { - this._dragging = false; - } - - private _addBadge() { - fireEvent(this, "ll-create-badge"); - } - - render() { - if (!this.lovelace) return nothing; - - const editMode = this.lovelace.editMode; - - const badges = this.badges; - - return html` - ${badges?.length > 0 || editMode - ? html` - -
- ${repeat( - badges, - (badge) => this._getBadgeKey(badge), - (badge, idx) => { - const badgePath = [ - this.viewIndex, - this.sectionIndex, - idx, - ] as LovelaceCardPath; - return html` - ${editMode - ? html` - - ${badge} - - ` - : badge} - `; - } - )} - ${editMode - ? html` - - ` - : nothing} -
-
- ` - : nothing} - `; - } - - static styles = css` - :host([hidden]) { - display: none !important; - } - - .badges { - display: flex; - align-items: flex-start; - flex-wrap: var(--badges-wrap, wrap); - justify-content: var(--badges-aligmnent, center); - gap: 8px; - margin: 0; - } - - hui-badge-edit-mode { - display: block; - position: relative; - min-width: 36px; - min-height: 36px; - } - - .add { - position: relative; - display: flex; - flex-direction: row; - align-items: center; - gap: 8px; - height: 36px; - padding: 6px 20px 6px 20px; - box-sizing: border-box; - width: auto; - border-radius: 18px; - background-color: transparent; - border-width: 2px; - border-style: dashed; - border-color: var(--primary-color); - --mdc-icon-size: 18px; - cursor: pointer; - font-size: 14px; - order: 1; - color: var(--primary-text-color); - --ha-ripple-color: var(--primary-color); - --ha-ripple-hover-opacity: 0.04; - --ha-ripple-pressed-opacity: 0.12; - } - .add:focus { - border-style: solid; - } - `; -} - -declare global { - interface HTMLElementTagNameMap { - "hui-section-badges": HuiSectionBadges; - } -} From aee84ce307d0a4ff960e4b62168fb439e6370882 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 19 Feb 2025 18:37:12 +0100 Subject: [PATCH 25/33] Clean section --- src/data/lovelace.ts | 1 - src/data/lovelace/config/section.ts | 4 ---- src/panels/lovelace/badges/hui-view-badges.ts | 2 +- src/panels/lovelace/create-element/create-section-element.ts | 3 +-- .../lovelace/editor/badge-editor/hui-dialog-suggest-badge.ts | 5 +---- src/panels/lovelace/editor/lovelace-path.ts | 5 ++++- src/panels/lovelace/sections/hui-grid-section.ts | 2 +- src/panels/lovelace/sections/hui-section.ts | 2 +- 8 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts index cd6b231dd6d8..98ed39e912fe 100644 --- a/src/data/lovelace.ts +++ b/src/data/lovelace.ts @@ -33,7 +33,6 @@ export interface LovelaceSectionElement extends HTMLElement { viewIndex?: number; index?: number; cards?: HuiCard[]; - badges?: HuiBadge[]; isStrategy: boolean; importOnly?: boolean; setConfig(config: LovelaceSectionConfig): void; diff --git a/src/data/lovelace/config/section.ts b/src/data/lovelace/config/section.ts index 71ab2416c0c3..ad2c66e2aa50 100644 --- a/src/data/lovelace/config/section.ts +++ b/src/data/lovelace/config/section.ts @@ -10,10 +10,6 @@ export interface LovelaceBaseSectionConfig { * @deprecated Use heading card instead. */ title?: string; - // Only used for heading section, it should move to a section view config type when the sections will have dedicated editor. - layout?: "start" | "center" | "responsive"; - badges_position?: "bottom" | "top"; - extra_space?: boolean; } export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig { diff --git a/src/panels/lovelace/badges/hui-view-badges.ts b/src/panels/lovelace/badges/hui-view-badges.ts index c4fe52e8588f..dcf833e215ec 100644 --- a/src/panels/lovelace/badges/hui-view-badges.ts +++ b/src/panels/lovelace/badges/hui-view-badges.ts @@ -101,7 +101,7 @@ export class HuiViewBadges extends LitElement { private _badgeRemoved(ev) { ev.stopPropagation(); - // Do nothing, it's handle by the "card-added" event from the new parent. + // Do nothing, it's handle by the "item-added" event from the new parent. } private _dragStart() { diff --git a/src/panels/lovelace/create-element/create-section-element.ts b/src/panels/lovelace/create-element/create-section-element.ts index e9f04d704277..bdfaca27fd59 100644 --- a/src/panels/lovelace/create-element/create-section-element.ts +++ b/src/panels/lovelace/create-element/create-section-element.ts @@ -2,10 +2,9 @@ import type { LovelaceSectionElement } from "../../../data/lovelace"; import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; import type { HuiErrorCard } from "../cards/hui-error-card"; import "../sections/hui-grid-section"; -import "../sections/hui-heading-section"; import { createLovelaceElement } from "./create-element-base"; -const ALWAYS_LOADED_LAYOUTS = new Set(["grid", "heading"]); +const ALWAYS_LOADED_LAYOUTS = new Set(["grid"]); const LAZY_LOAD_LAYOUTS = {}; diff --git a/src/panels/lovelace/editor/badge-editor/hui-dialog-suggest-badge.ts b/src/panels/lovelace/editor/badge-editor/hui-dialog-suggest-badge.ts index e6bc94f02f87..277fc0448e0b 100644 --- a/src/panels/lovelace/editor/badge-editor/hui-dialog-suggest-badge.ts +++ b/src/panels/lovelace/editor/badge-editor/hui-dialog-suggest-badge.ts @@ -168,13 +168,10 @@ export class HuiDialogSuggestBadge extends LitElement { config: LovelaceConfig, path: LovelaceContainerPath ): LovelaceConfig { - const { viewIndex, sectionIndex } = parseLovelaceContainerPath(path); + const { viewIndex } = parseLovelaceContainerPath(path); const newBadges = this._badgeConfig!; - if (sectionIndex != null) { - return addBadges(config, [viewIndex, sectionIndex], newBadges); - } return addBadges(config, [viewIndex], newBadges); } diff --git a/src/panels/lovelace/editor/lovelace-path.ts b/src/panels/lovelace/editor/lovelace-path.ts index c3a84e2f1682..259a2ce54ef9 100644 --- a/src/panels/lovelace/editor/lovelace-path.ts +++ b/src/panels/lovelace/editor/lovelace-path.ts @@ -205,5 +205,8 @@ export const findLovelaceItems = ( if (isStrategySection(section)) { throw new Error("Can not find cards in a strategy section"); } - return section[key] as LovelaceItemKeys[T] | undefined; + if (key === "cards") { + return section[key as "cards"] as LovelaceItemKeys[T] | undefined; + } + throw new Error(`${key} is not supported in section`); }; diff --git a/src/panels/lovelace/sections/hui-grid-section.ts b/src/panels/lovelace/sections/hui-grid-section.ts index 059fe80852a6..6fd9c8cc2b27 100644 --- a/src/panels/lovelace/sections/hui-grid-section.ts +++ b/src/panels/lovelace/sections/hui-grid-section.ts @@ -193,7 +193,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement { private _cardRemoved(ev) { ev.stopPropagation(); - // Do nothing, it's handle by the "card-added" event from the new parent. + // Do nothing, it's handle by the "item-added" event from the new parent. } private _dragStart() { diff --git a/src/panels/lovelace/sections/hui-section.ts b/src/panels/lovelace/sections/hui-section.ts index 718e04167e5f..5155e996ea27 100644 --- a/src/panels/lovelace/sections/hui-section.ts +++ b/src/panels/lovelace/sections/hui-section.ts @@ -5,7 +5,6 @@ import { fireEvent } from "../../../common/dom/fire_event"; import type { MediaQueriesListener } from "../../../common/dom/media_query"; import "../../../components/ha-svg-icon"; import type { LovelaceSectionElement } from "../../../data/lovelace"; - import type { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import type { LovelaceSectionConfig, @@ -196,6 +195,7 @@ export class HuiSection extends ReactiveElement { addLayoutElement = true; this._createLayoutElement(sectionConfig); } + this._createCards(sectionConfig); this._layoutElement!.isStrategy = isStrategy; this._layoutElement!.hass = this.hass; From 1740bd815c87c8595a41b62fe18f7fe94c42dcc5 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 19 Feb 2025 18:42:01 +0100 Subject: [PATCH 26/33] Fix header visibility --- src/panels/lovelace/badges/hui-view-header.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/panels/lovelace/badges/hui-view-header.ts b/src/panels/lovelace/badges/hui-view-header.ts index 5ffb6028051b..255cf426597b 100644 --- a/src/panels/lovelace/badges/hui-view-header.ts +++ b/src/panels/lovelace/badges/hui-view-header.ts @@ -33,14 +33,16 @@ export class HuiViewHeader extends LitElement { @property({ attribute: false }) public viewIndex!: number; - private _checkAllHidden() { + private _checkHidden() { const allHidden = - !this.lovelace.editMode && this.badges.every((badges) => badges.hidden); + !this.card && + !this.lovelace.editMode && + this.badges.every((badges) => badges.hidden); this.toggleAttribute("hidden", allHidden); } private _badgeVisibilityChanged = () => { - this._checkAllHidden(); + this._checkHidden(); }; connectedCallback(): void { @@ -60,8 +62,12 @@ export class HuiViewHeader extends LitElement { } willUpdate(changedProperties: PropertyValues): void { - if (changedProperties.has("badges") || changedProperties.has("lovelace")) { - this._checkAllHidden(); + if ( + changedProperties.has("badges") || + changedProperties.has("lovelace") || + changedProperties.has("card") + ) { + this._checkHidden(); } if (changedProperties.has("config")) { From 141d1b104545e42ce20e1ea3b7c8a72013b160b5 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 19 Feb 2025 18:44:49 +0100 Subject: [PATCH 27/33] Update translations --- src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index 87308c5beb5d..dd7b518078a5 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6683,7 +6683,7 @@ "layout": "Layout", "badges_position": "Badges position", "extra_space": "Add extra space at the top for background", - "extra_space_helper": "Make the content more touch friendly" + "extra_space_helper": "Make the content more touch-friendly" }, "visibility": { "explanation": "The section will be shown when ALL conditions below are fulfilled. If no conditions are set, the section will always be shown." From 1e7ea43bbec54cd85fc3029bc8a24e0467a1c4e4 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 20 Feb 2025 11:55:03 +0100 Subject: [PATCH 28/33] Add view header editor --- .../hui-section-settings-editor.ts | 85 +----- .../hui-dialog-edit-view-header.ts | 245 ++++++++++++++++++ .../hui-view-header-settings-editor.ts | 137 ++++++++++ .../show-edit-view-header-dialog.ts | 18 ++ .../lovelace/views/hui-sections-view.ts | 2 +- .../{badges => views}/hui-view-header.ts | 61 ++++- src/translations/en.json | 20 +- 7 files changed, 481 insertions(+), 87 deletions(-) create mode 100644 src/panels/lovelace/editor/view-header/hui-dialog-edit-view-header.ts create mode 100644 src/panels/lovelace/editor/view-header/hui-view-header-settings-editor.ts create mode 100644 src/panels/lovelace/editor/view-header/show-edit-view-header-dialog.ts rename src/panels/lovelace/{badges => views}/hui-view-header.ts (85%) diff --git a/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts b/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts index 29979151e678..923b4164b431 100644 --- a/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts +++ b/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts @@ -2,22 +2,18 @@ import { LitElement, html } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-form/ha-form"; import type { HaFormSchema, SchemaUnion, } from "../../../../components/ha-form/types"; -import "../../../../components/ha-form/ha-form"; -import { - isStrategySection, - type LovelaceSectionRawConfig, -} from "../../../../data/lovelace/config/section"; +import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section"; import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; import type { HomeAssistant } from "../../../../types"; -type SettingsData = Pick< - LovelaceSectionRawConfig, - "layout" | "extra_space" | "badges_position" | "column_span" ->; +interface SettingsData { + column_span?: number; +} @customElement("hui-section-settings-editor") export class HuiDialogEditSection extends LitElement { @@ -27,51 +23,6 @@ export class HuiDialogEditSection extends LitElement { @property({ attribute: false }) public viewConfig!: LovelaceViewConfig; - private _headingSchema = memoizeOne( - () => - [ - { - name: "layout", - selector: { - select: { - options: [ - { - value: "responsive", - label: "Responsive (Stacked on mobile)", - }, - { - value: "start", - label: "Left aligned (Always stacked)", - }, - { - value: "center", - label: "Centered aligned (Always stacked)", - }, - ], - }, - }, - }, - { - name: "badges_position", - selector: { - select: { - options: [ - { - value: "bottom", - label: "Bottom", - }, - { - value: "top", - label: "Top", - }, - ], - }, - }, - }, - { name: "extra_space", selector: { boolean: {} } }, - ] as const satisfies HaFormSchema[] - ); - private _schema = memoizeOne( (maxColumns: number) => [ @@ -88,29 +39,11 @@ export class HuiDialogEditSection extends LitElement { ] as const satisfies HaFormSchema[] ); - private _getData(): SettingsData { - if (!isStrategySection(this.config) && this.config.type === "heading") { - return { - layout: this.config.layout || "responsive", - extra_space: this.config.extra_space || false, - badges_position: this.config.badges_position || "bottom", - }; - } - return { + render() { + const data: SettingsData = { column_span: this.config.column_span || 1, }; - } - - private _getSchema(): HaFormSchema[] { - if (!isStrategySection(this.config) && this.config.type === "heading") { - return this._headingSchema(); - } - return this._schema(this.viewConfig.max_columns || 4); - } - - render() { - const data = this._getData(); - const schema = this._getSchema(); + const schema = this._schema(this.viewConfig.max_columns || 4); return html` + `; + } else { + content = html` + + `; + } + + const title = this.hass.localize( + "ui.panel.lovelace.editor.edit_view_header.header" + ); + + return html` + + + +

${title}

+ + + + ${this.hass!.localize( + `ui.panel.lovelace.editor.edit_view_header.edit_${!this._yamlMode ? "yaml" : "ui"}` + )} + + + +
+ ${content} + + ${this._saving + ? html`` + : nothing} + ${this.hass!.localize("ui.common.save")} +
+ `; + } + + private async _handleAction(ev: CustomEvent) { + ev.stopPropagation(); + ev.preventDefault(); + switch (ev.detail.index) { + case 0: + this._yamlMode = !this._yamlMode; + break; + } + } + + private _configChanged(ev: CustomEvent): void { + if ( + ev.detail && + ev.detail.config && + !deepEqual(this._config, ev.detail.config) + ) { + this._config = ev.detail.config; + this._dirty = true; + } + } + + private _viewYamlChanged(ev: CustomEvent) { + ev.stopPropagation(); + if (!ev.detail.isValid) { + return; + } + this._config = ev.detail.value; + this._dirty = true; + } + + private async _save(): Promise { + if (!this._params || !this._config) { + return; + } + + this._saving = true; + + try { + await this._params.saveConfig(this._config); + this.closeDialog(); + } catch (err: any) { + showAlertDialog(this, { + text: `${this.hass!.localize( + "ui.panel.lovelace.editor.edit_view_header.saving_failed" + )}: ${err.message}`, + }); + } finally { + this._saving = false; + } + } + + static get styles(): CSSResultGroup { + return [ + haStyleDialog, + css` + ha-dialog { + /* Set the top top of the dialog to a fixed position, so it doesnt jump when the content changes size */ + --vertical-align-dialog: flex-start; + --dialog-surface-margin-top: 40px; + } + + @media all and (max-width: 450px), all and (max-height: 500px) { + /* When in fullscreen dialog should be attached to top */ + ha-dialog { + --dialog-surface-margin-top: 0px; + } + } + ha-dialog.yaml-mode { + --dialog-content-padding: 0; + } + h2 { + margin: 0; + font-size: inherit; + font-weight: inherit; + } + + @media all and (min-width: 600px) { + ha-dialog { + --mdc-dialog-min-width: 600px; + } + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-dialog-edit-view-header": HuiDialogEditViewHeader; + } +} diff --git a/src/panels/lovelace/editor/view-header/hui-view-header-settings-editor.ts b/src/panels/lovelace/editor/view-header/hui-view-header-settings-editor.ts new file mode 100644 index 000000000000..8dbadbd0f9dc --- /dev/null +++ b/src/panels/lovelace/editor/view-header/hui-view-header-settings-editor.ts @@ -0,0 +1,137 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-form/ha-form"; +import type { + HaFormSchema, + SchemaUnion, +} from "../../../../components/ha-form/types"; +import type { + LovelaceViewConfig, + LovelaceViewHeaderConfig, +} from "../../../../data/lovelace/config/view"; +import type { HomeAssistant } from "../../../../types"; + +@customElement("hui-view-header-settings-editor") +export class HuiViewHeaderSettingsEditor extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public config?: LovelaceViewHeaderConfig; + + private _schema = memoizeOne( + () => + [ + { + name: "layout", + selector: { + select: { + options: [ + { + value: "responsive", + label: "Responsive (Stacked on mobile)", + }, + { + value: "start", + label: "Left aligned (Always stacked)", + }, + { + value: "center", + label: "Centered aligned (Always stacked)", + }, + ], + }, + }, + }, + { + name: "badges_position", + selector: { + select: { + options: [ + { + value: "bottom", + label: "Bottom", + }, + { + value: "top", + label: "Top", + }, + ], + }, + }, + }, + { name: "extra_space", selector: { boolean: {} } }, + ] as const satisfies HaFormSchema[] + ); + + protected render() { + if (!this.hass) { + return nothing; + } + + const data = { + layout: this.config?.layout || "responsive", + extra_space: this.config?.extra_space || false, + badges_position: this.config?.badges_position || "bottom", + }; + + const schema = this._schema(); + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + ev.stopPropagation(); + const newData = ev.detail.value as LovelaceViewConfig; + + const config: LovelaceViewHeaderConfig = { + ...this.config, + ...newData, + }; + + fireEvent(this, "config-changed", { config }); + } + + private _computeLabel = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + case "layout": + case "badges_position": + case "extra_space": + return this.hass.localize( + `ui.panel.lovelace.editor.edit_view_header.settings.${schema.name}` + ); + default: + return ""; + } + }; + + private _computeHelper = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + case "extra_space": + return this.hass.localize( + `ui.panel.lovelace.editor.edit_view_header.settings.${schema.name}_helper` + ); + default: + return undefined; + } + }; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-view-header-settings-editor": HuiViewHeaderSettingsEditor; + } +} diff --git a/src/panels/lovelace/editor/view-header/show-edit-view-header-dialog.ts b/src/panels/lovelace/editor/view-header/show-edit-view-header-dialog.ts new file mode 100644 index 000000000000..d2aef306c33d --- /dev/null +++ b/src/panels/lovelace/editor/view-header/show-edit-view-header-dialog.ts @@ -0,0 +1,18 @@ +import { fireEvent } from "../../../../common/dom/fire_event"; +import type { LovelaceViewHeaderConfig } from "../../../../data/lovelace/config/view"; + +export interface EditViewHeaderDialogParams { + saveConfig: (config: LovelaceViewHeaderConfig) => void; + config: LovelaceViewHeaderConfig; +} + +export const showEditViewHeaderDialog = ( + element: HTMLElement, + dialogParams: EditViewHeaderDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "hui-dialog-edit-view-header", + dialogImport: () => import("./hui-dialog-edit-view-header"), + dialogParams: dialogParams, + }); +}; diff --git a/src/panels/lovelace/views/hui-sections-view.ts b/src/panels/lovelace/views/hui-sections-view.ts index e148d10fe584..ff27e131ca2c 100644 --- a/src/panels/lovelace/views/hui-sections-view.ts +++ b/src/panels/lovelace/views/hui-sections-view.ts @@ -25,7 +25,7 @@ import type { LovelaceViewConfig } from "../../../data/lovelace/config/view"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import type { HomeAssistant } from "../../../types"; import type { HuiBadge } from "../badges/hui-badge"; -import "../badges/hui-view-header"; +import "./hui-view-header"; import type { HuiCard } from "../cards/hui-card"; import "../components/hui-badge-edit-mode"; import { diff --git a/src/panels/lovelace/badges/hui-view-header.ts b/src/panels/lovelace/views/hui-view-header.ts similarity index 85% rename from src/panels/lovelace/badges/hui-view-header.ts rename to src/panels/lovelace/views/hui-view-header.ts index 255cf426597b..5d65125eeb56 100644 --- a/src/panels/lovelace/badges/hui-view-header.ts +++ b/src/panels/lovelace/views/hui-view-header.ts @@ -1,4 +1,4 @@ -import { mdiPlus } from "@mdi/js"; +import { mdiPencil, mdiPlus } from "@mdi/js"; import type { PropertyValues } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; @@ -12,12 +12,13 @@ import type { LovelaceViewHeaderConfig, } from "../../../data/lovelace/config/view"; import type { HomeAssistant } from "../../../types"; +import type { HuiBadge } from "../badges/hui-badge"; +import "../badges/hui-view-badges"; import type { HuiCard } from "../cards/hui-card"; import "../components/hui-badge-edit-mode"; import { replaceView } from "../editor/config-util"; +import { showEditViewHeaderDialog } from "../editor/view-header/show-edit-view-header-dialog"; import type { Lovelace } from "../types"; -import type { HuiBadge } from "./hui-badge"; -import "./hui-view-badges"; @customElement("hui-view-header") export class HuiViewHeader extends LitElement { @@ -141,6 +142,15 @@ export class HuiViewHeader extends LitElement { this.lovelace.saveConfig(updatedConfig); } + private _configure = () => { + showEditViewHeaderDialog(this, { + config: this.config!, + saveConfig: (config: LovelaceViewHeaderConfig) => { + this._saveHeaderConfig(config); + }, + }); + }; + render() { if (!this.lovelace) return nothing; @@ -155,6 +165,19 @@ export class HuiViewHeader extends LitElement { const hasBadges = this.badges.length > 0; return html` + ${editMode + ? html` +
+
+ +
+
+ ` + : nothing}
Date: Thu, 20 Feb 2025 11:57:02 +0100 Subject: [PATCH 29/33] Use new markdown card option --- src/panels/lovelace/cards/types.ts | 1 - src/panels/lovelace/views/hui-view-header.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 356320f4be11..9771067a18de 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -342,7 +342,6 @@ export interface MarkdownCardConfig extends LovelaceCardConfig { entity_ids?: string | string[]; theme?: string; show_empty?: boolean; - no_border?: boolean; } export interface MediaControlCardConfig extends LovelaceCardConfig { diff --git a/src/panels/lovelace/views/hui-view-header.ts b/src/panels/lovelace/views/hui-view-header.ts index 5d65125eeb56..af823d0382d6 100644 --- a/src/panels/lovelace/views/hui-view-header.ts +++ b/src/panels/lovelace/views/hui-view-header.ts @@ -102,7 +102,7 @@ export class HuiViewHeader extends LitElement { private _addCard() { const cardConfig: LovelaceCardConfig = { type: "markdown", - no_border: true, + text_only: true, content: "# Hello {{ user }}\nToday is going to be warm and humid outside. Home Assistant will adjust the temperature throughout the day while you and your family is at home. ✨", }; From 6ccd497544623da29794b1374173b512dcd1e3fb Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 20 Feb 2025 11:59:47 +0100 Subject: [PATCH 30/33] Revert useless changes --- src/panels/lovelace/cards/hui-markdown-card.ts | 9 --------- .../editor/badge-editor/hui-dialog-create-badge.ts | 2 +- .../editor/badge-editor/hui-dialog-suggest-badge.ts | 1 - .../editor/card-editor/hui-dialog-create-card.ts | 2 +- .../editor/section-editor/hui-section-settings-editor.ts | 3 ++- src/panels/lovelace/views/hui-view-header.ts | 2 +- src/translations/en.json | 5 ++--- 7 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/panels/lovelace/cards/hui-markdown-card.ts b/src/panels/lovelace/cards/hui-markdown-card.ts index a48bbeff2589..b1f2d71ca9f9 100644 --- a/src/panels/lovelace/cards/hui-markdown-card.ts +++ b/src/panels/lovelace/cards/hui-markdown-card.ts @@ -232,15 +232,6 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard { ha-card { height: 100%; } - ha-card.no-border { - border: none; - box-shadow: none; - background: none; - padding: 0; - } - ha-card.no-border ha-markdown { - padding: 0; - } ha-alert { margin-bottom: 8px; } diff --git a/src/panels/lovelace/editor/badge-editor/hui-dialog-create-badge.ts b/src/panels/lovelace/editor/badge-editor/hui-dialog-create-badge.ts index bd7a713f4ca3..58d37b40ed73 100644 --- a/src/panels/lovelace/editor/badge-editor/hui-dialog-create-badge.ts +++ b/src/panels/lovelace/editor/badge-editor/hui-dialog-create-badge.ts @@ -260,7 +260,7 @@ export class HuiCreateDialogBadge showSuggestBadgeDialog(this, { lovelaceConfig: this._params!.lovelaceConfig, saveConfig: this._params!.saveConfig, - path: this._params!.path, + path: this._params!.path as [number], entities: this._selectedEntities, badgeConfig, }); diff --git a/src/panels/lovelace/editor/badge-editor/hui-dialog-suggest-badge.ts b/src/panels/lovelace/editor/badge-editor/hui-dialog-suggest-badge.ts index 277fc0448e0b..b25193385257 100644 --- a/src/panels/lovelace/editor/badge-editor/hui-dialog-suggest-badge.ts +++ b/src/panels/lovelace/editor/badge-editor/hui-dialog-suggest-badge.ts @@ -171,7 +171,6 @@ export class HuiDialogSuggestBadge extends LitElement { const { viewIndex } = parseLovelaceContainerPath(path); const newBadges = this._badgeConfig!; - return addBadges(config, [viewIndex], newBadges); } diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts index fa7e1360756a..403bc7c31ee0 100644 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts @@ -320,7 +320,7 @@ export class HuiCreateDialogCard showSuggestCardDialog(this, { lovelaceConfig: this._params!.lovelaceConfig, saveConfig: this._params!.saveConfig, - path: this._params!.path, + path: this._params!.path as [number], entities: this._selectedEntities, cardConfig, sectionConfig, diff --git a/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts b/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts index 923b4164b431..4bf53b4f1e95 100644 --- a/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts +++ b/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts @@ -2,11 +2,11 @@ import { LitElement, html } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; -import "../../../../components/ha-form/ha-form"; import type { HaFormSchema, SchemaUnion, } from "../../../../components/ha-form/types"; +import "../../../../components/ha-form/ha-form"; import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section"; import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; import type { HomeAssistant } from "../../../../types"; @@ -43,6 +43,7 @@ export class HuiDialogEditSection extends LitElement { const data: SettingsData = { column_span: this.config.column_span || 1, }; + const schema = this._schema(this.viewConfig.max_columns || 4); return html` diff --git a/src/panels/lovelace/views/hui-view-header.ts b/src/panels/lovelace/views/hui-view-header.ts index af823d0382d6..f0c520451198 100644 --- a/src/panels/lovelace/views/hui-view-header.ts +++ b/src/panels/lovelace/views/hui-view-header.ts @@ -211,7 +211,7 @@ export class HuiViewHeader extends LitElement { ${this.hass.localize( - "ui.panel.lovelace.editor.section.add_title" + "ui.panel.lovelace.editor.edit_view_header.add_title" )} ` diff --git a/src/translations/en.json b/src/translations/en.json index 803976b3eb13..6441d0a2d282 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6556,6 +6556,7 @@ "move_to_dashboard": "Move to dashboard" }, "edit_view_header": { + "add_title": "Add title", "header": "Header settings", "edit_ui": "[%key:ui::panel::lovelace::editor::edit_view::edit_ui%]", "edit_yaml": "[%key:ui::panel::lovelace::editor::edit_view::edit_yaml%]", @@ -6669,13 +6670,11 @@ "section": { "add_badge": "Add badge", "add_card": "[%key:ui::panel::lovelace::editor::edit_card::add%]", - "add_title": "Add title", "drop_card_create_section": "Drop card here to create a new section", "create_section": "Create section", "default_section_title": "New section", "imported_cards_title": "Imported cards", - "imported_cards_description": "These cards are imported from another view. They will only be displayed in edit mode. Move them into sections to display them in your view.", - "add_header": "Add header" + "imported_cards_description": "These cards are imported from another view. They will only be displayed in edit mode. Move them into sections to display them in your view." }, "delete_section": { "title": "Delete section", From f8aab92fdb9df0d8fd0a28b9b9ff0ba507447d3b Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 20 Feb 2025 12:13:52 +0100 Subject: [PATCH 31/33] Add options translations --- .../hui-view-header-settings-editor.ts | 27 ++++++++++++++----- src/panels/lovelace/views/hui-view-header.ts | 2 +- src/translations/en.json | 10 +++++++ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/panels/lovelace/editor/view-header/hui-view-header-settings-editor.ts b/src/panels/lovelace/editor/view-header/hui-view-header-settings-editor.ts index 8dbadbd0f9dc..cd1d194fbd9a 100644 --- a/src/panels/lovelace/editor/view-header/hui-view-header-settings-editor.ts +++ b/src/panels/lovelace/editor/view-header/hui-view-header-settings-editor.ts @@ -2,6 +2,8 @@ import { html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; +import type { LocalizeFunc } from "../../../../common/translations/localize"; +import { computeRTL } from "../../../../common/util/compute_rtl"; import "../../../../components/ha-form/ha-form"; import type { HaFormSchema, @@ -20,7 +22,7 @@ export class HuiViewHeaderSettingsEditor extends LitElement { @property({ attribute: false }) public config?: LovelaceViewHeaderConfig; private _schema = memoizeOne( - () => + (localize: LocalizeFunc, isRTL: boolean) => [ { name: "layout", @@ -29,15 +31,21 @@ export class HuiViewHeaderSettingsEditor extends LitElement { options: [ { value: "responsive", - label: "Responsive (Stacked on mobile)", + label: localize( + "ui.panel.lovelace.editor.edit_view_header.settings.layout_options.responsive" + ), }, { value: "start", - label: "Left aligned (Always stacked)", + label: localize( + `ui.panel.lovelace.editor.edit_view_header.settings.layout_options.${isRTL ? "start_rtl" : "start"}` + ), }, { value: "center", - label: "Centered aligned (Always stacked)", + label: localize( + "ui.panel.lovelace.editor.edit_view_header.settings.layout_options.center" + ), }, ], }, @@ -50,11 +58,15 @@ export class HuiViewHeaderSettingsEditor extends LitElement { options: [ { value: "bottom", - label: "Bottom", + label: localize( + `ui.panel.lovelace.editor.edit_view_header.settings.badges_position_options.bottom` + ), }, { value: "top", - label: "Top", + label: localize( + `ui.panel.lovelace.editor.edit_view_header.settings.badges_position_options.top` + ), }, ], }, @@ -75,7 +87,8 @@ export class HuiViewHeaderSettingsEditor extends LitElement { badges_position: this.config?.badges_position || "bottom", }; - const schema = this._schema(); + const isRTL = computeRTL(this.hass); + const schema = this._schema(this.hass.localize, isRTL); return html` Date: Thu, 20 Feb 2025 14:00:22 +0100 Subject: [PATCH 32/33] Use edit dialog --- src/panels/lovelace/views/hui-view-header.ts | 31 ++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/panels/lovelace/views/hui-view-header.ts b/src/panels/lovelace/views/hui-view-header.ts index d5503bcb49d0..fe89e31b8d3c 100644 --- a/src/panels/lovelace/views/hui-view-header.ts +++ b/src/panels/lovelace/views/hui-view-header.ts @@ -19,6 +19,7 @@ import "../components/hui-badge-edit-mode"; import { replaceView } from "../editor/config-util"; import { showEditViewHeaderDialog } from "../editor/view-header/show-edit-view-header-dialog"; import type { Lovelace } from "../types"; +import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"; @customElement("hui-view-header") export class HuiViewHeader extends LitElement { @@ -107,10 +108,16 @@ export class HuiViewHeader extends LitElement { "# Hello {{ user }}\nToday is going to be warm and humid outside. Home Assistant will adjust the temperature throughout the day while you and your family is at home. ✨", }; - // Todo: open edit card dialog - const newConfig = { ...this.config }; - newConfig.card = cardConfig; - this._saveHeaderConfig(newConfig); + showEditCardDialog(this, { + cardConfig, + lovelaceConfig: this.lovelace.config, + saveCardConfig: (newCardConfig: LovelaceCardConfig) => { + const newConfig = { ...this.config }; + newConfig.card = newCardConfig; + this._saveHeaderConfig(newConfig); + }, + isNew: true, + }); } private _deleteCard(ev) { @@ -122,7 +129,21 @@ export class HuiViewHeader extends LitElement { private _editCard(ev) { ev.stopPropagation(); - // Todo: open edit card dialog + const cardConfig = this.config!.card; + + if (!cardConfig) { + return; + } + + showEditCardDialog(this, { + cardConfig, + lovelaceConfig: this.lovelace.config, + saveCardConfig: (newCardConfig: LovelaceCardConfig) => { + const newConfig = { ...this.config }; + newConfig.card = newCardConfig; + this._saveHeaderConfig(newConfig); + }, + }); } private _saveHeaderConfig(headerConfig: LovelaceViewHeaderConfig) { From e90f2f7e845a8b2438bcbc47e9e8f3a8a12a04d7 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 20 Feb 2025 14:05:43 +0100 Subject: [PATCH 33/33] Unify font --- src/panels/lovelace/badges/hui-view-badges.ts | 1 + src/panels/lovelace/views/hui-view-header.ts | 28 +++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/panels/lovelace/badges/hui-view-badges.ts b/src/panels/lovelace/badges/hui-view-badges.ts index dcf833e215ec..9e83358a70a8 100644 --- a/src/panels/lovelace/badges/hui-view-badges.ts +++ b/src/panels/lovelace/badges/hui-view-badges.ts @@ -218,6 +218,7 @@ export class HuiViewBadges extends LitElement { display: flex; flex-direction: row; align-items: center; + outline: none; gap: 8px; height: 36px; padding: 6px 20px 6px 20px; diff --git a/src/panels/lovelace/views/hui-view-header.ts b/src/panels/lovelace/views/hui-view-header.ts index fe89e31b8d3c..837c3c8b9f20 100644 --- a/src/panels/lovelace/views/hui-view-header.ts +++ b/src/panels/lovelace/views/hui-view-header.ts @@ -409,26 +409,26 @@ export class HuiViewHeader extends LitElement { .add { position: relative; display: flex; - align-items: center; - justify-content: center; flex-direction: row; + align-items: center; outline: none; - background: none; - cursor: pointer; - border-radius: var(--ha-card-border-radius, 12px); - border: 2px dashed var(--primary-color); - min-height: 36px; gap: 8px; - padding: 0 10px; + height: 36px; + padding: 6px 20px 6px 20px; + box-sizing: border-box; + width: auto; + border-radius: var(--ha-card-border-radius, 12px); + background-color: transparent; + border-width: 2px; + border-style: dashed; + border-color: var(--primary-color); + --mdc-icon-size: 18px; + cursor: pointer; + font-size: 14px; + color: var(--primary-text-color); --ha-ripple-color: var(--primary-color); --ha-ripple-hover-opacity: 0.04; --ha-ripple-pressed-opacity: 0.12; - display: flex; - align-items: center; - justify-content: center; - font-weight: 400; - line-height: 20px; - width: auto; } .add:focus {