mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 17:26:42 +00:00
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:
parent
de5f02d706
commit
7ca2ef4c4c
21
src/panels/lovelace/common/data.ts
Normal file
21
src/panels/lovelace/common/data.ts
Normal 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,
|
||||
});
|
99
src/panels/lovelace/components/ha-dialog-edit-card.ts
Normal file
99
src/panels/lovelace/components/ha-dialog-edit-card.ts
Normal 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);
|
68
src/panels/lovelace/components/hui-card-options.ts
Normal file
68
src/panels/lovelace/components/hui-card-options.ts
Normal 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);
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user