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>
|
></hui-notification-drawer>
|
||||||
<ha-app-layout id="layout">
|
<ha-app-layout id="layout">
|
||||||
<app-header slot="header" effects="waterfall" fixed condenses>
|
<app-header slot="header" effects="waterfall" fixed condenses>
|
||||||
<app-toolbar>
|
<template is='dom-if' if="[[!_editMode]]">
|
||||||
<ha-menu-button narrow='[[narrow]]' show-menu='[[showMenu]]'></ha-menu-button>
|
<app-toolbar>
|
||||||
<div main-title>[[_computeTitle(config)]]</div>
|
<ha-menu-button narrow='[[narrow]]' show-menu='[[showMenu]]'></ha-menu-button>
|
||||||
<hui-notifications-button
|
<div main-title>[[_computeTitle(config)]]</div>
|
||||||
hass="[[hass]]"
|
<hui-notifications-button
|
||||||
notifications-open="{{notificationsOpen}}"
|
hass="[[hass]]"
|
||||||
notifications="[[_notifications]]"
|
notifications-open="{{notificationsOpen}}"
|
||||||
></hui-notifications-button>
|
notifications="[[_notifications]]"
|
||||||
<ha-start-voice-button hass="[[hass]]"></ha-start-voice-button>
|
></hui-notifications-button>
|
||||||
<paper-menu-button
|
<ha-start-voice-button hass="[[hass]]"></ha-start-voice-button>
|
||||||
no-animations
|
<paper-menu-button
|
||||||
horizontal-align="right"
|
no-animations
|
||||||
horizontal-offset="-5"
|
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-icon-button icon="hass:dots-vertical" slot="dropdown-trigger"></paper-icon-button>
|
||||||
<paper-item on-click="_handleRefresh">Refresh</paper-item>
|
<paper-listbox on-iron-select="_deselect" slot="dropdown-content">
|
||||||
<paper-item on-click="_handleUnusedEntities">Unused entities</paper-item>
|
<paper-item on-click="_handleRefresh">Refresh</paper-item>
|
||||||
<paper-item on-click="_handleHelp">Help</paper-item>
|
<paper-item on-click="_handleUnusedEntities">Unused entities</paper-item>
|
||||||
</paper-listbox>
|
<paper-item on-click="_editModeEnable">Configure UI</paper-item>
|
||||||
</paper-menu-button>
|
<paper-item on-click="_handleHelp">Help</paper-item>
|
||||||
</app-toolbar>
|
</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)]]">
|
<div sticky hidden$="[[_computeTabsHidden(config.views)]]">
|
||||||
<paper-tabs scrollable selected="[[_curView]]" on-iron-activate="_handleViewSelected">
|
<paper-tabs scrollable selected="[[_curView]]" on-iron-activate="_handleViewSelected">
|
||||||
@ -170,6 +182,12 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
|
|||||||
computed: "_updateNotifications(hass.states, _persistentNotifications)",
|
computed: "_updateNotifications(hass.states, _persistentNotifications)",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_editMode: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
observer: "_editModeChanged",
|
||||||
|
},
|
||||||
|
|
||||||
routeData: Object,
|
routeData: Object,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -255,6 +273,18 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
|
|||||||
window.open("https://www.home-assistant.io/lovelace/", "_blank");
|
window.open("https://www.home-assistant.io/lovelace/", "_blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_editModeEnable() {
|
||||||
|
this._editMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_editModeDisable() {
|
||||||
|
this._editMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_editModeChanged() {
|
||||||
|
this._selectView(this._curView);
|
||||||
|
}
|
||||||
|
|
||||||
_handleViewSelected(ev) {
|
_handleViewSelected(ev) {
|
||||||
const index = ev.detail.selected;
|
const index = ev.detail.selected;
|
||||||
if (index !== this._curView) {
|
if (index !== this._curView) {
|
||||||
@ -284,10 +314,12 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
|
|||||||
if (viewConfig.panel) {
|
if (viewConfig.panel) {
|
||||||
view = createCardElement(viewConfig.cards[0]);
|
view = createCardElement(viewConfig.cards[0]);
|
||||||
view.isPanel = true;
|
view.isPanel = true;
|
||||||
|
view.editMode = this._editMode;
|
||||||
} else {
|
} else {
|
||||||
view = document.createElement("hui-view");
|
view = document.createElement("hui-view");
|
||||||
view.config = viewConfig;
|
view.config = viewConfig;
|
||||||
view.columns = this.columns;
|
view.columns = this.columns;
|
||||||
|
view.editMode = this._editMode;
|
||||||
}
|
}
|
||||||
if (viewConfig.background) background = viewConfig.background;
|
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 { PolymerElement } from "@polymer/polymer/polymer-element.js";
|
||||||
|
|
||||||
import "../../components/entity/ha-state-label-badge.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 applyThemesOnElement from "../../common/dom/apply_themes_on_element.js";
|
||||||
|
|
||||||
import createCardElement from "./common/create-card-element";
|
import createCardElement from "./common/create-card-element";
|
||||||
|
import computeCardSize from "./common/compute-card-size";
|
||||||
|
|
||||||
class HUIView extends PolymerElement {
|
class HUIView extends PolymerElement {
|
||||||
static get template() {
|
static get template() {
|
||||||
@ -73,6 +75,7 @@ class HUIView extends PolymerElement {
|
|||||||
},
|
},
|
||||||
config: Object,
|
config: Object,
|
||||||
columns: Number,
|
columns: Number,
|
||||||
|
editMode: Boolean,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +83,7 @@ class HUIView extends PolymerElement {
|
|||||||
return [
|
return [
|
||||||
// Put all properties in 1 observer so we only call configChanged once
|
// Put all properties in 1 observer so we only call configChanged once
|
||||||
"_createBadges(config)",
|
"_createBadges(config)",
|
||||||
"_createCards(config, columns)",
|
"_createCards(config, columns, editMode)",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,11 +133,25 @@ class HUIView extends PolymerElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const elements = config.cards.map((cardConfig) => {
|
const elements = [];
|
||||||
|
const elementsToAppend = [];
|
||||||
|
for (const cardConfig of config.cards) {
|
||||||
const element = createCardElement(cardConfig);
|
const element = createCardElement(cardConfig);
|
||||||
element.hass = this.hass;
|
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 = [];
|
let columns = [];
|
||||||
const columnEntityCount = [];
|
const columnEntityCount = [];
|
||||||
@ -161,15 +178,10 @@ class HUIView extends PolymerElement {
|
|||||||
return minIndex;
|
return minIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
elements.forEach((el) => {
|
elements.forEach((el, index) => {
|
||||||
// Trigger custom elements to build up DOM. This is needed for some elements
|
const cardSize = computeCardSize(el);
|
||||||
// that use the DOM to decide their height. We don't have to clean this up
|
// Element to append might be the wrapped card when we're editing.
|
||||||
// because a DOM element can only be in 1 position, so it will be removed from
|
columns[getColumnIndex(cardSize)].push(elementsToAppend[index]);
|
||||||
// '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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove empty columns
|
// Remove empty columns
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2017",
|
"target": "es2017",
|
||||||
"module": "es2015",
|
"module": "esnext",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"lib": ["es2017", "dom", "dom.iterable"],
|
"lib": ["es2017", "dom", "dom.iterable"],
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
@ -10,6 +10,7 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noImplicitAny": false
|
"noImplicitAny": false,
|
||||||
|
"skipLibCheck": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user