diff --git a/src/panels/developer-tools/template/developer-tools-template.js b/src/panels/developer-tools/template/developer-tools-template.js deleted file mode 100644 index cf6369ee2e..0000000000 --- a/src/panels/developer-tools/template/developer-tools-template.js +++ /dev/null @@ -1,204 +0,0 @@ -import "../../../components/ha-circular-progress"; -import { timeOut } from "@polymer/polymer/lib/utils/async"; -import { Debouncer } from "@polymer/polymer/lib/utils/debounce"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "../../../components/ha-code-editor"; -import LocalizeMixin from "../../../mixins/localize-mixin"; -import "../../../styles/polymer-ha-style"; - -class HaPanelDevTemplate extends LocalizeMixin(PolymerElement) { - static get template() { - return html` - - - -
-
-

- [[localize('ui.panel.developer-tools.tabs.templates.description')]] -

- -

[[localize('ui.panel.developer-tools.tabs.templates.editor')]]

- -
- -
- -
[[processed]]
-
-
- `; - } - - static get properties() { - return { - hass: { - type: Object, - }, - - error: { - type: Boolean, - value: false, - }, - - rendering: { - type: Boolean, - value: false, - }, - - template: { - type: String, - /* eslint-disable max-len */ - value: `Imitate available variables: -{% set my_test_json = { - "temperature": 25, - "unit": "°C" -} %} - -The temperature is {{ my_test_json.temperature }} {{ my_test_json.unit }}. - -{% if is_state("device_tracker.paulus", "home") and - is_state("device_tracker.anne_therese", "home") -%} - You are both home, you silly -{%- else -%} - Anne Therese is at {{ states("device_tracker.anne_therese") }} - Paulus is at {{ states("device_tracker.paulus") }} -{%- endif %} - -For loop example: -{% for state in states.sensor -%} - {%- if loop.first %}The {% elif loop.last %} and the {% else %}, the {% endif -%} - {{ state.name | lower }} is {{state.state_with_unit}} -{%- endfor %}.`, - /* eslint-enable max-len */ - }, - - processed: { - type: String, - value: "", - }, - }; - } - - ready() { - super.ready(); - this.renderTemplate(); - } - - computeFormClasses(narrow) { - return narrow ? "content" : "content layout horizontal"; - } - - computeRenderedClasses(error) { - return error ? "error rendered" : "rendered"; - } - - templateChanged(ev) { - this.template = ev.detail.value; - if (this.error) { - this.error = false; - } - this._debouncer = Debouncer.debounce( - this._debouncer, - timeOut.after(500), - () => { - this.renderTemplate(); - } - ); - } - - renderTemplate() { - this.rendering = true; - - this.hass.callApi("POST", "template", { template: this.template }).then( - function (processed) { - this.processed = processed; - this.rendering = false; - }.bind(this), - function (error) { - this.processed = - (error && error.body && error.body.message) || - this.hass.localize( - "ui.panel.developer-tools.tabs.templates.unknown_error_template" - ); - this.error = true; - this.rendering = false; - }.bind(this) - ); - } -} - -customElements.define("developer-tools-template", HaPanelDevTemplate); diff --git a/src/panels/developer-tools/template/developer-tools-template.ts b/src/panels/developer-tools/template/developer-tools-template.ts new file mode 100644 index 0000000000..0bb9d0f78e --- /dev/null +++ b/src/panels/developer-tools/template/developer-tools-template.ts @@ -0,0 +1,280 @@ +import "@material/mwc-button/mwc-button"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { + css, + CSSResultArray, + customElement, + html, + internalProperty, + LitElement, + property, +} from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; +import { debounce } from "../../../common/util/debounce"; +import "../../../components/ha-circular-progress"; +import "../../../components/ha-code-editor"; +import { subscribeRenderTemplate } from "../../../data/ws-templates"; +import { haStyle } from "../../../resources/styles"; +import { HomeAssistant } from "../../../types"; + +const DEMO_TEMPLATE = `{## Imitate available variables: ##} +{% set my_test_json = { + "temperature": 25, + "unit": "°C" +} %} + +The temperature is {{ my_test_json.temperature }} {{ my_test_json.unit }}. + +{% if is_state("sun.sun", "above_horizon") -%} + The sun rose {{ relative_time(states.sun.sun.last_changed) }} ago. +{%- else -%} + The sun will rise at {{ as_timestamp(strptime(state_attr("sun.sun", "next_rising"), "")) | timestamp_local }}. +{%- endif %} + +For loop example getting 3 entity values: + +{% for states in states | slice(3) -%} + {% set state = states | first %} + {%- if loop.first %}The {% elif loop.last %} and the {% else %}, the {% endif -%} + {{ state.name | lower }} is {{state.state_with_unit}} +{%- endfor %}.`; + +@customElement("developer-tools-template") +class HaPanelDevTemplate extends LitElement { + @property() public hass!: HomeAssistant; + + @property() public narrow!: boolean; + + @internalProperty() private _error = false; + + @internalProperty() private _rendering = false; + + @internalProperty() private _processed = ""; + + @internalProperty() private _unsubRenderTemplate?: Promise; + + private _template = ""; + + private _inited = false; + + public connectedCallback() { + super.connectedCallback(); + if (this._template && !this._unsubRenderTemplate) { + this._subscribeTemplate(); + } + } + + public disconnectedCallback() { + this._unsubscribeTemplate(); + } + + protected firstUpdated() { + if (localStorage && localStorage["panel-dev-template-template"]) { + this._template = localStorage["panel-dev-template-template"]; + } else { + this._template = DEMO_TEMPLATE; + } + this._subscribeTemplate(); + this._inited = true; + } + + protected render() { + return html` +
+
+

+ ${this.hass.localize( + "ui.panel.developer-tools.tabs.templates.description" + )} +

+ +

+ ${this.hass.localize( + "ui.panel.developer-tools.tabs.templates.editor" + )} +

+ + + ${this.hass.localize( + "ui.panel.developer-tools.tabs.templates.reset" + )} + +
+ +
+ +
+${this._processed}
+
+
+ `; + } + + static get styles(): CSSResultArray { + return [ + haStyle, + css` + :host { + -ms-user-select: initial; + -webkit-user-select: initial; + -moz-user-select: initial; + } + + .content { + padding: 16px; + direction: ltr; + } + + .edit-pane { + margin-right: 16px; + } + + .edit-pane a { + color: var(--primary-color); + } + + .horizontal .edit-pane { + max-width: 50%; + } + + .render-pane { + position: relative; + max-width: 50%; + } + + .render-spinner { + position: absolute; + top: 8px; + right: 8px; + } + + .rendered { + @apply --paper-font-code1; + clear: both; + white-space: pre-wrap; + } + + .rendered.error { + color: var(--error-color); + } + `, + ]; + } + + private _debounceRender = debounce( + () => { + this._subscribeTemplate(); + this._storeTemplate(); + }, + 500, + false + ); + + private _templateChanged(ev) { + this._template = ev.detail.value; + if (this._error) { + this._error = false; + } + this._debounceRender(); + } + + private async _subscribeTemplate() { + this._rendering = true; + await this._unsubscribeTemplate(); + try { + this._unsubRenderTemplate = subscribeRenderTemplate( + this.hass.connection, + (result) => { + this._processed = result; + }, + { + template: this._template, + } + ); + await this._unsubRenderTemplate; + } catch (err) { + this._error = true; + if (err.message) { + this._processed = err.message; + } + this._unsubRenderTemplate = undefined; + } finally { + this._rendering = false; + } + } + + private async _unsubscribeTemplate(): Promise { + if (!this._unsubRenderTemplate) { + return; + } + + try { + const unsub = await this._unsubRenderTemplate; + unsub(); + this._unsubRenderTemplate = undefined; + } catch (e) { + if (e.code === "not_found") { + // If we get here, the connection was probably already closed. Ignore. + } else { + throw e; + } + } + } + + private _storeTemplate() { + if (!this._inited) { + return; + } + localStorage["panel-dev-template-template"] = this._template; + } + + private _restoreDemo() { + this._template = DEMO_TEMPLATE; + this._subscribeTemplate(); + delete localStorage["panel-dev-template-template"]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "developer-tools-template": HaPanelDevTemplate; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index 7bdac00b42..edb42ed2fe 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2592,6 +2592,7 @@ "title": "Template", "description": "Templates are rendered using the Jinja2 template engine with some Home Assistant specific extensions.", "editor": "Template editor", + "reset": "Reset to demo template", "jinja_documentation": "Jinja2 template documentation", "template_extensions": "Home Assistant template extensions", "unknown_error_template": "Unknown error rendering template"