Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions src/panels/lovelace/cards/hui-glance-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,23 @@ import {
import { TemplateResult } from "lit-html";
import { classMap } from "lit-html/directives/classMap";

import computeStateDisplay from "../../../common/entity/compute_state_display";
import computeStateName from "../../../common/entity/compute_state_name";
import processConfigEntities from "../common/process-config-entities";
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event.js";

Copy link
Copy Markdown
Member

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.

import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { HomeAssistant } from "../../../types.js";
import { LovelaceCard, LovelaceConfig, LovelaceCardEditor } from "../types.js";
import { longPress } from "../common/directives/long-press-directive";

import toggleEntity from "../common/entity/toggle-entity";
import computeStateDisplay from "../../../common/entity/compute_state_display.js";
import computeStateName from "../../../common/entity/compute_state_name.js";
import processConfigEntities from "../common/process-config-entities";
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element.js";
import toggleEntity from "../common/entity/toggle-entity.js";

import "../../../components/entity/state-badge";
import "../../../components/ha-card";
import "../../../components/ha-icon";

import { fireEvent } from "../../../common/dom/fire_event";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { HomeAssistant } from "../../../types";
import { LovelaceCard, LovelaceConfig } from "../types";
import { longPress } from "../common/directives/long-press-directive";

interface EntityConfig {
export interface EntityConfig {
name: string;
icon: string;
entity: string;
Expand All @@ -34,7 +33,7 @@ interface EntityConfig {
service_data?: object;
}

interface Config extends LovelaceConfig {
export interface Config extends LovelaceConfig {
show_name?: boolean;
show_state?: boolean;
title?: string;
Expand All @@ -45,6 +44,11 @@ interface Config extends LovelaceConfig {

export class HuiGlanceCard extends hassLocalizeLitMixin(LitElement)
implements LovelaceCard {
public static async getConfigElement(): Promise<LovelaceCardEditor> {
await import("../editor/hui-glance-card-editor");
return document.createElement("hui-glance-card-editor");
}

public hass?: HomeAssistant;
private _config?: Config;
private _configEntities?: EntityConfig[];
Expand Down
127 changes: 101 additions & 26 deletions src/panels/lovelace/editor/hui-dialog-edit-card.ts
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";
Comment thread
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";
Expand All @@ -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();
Expand All @@ -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 {
Comment thread
zsarnett marked this conversation as resolved.
Outdated
this._currentConfigYaml = yaml.safeDump(value);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We *should* convert every change to YAML. Do less and be lazy: we only need it as YAML when the user presses thet save button. We should store the exact same format as that we pass the preview element.

Sorry I was a bit confused... I should have asked for clarification. Will make the change in the PR

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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)
Comment thread
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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 try … catch

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

catch (err) {
      if (!(err instanceof TypeError)) {
        // tslint:disable-next-line:no-console
        console.error(err);
      }
      this._configElement = null;
    }

Something like this so we can log the errors we are unexpected?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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> {
Comment thread
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) {
Expand Down
79 changes: 79 additions & 0 deletions src/panels/lovelace/editor/hui-glance-card-editor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
Comment thread
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);
1 change: 0 additions & 1 deletion src/panels/lovelace/editor/hui-yaml-card-preview.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import yaml from "js-yaml";

import "@polymer/paper-input/paper-textarea";

import createCardElement from "../common/create-card-element";
Expand Down
15 changes: 9 additions & 6 deletions src/panels/lovelace/editor/hui-yaml-editor.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { fireEvent } from "../../../common/dom/fire_event";

import { TemplateResult } from "lit-html";
import "@polymer/paper-input/paper-textarea";

import { fireEvent } from "../../../common/dom/fire_event";

export class HuiYAMLEditor extends LitElement {
public yaml?: string;

Expand All @@ -12,23 +13,25 @@ export class HuiYAMLEditor extends LitElement {
};
}

protected render() {
protected render(): TemplateResult {
return html`
<style>
paper-textarea {
--paper-input-container-shared-input-style_-_font-family: monospace;
}
</style>
<paper-textarea
max-rows=10
value="${this.yaml}"
@value-changed="${this._valueChanged}"
></paper-textarea>
`;
}

private _valueChanged(ev) {
this.yaml = ev.target.value;
fireEvent(this, "yaml-changed", { yaml: ev.target.value });
private _valueChanged(ev: MouseEvent): void {
const target = ev.target! as any;
this.yaml = target.value;
fireEvent(this, "yaml-changed", { yaml: target.value });
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/panels/lovelace/editor/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface YamlChangedEvent extends Event {
detail: {
yaml: string;
};
}
Loading