-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Add getElementConfig to Glance + Add Form UI for updating YAML #1944
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
Changes from all commits
7c39159
5a0b807
4e11d86
1060860
84f3073
016a0de
3ac23ce
1a545a6
b03c641
982266e
ed88dd3
499b6ab
36a6e15
6a93d83
39e0e39
05138bf
151663d
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 |
|---|---|---|
| @@ -1,5 +1,7 @@ | ||
| import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; | ||
| import { fireEvent } from "../../../common/dom/fire_event"; | ||
| import yaml from "js-yaml"; | ||
|
zsarnett marked this conversation as resolved.
|
||
| import { when } from "lit-html/directives/when"; | ||
| import { TemplateResult } from "lit-html"; | ||
|
|
||
| import "@polymer/paper-button/paper-button"; | ||
| import "@polymer/paper-input/paper-textarea"; | ||
|
|
@@ -10,36 +12,47 @@ import "@polymer/paper-dialog/paper-dialog"; | |
| import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog"; | ||
| import { HomeAssistant } from "../../../types"; | ||
| import { getCardConfig, updateCardConfig } from "../common/data"; | ||
| import { fireEvent } from "../../../common/dom/fire_event"; | ||
|
|
||
| import "./hui-yaml-editor"; | ||
| import "./hui-yaml-card-preview"; | ||
| // This is not a duplicate import, one is for types, one is for element. | ||
| // tslint:disable-next-line | ||
| import { HuiYAMLCardPreview } from "./hui-yaml-card-preview"; | ||
| import { LovelaceCardEditor, LovelaceConfig } from "../types"; | ||
| import { YamlChangedEvent, ConfigValue } from "./types"; | ||
|
|
||
| const CUSTOM_TYPE_PREFIX = "custom:"; | ||
|
|
||
| export class HuiDialogEditCard extends LitElement { | ||
| protected hass?: HomeAssistant; | ||
| private _cardId?: string; | ||
| private _cardConfig?: string; | ||
| private _originalConfigYaml?: string; | ||
| private _configElement?: LovelaceCardEditor | null; | ||
| private _reloadLovelace?: () => void; | ||
| private _editorToggle?: boolean; | ||
| private _configValue?: ConfigValue; | ||
|
|
||
| static get properties(): PropertyDeclarations { | ||
| return { | ||
| hass: {}, | ||
| cardId: { | ||
| type: Number, | ||
| }, | ||
| _cardConfig: {}, | ||
| _dialogClosedCallback: {}, | ||
| _configElement: {}, | ||
| _editorToggle: {}, | ||
| }; | ||
| } | ||
|
|
||
| public async showDialog({ hass, cardId, reloadLovelace }) { | ||
| this.hass = hass; | ||
| this._cardId = cardId; | ||
| this._reloadLovelace = reloadLovelace; | ||
| this._cardConfig = ""; | ||
| this._loadConfig(); | ||
| this._editorToggle = true; | ||
| this._configElement = undefined; | ||
| this._configValue = { format: "yaml", value: "" }; | ||
| this._loadConfig().then(() => this._loadConfigElement()); | ||
| // Wait till dialog is rendered. | ||
| await this.updateComplete; | ||
| this._dialog.open(); | ||
|
|
@@ -53,58 +66,147 @@ export class HuiDialogEditCard extends LitElement { | |
| return this.shadowRoot!.querySelector("hui-yaml-card-preview")!; | ||
| } | ||
|
|
||
| protected render() { | ||
| protected render(): TemplateResult { | ||
| return html` | ||
| <style> | ||
| paper-dialog { | ||
| width: 650px; | ||
| } | ||
| .element-editor { | ||
| margin-bottom: 16px; | ||
| } | ||
| </style> | ||
| <paper-dialog with-backdrop> | ||
| <h2>Card Configuration</h2> | ||
| <paper-dialog-scrollable> | ||
| <hui-yaml-editor | ||
| .yaml="${this._cardConfig}" | ||
| @yaml-changed="${this._handleYamlChanged}" | ||
| ></hui-yaml-editor> | ||
| ${ | ||
| this._editorToggle && this._configElement !== null | ||
| ? html`<div class="element-editor">${when( | ||
| this._configElement, | ||
| () => this._configElement, | ||
| () => html`Loading...` | ||
| )}</div>` | ||
| : html` | ||
| <hui-yaml-editor | ||
| .yaml="${this._configValue!.value}" | ||
| @yaml-changed="${this._handleYamlChanged}" | ||
| ></hui-yaml-editor>` | ||
| } | ||
| <hui-yaml-card-preview | ||
| .hass="${this.hass}" | ||
| .yaml="${this._cardConfig}" | ||
| .value="${this._configValue}" | ||
| ></hui-yaml-card-preview> | ||
| </paper-dialog-scrollable> | ||
| <div class="paper-dialog-buttons"> | ||
| <paper-button @click="${this._closeDialog}">Cancel</paper-button> | ||
| <paper-button @click="${this._updateConfig}">Save</paper-button> | ||
| <paper-button | ||
| @click="${this._toggleEditor}" | ||
| >Toggle Editor</paper-button> | ||
| <paper-button | ||
| @click="${this._closeDialog}" | ||
| >Cancel</paper-button> | ||
| <paper-button | ||
| @click="${this._updateConfigInBackend}"' | ||
| >Save</paper-button> | ||
| </div> | ||
| </paper-dialog> | ||
| `; | ||
| } | ||
|
|
||
| private _handleYamlChanged(ev) { | ||
| this._previewEl.yaml = ev.detail.yaml; | ||
| private _handleYamlChanged(ev: YamlChangedEvent): void { | ||
| this._configValue = { format: "yaml", value: ev.detail.yaml }; | ||
| this._updatePreview(this._configValue); | ||
| } | ||
|
|
||
| private _handleJSConfigChanged(value: LovelaceConfig): void { | ||
| this._configElement!.setConfig(value); | ||
| this._configValue = { format: "js", value }; | ||
| this._updatePreview(this._configValue); | ||
| } | ||
|
|
||
| private _updatePreview(value: ConfigValue) { | ||
| if (!this._previewEl) { | ||
| return; | ||
| } | ||
| this._previewEl.value = value; | ||
| } | ||
|
|
||
| private _closeDialog() { | ||
| private _closeDialog(): void { | ||
| this._dialog.close(); | ||
| } | ||
|
|
||
| private async _loadConfig() { | ||
| this._cardConfig = await getCardConfig(this.hass!, this._cardId!); | ||
| await this.updateComplete; | ||
| // This will center the dialog with the updated config | ||
| private _toggleEditor(): void { | ||
| if (this._editorToggle && this._configValue!.format === "js") { | ||
| this._configValue = { | ||
| format: "yaml", | ||
| value: yaml.safeDump(this._configValue!.value), | ||
| }; | ||
| } else if (this._configElement && this._configValue!.format === "yaml") { | ||
| this._configValue = { | ||
| format: "js", | ||
| value: yaml.safeLoad(this._configValue!.value), | ||
| }; | ||
| this._configElement.setConfig(this._configValue!.value as LovelaceConfig); | ||
| } | ||
| this._editorToggle = !this._editorToggle; | ||
| } | ||
|
|
||
| private async _loadConfig(): Promise<void> { | ||
| const cardConfig = await getCardConfig(this.hass!, this._cardId!); | ||
| this._configValue = { | ||
| format: "yaml", | ||
| value: cardConfig, | ||
| }; | ||
| this._originalConfigYaml = cardConfig; | ||
| } | ||
|
|
||
| private async _loadConfigElement(): Promise<void> { | ||
| const conf = yaml.safeLoad(this._configValue!.value); | ||
|
|
||
| const tag = conf.type.startsWith(CUSTOM_TYPE_PREFIX) | ||
|
balloob marked this conversation as resolved.
|
||
| ? conf.type.substr(CUSTOM_TYPE_PREFIX.length) | ||
| : `hui-${conf.type}-card`; | ||
|
|
||
| const elClass = customElements.get(tag); | ||
| let configElement; | ||
|
|
||
| try { | ||
| configElement = await elClass.getConfigElement(); | ||
| } catch (err) { | ||
|
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. Which things can raise here? We should make sure we only wrap those in our
Contributor
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. Something like this so we can log the errors we are unexpected?
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. we shouldn't wrap anything that can raise a type error. Whenever you see try…catch, we need to know exactly why the code inside can fail and guard for those clauses. I think in this case just the fetching of config element is something we want to guard for ?
Contributor
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. ah gotcha. I missunderstood. |
||
| this._configElement = null; | ||
|
zsarnett marked this conversation as resolved.
|
||
| return; | ||
| } | ||
|
|
||
| configElement.setConfig(conf); | ||
| configElement.hass = this.hass; | ||
| configElement.addEventListener("config-changed", (ev) => | ||
| this._handleJSConfigChanged(ev.detail.config) | ||
| ); | ||
| this._configValue = { format: "js", value: conf }; | ||
| this._configElement = configElement; | ||
|
|
||
| // This will center the dialog with the updated config Element | ||
| fireEvent(this._dialog, "iron-resize"); | ||
| } | ||
|
|
||
| private async _updateConfig() { | ||
| const newCardConfig = this.shadowRoot!.querySelector("hui-yaml-editor")! | ||
| .yaml; | ||
| private async _updateConfigInBackend(): Promise<void> { | ||
| if (this._configValue!.format === "js") { | ||
| this._configValue = { | ||
| format: "yaml", | ||
| value: yaml.safeDump(this._configValue!.value), | ||
| }; | ||
| } | ||
|
|
||
| if (this._cardConfig === newCardConfig) { | ||
| if (this._configValue!.value === this._originalConfigYaml) { | ||
| this._dialog.close(); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| await updateCardConfig(this.hass!, this._cardId!, newCardConfig); | ||
| await updateCardConfig( | ||
| this.hass!, | ||
| this._cardId!, | ||
| this._configValue!.value | ||
| ); | ||
| this._dialog.close(); | ||
| this._reloadLovelace!(); | ||
| } catch (err) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; | ||
|
zsarnett marked this conversation as resolved.
|
||
| import { TemplateResult } from "lit-html"; | ||
| import "@polymer/paper-checkbox/paper-checkbox.js"; | ||
|
|
||
| import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; | ||
| import { HomeAssistant } from "../../../types.js"; | ||
| import { LovelaceCardEditor } from "../types.js"; | ||
| import { fireEvent } from "../../../common/dom/fire_event.js"; | ||
| import { Config } from "../cards/hui-glance-card"; | ||
|
|
||
| import "../../../components/entity/state-badge.js"; | ||
| import "../../../components/entity/ha-entity-picker"; | ||
| import "../../../components/ha-card.js"; | ||
| import "../../../components/ha-icon.js"; | ||
|
|
||
| export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement) | ||
| implements LovelaceCardEditor { | ||
| public hass?: HomeAssistant; | ||
| private _config?: Config; | ||
|
|
||
| static get properties(): PropertyDeclarations { | ||
| return { | ||
| hass: {}, | ||
| _config: {}, | ||
| }; | ||
| } | ||
|
|
||
| public setConfig(config: Config): void { | ||
| this._config = { type: "glance", ...config }; | ||
| } | ||
|
|
||
| protected render(): TemplateResult { | ||
| if (!this.hass) { | ||
| return html``; | ||
| } | ||
|
|
||
| return html` | ||
| <paper-input | ||
| label="Title" | ||
| value="${this._config!.title}" | ||
| .configValue=${"title"} | ||
| @value-changed="${this._valueChanged}" | ||
| ></paper-input><br> | ||
| <paper-checkbox | ||
| ?checked="${this._config!.show_name !== false}" | ||
| .configValue=${"show_name"} | ||
| @change="${this._valueChanged}" | ||
| >Show Entity's Name?</paper-checkbox><br><br> | ||
| <paper-checkbox | ||
| ?checked="${this._config!.show_state !== false}" | ||
| .configValue=${"show_state"} | ||
| @change="${this._valueChanged}" | ||
| >Show Entity's State Text?</paper-checkbox><br> | ||
| `; | ||
| } | ||
|
|
||
| private _valueChanged(ev: MouseEvent): void { | ||
| if (!this._config || !this.hass) { | ||
| return; | ||
| } | ||
|
|
||
| const target = ev.target! as any; | ||
|
|
||
| const newValue = | ||
| target.checked !== undefined ? target.checked : target.value; | ||
|
|
||
| fireEvent(this, "config-changed", { | ||
| config: { ...this._config, [target.configValue]: newValue }, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| declare global { | ||
| interface HTMLElementTagNameMap { | ||
| "hui-glance-card-editor": HuiGlanceCardEditor; | ||
| } | ||
| } | ||
|
|
||
| customElements.define("hui-glance-card-editor", HuiGlanceCardEditor); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just as a future note; these cleanups should be done in a separate PR. Each time I review this PR (and it's been a few times!), I see all these changes that are actually just noise of the actual code/features.