Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
152 changes: 127 additions & 25 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,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();
Expand All @@ -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)
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 configElement;

try {
configElement = await elClass.getConfigElement();
} 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._configElement = null;
Comment thread
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) {
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);
Loading