diff --git a/.hass_dev/lovelace-mushroom-showcase.yaml b/.hass_dev/lovelace-mushroom-showcase.yaml
index a67e95208..9a7d201f6 100644
--- a/.hass_dev/lovelace-mushroom-showcase.yaml
+++ b/.hass_dev/lovelace-mushroom-showcase.yaml
@@ -6,5 +6,6 @@ views:
- !include views/person-view.yaml
- !include views/alarm-control-panel-view.yaml
- !include views/fan-view.yaml
- - !include views/chips-view.yaml
- - !include views/sensor-view.yaml
\ No newline at end of file
+ - !include views/template-view.yaml
+ - !include views/sensor-view.yaml
+ - !include views/chips-view.yaml
\ No newline at end of file
diff --git a/.hass_dev/views/chips-view.yaml b/.hass_dev/views/chips-view.yaml
index c83bfdb9f..ff2150f8e 100644
--- a/.hass_dev/views/chips-view.yaml
+++ b/.hass_dev/views/chips-view.yaml
@@ -90,4 +90,31 @@ cards:
icon: mdi:github
tap_action:
action: url
- url_path: https://github.com/piitaya/mushroom-cards
\ No newline at end of file
+ url_path: https://github.com/piitaya/mushroom-cards
+ - type: vertical-stack
+ title: Template chip
+ cards:
+ - type: custom:mushroom-chips-card
+ chips:
+ - type: template
+ content: |
+ {% if is_state('light.bed_light', 'on') %}
+ The bedroom light is on !
+ {% else %}
+ The bedroom light is not on !
+ {% endif %}
+ icon: |
+ {% if is_state('light.bed_light', 'on') %}
+ mdi:ceiling-light
+ {% else %}
+ mdi:ceiling-light-outline
+ {% endif %}
+ tap_action:
+ action: call-service
+ service: light.toggle
+ target:
+ entity_id: light.bed_light
+ - type: template
+ content: |
+ {{ states | count }} entities
+ icon: mdi:format-list-bulleted
\ No newline at end of file
diff --git a/.hass_dev/views/template-view.yaml b/.hass_dev/views/template-view.yaml
new file mode 100644
index 000000000..c6990df24
--- /dev/null
+++ b/.hass_dev/views/template-view.yaml
@@ -0,0 +1,35 @@
+
+
+title: Switch
+icon: mdi:card-text
+cards:
+ - type: grid
+ title: Simple
+ cards:
+ - type: custom:mushroom-template-card
+ name: Hello, {{user}}
+ state: How are you?
+ icon: mdi:home
+ - type: custom:mushroom-template-card
+ name: Number of entities
+ state: |
+ {{ states | count }} entities
+ icon: mdi:format-list-bulleted
+ columns: 2
+ square: false
+ - type: grid
+ title: Vertical
+ cards:
+ - type: custom:mushroom-template-card
+ name: Hello, {{user}}
+ state: How are you?
+ icon: mdi:home
+ vertical: true
+ - type: custom:mushroom-template-card
+ name: Number of entities
+ state: |
+ {{ states | count }} entities
+ icon: mdi:format-list-bulleted
+ vertical: true
+ columns: 2
+ square: false
\ No newline at end of file
diff --git a/src/cards/chips-card/chips-card-chips-editor.ts b/src/cards/chips-card/chips-card-chips-editor.ts
index 69f885223..b17564d43 100644
--- a/src/cards/chips-card/chips-card-chips-editor.ts
+++ b/src/cards/chips-card/chips-card-chips-editor.ts
@@ -130,6 +130,7 @@ export class ChipsCardEditorChips extends LitElement {
Entity
Weather
Action
+ Template
`;
diff --git a/src/cards/chips-card/chips-card.ts b/src/cards/chips-card/chips-card.ts
index 805ebdf27..398b62754 100644
--- a/src/cards/chips-card/chips-card.ts
+++ b/src/cards/chips-card/chips-card.ts
@@ -9,7 +9,13 @@ import { customElement, property, state } from "lit/decorators.js";
import { registerCustomCard } from "../../utils/custom-cards";
import { CHIPS_CARD_EDITOR_NAME, CHIPS_CARD_NAME } from "./const";
import "../../shared/chip";
-import { BackChip, createChipElement, EntityChip, WeatherChip } from "./chips";
+import {
+ BackChip,
+ createChipElement,
+ EntityChip,
+ LovelaceChip,
+ WeatherChip,
+} from "./chips";
import "./chips";
import "./chips-card-editor";
import { LovelaceChipConfig } from "../../utils/lovelace/chip/types";
@@ -45,10 +51,19 @@ export class ChipsCard extends LitElement implements LovelaceCard {
};
}
- @property({ attribute: false }) public hass!: HomeAssistant;
-
@state() private _config?: ChipsCardConfig;
+ private _hass?: HomeAssistant;
+
+ set hass(hass: HomeAssistant) {
+ this._hass = hass;
+ this.shadowRoot
+ ?.querySelectorAll("div > *")
+ .forEach((element: unknown) => {
+ (element as LovelaceChip).hass = hass;
+ });
+ }
+
getCardSize(): number | Promise {
return 1;
}
@@ -58,7 +73,7 @@ export class ChipsCard extends LitElement implements LovelaceCard {
}
protected render(): TemplateResult {
- if (!this._config || !this.hass) {
+ if (!this._config || !this._hass) {
return html``;
}
@@ -74,10 +89,10 @@ export class ChipsCard extends LitElement implements LovelaceCard {
if (!element) {
return html``;
}
- if (this.hass) {
- element.hass = this.hass;
+ if (this._hass) {
+ element.hass = this._hass;
}
- return html`${element}`;
+ return html`${element}
`;
}
static get styles(): CSSResultGroup {
diff --git a/src/cards/chips-card/chips/index.ts b/src/cards/chips-card/chips/index.ts
index 2ee8e7f2d..7f2dfe47a 100644
--- a/src/cards/chips-card/chips/index.ts
+++ b/src/cards/chips-card/chips/index.ts
@@ -29,3 +29,4 @@ export { WeatherChip } from "./weather-chip";
export { BackChip } from "./back-chip";
export { ActionChip } from "./action-chip";
export { MenuChip } from "./menu-chip";
+export { TemplateChip } from "./template-chip";
diff --git a/src/cards/chips-card/chips/template-chip-editor.ts b/src/cards/chips-card/chips/template-chip-editor.ts
new file mode 100644
index 000000000..4a3ff39f1
--- /dev/null
+++ b/src/cards/chips-card/chips/template-chip-editor.ts
@@ -0,0 +1,131 @@
+import { fireEvent, HomeAssistant } from "custom-card-helpers";
+import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
+import { customElement, property, state } from "lit/decorators.js";
+import setupCustomlocalize from "../../../localize";
+import { configElementStyle } from "../../../utils/editor-styles";
+import { TemplateChipConfig } from "../../../utils/lovelace/chip/types";
+import { EditorTarget } from "../../../utils/lovelace/editor/types";
+import { LovelaceChipEditor } from "../../../utils/lovelace/types";
+import { computeChipEditorComponentName } from "../utils";
+
+const actions = ["navigate", "url", "call-service", "none"];
+
+@customElement(computeChipEditorComponentName("template"))
+export class EntityChipEditor extends LitElement implements LovelaceChipEditor {
+ @property({ attribute: false }) public hass?: HomeAssistant;
+
+ @state() private _config?: TemplateChipConfig;
+
+ public setConfig(config: TemplateChipConfig): void {
+ this._config = config;
+ }
+
+ protected render(): TemplateResult {
+ if (!this.hass || !this._config) {
+ return html``;
+ }
+
+ const customlocalize = setupCustomlocalize(this.hass);
+
+ return html`
+
+ `;
+ }
+
+ private _ignoreKeydown(ev: KeyboardEvent) {
+ // Stop keyboard events from the paper-textarea from propagating to avoid accidentally closing the dialog when the user presses Enter.
+ ev.stopPropagation();
+ }
+
+ private _valueChanged(ev: CustomEvent): void {
+ if (!this._config || !this.hass) {
+ return;
+ }
+ const target = ev.target! as EditorTarget;
+ const value =
+ target.checked !== undefined ? target.checked : ev.detail.value;
+
+ if (this[`_${target.configValue}`] === value) {
+ return;
+ }
+
+ let newConfig;
+ if (target.configValue) {
+ if (!value) {
+ newConfig = { ...this._config };
+ delete newConfig[target.configValue!];
+ } else {
+ newConfig = {
+ ...this._config,
+ [target.configValue!]: value,
+ };
+ }
+ }
+ fireEvent(this, "config-changed", { config: newConfig });
+ }
+
+ static get styles(): CSSResultGroup {
+ return configElementStyle;
+ }
+}
diff --git a/src/cards/chips-card/chips/template-chip.ts b/src/cards/chips-card/chips/template-chip.ts
new file mode 100644
index 000000000..68cd087ca
--- /dev/null
+++ b/src/cards/chips-card/chips/template-chip.ts
@@ -0,0 +1,198 @@
+import {
+ ActionHandlerEvent,
+ handleAction,
+ HomeAssistant,
+} from "custom-card-helpers";
+import { UnsubscribeFunc } from "home-assistant-js-websocket";
+import {
+ css,
+ CSSResultGroup,
+ html,
+ LitElement,
+ PropertyValues,
+ TemplateResult,
+} from "lit";
+import { customElement, property, state } from "lit/decorators.js";
+import { LovelaceChip } from ".";
+import { actionHandler } from "../../../utils/directives/action-handler-directive";
+import { TemplateChipConfig } from "../../../utils/lovelace/chip/types";
+import { LovelaceChipEditor } from "../../../utils/lovelace/types";
+import {
+ RenderTemplateResult,
+ subscribeRenderTemplate,
+} from "../../../utils/ws-templates";
+import {
+ computeChipComponentName,
+ computeChipEditorComponentName,
+} from "../utils";
+import "./menu-chip-editor";
+import "./template-chip-editor";
+
+const TEMPLATE_KEYS = ["content", "icon"] as const;
+type TemplateKey = typeof TEMPLATE_KEYS[number];
+
+@customElement(computeChipComponentName("template"))
+export class TemplateChip extends LitElement implements LovelaceChip {
+ public static async getConfigElement(): Promise {
+ return document.createElement(
+ computeChipEditorComponentName("template")
+ ) as LovelaceChipEditor;
+ }
+
+ public static async getStubConfig(
+ _hass: HomeAssistant
+ ): Promise {
+ return {
+ type: `template`,
+ };
+ }
+
+ @property({ attribute: false }) public hass?: HomeAssistant;
+
+ @state() private _config?: TemplateChipConfig;
+
+ @state() private _templateResults: Partial<
+ Record
+ > = {};
+
+ @state() private _unsubRenderTemplates: Map<
+ TemplateKey,
+ Promise
+ > = new Map();
+
+ public setConfig(config: TemplateChipConfig): void {
+ TEMPLATE_KEYS.forEach((key) => {
+ if (this._config?.[key] !== config[key]) {
+ this._tryDisconnectKey(key);
+ }
+ });
+ this._config = config;
+ }
+
+ public connectedCallback() {
+ super.connectedCallback();
+ this._tryConnect();
+ }
+
+ public disconnectedCallback() {
+ this._tryDisconnect();
+ }
+
+ private _handleAction(ev: ActionHandlerEvent) {
+ handleAction(this, this.hass!, this._config!, ev.detail.action!);
+ }
+
+ protected render(): TemplateResult {
+ if (!this.hass || !this._config) {
+ return html``;
+ }
+
+ const icon = this._templateResults.icon?.result;
+ const content = this._templateResults.content?.result;
+
+ return html`
+
+ ${icon ? html`` : null}
+ ${content ? html`${content}` : null}
+
+ `;
+ }
+
+ protected updated(changedProps: PropertyValues): void {
+ super.updated(changedProps);
+ if (!this._config || !this.hass) {
+ return;
+ }
+
+ this._tryConnect();
+ }
+
+ private async _tryConnect(): Promise {
+ TEMPLATE_KEYS.forEach((key) => {
+ this._tryConnectKey(key);
+ });
+ }
+
+ private async _tryConnectKey(key: TemplateKey): Promise {
+ if (
+ this._unsubRenderTemplates.get(key) !== undefined ||
+ !this.hass ||
+ !this._config
+ ) {
+ return;
+ }
+
+ try {
+ this._unsubRenderTemplates.set(
+ key,
+ subscribeRenderTemplate(
+ this.hass.connection,
+ (result) => {
+ this._templateResults = {
+ ...this._templateResults,
+ [key]: result,
+ };
+ },
+ {
+ template: this._config[key] ?? "",
+ entity_ids: this._config.entity_id,
+ variables: {
+ config: this._config,
+ user: this.hass.user!.name,
+ },
+ }
+ )
+ );
+ } catch (_err) {
+ const result = {
+ result: this._config[key] ?? "",
+ listeners: {
+ all: false,
+ domains: [],
+ entities: [],
+ time: false,
+ },
+ };
+ this._templateResults = {
+ ...this._templateResults,
+ [key]: result,
+ };
+ this._unsubRenderTemplates.delete(key);
+ }
+ }
+ private async _tryDisconnect(): Promise {
+ TEMPLATE_KEYS.forEach((key) => {
+ this._tryDisconnectKey(key);
+ });
+ }
+
+ private async _tryDisconnectKey(key: TemplateKey): Promise {
+ const unsubRenderTemplate = this._unsubRenderTemplates.get(key);
+ if (!unsubRenderTemplate) {
+ return;
+ }
+
+ try {
+ const unsub = await unsubRenderTemplate;
+ unsub();
+ this._unsubRenderTemplates.delete(key);
+ } catch (err: any) {
+ if (err.code === "not_found") {
+ // If we get here, the connection was probably already closed. Ignore.
+ } else {
+ throw err;
+ }
+ }
+ }
+
+ static get styles(): CSSResultGroup {
+ return css`
+ mushroom-chip {
+ cursor: pointer;
+ }
+ `;
+ }
+}
diff --git a/src/cards/template-card/const.ts b/src/cards/template-card/const.ts
new file mode 100644
index 000000000..62e68b32a
--- /dev/null
+++ b/src/cards/template-card/const.ts
@@ -0,0 +1,4 @@
+import { PREFIX_NAME } from "../../const";
+
+export const TEMPLATE_CARD_NAME = `${PREFIX_NAME}-template-card`;
+export const TEMPLATE_CARD_EDITOR_NAME = `${TEMPLATE_CARD_NAME}-editor`;
diff --git a/src/cards/template-card/template-card-config.ts b/src/cards/template-card/template-card-config.ts
new file mode 100644
index 000000000..bddb5277d
--- /dev/null
+++ b/src/cards/template-card/template-card-config.ts
@@ -0,0 +1,35 @@
+import { ActionConfig, LovelaceCardConfig } from "custom-card-helpers";
+import {
+ array,
+ assign,
+ boolean,
+ object,
+ optional,
+ string,
+ union,
+} from "superstruct";
+import { actionConfigStruct } from "../../utils/action-struct";
+import { baseLovelaceCardConfig } from "../../utils/editor-styles";
+
+export interface TemplateCardConfig extends LovelaceCardConfig {
+ name?: string;
+ icon?: string;
+ state?: string;
+ vertical?: boolean;
+ tap_action?: ActionConfig;
+ hold_action?: ActionConfig;
+ entity_id?: string | string[];
+}
+
+export const templateCardConfigStruct = assign(
+ baseLovelaceCardConfig,
+ object({
+ name: optional(string()),
+ icon: optional(string()),
+ state: optional(string()),
+ vertical: optional(boolean()),
+ tap_action: optional(actionConfigStruct),
+ hold_action: optional(actionConfigStruct),
+ entity_id: optional(union([string(), array(string())])),
+ })
+);
diff --git a/src/cards/template-card/template-card-editor.ts b/src/cards/template-card/template-card-editor.ts
new file mode 100644
index 000000000..6ef84e3f6
--- /dev/null
+++ b/src/cards/template-card/template-card-editor.ts
@@ -0,0 +1,158 @@
+import {
+ computeRTLDirection,
+ fireEvent,
+ HomeAssistant,
+ LovelaceCardEditor,
+ stateIcon,
+} from "custom-card-helpers";
+import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
+import { customElement, property, state } from "lit/decorators.js";
+import { assert } from "superstruct";
+import setupCustomlocalize from "../../localize";
+import { configElementStyle } from "../../utils/editor-styles";
+import { EditorTarget } from "../../utils/lovelace/editor/types";
+import { TEMPLATE_CARD_EDITOR_NAME } from "./const";
+import {
+ TemplateCardConfig,
+ templateCardConfigStruct,
+} from "./template-card-config";
+
+const actions = ["more-info", "navigate", "url", "call-service", "none"];
+
+@customElement(TEMPLATE_CARD_EDITOR_NAME)
+export class TemplateCardEditor
+ extends LitElement
+ implements LovelaceCardEditor
+{
+ @property({ attribute: false }) public hass?: HomeAssistant;
+
+ @state() private _config?: TemplateCardConfig;
+
+ public setConfig(config: TemplateCardConfig): void {
+ assert(config, templateCardConfigStruct);
+ this._config = config;
+ }
+
+ protected render(): TemplateResult {
+ if (!this.hass || !this._config) {
+ return html``;
+ }
+
+ const customlocalize = setupCustomlocalize(this.hass);
+
+ return html`
+
+ `;
+ }
+
+ private _ignoreKeydown(ev: KeyboardEvent) {
+ // Stop keyboard events from the paper-textarea from propagating to avoid accidentally closing the dialog when the user presses Enter.
+ ev.stopPropagation();
+ }
+
+ private _valueChanged(ev: CustomEvent): void {
+ if (!this._config || !this.hass) {
+ return;
+ }
+ const target = ev.target! as EditorTarget;
+ const value =
+ target.checked !== undefined ? target.checked : ev.detail.value;
+
+ if (this[`_${target.configValue}`] === value) {
+ return;
+ }
+
+ let newConfig;
+ if (target.configValue) {
+ if (!value) {
+ newConfig = { ...this._config };
+ delete newConfig[target.configValue!];
+ } else {
+ newConfig = {
+ ...this._config,
+ [target.configValue!]: value,
+ };
+ }
+ }
+ fireEvent(this, "config-changed", { config: newConfig });
+ }
+
+ static get styles(): CSSResultGroup {
+ return configElementStyle;
+ }
+}
diff --git a/src/cards/template-card/template-card.ts b/src/cards/template-card/template-card.ts
new file mode 100644
index 000000000..48224ba3f
--- /dev/null
+++ b/src/cards/template-card/template-card.ts
@@ -0,0 +1,249 @@
+import {
+ ActionHandlerEvent,
+ computeStateDisplay,
+ handleAction,
+ hasAction,
+ HomeAssistant,
+ LovelaceCard,
+ LovelaceCardEditor,
+ stateIcon,
+} from "custom-card-helpers";
+import {
+ css,
+ CSSResultGroup,
+ html,
+ LitElement,
+ PropertyValues,
+ TemplateResult,
+} from "lit";
+import { customElement, property, state } from "lit/decorators.js";
+import "../../shared/shape-icon";
+import "../../shared/state-info";
+import "../../shared/state-item";
+import { cardStyle } from "../../utils/card-styles";
+import { registerCustomCard } from "../../utils/custom-cards";
+import { actionHandler } from "../../utils/directives/action-handler-directive";
+import { TEMPLATE_CARD_EDITOR_NAME, TEMPLATE_CARD_NAME } from "./const";
+import { TemplateCardConfig } from "./template-card-config";
+import "./template-card-editor";
+import {
+ RenderTemplateResult,
+ subscribeRenderTemplate,
+} from "../../utils/ws-templates";
+import { UnsubscribeFunc } from "home-assistant-js-websocket";
+
+registerCustomCard({
+ type: TEMPLATE_CARD_NAME,
+ name: "Mushroom Template Card",
+ description: "Card for custom rendering with templates",
+});
+
+const TEMPLATE_KEYS = ["state", "icon", "name"] as const;
+type TemplateKey = typeof TEMPLATE_KEYS[number];
+
+@customElement(TEMPLATE_CARD_NAME)
+export class TemplateCard extends LitElement implements LovelaceCard {
+ public static async getConfigElement(): Promise {
+ return document.createElement(
+ TEMPLATE_CARD_EDITOR_NAME
+ ) as LovelaceCardEditor;
+ }
+
+ public static async getStubConfig(
+ _hass: HomeAssistant
+ ): Promise {
+ return {
+ type: `custom:${TEMPLATE_CARD_NAME}`,
+ name: "Hello, {{user}}",
+ state: "How are you?",
+ icon: "mdi:home",
+ };
+ }
+
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @state() private _config?: TemplateCardConfig;
+
+ @state() private _templateResults: Partial<
+ Record
+ > = {};
+
+ @state() private _unsubRenderTemplates: Map<
+ TemplateKey,
+ Promise
+ > = new Map();
+
+ getCardSize(): number | Promise {
+ return 1;
+ }
+
+ setConfig(config: TemplateCardConfig): void {
+ TEMPLATE_KEYS.forEach((key) => {
+ if (this._config?.[key] !== config[key]) {
+ this._tryDisconnectKey(key);
+ }
+ });
+ this._config = {
+ tap_action: {
+ action: "toggle",
+ },
+ hold_action: {
+ action: "more-info",
+ },
+ ...config,
+ };
+ }
+
+ public connectedCallback() {
+ super.connectedCallback();
+ this._tryConnect();
+ }
+
+ public disconnectedCallback() {
+ this._tryDisconnect();
+ }
+
+ private _handleAction(ev: ActionHandlerEvent) {
+ handleAction(this, this.hass!, this._config!, ev.detail.action!);
+ }
+
+ protected render(): TemplateResult {
+ if (!this._config || !this.hass) {
+ return html``;
+ }
+
+ const name = this._templateResults.name?.result;
+ const icon = this._templateResults.icon?.result;
+ const state = this._templateResults.state?.result;
+
+ const vertical = this._config.vertical;
+
+ return html`
+
+
+
+
+
+
+ `;
+ }
+
+ protected updated(changedProps: PropertyValues): void {
+ super.updated(changedProps);
+ if (!this._config || !this.hass) {
+ return;
+ }
+
+ this._tryConnect();
+ }
+
+ private async _tryConnect(): Promise {
+ TEMPLATE_KEYS.forEach((key) => {
+ this._tryConnectKey(key);
+ });
+ }
+
+ private async _tryConnectKey(key: TemplateKey): Promise {
+ if (
+ this._unsubRenderTemplates.get(key) !== undefined ||
+ !this.hass ||
+ !this._config
+ ) {
+ return;
+ }
+
+ try {
+ this._unsubRenderTemplates.set(
+ key,
+ subscribeRenderTemplate(
+ this.hass.connection,
+ (result) => {
+ this._templateResults = {
+ ...this._templateResults,
+ [key]: result,
+ };
+ },
+ {
+ template: this._config[key] ?? "",
+ entity_ids: this._config.entity_id,
+ variables: {
+ config: this._config,
+ user: this.hass.user!.name,
+ },
+ }
+ )
+ );
+ } catch (_err) {
+ const result = {
+ result: this._config[key] ?? "",
+ listeners: {
+ all: false,
+ domains: [],
+ entities: [],
+ time: false,
+ },
+ };
+ this._templateResults = {
+ ...this._templateResults,
+ [key]: result,
+ };
+ this._unsubRenderTemplates.delete(key);
+ }
+ }
+ private async _tryDisconnect(): Promise {
+ TEMPLATE_KEYS.forEach((key) => {
+ this._tryDisconnectKey(key);
+ });
+ }
+
+ private async _tryDisconnectKey(key: TemplateKey): Promise {
+ const unsubRenderTemplate = this._unsubRenderTemplates.get(key);
+ if (!unsubRenderTemplate) {
+ return;
+ }
+
+ try {
+ const unsub = await unsubRenderTemplate;
+ unsub();
+ this._unsubRenderTemplates.delete(key);
+ } catch (err: any) {
+ if (err.code === "not_found") {
+ // If we get here, the connection was probably already closed. Ignore.
+ } else {
+ throw err;
+ }
+ }
+ }
+
+ static get styles(): CSSResultGroup {
+ return [
+ cardStyle,
+ css`
+ :host {
+ --rgb-color: 61, 90, 254;
+ }
+ mushroom-state-item {
+ cursor: pointer;
+ }
+ mushroom-shape-icon {
+ --icon-color: rgba(var(--rgb-color), 1);
+ --shape-color: rgba(var(--rgb-color), 0.2);
+ }
+ `,
+ ];
+ }
+}
diff --git a/src/index.ts b/src/index.ts
index 37c675233..5ba048b22 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -8,6 +8,7 @@ export { LightCard } from "./cards/light-card/light-card";
export { PersonCard } from "./cards/person-card/person-card";
export { SensorCard } from "./cards/sensor-card/sensor-card";
export { SwitchCard } from "./cards/switch-card/switch-card";
+export { TemplateCard } from "./cards/template-card/template-card";
console.info(
`%c🍄 Mushroom 🍄 - ${version}`,
diff --git a/src/translations/en.json b/src/translations/en.json
index b0e278454..c7fec69de 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -3,7 +3,8 @@
"card": {
"generic": {
"vertical": "Vertical?",
- "hide_state": "Hide state?"
+ "hide_state": "Hide state?",
+ "state": "State"
},
"light": {
"show_brightness_control": "Brightness control?",
@@ -33,12 +34,15 @@
"edit": "Edit",
"clear": "Clear"
},
+ "generic": {
+ "icon_color": "Icon color"
+ },
"weather": {
"show_conditions": "Conditions?",
"show_temperature": "Temperature?"
},
- "generic": {
- "icon_color": "Icon color"
+ "template": {
+ "content": "Content"
}
}
}
diff --git a/src/translations/fr.json b/src/translations/fr.json
index ccd9e4f1b..20d97283d 100644
--- a/src/translations/fr.json
+++ b/src/translations/fr.json
@@ -3,7 +3,8 @@
"card": {
"generic": {
"vertical": "Vertical ?",
- "hide_state": "Cacher l'état ?"
+ "hide_state": "Cacher l'état ?",
+ "state": "État"
},
"light": {
"show_brightness_control": "Contrôle de luminosité ?",
@@ -33,12 +34,15 @@
"edit": "Modifier",
"clear": "Effacer"
},
+ "generic": {
+ "icon_color": "Couleur de l'icône"
+ },
"weather": {
"show_conditons": "Conditions ?",
"show_temperature": "Température ?"
},
- "generic": {
- "icon_color": "Couleur de l'icône"
+ "template": {
+ "content": "Contenu"
}
}
}
diff --git a/src/utils/lovelace/chip/types.ts b/src/utils/lovelace/chip/types.ts
index d6346eaa2..ca8795f29 100644
--- a/src/utils/lovelace/chip/types.ts
+++ b/src/utils/lovelace/chip/types.ts
@@ -38,9 +38,19 @@ export type WeatherChipConfig = {
show_conditions?: boolean;
};
+export type TemplateChipConfig = {
+ type: "template";
+ hold_action?: ActionConfig;
+ tap_action?: ActionConfig;
+ content?: string;
+ icon?: string;
+ entity_id?: string | string[];
+};
+
export type LovelaceChipConfig =
| ActionChipConfig
| BackChipConfig
| EntityChipConfig
| MenuChipConfig
- | WeatherChipConfig;
+ | WeatherChipConfig
+ | TemplateChipConfig;
diff --git a/src/utils/ws-templates.ts b/src/utils/ws-templates.ts
new file mode 100644
index 000000000..3893fb7a0
--- /dev/null
+++ b/src/utils/ws-templates.ts
@@ -0,0 +1,28 @@
+import { Connection, UnsubscribeFunc } from "home-assistant-js-websocket";
+
+export interface RenderTemplateResult {
+ result: string;
+ listeners: TemplateListeners;
+}
+
+interface TemplateListeners {
+ all: boolean;
+ domains: string[];
+ entities: string[];
+ time: boolean;
+}
+
+export const subscribeRenderTemplate = (
+ conn: Connection,
+ onChange: (result: RenderTemplateResult) => void,
+ params: {
+ template: string;
+ entity_ids?: string | string[];
+ variables?: Record;
+ timeout?: number;
+ }
+): Promise =>
+ conn.subscribeMessage((msg: RenderTemplateResult) => onChange(msg), {
+ type: "render_template",
+ ...params,
+ });