Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new badges design with UI editor #21401

Merged
merged 32 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f234171
Add new entity badge
piitaya Jul 4, 2024
43307a0
Improve badge render
piitaya Jul 8, 2024
024fc95
Add edit mode
piitaya Jul 8, 2024
9908526
Add editor
piitaya Jul 9, 2024
d761dc3
Increase height
piitaya Jul 9, 2024
60e02b9
Use hui-badge
piitaya Jul 9, 2024
2484a8d
Add editor
piitaya Jul 9, 2024
5d3e810
Add drag and drop
piitaya Jul 9, 2024
5d0ba70
Fix editor translations
piitaya Jul 15, 2024
609f102
Fix icon
piitaya Jul 15, 2024
b1e6eb4
Fix inactive color
piitaya Jul 15, 2024
72c12ec
Add state content
piitaya Jul 15, 2024
638632e
Add default config
piitaya Jul 15, 2024
b2c7d0b
Fix types
piitaya Jul 15, 2024
ded9346
Add custom badge support to editor
piitaya Jul 15, 2024
d4c5705
Fix custom badges
piitaya Jul 15, 2024
c4a0429
Add new badges to masonry view
piitaya Jul 15, 2024
e5b9701
fix lint
piitaya Jul 15, 2024
815ef6b
Fix inactive color
piitaya Jul 16, 2024
69f30d8
Fix entity filter card
piitaya Jul 16, 2024
ad848d9
Add display type option
piitaya Jul 17, 2024
91422e8
Add support for picture
piitaya Jul 17, 2024
2267c79
Improve focus style
piitaya Jul 17, 2024
19651a2
Add visibility editor
piitaya Jul 17, 2024
19cd92a
Fix visibility
piitaya Jul 17, 2024
2402df0
Fix add/delete card inside section
piitaya Jul 17, 2024
0b4494b
Fix translations
piitaya Jul 17, 2024
0e2e78f
Add error badge
piitaya Jul 17, 2024
8133b2f
Rename classes
piitaya Jul 17, 2024
1615af7
Fix badge type
piitaya Jul 18, 2024
6357bfa
Remove badges from section type
piitaya Jul 18, 2024
84bd453
Add missing types
piitaya Jul 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/data/lovelace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import {
getCollection,
HassEventBase,
} from "home-assistant-js-websocket";
import { HuiBadge } from "../panels/lovelace/badges/hui-badge";
import type { HuiCard } from "../panels/lovelace/cards/hui-card";
import type { HuiSection } from "../panels/lovelace/sections/hui-section";
import { Lovelace, LovelaceBadge } from "../panels/lovelace/types";
import { Lovelace } from "../panels/lovelace/types";
import { HomeAssistant } from "../types";
import { LovelaceSectionConfig } from "./lovelace/config/section";
import { fetchConfig, LegacyLovelaceConfig } from "./lovelace/config/types";
Expand All @@ -21,7 +22,7 @@ export interface LovelaceViewElement extends HTMLElement {
narrow?: boolean;
index?: number;
cards?: HuiCard[];
badges?: LovelaceBadge[];
badges?: HuiBadge[];
sections?: HuiSection[];
isStrategy: boolean;
setConfig(config: LovelaceViewConfig): void;
Expand Down
3 changes: 3 additions & 0 deletions src/data/lovelace/config/badge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Condition } from "../../../panels/lovelace/common/validate-condition";

export interface LovelaceBadgeConfig {
type?: string;
[key: string]: any;
visibility?: Condition[];
piitaya marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 2 additions & 0 deletions src/data/lovelace/config/section.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Condition } from "../../../panels/lovelace/common/validate-condition";
import { LovelaceBadgeConfig } from "./badge";
import type { LovelaceCardConfig } from "./card";
import type { LovelaceStrategyConfig } from "./strategy";

