-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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 11 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,27 +1,34 @@ | ||
| 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 "@polymer/paper-button/paper-button"; | ||
| import "@polymer/paper-input/paper-textarea"; | ||
| import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; | ||
| import "@polymer/paper-dialog/paper-dialog"; | ||
| import "@polymer/paper-button/paper-button.js"; | ||
|
zsarnett marked this conversation as resolved.
Outdated
|
||
| import "@polymer/paper-input/paper-textarea.js"; | ||
| import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js"; | ||
| import "@polymer/paper-dialog/paper-dialog.js"; | ||
| // This is not a duplicate import, one is for types, one is for element. | ||
| // tslint:disable-next-line | ||
| 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.js"; | ||
|
|
||
| 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 { TemplateResult } from "lit-html"; | ||
|
zsarnett marked this conversation as resolved.
Outdated
|
||
|
|
||
| export class HuiDialogEditCard extends LitElement { | ||
| protected hass?: HomeAssistant; | ||
| private _cardId?: string; | ||
| private _cardConfig?: string; | ||
| private _elementConfig?: LovelaceCardEditor | null; | ||
| private _reloadLovelace?: () => void; | ||
| private _editorToggle?: boolean; | ||
| private _newConfigYaml?: string; | ||
|
|
||
| static get properties(): PropertyDeclarations { | ||
| return { | ||
|
|
@@ -31,6 +38,8 @@ export class HuiDialogEditCard extends LitElement { | |
| }, | ||
| _cardConfig: {}, | ||
| _dialogClosedCallback: {}, | ||
| _elementConfig: {}, | ||
| _editorToggle: {}, | ||
| }; | ||
| } | ||
|
|
||
|
|
@@ -39,7 +48,9 @@ export class HuiDialogEditCard extends LitElement { | |
| this._cardId = cardId; | ||
| this._reloadLovelace = reloadLovelace; | ||
| this._cardConfig = ""; | ||
| this._loadConfig(); | ||
| this._editorToggle = true; | ||
| this._elementConfig = undefined; | ||
| this._loadConfig().then(() => this._loadElementConfig()); | ||
| // Wait till dialog is rendered. | ||
| await this.updateComplete; | ||
| this._dialog.open(); | ||
|
|
@@ -53,58 +64,115 @@ 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._elementConfig !== null | ||
| ? html`<div class="element-editor">${when( | ||
| this._elementConfig, | ||
| () => this._elementConfig, | ||
| () => html`Loading...` | ||
| )}</div>` | ||
| : html` | ||
| <hui-yaml-editor | ||
| .yaml="${this._cardConfig}" | ||
| @yaml-changed="${this._handleYamlChanged}" | ||
| ></hui-yaml-editor>` | ||
| } | ||
| <hui-yaml-card-preview | ||
| .hass="${this.hass}" | ||
| .yaml="${this._cardConfig}" | ||
| ></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._updateConfig}"' | ||
| >Save</paper-button> | ||
| </div> | ||
| </paper-dialog> | ||
| `; | ||
| } | ||
|
|
||
| private _handleYamlChanged(ev) { | ||
| this._previewEl.yaml = ev.detail.yaml; | ||
| protected updated(): void { | ||
| // This will center the dialog with the updated config | ||
| fireEvent(this._dialog, "iron-resize"); | ||
|
zsarnett marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| private _handleYamlChanged(ev: MouseEvent): void { | ||
|
zsarnett marked this conversation as resolved.
Outdated
|
||
| this._handleConfigChanged("yaml", (ev.detail as any).yaml); | ||
| } | ||
|
|
||
| private _closeDialog() { | ||
| private _handleConfigChanged( | ||
| format: string, | ||
| value: LovelaceConfig | string | ||
| ): void { | ||
| if (!this._previewEl) { | ||
| return; | ||
| } | ||
|
|
||
| if (format === "js") { | ||
| this._newConfigYaml = yaml.safeDump(value); | ||
|
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. Two things are weird here:
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. This removes the need for configValue. I can just always update the YAML and send the Yaml to the backend and the review. So now it knows its always YAML |
||
| } else if (this._elementConfig) { | ||
| this._elementConfig.setConfig(yaml.safeLoad(value)); | ||
| } | ||
|
|
||
| this._previewEl.value = { format, value }; | ||
| } | ||
|
|
||
| private _closeDialog(): void { | ||
| this._dialog.close(); | ||
| } | ||
|
|
||
| private async _loadConfig() { | ||
| private _toggleEditor(): void { | ||
| this._editorToggle = !this._editorToggle; | ||
| } | ||
|
|
||
| private async _loadConfig(): Promise<void> { | ||
| this._cardConfig = await getCardConfig(this.hass!, this._cardId!); | ||
| await this.updateComplete; | ||
| // This will center the dialog with the updated config | ||
| fireEvent(this._dialog, "iron-resize"); | ||
| } | ||
|
|
||
| private async _updateConfig() { | ||
| const newCardConfig = this.shadowRoot!.querySelector("hui-yaml-editor")! | ||
| .yaml; | ||
| private async _loadElementConfig(): Promise<void> { | ||
|
zsarnett marked this conversation as resolved.
Outdated
|
||
| const conf = yaml.safeLoad(this._cardConfig); | ||
| const elClass = customElements.get(`hui-${conf.type}-card`); | ||
|
zsarnett marked this conversation as resolved.
Outdated
|
||
| let elementConfig; | ||
|
|
||
| try { | ||
| elementConfig = await elClass.getConfigElement(); | ||
| elementConfig.setConfig(conf); | ||
| elementConfig.hass = this.hass; | ||
| elementConfig.addEventListener("config-changed", (ev) => | ||
| this._handleConfigChanged("js", ev.detail.config) | ||
| ); | ||
| this._elementConfig = elementConfig; | ||
| } 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._elementConfig = null; | ||
| } | ||
| } | ||
|
|
||
| if (this._cardConfig === newCardConfig) { | ||
| private async _updateConfig(): Promise<void> { | ||
|
zsarnett marked this conversation as resolved.
Outdated
|
||
| if (this._cardConfig === this._newConfigYaml) { | ||
| this._dialog.close(); | ||
| return; | ||
| } | ||
| try { | ||
| await updateCardConfig(this.hass!, this._cardId!, newCardConfig); | ||
| await updateCardConfig(this.hass!, this._cardId!, this._newConfigYaml); | ||
| this._dialog.close(); | ||
| this._reloadLovelace!(); | ||
| } catch (err) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; | ||
|
zsarnett marked this conversation as resolved.
|
||
| import "@polymer/paper-checkbox/paper-checkbox.js"; | ||
|
|
||
| import processConfigEntities from "../common/process-config-entities"; | ||
| 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, EntityConfig } 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"; | ||
| import { TemplateResult } from "lit-html"; | ||
|
zsarnett marked this conversation as resolved.
Outdated
|
||
|
|
||
| export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement) | ||
| implements LovelaceCardEditor { | ||
| public hass?: HomeAssistant; | ||
| private _config?: Config; | ||
| private _configEntities?: EntityConfig[]; | ||
|
|
||
| static get properties(): PropertyDeclarations { | ||
| return { | ||
| hass: {}, | ||
| _config: {}, | ||
| }; | ||
| } | ||
|
|
||
| public setConfig(config: Config): void { | ||
| this._config = config; | ||
| const entities = processConfigEntities(config.entities); | ||
|
|
||
| this._configEntities = entities; | ||
| } | ||
|
|
||
| protected render(): TemplateResult { | ||
| if (!this._config || !this.hass) { | ||
|
zsarnett marked this conversation as resolved.
Outdated
|
||
| return html``; | ||
| } | ||
|
|
||
| return html` | ||
| <paper-input | ||
| id="title" | ||
| @value-changed="${this._valueChanged}" | ||
| label="Title" | ||
| value="${this._config.title}" | ||
| ></paper-input> | ||
| ${this._configEntities!.map((entityConf) => | ||
| this.renderEntity(entityConf) | ||
| )}<br> | ||
| <paper-checkbox | ||
| id="show_name" | ||
| @change="${this._valueChanged}" | ||
| ?checked="${this._config.show_name !== false}" | ||
| >Show Entity's Name?</paper-checkbox><br><br> | ||
| <paper-checkbox | ||
| id="show_state" | ||
| @change="${this._valueChanged}" | ||
| ?checked="${this._config.show_state !== false}" | ||
| >Show Entity's State Text?</paper-checkbox><br> | ||
| `; | ||
| } | ||
|
|
||
| private renderEntity(entityConf: EntityConfig): TemplateResult { | ||
| return html` | ||
| <ha-entity-picker | ||
| hass="${this.hass}" | ||
| value="${entityConf.entity || entityConf}" | ||
| allow-custom-entity | ||
| ></ha-entity-picker> | ||
| `; | ||
| } | ||
|
|
||
| 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; | ||
|
|
||
| // Unsure why I could not grab a propery value ".configValue='show_name'" and ev.target.configValue wasnt working | ||
| this._config[target.id] = newValue; | ||
|
zsarnett marked this conversation as resolved.
Outdated
|
||
|
|
||
| fireEvent(this, "config-changed", { | ||
| config: this._config, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| 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.