-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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 12 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,49 @@ 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 } from "./types"; | ||
|
|
||
| const CUSTOM_TYPE_PREFIX = "custom:"; | ||
|
|
||
| export class HuiDialogEditCard extends LitElement { | ||
| protected hass?: HomeAssistant; | ||
| private _cardId?: string; | ||
| private _cardConfig?: string; | ||
| private _currentConfigYaml?: string; | ||
| private _originalConfigYaml?: string; | ||
| private _elementConfig?: LovelaceCardEditor | null; | ||
| private _reloadLovelace?: () => void; | ||
| private _editorToggle?: boolean; | ||
|
|
||
| static get properties(): PropertyDeclarations { | ||
| return { | ||
| hass: {}, | ||
| cardId: { | ||
| type: Number, | ||
| }, | ||
| _cardConfig: {}, | ||
| _currentConfigYaml: {}, | ||
| _dialogClosedCallback: {}, | ||
| _elementConfig: {}, | ||
| _editorToggle: {}, | ||
| }; | ||
| } | ||
|
|
||
| public async showDialog({ hass, cardId, reloadLovelace }) { | ||
| this.hass = hass; | ||
| this._cardId = cardId; | ||
| this._reloadLovelace = reloadLovelace; | ||
| this._cardConfig = ""; | ||
| this._loadConfig(); | ||
| this._currentConfigYaml = ""; | ||
| this._editorToggle = true; | ||
| this._elementConfig = undefined; | ||
| this._loadConfig().then(() => this._loadConfigElement()); | ||
| this._originalConfigYaml = this._currentConfigYaml; | ||
| // Wait till dialog is rendered. | ||
| await this.updateComplete; | ||
| this._dialog.open(); | ||
|
|
@@ -53,58 +68,118 @@ 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._currentConfigYaml}" | ||
| @yaml-changed="${this._handleYamlChanged}" | ||
| ></hui-yaml-editor>` | ||
| } | ||
| <hui-yaml-card-preview | ||
| .hass="${this.hass}" | ||
| .yaml="${this._cardConfig}" | ||
| .yaml="${this._currentConfigYaml}" | ||
| ></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; | ||
| private _handleYamlChanged(ev: YamlChangedEvent): void { | ||
| this._updatePreview(ev.detail.yaml); | ||
| } | ||
|
|
||
| private _handleConfigChanged(value: LovelaceConfig): void { | ||
|
zsarnett marked this conversation as resolved.
Outdated
|
||
| this._currentConfigYaml = 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. I already wrote this in my previous comment and I will not allow this. This is SUPER INEFFICIENT. Every. single. key. stroke. will now result in a whole object being converted to YAML for no reason. To make it worse, it is converted back to JS objects inside the preview element. We should only convert it to YAML when the users presses save. This PR cannot be merged with any other logic.
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. Sorry I was a bit confused... I should have asked for clarification. Will make the change in the PR
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. 🤦♂️ damn it. Sorry about that. |
||
| this._updatePreview(this._currentConfigYaml!); | ||
| } | ||
|
|
||
| private _updatePreview(value: string) { | ||
| if (!this._previewEl) { | ||
| return; | ||
| } | ||
| if (this._elementConfig) { | ||
| this._elementConfig.setConfig(yaml.safeLoad(value)); | ||
| } | ||
| this._previewEl.yaml = 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 | ||
| fireEvent(this._dialog, "iron-resize"); | ||
| private _toggleEditor(): void { | ||
| this._editorToggle = !this._editorToggle; | ||
| } | ||
|
|
||
| private async _loadConfig(): Promise<void> { | ||
| this._currentConfigYaml = await getCardConfig(this.hass!, this._cardId!); | ||
| } | ||
|
|
||
| private async _updateConfig() { | ||
| const newCardConfig = this.shadowRoot!.querySelector("hui-yaml-editor")! | ||
| .yaml; | ||
| private async _loadConfigElement(): Promise<void> { | ||
| const conf = yaml.safeLoad(this._currentConfigYaml); | ||
| 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 elementConfig; | ||
| try { | ||
| elementConfig = await elClass.getConfigElement(); | ||
| elementConfig.setConfig(conf); | ||
| elementConfig.hass = this.hass; | ||
| elementConfig.addEventListener("config-changed", (ev) => | ||
| this._handleConfigChanged(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; | ||
| } | ||
|
|
||
| // This will center the dialog with the updated config Element | ||
| fireEvent(this._dialog, "iron-resize"); | ||
| } | ||
|
|
||
| if (this._cardConfig === newCardConfig) { | ||
| private async _updateConfig(): Promise<void> { | ||
|
zsarnett marked this conversation as resolved.
Outdated
|
||
| if (this._currentConfigYaml === this._originalConfigYaml) { | ||
| this._dialog.close(); | ||
| return; | ||
| } | ||
| try { | ||
| await updateCardConfig(this.hass!, this._cardId!, newCardConfig); | ||
| await updateCardConfig( | ||
| this.hass!, | ||
| this._cardId!, | ||
| this._currentConfigYaml | ||
| ); | ||
| 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); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export interface YamlChangedEvent extends Event { | ||
| detail: { | ||
| yaml: string; | ||
| }; | ||
| } |
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.