-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Custom badges #3867
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
Custom badges #3867
Changes from 9 commits
df2b2e2
43f2503
2fd3c16
23e3880
4bccd5d
1cf80f4
d6b5ca0
6cf88b5
693b8e2
ecac132
3e35bb3
2d5258e
170472a
2736654
1473415
2344e6c
ed5396f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| import { createBadgeElement } from "../common/create-badge-element"; | ||
| import { processConfigEntities } from "../common/process-config-entities"; | ||
| import { LovelaceBadge } from "../types"; | ||
| import { EntityFilterEntityConfig } from "../entity-rows/types"; | ||
| import { HomeAssistant } from "../../../types"; | ||
| import { EntityFilterBadgeConfig } from "./types"; | ||
| import { evaluateFilter } from "../common/evaluate-filter"; | ||
|
|
||
| class EntityFilterBadge extends HTMLElement implements LovelaceBadge { | ||
| public isPanel?: boolean; | ||
|
iantrich marked this conversation as resolved.
Outdated
|
||
| private _elements?: LovelaceBadge[]; | ||
| private _config?: EntityFilterBadgeConfig; | ||
| private _configEntities?: EntityFilterEntityConfig[]; | ||
| private _hass?: HomeAssistant; | ||
| private _oldEntities?: EntityFilterEntityConfig[]; | ||
|
|
||
| public setConfig(config: EntityFilterBadgeConfig): void { | ||
| if (!config.entities || !Array.isArray(config.entities)) { | ||
| throw new Error("entities must be specified."); | ||
| } | ||
|
|
||
| if ( | ||
| !(config.state_filter && Array.isArray(config.state_filter)) && | ||
| !config.entities.every( | ||
| (entity) => | ||
| typeof entity === "object" && | ||
| entity.state_filter && | ||
| Array.isArray(entity.state_filter) | ||
| ) | ||
| ) { | ||
| throw new Error("Incorrect filter config."); | ||
| } | ||
|
|
||
| this._config = config; | ||
| this._configEntities = undefined; | ||
|
|
||
| if (this.lastChild) { | ||
| this.removeChild(this.lastChild); | ||
| this._elements = undefined; | ||
| } | ||
| } | ||
|
|
||
| set hass(hass: HomeAssistant) { | ||
| if (!hass || !this._config) { | ||
| return; | ||
| } | ||
|
|
||
| if (!this.haveEntitiesChanged(hass)) { | ||
| this._hass = hass; | ||
|
iantrich marked this conversation as resolved.
|
||
|
|
||
| if (this._elements) { | ||
| for (const element of this._elements) { | ||
| element.hass = hass; | ||
| } | ||
| } | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| this._hass = hass; | ||
|
|
||
| if (!this._configEntities) { | ||
| this._configEntities = processConfigEntities(this._config.entities); | ||
| } | ||
|
|
||
| const entitiesList = this._configEntities.filter((entityConf) => { | ||
| const stateObj = hass.states[entityConf.entity]; | ||
|
|
||
| if (!stateObj) { | ||
| return false; | ||
| } | ||
|
|
||
| if (entityConf.state_filter) { | ||
| for (const filter of entityConf.state_filter) { | ||
| if (evaluateFilter(stateObj, filter)) { | ||
| return true; | ||
| } | ||
| } | ||
| } else { | ||
| for (const filter of this._config!.state_filter) { | ||
| if (evaluateFilter(stateObj, filter)) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| }); | ||
|
|
||
| if (entitiesList.length === 0) { | ||
| this.style.display = "none"; | ||
| return; | ||
| } | ||
|
|
||
| const isSame = | ||
| this._oldEntities && | ||
| entitiesList.length === this._oldEntities.length && | ||
| entitiesList.every((entity, idx) => entity === this._oldEntities![idx]); | ||
|
|
||
| if (!isSame) { | ||
| this._elements = []; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to remove all the old elements ?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You thinking we should keep a record of what is where and drop/add differences? Not sure if that's worth it
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| for (const badgeConfig of entitiesList) { | ||
| const element = createBadgeElement(badgeConfig); | ||
| this._elements.push(element); | ||
| } | ||
| this._oldEntities = entitiesList; | ||
| } | ||
|
|
||
| if (!this._elements) { | ||
| return; | ||
| } | ||
|
|
||
| for (const element of this._elements) { | ||
| element.hass = hass; | ||
|
iantrich marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| // Attach element if it has never been attached. | ||
| if (!this.lastChild) { | ||
| for (const element of this._elements) { | ||
| this.appendChild(element); | ||
| } | ||
| } | ||
|
|
||
| this.style.display = "inline"; | ||
|
iantrich marked this conversation as resolved.
|
||
| } | ||
|
|
||
| private haveEntitiesChanged(hass: HomeAssistant): boolean { | ||
| if (!this._hass) { | ||
| return true; | ||
| } | ||
|
|
||
| if (!this._configEntities || this._hass.localize !== hass.localize) { | ||
| return true; | ||
| } | ||
|
|
||
| for (const config of this._configEntities) { | ||
| if (this._hass.states[config.entity] !== hass.states[config.entity]) { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
| } | ||
| customElements.define("hui-entity-filter-badge", EntityFilterBadge); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import { | ||
| html, | ||
| LitElement, | ||
| TemplateResult, | ||
| customElement, | ||
| property, | ||
| css, | ||
| CSSResult, | ||
| } from "lit-element"; | ||
|
|
||
| import { LovelaceBadge } from "../types"; | ||
| import { HomeAssistant } from "../../../types"; | ||
| import { ErrorBadgeConfig } from "./types"; | ||
|
|
||
| import "../../../components/ha-label-badge"; | ||
|
|
||
| export const createErrorBadgeElement = (config) => { | ||
| const el = document.createElement("hui-error-badge"); | ||
| el.setConfig(config); | ||
| return el; | ||
| }; | ||
|
|
||
| export const createErrorBadgeConfig = (error) => ({ | ||
| type: "error", | ||
| error, | ||
| }); | ||
|
|
||
| @customElement("hui-error-badge") | ||
| export class HuiErrorBadge extends LitElement implements LovelaceBadge { | ||
| public hass?: HomeAssistant; | ||
|
|
||
| @property() private _config?: ErrorBadgeConfig; | ||
|
|
||
| public setConfig(config: ErrorBadgeConfig): void { | ||
| this._config = config; | ||
| } | ||
|
|
||
| protected render(): TemplateResult | void { | ||
| if (!this._config) { | ||
| return html``; | ||
| } | ||
|
|
||
| return html` | ||
| <ha-label-badge | ||
| label="Error" | ||
| icon="hass:alert" | ||
| description=${this._config.error} | ||
| ></ha-label-badge> | ||
| `; | ||
| } | ||
|
|
||
| static get styles(): CSSResult { | ||
| return css` | ||
| :host { | ||
| --ha-label-badge-color: var(--label-badge-yellow, #fce588); | ||
|
iantrich marked this conversation as resolved.
Outdated
|
||
| } | ||
| `; | ||
| } | ||
| } | ||
|
|
||
| declare global { | ||
| interface HTMLElementTagNameMap { | ||
| "hui-error-badge": HuiErrorBadge; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import { | ||
| html, | ||
| LitElement, | ||
| TemplateResult, | ||
| customElement, | ||
| property, | ||
| } from "lit-element"; | ||
|
|
||
| import "../../../components/entity/ha-state-label-badge"; | ||
| import "../components/hui-warning-element"; | ||
|
|
||
| import { LovelaceBadge } from "../types"; | ||
| import { HomeAssistant } from "../../../types"; | ||
| import { computeStateName } from "../../../common/entity/compute_state_name"; | ||
| import { LovelaceBadgeConfig } from "../../../data/lovelace"; | ||
|
|
||
| @customElement("hui-state-label-badge") | ||
| export class HuiStateLabelBadge extends LitElement implements LovelaceBadge { | ||
| @property() public hass?: HomeAssistant; | ||
| @property() protected _config?: LovelaceBadgeConfig; | ||
|
|
||
| public setConfig(config: LovelaceBadgeConfig): void { | ||
| this._config = config; | ||
| } | ||
|
|
||
| protected render(): TemplateResult | void { | ||
| if (!this._config || !this.hass) { | ||
| return html``; | ||
| } | ||
|
|
||
| const stateObj = this.hass.states[this._config.entity!]; | ||
|
|
||
| return html` | ||
| <ha-state-label-badge | ||
| .hass=${this.hass} | ||
| .state=${stateObj} | ||
| .title=${this._config.name === undefined | ||
| ? stateObj | ||
| ? computeStateName(stateObj) | ||
| : "" | ||
| : this._config.name === null | ||
| ? "" | ||
| : this._config.name} | ||
|
iantrich marked this conversation as resolved.
Outdated
|
||
| .icon=${this._config.icon} | ||
| .image=${this._config.image} | ||
| ></ha-state-label-badge> | ||
| `; | ||
| } | ||
| } | ||
|
|
||
| declare global { | ||
| interface HTMLElementTagNameMap { | ||
| "hui-state-label-badge": HuiStateLabelBadge; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { LovelaceBadgeConfig } from "../../../data/lovelace"; | ||
| import { EntityFilterEntityConfig } from "../entity-rows/types"; | ||
|
|
||
| export interface EntityFilterBadgeConfig extends LovelaceBadgeConfig { | ||
| type: "entity-filter"; | ||
| entities: Array<EntityFilterEntityConfig | string>; | ||
| state_filter: Array<{ key: string } | string>; | ||
| } | ||
|
|
||
| export interface ErrorBadgeConfig extends LovelaceBadgeConfig { | ||
| error: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import deepClone from "deep-clone-simple"; | ||
|
|
||
| import "../badges/hui-entity-filter-badge"; | ||
| import "../badges/hui-state-label-badge"; | ||
|
|
||
| import { | ||
| createErrorBadgeElement, | ||
| createErrorBadgeConfig, | ||
| HuiErrorBadge, | ||
| } from "../badges/hui-error-badge"; | ||
| import { LovelaceBadge } from "../types"; | ||
| import { LovelaceBadgeConfig } from "../../../data/lovelace"; | ||
|
|
||
| const BADGE_TYPES = new Set(["entity-filter", "error", "state-label"]); | ||
| const CUSTOM_TYPE_PREFIX = "custom:"; | ||
| const TIMEOUT = 2000; | ||
|
|
||
| const _createElement = ( | ||
| tag: string, | ||
| config: LovelaceBadgeConfig | ||
| ): LovelaceBadge | HuiErrorBadge => { | ||
|
iantrich marked this conversation as resolved.
Outdated
|
||
| const element = document.createElement(tag) as LovelaceBadge; | ||
| try { | ||
| element.setConfig(deepClone(config)); | ||
|
iantrich marked this conversation as resolved.
Outdated
|
||
| } catch (err) { | ||
| // tslint:disable-next-line | ||
| console.error(tag, err); | ||
| return _createErrorElement(err.message); | ||
| } | ||
| return element; | ||
| }; | ||
|
|
||
| const _createErrorElement = (error: string): HuiErrorBadge => | ||
| createErrorBadgeElement(createErrorBadgeConfig(error)); | ||
|
|
||
| export const createBadgeElement = ( | ||
| config: LovelaceBadgeConfig | ||
| ): LovelaceBadge | HuiErrorBadge => { | ||
|
iantrich marked this conversation as resolved.
Outdated
|
||
| if (!config || typeof config !== "object") { | ||
| return _createErrorElement("No config"); | ||
| } | ||
|
|
||
| let type = config.type; | ||
|
|
||
| if (!type) { | ||
| type = "state-label"; | ||
| } | ||
|
|
||
| if (type.startsWith(CUSTOM_TYPE_PREFIX)) { | ||
| const tag = type.substr(CUSTOM_TYPE_PREFIX.length); | ||
|
|
||
| if (customElements.get(tag)) { | ||
| return _createElement(tag, config); | ||
| } | ||
| const element = _createErrorElement(`Type doesn't exist: ${tag}`); | ||
| element.style.display = "None"; | ||
| const timer = window.setTimeout(() => { | ||
| element.style.display = ""; | ||
| }, TIMEOUT); | ||
|
|
||
| customElements.whenDefined(tag).then(() => { | ||
| clearTimeout(timer); | ||
|
iantrich marked this conversation as resolved.
|
||
| }); | ||
|
|
||
| return element; | ||
| } | ||
|
|
||
| if (!BADGE_TYPES.has(type)) { | ||
| return _createErrorElement(`Unknown type: ${type}`); | ||
| } | ||
|
|
||
| return _createElement(`hui-${type}-badge`, config); | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.