From f680832f781e6a6294ad4712596c227b62025531 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 4 Dec 2018 16:49:12 +0100 Subject: [PATCH] Add edit/add/delete view (#2172) * Add edit/add/delete view * Add delete * Comments * Lint * Fix delete with numeric ids * fix translations * add translations --- src/data/lovelace.ts | 34 +++ .../lovelace/components/hui-card-options.ts | 13 +- src/panels/lovelace/editor/delete-card.ts | 2 +- src/panels/lovelace/editor/delete-view.ts | 18 ++ src/panels/lovelace/editor/hui-card-picker.ts | 6 +- .../lovelace/editor/hui-dialog-edit-view.ts | 101 +++++++ src/panels/lovelace/editor/hui-edit-card.ts | 12 +- src/panels/lovelace/editor/hui-edit-view.ts | 264 ++++++++++++++++++ src/panels/lovelace/hui-root.js | 85 +++++- src/panels/lovelace/hui-view.js | 5 +- src/translations/en.json | 20 +- 11 files changed, 535 insertions(+), 25 deletions(-) create mode 100644 src/panels/lovelace/editor/delete-view.ts create mode 100644 src/panels/lovelace/editor/hui-dialog-edit-view.ts create mode 100644 src/panels/lovelace/editor/hui-edit-view.ts diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts index cfb4489bb6..c731360321 100644 --- a/src/data/lovelace.ts +++ b/src/data/lovelace.ts @@ -12,6 +12,7 @@ export interface LovelaceViewConfig { cards?: LovelaceCardConfig[]; id?: string; icon?: string; + theme?: string; } export interface LovelaceCardConfig { @@ -84,3 +85,36 @@ export const addCard = ( card_config: config, format, }); + +export const updateViewConfig = ( + hass: HomeAssistant, + viewId: string, + config: LovelaceViewConfig | string, + format: "json" | "yaml" +): Promise => + hass.callWS({ + type: "lovelace/config/view/update", + view_id: viewId, + view_config: config, + format, + }); + +export const deleteView = ( + hass: HomeAssistant, + viewId: string +): Promise => + hass.callWS({ + type: "lovelace/config/view/delete", + view_id: viewId, + }); + +export const addView = ( + hass: HomeAssistant, + config: LovelaceViewConfig | string, + format: "json" | "yaml" +): Promise => + hass.callWS({ + type: "lovelace/config/view/add", + view_config: config, + format, + }); diff --git a/src/panels/lovelace/components/hui-card-options.ts b/src/panels/lovelace/components/hui-card-options.ts index d64ed72ea0..d66297d7fe 100644 --- a/src/panels/lovelace/components/hui-card-options.ts +++ b/src/panels/lovelace/components/hui-card-options.ts @@ -3,6 +3,7 @@ import "@polymer/paper-button/paper-button"; import { fireEvent } from "../../../common/dom/fire_event"; import { showEditCardDialog } from "../editor/hui-dialog-edit-card"; +import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; import { confDeleteCard } from "../editor/delete-card"; import { HomeAssistant } from "../../../types"; import { LovelaceCardConfig } from "../../../data/lovelace"; @@ -19,7 +20,7 @@ declare global { } } -export class HuiCardOptions extends LitElement { +export class HuiCardOptions extends hassLocalizeLitMixin(LitElement) { public cardConfig?: LovelaceCardConfig; protected hass?: HomeAssistant; @@ -50,8 +51,14 @@ export class HuiCardOptions extends LitElement {
DELETEEDIT + >${ + this.localize("ui.panel.lovelace.editor.edit_card.delete") + }${ + this.localize("ui.panel.lovelace.editor.edit_card.edit") + }
`; } diff --git a/src/panels/lovelace/editor/delete-card.ts b/src/panels/lovelace/editor/delete-card.ts index 9a2ed8fd6e..11646605cc 100644 --- a/src/panels/lovelace/editor/delete-card.ts +++ b/src/panels/lovelace/editor/delete-card.ts @@ -10,7 +10,7 @@ export async function confDeleteCard( return; } try { - await deleteCard(hass, cardId); + await deleteCard(hass, String(cardId)); reloadLovelace(); } catch (err) { alert(`Deleting failed: ${err.message}`); diff --git a/src/panels/lovelace/editor/delete-view.ts b/src/panels/lovelace/editor/delete-view.ts new file mode 100644 index 0000000000..740c00ede6 --- /dev/null +++ b/src/panels/lovelace/editor/delete-view.ts @@ -0,0 +1,18 @@ +import { deleteView } from "../../../data/lovelace"; +import { HomeAssistant } from "../../../types"; + +export async function confDeleteView( + hass: HomeAssistant, + viewId: string, + reloadLovelace: () => void +): Promise { + if (!confirm("Are you sure you want to delete this view?")) { + return; + } + try { + await deleteView(hass, String(viewId)); + reloadLovelace(); + } catch (err) { + alert(`Deleting failed: ${err.message}`); + } +} diff --git a/src/panels/lovelace/editor/hui-card-picker.ts b/src/panels/lovelace/editor/hui-card-picker.ts index 18fb90d21f..af8950750c 100644 --- a/src/panels/lovelace/editor/hui-card-picker.ts +++ b/src/panels/lovelace/editor/hui-card-picker.ts @@ -7,6 +7,8 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { LovelaceCardConfig } from "../../../data/lovelace"; import { getCardElementTag } from "../common/get-card-element-tag"; import { CardPickTarget } from "./types"; +import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; + import { uid } from "../../../common/util/uid"; declare global { @@ -44,13 +46,13 @@ const cards = [ { name: "Weather Forecast", type: "weather-forecast" }, ]; -export class HuiCardPicker extends LitElement { +export class HuiCardPicker extends hassLocalizeLitMixin(LitElement) { protected hass?: HomeAssistant; protected render(): TemplateResult { return html` ${this.renderStyle()} -

Pick the card you want to add:

+

${this.localize("ui.panel.lovelace.editor.edit_card.pick_card")}

${ cards.map((card) => { diff --git a/src/panels/lovelace/editor/hui-dialog-edit-view.ts b/src/panels/lovelace/editor/hui-dialog-edit-view.ts new file mode 100644 index 0000000000..f962e4d43e --- /dev/null +++ b/src/panels/lovelace/editor/hui-dialog-edit-view.ts @@ -0,0 +1,101 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; + +import { HomeAssistant } from "../../../types"; +import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; +import { LovelaceViewConfig } from "../../../data/lovelace"; +import "./hui-edit-view"; +import "./hui-migrate-config"; + +declare global { + // for fire event + interface HASSDomEvents { + "reload-lovelace": undefined; + "show-edit-view": EditViewDialogParams; + } + // for add event listener + interface HTMLElementEventMap { + "reload-lovelace": HASSDomEvent; + } +} + +let registeredDialog = false; +const dialogShowEvent = "show-edit-view"; +const dialogTag = "hui-dialog-edit-view"; + +export interface EditViewDialogParams { + viewConfig?: LovelaceViewConfig; + add?: boolean; + reloadLovelace: () => void; +} + +const registerEditViewDialog = (element: HTMLElement) => + fireEvent(element, "register-dialog", { + dialogShowEvent, + dialogTag, + dialogImport: () => import("./hui-dialog-edit-view"), + }); + +export const showEditViewDialog = ( + element: HTMLElement, + editViewDialogParams: EditViewDialogParams +) => { + if (!registeredDialog) { + registeredDialog = true; + registerEditViewDialog(element); + } + fireEvent(element, dialogShowEvent, editViewDialogParams); +}; + +export class HuiDialogEditView extends LitElement { + protected hass?: HomeAssistant; + private _params?: EditViewDialogParams; + + static get properties(): PropertyDeclarations { + return { + hass: {}, + _params: {}, + }; + } + + public async showDialog(params: EditViewDialogParams): Promise { + this._params = params; + await this.updateComplete; + (this.shadowRoot!.children[0] as any).showDialog(); + } + + protected render(): TemplateResult { + if (!this._params) { + return html``; + } + if ( + !this._params.add && + this._params.viewConfig && + !this._params.viewConfig.id + ) { + return html` + + `; + } + return html` + + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-dialog-edit-view": HuiDialogEditView; + } +} + +customElements.define(dialogTag, HuiDialogEditView); diff --git a/src/panels/lovelace/editor/hui-edit-card.ts b/src/panels/lovelace/editor/hui-edit-card.ts index f8fe186f28..5a523617c9 100644 --- a/src/panels/lovelace/editor/hui-edit-card.ts +++ b/src/panels/lovelace/editor/hui-edit-card.ts @@ -14,7 +14,6 @@ import "@polymer/paper-dialog/paper-dialog"; // tslint:disable-next-line import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog"; import "@polymer/paper-button/paper-button"; -import "@polymer/paper-input/paper-textarea"; import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; import { HomeAssistant } from "../../../types"; import { @@ -108,7 +107,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { this._dialog.open(); } - protected async updated(changedProperties: PropertyValues): Promise { + protected updated(changedProperties: PropertyValues): void { super.updated(changedProperties); if ( !changedProperties.has("cardConfig") && @@ -170,7 +169,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { return html` ${this.renderStyle()} -

${this.localize("ui.panel.lovelace.editor.edit.header")}

+

${this.localize("ui.panel.lovelace.editor.edit_card.header")}

${ this.localize( - "ui.panel.lovelace.editor.edit.toggle_editor" + "ui.panel.lovelace.editor.edit_card.toggle_editor" ) } @@ -216,9 +215,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { ?active="${this._saving}" alt="Saving" > - ${ - this.localize("ui.panel.lovelace.editor.edit.save") - }
` @@ -331,6 +328,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { this._configValue!.format ); } + fireEvent(this, "reload-lovelace"); this._closeDialog(); this._saveDone(); } catch (err) { diff --git a/src/panels/lovelace/editor/hui-edit-view.ts b/src/panels/lovelace/editor/hui-edit-view.ts new file mode 100644 index 0000000000..cf9a75a40b --- /dev/null +++ b/src/panels/lovelace/editor/hui-edit-view.ts @@ -0,0 +1,264 @@ +import { + html, + LitElement, + PropertyDeclarations, + PropertyValues, +} from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; + +import "@polymer/paper-spinner/paper-spinner"; +import "@polymer/paper-dialog/paper-dialog"; +// 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 "@polymer/paper-button/paper-button"; +import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; +import "../components/hui-theme-select-editor"; +import { HomeAssistant } from "../../../types"; +import { + addView, + updateViewConfig, + LovelaceViewConfig, +} from "../../../data/lovelace"; +import { fireEvent } from "../../../common/dom/fire_event"; +import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; +import { EditorTarget } from "./types"; + +export class HuiEditView extends hassLocalizeLitMixin(LitElement) { + static get properties(): PropertyDeclarations { + return { + hass: {}, + viewConfig: {}, + add: {}, + _config: {}, + _saving: {}, + }; + } + + public viewConfig?: LovelaceViewConfig; + public add?: boolean; + public reloadLovelace?: () => {}; + protected hass?: HomeAssistant; + private _config?: LovelaceViewConfig; + private _saving: boolean; + + protected constructor() { + super(); + this._saving = false; + } + + public async showDialog(): Promise { + // Wait till dialog is rendered. + if (this._dialog == null) { + await this.updateComplete; + } + this._dialog.open(); + } + + protected updated(changedProperties: PropertyValues): void { + super.updated(changedProperties); + if (!changedProperties.has("viewConfig") && !changedProperties.has("add")) { + return; + } + if ( + this.viewConfig && + (!changedProperties.get("viewConfig") || + this.viewConfig.id !== + (changedProperties.get("viewConfig") as LovelaceViewConfig).id) + ) { + this._config = this.viewConfig; + } else if (changedProperties.has("add")) { + this._config = { cards: [] }; + } + this._resizeDialog(); + } + + private get _dialog(): PaperDialogElement { + return this.shadowRoot!.querySelector("paper-dialog")!; + } + + get _id(): string { + if (!this._config) { + return ""; + } + return this._config.id || ""; + } + + get _title(): string { + if (!this._config) { + return ""; + } + return this._config.title || ""; + } + + get _icon(): string { + if (!this._config) { + return ""; + } + return this._config.icon || ""; + } + + get _theme(): string { + if (!this._config) { + return ""; + } + return this._config.theme || "Backend-selected"; + } + + protected render(): TemplateResult { + return html` + ${this.renderStyle()} + +

${this.localize("ui.panel.lovelace.editor.edit_view.header")}

+ +
+ + + + +
+
+
+ ${this.localize("ui.common.cancel")} + + + ${this.localize("ui.common.save")} +
+
+ `; + } + + private renderStyle(): TemplateResult { + return html` + + `; + } + + private _save(): void { + this._saving = true; + this._updateConfigInBackend(); + } + + private async _resizeDialog(): Promise { + await this.updateComplete; + fireEvent(this._dialog, "iron-resize"); + } + + private _closeDialog(): void { + this._config = { cards: [] }; + this.viewConfig = undefined; + this._dialog.close(); + } + + private async _updateConfigInBackend(): Promise { + if (!this._isConfigChanged()) { + this._closeDialog(); + this._saving = false; + return; + } + + try { + if (this.add) { + await addView(this.hass!, this._config!, "json"); + } else { + await updateViewConfig( + this.hass!, + this.viewConfig!.id!, + this._config!, + "json" + ); + } + this.reloadLovelace!(); + this._closeDialog(); + this._saving = false; + } catch (err) { + alert(`Saving failed: ${err.message}`); + this._saving = false; + } + } + + private _valueChanged(ev: Event): void { + if (!this._config || !this.hass) { + return; + } + + const target = ev.currentTarget! as EditorTarget; + + if (this[`_${target.configValue}`] === target.value) { + return; + } + + if (target.configValue) { + this._config = { + ...this._config, + [target.configValue]: target.value, + }; + } + } + + private _isConfigChanged(): boolean { + if (!this.add) { + return true; + } + return JSON.stringify(this._config) !== JSON.stringify(this.viewConfig); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-edit-view": HuiEditView; + } +} + +customElements.define("hui-edit-view", HuiEditView); diff --git a/src/panels/lovelace/hui-root.js b/src/panels/lovelace/hui-root.js index d5fbf11282..a20864fb27 100644 --- a/src/panels/lovelace/hui-root.js +++ b/src/panels/lovelace/hui-root.js @@ -4,6 +4,7 @@ import "@polymer/app-layout/app-scroll-effects/effects/waterfall"; import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/app-route/app-route"; import "@polymer/paper-icon-button/paper-icon-button"; +import "@polymer/paper-button/paper-button"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-menu-button/paper-menu-button"; @@ -16,6 +17,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element"; import scrollToTarget from "../../common/dom/scroll-to-target"; import EventsMixin from "../../mixins/events-mixin"; +import localizeMixin from "../../mixins/localize-mixin"; import NavigateMixin from "../../mixins/navigate-mixin"; import "../../layouts/ha-app-layout"; @@ -31,12 +33,16 @@ import "./hui-view"; import debounce from "../../common/util/debounce"; import createCardElement from "./common/create-card-element"; import { showSaveDialog } from "./editor/hui-dialog-save-config"; +import { showEditViewDialog } from "./editor/hui-dialog-edit-view"; +import { confDeleteView } from "./editor/delete-view"; // CSS and JS should only be imported once. Modules and HTML are safe. const CSS_CACHE = {}; const JS_CACHE = {}; -class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) { +class HUIRoot extends NavigateMixin( + EventsMixin(localizeMixin(PolymerElement)) +) { static get template() { return html`