Expand All @@ -10,6 +11,7 @@ export interface LovelaceBaseSectionConfig {
export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig {
type?: string;
cards?: LovelaceCardConfig[];
badges?: LovelaceBadgeConfig[]; // Not supported yet
piitaya marked this conversation as resolved.
Show resolved Hide resolved
piitaya marked this conversation as resolved.
Show resolved Hide resolved
}

export interface LovelaceStrategySectionConfig
Expand Down
2 changes: 1 addition & 1 deletion src/data/lovelace/config/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface LovelaceBaseViewConfig {

export interface LovelaceViewConfig extends LovelaceBaseViewConfig {
type?: string;
badges?: Array<string | LovelaceBadgeConfig>;
badges?: LovelaceBadgeConfig[];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should still support just strings, no?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes for backward compatibility, I reverted that change.

cards?: LovelaceCardConfig[];
sections?: LovelaceSectionRawConfig[];
}
Expand Down
16 changes: 16 additions & 0 deletions src/data/lovelace_custom_cards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ export interface CustomCardEntry {
documentationURL?: string;
}

export interface CustomBadgeEntry {
type: string;
name?: string;
description?: string;
preview?: boolean;
documentationURL?: string;
}

export interface CustomCardFeatureEntry {
type: string;
name?: string;
Expand All @@ -18,6 +26,7 @@ export interface CustomCardFeatureEntry {
export interface CustomCardsWindow {
customCards?: CustomCardEntry[];
customCardFeatures?: CustomCardFeatureEntry[];
customBadges?: CustomBadgeEntry[];
/**
* @deprecated Use customCardFeatures
*/
Expand All @@ -34,6 +43,9 @@ if (!("customCards" in customCardsWindow)) {
if (!("customCardFeatures" in customCardsWindow)) {
customCardsWindow.customCardFeatures = [];
}
if (!("customBadges" in customCardsWindow)) {
customCardsWindow.customBadges = [];
}
if (!("customTileFeatures" in customCardsWindow)) {
customCardsWindow.customTileFeatures = [];
}
Expand All @@ -43,10 +55,14 @@ export const getCustomCardFeatures = () => [
...customCardsWindow.customCardFeatures!,
...customCardsWindow.customTileFeatures!,
];
export const customBadges = customCardsWindow.customBadges!;

export const getCustomCardEntry = (type: string) =>
customCards.find((card) => card.type === type);

export const getCustomBadgeEntry = (type: string) =>
customBadges.find((badge) => badge.type === type);

export const isCustomType = (type: string) =>
type.startsWith(CUSTOM_TYPE_PREFIX);

Expand Down
200 changes: 200 additions & 0 deletions src/panels/lovelace/badges/hui-badge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { PropertyValues, ReactiveElement } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { MediaQueriesListener } from "../../../common/dom/media_query";
import "../../../components/ha-svg-icon";
import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
import type { HomeAssistant } from "../../../types";
import {
attachConditionMediaQueriesListeners,
checkConditionsMet,
} from "../common/validate-condition";
import { createBadgeElement } from "../create-element/create-badge-element";
import { createErrorBadgeConfig } from "../create-element/create-element-base";
import type { LovelaceBadge } from "../types";

declare global {
interface HASSDomEvents {
"badge-updated": undefined;
}
}

@customElement("hui-badge")
export class HuiBadge extends ReactiveElement {
@property({ type: Boolean }) public preview = false;

@property({ attribute: false }) public config?: LovelaceBadgeConfig;

@property({ attribute: false }) public hass?: HomeAssistant;

private _elementConfig?: LovelaceBadgeConfig;

public load() {
if (!this.config) {
throw new Error("Cannot build badge without config");
}
this._loadElement(this.config);
}

private _element?: LovelaceBadge;

private _listeners: MediaQueriesListener[] = [];

protected createRenderRoot() {
return this;
}

public disconnectedCallback() {
super.disconnectedCallback();
this._clearMediaQueries();
}

public connectedCallback() {
super.connectedCallback();
this._listenMediaQueries();
this._updateVisibility();
}

private _updateElement(config: LovelaceBadgeConfig) {
if (!this._element) {
return;
}
this._element.setConfig(config);
this._elementConfig = config;
fireEvent(this, "badge-updated");
}

private _loadElement(config: LovelaceBadgeConfig) {
this._element = createBadgeElement(config);
this._elementConfig = config;
if (this.hass) {
this._element.hass = this.hass;
}
this._element.addEventListener(
"ll-upgrade",
(ev: Event) => {
ev.stopPropagation();
if (this.hass) {
this._element!.hass = this.hass;
}
fireEvent(this, "badge-updated");
},
{ once: true }
);
this._element.addEventListener(
"ll-rebuild",
(ev: Event) => {
ev.stopPropagation();
this._loadElement(config);
fireEvent(this, "badge-updated");
},
{ once: true }
);
while (this.lastChild) {
this.removeChild(this.lastChild);
}
this._updateVisibility();
}

protected willUpdate(changedProps: PropertyValues<typeof this>): void {
super.willUpdate(changedProps);

if (!this._element) {
this.load();
}
}

protected update(changedProps: PropertyValues<typeof this>) {
super.update(changedProps);

if (this._element) {
if (changedProps.has("config")) {
const elementConfig = this._elementConfig;
if (this.config !== elementConfig && this.config) {
const typeChanged = this.config?.type !== elementConfig?.type;
if (typeChanged) {
this._loadElement(this.config);
} else {
this._updateElement(this.config);
}
}
}
if (changedProps.has("hass")) {
try {
if (this.hass) {
this._element.hass = this.hass;
}
} catch (e: any) {
this._loadElement(createErrorBadgeConfig(e.message, null));
}
}
}

if (changedProps.has("hass") || changedProps.has("preview")) {
this._updateVisibility();
}
}

private _clearMediaQueries() {
this._listeners.forEach((unsub) => unsub());
this._listeners = [];
}

private _listenMediaQueries() {
this._clearMediaQueries();
if (!this.config?.visibility) {
return;
}
const conditions = this.config.visibility;
const hasOnlyMediaQuery =
conditions.length === 1 &&
conditions[0].condition === "screen" &&
!!conditions[0].media_query;

this._listeners = attachConditionMediaQueriesListeners(
this.config.visibility,
(matches) => {
this._updateVisibility(hasOnlyMediaQuery && matches);
}
);
}

private _updateVisibility(forceVisible?: boolean) {
if (!this._element || !this.hass) {
return;
}

if (this._element.hidden) {
this._setElementVisibility(false);
return;
}

const visible =
forceVisible ||
this.preview ||
!this.config?.visibility ||
checkConditionsMet(this.config.visibility, this.hass);
this._setElementVisibility(visible);
}

private _setElementVisibility(visible: boolean) {
if (!this._element) return;

if (this.hidden !== !visible) {
this.style.setProperty("display", visible ? "" : "none");
this.toggleAttribute("hidden", !visible);
}

if (!visible && this._element.parentElement) {
this.removeChild(this._element);
} else if (visible && !this._element.parentElement) {
this.appendChild(this._element);
}
}
}

declare global {
interface HTMLElementTagNameMap {
"hui-badge": HuiBadge;
}
}
Loading
Loading