mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-08 18:39:40 +00:00
Compare commits
9 Commits
20250901.0
...
tile-templ
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a4080f866 | ||
|
|
664ace6644 | ||
|
|
bd89553008 | ||
|
|
64993ff409 | ||
|
|
273aa3db5c | ||
|
|
75ff4a8c04 | ||
|
|
8b523a5aaa | ||
|
|
5261d7bb7b | ||
|
|
024c70c49e |
3
gallery/src/pages/misc/ha-template.markdown
Normal file
3
gallery/src/pages/misc/ha-template.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Template
|
||||
---
|
||||
65
gallery/src/pages/misc/ha-template.ts
Normal file
65
gallery/src/pages/misc/ha-template.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-template";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
|
||||
interface TemplateContent {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const templates: TemplateContent[] = [
|
||||
{ content: "{{ states('sensor.temperature') }}" },
|
||||
{
|
||||
content: "{{ 'Day' if is_state('sun.sun', 'above_horizon') else 'Night' }}",
|
||||
},
|
||||
];
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("sensor", "temperature", "25", {
|
||||
friendly_name: "Temperature",
|
||||
}),
|
||||
getEntity("sun", "sun", "above_horizon", {
|
||||
friendly_name: "Controller 2",
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-misc-ha-template")
|
||||
export class DemoMiscTemplate extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
protected firstUpdated() {
|
||||
const hass = provideHass(this);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="container">
|
||||
${templates.map(
|
||||
(t) =>
|
||||
html`<ha-card>
|
||||
<pre>Template: ${t.content}</pre>
|
||||
<pre>Result: <ha-template
|
||||
.hass=${this.hass} .content=${t.content}></ha-template></pre>
|
||||
</ha-card>`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-card {
|
||||
margin: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-misc-ha-template": DemoMiscTemplate;
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"marked": "16.2.0",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "4.0.3",
|
||||
"nunjucks": "3.2.4",
|
||||
"object-hash": "3.0.0",
|
||||
"punycode": "2.3.1",
|
||||
"qr-scanner": "1.4.2",
|
||||
@@ -174,6 +175,7 @@
|
||||
"@types/lodash.merge": "4.6.9",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/mocha": "10.0.10",
|
||||
"@types/nunjucks": "^3",
|
||||
"@types/qrcode": "1.5.5",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@types/tar": "6.1.13",
|
||||
|
||||
137
src/common/template.ts
Normal file
137
src/common/template.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import nunjucks, { Environment, Template as NunjucksTemplate } from "nunjucks";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
nunjucks.installJinjaCompat();
|
||||
|
||||
function createEnv() {
|
||||
const env = new Environment();
|
||||
// Add filters and globals that don't use hass
|
||||
env.addFilter("min", (numbers: number[]) => Math.min(...numbers));
|
||||
env.addFilter("max", (numbers: number[]) => Math.max(...numbers));
|
||||
|
||||
env.addGlobal(
|
||||
"states",
|
||||
(
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
round = false,
|
||||
withUnit = false
|
||||
): string => {
|
||||
if (!hass?.states[id]) {
|
||||
return "unknown";
|
||||
}
|
||||
const state = hass?.states[id]?.state;
|
||||
if (state == null) {
|
||||
return "unavailable";
|
||||
}
|
||||
if (round) {
|
||||
return String(Math.round(Number(state)));
|
||||
}
|
||||
if (withUnit) {
|
||||
return `${state} ${hass?.states[id]?.attributes.unit_of_measurement}`;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
);
|
||||
env.addGlobal(
|
||||
"state_attr",
|
||||
(hass: HomeAssistant, id: string, attr: string) =>
|
||||
hass?.states[id]?.attributes[attr]
|
||||
);
|
||||
env.addGlobal(
|
||||
"is_state",
|
||||
(hass: HomeAssistant, id: string, value: string) =>
|
||||
hass?.states[id]?.state === value
|
||||
);
|
||||
env.addGlobal(
|
||||
"is_state_attr",
|
||||
(hass: HomeAssistant, id: string, attr: string, value: string) =>
|
||||
hass?.states[id]?.attributes[attr] === value
|
||||
);
|
||||
env.addGlobal(
|
||||
"has_value",
|
||||
(hass: HomeAssistant, id: string) => hass?.states[id]?.state != null
|
||||
);
|
||||
env.addGlobal("state_translated", (hass: HomeAssistant, id: string) => {
|
||||
try {
|
||||
return hass?.formatEntityState(hass?.states[id], hass?.states[id]?.state);
|
||||
} catch {
|
||||
return hass?.states[id]?.state ?? undefined;
|
||||
}
|
||||
});
|
||||
return env;
|
||||
}
|
||||
|
||||
export class HaTemplate {
|
||||
private _njTemplate?: NunjucksTemplate;
|
||||
|
||||
private _hass?: HomeAssistant;
|
||||
|
||||
private _content?: string;
|
||||
|
||||
private _context?: Record<string, any>;
|
||||
|
||||
private _value = "";
|
||||
|
||||
public entityIds = new Set<string>();
|
||||
|
||||
public shouldUpdate = false;
|
||||
|
||||
private _env = createEnv();
|
||||
|
||||
constructor() {
|
||||
// functions that access the hass state have to be dynamic
|
||||
// in order to track which entities are used in the template
|
||||
[
|
||||
"states",
|
||||
"state_attr",
|
||||
"is_state",
|
||||
"is_state_attr",
|
||||
"has_value",
|
||||
"state_translated",
|
||||
].forEach((func) => {
|
||||
const original = this._env.getGlobal(func);
|
||||
this._env.addGlobal(func, (id: string, ...args: any[]): string => {
|
||||
this.entityIds.add(id);
|
||||
return original(this._hass, id, ...args);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public render(): string {
|
||||
if (this.shouldUpdate) {
|
||||
this.shouldUpdate = false;
|
||||
this.entityIds.clear();
|
||||
this._value = this._njTemplate!.render(this._context);
|
||||
}
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public set content(content: string) {
|
||||
if (this._content !== content) {
|
||||
this._content = content;
|
||||
this._njTemplate = new NunjucksTemplate(content, this._env);
|
||||
this.shouldUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
public set context(context: Record<string, any>) {
|
||||
if (this._context !== context) {
|
||||
this._context = context;
|
||||
this.shouldUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
public set hass(hass: HomeAssistant) {
|
||||
if (this._hass !== hass) {
|
||||
if (!this.shouldUpdate) {
|
||||
this.shouldUpdate =
|
||||
!this._hass !== !hass ||
|
||||
Array.from(this.entityIds).some(
|
||||
(id) => this._hass?.states[id]?.state !== hass.states[id]?.state
|
||||
);
|
||||
}
|
||||
this._hass = hass;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/components/ha-template.ts
Normal file
40
src/components/ha-template.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { HaTemplate } from "../common/template";
|
||||
|
||||
@customElement("ha-template")
|
||||
export class HaTemplateElement extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public content!: string;
|
||||
|
||||
@property({ attribute: false }) public context: Record<string, any> = {};
|
||||
|
||||
private _template = new HaTemplate();
|
||||
|
||||
protected shouldUpdate(): boolean {
|
||||
if (this.hass) {
|
||||
this._template.hass = this.hass;
|
||||
this._template.content = this.content;
|
||||
this._template.context = this.context;
|
||||
}
|
||||
return this._template.shouldUpdate;
|
||||
}
|
||||
|
||||
public render() {
|
||||
try {
|
||||
return this._template.render();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(`Error rendering template: ${error}`);
|
||||
return this.content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-template": HaTemplateElement;
|
||||
}
|
||||
}
|
||||
48
yarn.lock
48
yarn.lock
@@ -4802,6 +4802,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/nunjucks@npm:^3":
|
||||
version: 3.2.6
|
||||
resolution: "@types/nunjucks@npm:3.2.6"
|
||||
checksum: 10/6b77fd2e5e3a63f82149779ae83e6ade7f70779c63b9bdab9fdd52c388db4b442c8b5993f62917a9564fc5e5690784ea5558513c629bee5b8cf6b8e6d8eeac38
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/offscreencanvas@npm:^2019.6.4":
|
||||
version: 2019.7.3
|
||||
resolution: "@types/offscreencanvas@npm:2019.7.3"
|
||||
@@ -5554,6 +5561,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"a-sync-waterfall@npm:^1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "a-sync-waterfall@npm:1.0.1"
|
||||
checksum: 10/6069080aff936c88fc32f798cc172a8b541e35b993dc5d2e43b74b6f37c522744eec107e1d475d2c624825c6cb7d2ec9ec020dbe4520578afcae74f11902daa2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"abbrev@npm:^3.0.0":
|
||||
version: 3.0.1
|
||||
resolution: "abbrev@npm:3.0.1"
|
||||
@@ -5968,6 +5982,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"asap@npm:^2.0.3":
|
||||
version: 2.0.6
|
||||
resolution: "asap@npm:2.0.6"
|
||||
checksum: 10/b244c0458c571945e4b3be0b14eb001bea5596f9868cc50cc711dc03d58a7e953517d3f0dad81ccde3ff37d1f074701fa76a6f07d41aaa992d7204a37b915dda
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"assertion-error@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "assertion-error@npm:2.0.1"
|
||||
@@ -6818,6 +6839,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:^5.1.0":
|
||||
version: 5.1.0
|
||||
resolution: "commander@npm:5.1.0"
|
||||
checksum: 10/3e2ef5c003c5179250161e42ce6d48e0e69a54af970c65b7f985c70095240c260fd647453efd4c2c5a31b30ce468f373dc70f769c2f54a2c014abc4792aaca28
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"common-tags@npm:^1.8.0":
|
||||
version: 1.8.2
|
||||
resolution: "common-tags@npm:1.8.2"
|
||||
@@ -9397,6 +9425,7 @@ __metadata:
|
||||
"@types/lodash.merge": "npm:4.6.9"
|
||||
"@types/luxon": "npm:3.7.1"
|
||||
"@types/mocha": "npm:10.0.10"
|
||||
"@types/nunjucks": "npm:^3"
|
||||
"@types/qrcode": "npm:1.5.5"
|
||||
"@types/sortablejs": "npm:1.15.8"
|
||||
"@types/tar": "npm:6.1.13"
|
||||
@@ -9469,6 +9498,7 @@ __metadata:
|
||||
marked: "npm:16.2.0"
|
||||
memoize-one: "npm:6.0.0"
|
||||
node-vibrant: "npm:4.0.3"
|
||||
nunjucks: "npm:3.2.4"
|
||||
object-hash: "npm:3.0.0"
|
||||
pinst: "npm:3.0.0"
|
||||
prettier: "npm:3.6.2"
|
||||
@@ -11653,6 +11683,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nunjucks@npm:3.2.4":
|
||||
version: 3.2.4
|
||||
resolution: "nunjucks@npm:3.2.4"
|
||||
dependencies:
|
||||
a-sync-waterfall: "npm:^1.0.0"
|
||||
asap: "npm:^2.0.3"
|
||||
commander: "npm:^5.1.0"
|
||||
peerDependencies:
|
||||
chokidar: ^3.3.0
|
||||
peerDependenciesMeta:
|
||||
chokidar:
|
||||
optional: true
|
||||
bin:
|
||||
nunjucks-precompile: bin/precompile
|
||||
checksum: 10/8decb8bb762501aa1a44366acff50ab9d4ff9e57034455e62056b4ac117da40140e1f34f2270c38884f1a5b84b7d97c4afcb2e8c789ddd09f4dcfe71ce7b56bf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nwsapi@npm:^2.2.16":
|
||||
version: 2.2.21
|
||||
resolution: "nwsapi@npm:2.2.21"
|
||||
|
||||
Reference in New Issue
Block a user