Addition to Edit Love Lace Cards (#1885)

* Initial Commit

* Removing old code

* Switching to litlement and ts

* remove .ts from extension

* Addressing a few reviews

* Added ShowDialog Still no whammy

* Fix some things

* Extract one more data method

* Add more types

* Clean up imports

* Call super

* Finishing touches

* Fix typescript check
This commit is contained in:
Zack Arnett 2018-10-30 06:15:02 -04:00 committed by Paulus Schoutsen
parent de5f02d706
commit 7ca2ef4c4c
6 changed files with 270 additions and 37 deletions

View File

@ -0,0 +1,21 @@
import { HomeAssistant } from "../../../types";
export const getCardConfig = (
hass: HomeAssistant,
cardId: string
): Promise<string> =>
hass.callWS({
type: "lovelace/config/card/get",
card_id: cardId,
});
export const updateCardConfig = (
hass: HomeAssistant,
cardId: string,
config: any
): Promise<void> =>
hass!.callWS({
type: "lovelace/config/card/update",
card_id: cardId,
card_config: config,
});

View File

@ -0,0 +1,99 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { fireEvent } from "../../../common/dom/fire_event.js";
import "@polymer/paper-button/paper-button.js";
import "@polymer/paper-input/paper-textarea.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.js";
import { HomeAssistant } from "../../../types";
import { getCardConfig, updateCardConfig } from "../common/data";
export class HuiEditCardModal extends LitElement {
protected hass?: HomeAssistant;
private _cardId?: string;
private _cardConfig?: string;
private _reloadLovelace?: () => void;
static get properties(): PropertyDeclarations {
return {
hass: {},
cardId: {
type: Number,
},
_cardConfig: {},
_dialogClosedCallback: {},
};
}
public async showDialog({ hass, cardId, reloadLovelace }) {
this.hass = hass;
this._cardId = cardId;
this._reloadLovelace = reloadLovelace;
this._loadConfig();
// Wait till dialog is rendered.
await this.updateComplete;
this._dialog.open();
}
private get _dialog(): PaperDialogElement {
return this.shadowRoot!.querySelector("paper-dialog")!;
}
protected render() {
return html`
<style>
paper-dialog {
width: 650px;
}
</style>
<paper-dialog with-backdrop>
<h2>Card Configuration</h2>
<paper-textarea
value="${this._cardConfig}"
></paper-textarea>
<div class="paper-dialog-buttons">
<paper-button @click="${this._closeDialog}">Cancel</paper-button>
<paper-button @click="${this._updateConfig}">Save</paper-button>
</div>
</paper-dialog>
`;
}
private _closeDialog() {
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 async _updateConfig() {
const newCardConfig = this.shadowRoot!.querySelector("paper-textarea")!
.value;
if (this._cardConfig === newCardConfig) {
this._dialog.close();
return;
}
try {
await updateCardConfig(this.hass!, this._cardId!, newCardConfig);
this._dialog.close();
this._reloadLovelace!();
} catch (err) {
alert(`Saving failed: ${err.reason}`);
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-dialog-edit-card": HuiEditCardModal;
}
}
customElements.define("ha-dialog-edit-card", HuiEditCardModal);

View File

@ -0,0 +1,68 @@
import "@polymer/paper-button/paper-button.js";
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { fireEvent } from "../../../common/dom/fire_event.js";
import { HomeAssistant } from "../../../types.js";
let registeredDialog = false;
export class HuiCardOptions extends LitElement {
public cardId?: string;
protected hass?: HomeAssistant;
static get properties(): PropertyDeclarations {
return {
hass: {},
};
}
public connectedCallback() {
super.connectedCallback();
if (!registeredDialog) {
registeredDialog = true;
fireEvent(this, "register-dialog", {
dialogShowEvent: "show-edit-card",
dialogTag: "ha-dialog-edit-card",
dialogImport: () => import("./ha-dialog-edit-card"),
});
}
}
protected render() {
return html`
<style>
div {
border-top: 1px solid #e8e8e8;
padding: 5px 16px;
background: var(--paper-card-background-color, white);
box-shadow: rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px, rgba(0, 0, 0, 0.2) 0px 3px 1px -2px;
text-align: right;
}
paper-button {
color: var(--primary-color);
font-weight: 500;
}
</style>
<slot></slot>
<div>
<paper-button
@click="${this._editCard}"
>EDIT</paper-button>
</div>
`;
}
private _editCard() {
fireEvent(this, "show-edit-card", {
hass: this.hass,
cardId: this.cardId,
reloadLovelace: () => fireEvent(this, "config-refresh"),
});
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-card-options": HuiCardOptions;
}
}
customElements.define("hui-card-options", HuiCardOptions);

View File

@ -84,28 +84,40 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
></hui-notification-drawer>
<ha-app-layout id="layout">
<app-header slot="header" effects="waterfall" fixed condenses>
<app-toolbar>
<ha-menu-button narrow='[[narrow]]' show-menu='[[showMenu]]'></ha-menu-button>
<div main-title>[[_computeTitle(config)]]</div>
<hui-notifications-button
hass="[[hass]]"
notifications-open="{{notificationsOpen}}"
notifications="[[_notifications]]"
></hui-notifications-button>
<ha-start-voice-button hass="[[hass]]"></ha-start-voice-button>
<paper-menu-button
no-animations
horizontal-align="right"
horizontal-offset="-5"
>
<paper-icon-button icon="hass:dots-vertical" slot="dropdown-trigger"></paper-icon-button>
<paper-listbox on-iron-select="_deselect" slot="dropdown-content">
<paper-item on-click="_handleRefresh">Refresh</paper-item>
<paper-item on-click="_handleUnusedEntities">Unused entities</paper-item>
<paper-item on-click="_handleHelp">Help</paper-item>
</paper-listbox>
</paper-menu-button>
</app-toolbar>
<template is='dom-if' if="[[!_editMode]]">
<app-toolbar>
<ha-menu-button narrow='[[narrow]]' show-menu='[[showMenu]]'></ha-menu-button>
<div main-title>[[_computeTitle(config)]]</div>
<hui-notifications-button
hass="[[hass]]"
notifications-open="{{notificationsOpen}}"
notifications="[[_notifications]]"
></hui-notifications-button>
<ha-start-voice-button hass="[[hass]]"></ha-start-voice-button>
<paper-menu-button
no-animations
horizontal-align="right"
horizontal-offset="-5"
>
<paper-icon-button icon="hass:dots-vertical" slot="dropdown-trigger"></paper-icon-button>
<paper-listbox on-iron-select="_deselect" slot="dropdown-content">
<paper-item on-click="_handleRefresh">Refresh</paper-item>
<paper-item on-click="_handleUnusedEntities">Unused entities</paper-item>
<paper-item on-click="_editModeEnable">Configure UI</paper-item>
<paper-item on-click="_handleHelp">Help</paper-item>
</paper-listbox>
</paper-menu-button>
</app-toolbar>
</template>
<template is='dom-if' if="[[_editMode]]">
<app-toolbar>
<paper-icon-button
icon='hass:close'
on-click='_editModeDisable'
></paper-icon-button>
<div main-title>Edit UI</div>
</app-toolbar>
</template>
<div sticky hidden$="[[_computeTabsHidden(config.views)]]">
<paper-tabs scrollable selected="[[_curView]]" on-iron-activate="_handleViewSelected">
@ -170,6 +182,12 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
computed: "_updateNotifications(hass.states, _persistentNotifications)",
},
_editMode: {
type: Boolean,
value: false,
observer: "_editModeChanged",
},
routeData: Object,
};
}
@ -255,6 +273,18 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
window.open("https://www.home-assistant.io/lovelace/", "_blank");
}
_editModeEnable() {
this._editMode = true;
}
_editModeDisable() {
this._editMode = false;
}
_editModeChanged() {
this._selectView(this._curView);
}
_handleViewSelected(ev) {
const index = ev.detail.selected;
if (index !== this._curView) {
@ -284,10 +314,12 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
if (viewConfig.panel) {
view = createCardElement(viewConfig.cards[0]);
view.isPanel = true;
view.editMode = this._editMode;
} else {
view = document.createElement("hui-view");
view.config = viewConfig;
view.columns = this.columns;
view.editMode = this._editMode;
}
if (viewConfig.background) background = viewConfig.background;
}

View File

@ -2,10 +2,12 @@ import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import "../../components/entity/ha-state-label-badge.js";
import "./components/hui-card-options.ts";
import applyThemesOnElement from "../../common/dom/apply_themes_on_element.js";
import createCardElement from "./common/create-card-element";
import computeCardSize from "./common/compute-card-size";
class HUIView extends PolymerElement {
static get template() {
@ -73,6 +75,7 @@ class HUIView extends PolymerElement {
},
config: Object,
columns: Number,
editMode: Boolean,
};
}
@ -80,7 +83,7 @@ class HUIView extends PolymerElement {
return [
// Put all properties in 1 observer so we only call configChanged once
"_createBadges(config)",
"_createCards(config, columns)",
"_createCards(config, columns, editMode)",
];
}
@ -130,11 +133,25 @@ class HUIView extends PolymerElement {
return;
}
const elements = config.cards.map((cardConfig) => {
const elements = [];
const elementsToAppend = [];
for (const cardConfig of config.cards) {
const element = createCardElement(cardConfig);
element.hass = this.hass;
return element;
});
elements.push(element);
if (!this.editMode) {
elementsToAppend.push(element);
continue;
}
const wrapper = document.createElement("hui-card-options");
wrapper.hass = this.hass;
wrapper.cardId = cardConfig.id;
wrapper.editMode = this.editMode;
wrapper.appendChild(element);
elementsToAppend.push(wrapper);
}
let columns = [];
const columnEntityCount = [];
@ -161,15 +178,10 @@ class HUIView extends PolymerElement {
return minIndex;
}
elements.forEach((el) => {
// Trigger custom elements to build up DOM. This is needed for some elements
// that use the DOM to decide their height. We don't have to clean this up
// because a DOM element can only be in 1 position, so it will be removed from
// 'this' and added to the correct column afterwards.
this.appendChild(el);
const cardSize =
typeof el.getCardSize === "function" ? el.getCardSize() : 1;
columns[getColumnIndex(cardSize)].push(el);
elements.forEach((el, index) => {
const cardSize = computeCardSize(el);
// Element to append might be the wrapped card when we're editing.
columns[getColumnIndex(cardSize)].push(elementsToAppend[index]);
});
// Remove empty columns

View File

@ -1,7 +1,7 @@
{
"compilerOptions": {
"target": "es2017",
"module": "es2015",
"module": "esnext",
"moduleResolution": "node",
"lib": ["es2017", "dom", "dom.iterable"],
"noEmit": true,
@ -10,6 +10,7 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"strict": true,
"noImplicitAny": false
"noImplicitAny": false,
"skipLibCheck": true
}
}