GUI editors for stacks (#4999)

* GUI editors for stacks

* fix type checking

* lint

* Address review comments

* Cleanup. Removing inline functions, combining others

* Give the class a more fitting name

* Final tweak

* Update stack cards

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Thomas Lovén 2020-02-27 14:07:55 +01:00 committed by GitHub
parent e2de660bec
commit 3cc7deda04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 244 additions and 26 deletions

View File

@ -1,4 +1,4 @@
import { html, TemplateResult } from "lit-element";
import { CSSResult, css } from "lit-element";
import { computeCardSize } from "../common/compute-card-size";
import { HuiStackCard } from "./hui-stack-card";
@ -17,9 +17,10 @@ class HuiHorizontalStackCard extends HuiStackCard {
return totalSize;
}
protected renderStyle(): TemplateResult {
return html`
<style>
static get styles(): CSSResult[] {
return [
super.sharedStyles,
css`
#root {
display: flex;
}
@ -34,8 +35,8 @@ class HuiHorizontalStackCard extends HuiStackCard {
#root > *:last-child {
margin-right: 0;
}
</style>
`;
`,
];
}
}

View File

@ -1,18 +1,34 @@
import { html, LitElement, TemplateResult, CSSResult, css } from "lit-element";
import {
html,
LitElement,
TemplateResult,
CSSResult,
css,
property,
} from "lit-element";
import { createCardElement } from "../create-element/create-card-element";
import { LovelaceCard } from "../types";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { StackCardConfig } from "./types";
export abstract class HuiStackCard extends LitElement implements LovelaceCard {
static get properties() {
return {
_config: {},
};
public static async getConfigElement(): Promise<LovelaceCardEditor> {
await import(
/* webpackChunkName: "hui-stack-card-editor" */ "../editor/config-elements/hui-stack-card-editor"
);
return document.createElement("hui-stack-card-editor");
}
public static getStubConfig(): object {
return { cards: [] };
}
@property() protected _cards?: LovelaceCard[];
@property() private _config?: StackCardConfig;
private _hass?: HomeAssistant;
set hass(hass: HomeAssistant) {
this._hass = hass;
@ -24,9 +40,6 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard {
element.hass = this._hass;
}
}
protected _cards?: LovelaceCard[];
private _config?: StackCardConfig;
private _hass?: HomeAssistant;
public abstract getCardSize(): number;
@ -42,12 +55,11 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard {
}
protected render(): TemplateResult {
if (!this._config) {
if (!this._config || !this._cards) {
return html``;
}
return html`
${this.renderStyle()}
${this._config.title
? html`
<div class="card-header">${this._config.title}</div>
@ -57,9 +69,7 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard {
`;
}
protected abstract renderStyle(): TemplateResult;
static get styles(): CSSResult {
static get sharedStyles(): CSSResult {
return css`
.card-header {
color: var(--ha-card-header-color, --primary-text-color);

View File

@ -1,4 +1,4 @@
import { html, TemplateResult } from "lit-element";
import { CSSResult, css } from "lit-element";
import { computeCardSize } from "../common/compute-card-size";
import { HuiStackCard } from "./hui-stack-card";
@ -18,9 +18,10 @@ class HuiVerticalStackCard extends HuiStackCard {
return totalSize;
}
protected renderStyle(): TemplateResult {
return html`
<style>
static get styles(): CSSResult[] {
return [
super.sharedStyles,
css`
#root {
display: flex;
flex-direction: column;
@ -34,8 +35,8 @@ class HuiVerticalStackCard extends HuiStackCard {
#root > *:last-child {
margin-bottom: 0;
}
</style>
`;
`,
];
}
}

View File

@ -243,6 +243,7 @@ export interface ShoppingListCardConfig extends LovelaceCardConfig {
export interface StackCardConfig extends LovelaceCardConfig {
cards: LovelaceCardConfig[];
title?: string;
}
export interface ThermostatCardConfig extends LovelaceCardConfig {

View File

@ -93,6 +93,10 @@ export class HuiCardOptions extends LitElement {
static get styles(): CSSResult {
return css`
:host(:hover) {
outline: 2px solid var(--primary-color);
}
ha-card {
border-top-right-radius: 0;
border-top-left-radius: 0;

View File

@ -0,0 +1,201 @@
import {
html,
LitElement,
TemplateResult,
customElement,
property,
CSSResult,
css,
} from "lit-element";
import "@polymer/paper-tabs";
import { struct } from "../../common/structs/struct";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { StackCardConfig } from "../../cards/types";
import { fireEvent } from "../../../../common/dom/fire_event";
const cardConfigStruct = struct({
type: "string",
cards: ["any"],
title: "string?",
});
@customElement("hui-stack-card-editor")
export class HuiStackCardEditor extends LitElement
implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: StackCardConfig;
@property() private _selectedCard: number = 0;
public setConfig(config: StackCardConfig): void {
this._config = cardConfigStruct(config);
}
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
}
const selected = this._selectedCard!;
const numcards = this._config.cards.length;
return html`
<div class="card-config">
<div class="toolbar">
<paper-tabs
.selected=${selected}
scrollable
@iron-select=${this._handleSelectedCard}
>
${this._config.cards.map((_card, i) => {
return html`
<paper-tab>
${i + 1}
</paper-tab>
`;
})}
</paper-tabs>
<paper-tabs
id="add-card"
.selected=${selected === numcards ? "0" : undefined}
@iron-select=${this._handleSelectedCard}
>
<paper-tab>
<ha-icon icon="hass:plus"></ha-icon>
</paper-tab>
<paper-tabs>
</div>
<div id="editor">
${
selected < numcards
? html`
<div id="card-options">
<paper-icon-button
id="move-before"
title="Move card before"
icon="hass:arrow-left"
?disabled=${selected === 0}
@click=${this._handleMove}
></paper-icon-button>
<paper-icon-button
id="move-after"
title="Move card after"
icon="hass:arrow-right"
?disabled=${selected === numcards - 1}
@click=${this._handleMove}
></paper-icon-button>
<paper-icon-button
icon="hass:delete"
@click=${this._handleDeleteCard}
></paper-icon-button>
</div>
<hui-card-editor
.hass=${this.hass}
.value="${this._config.cards[selected]}"
@config-changed="${this._handleConfigChanged}"
></hui-card-editor>
`
: html`
<hui-card-picker
.hass="${this.hass}"
@config-changed="${this._handleCardPicked}"
></hui-card-picker>
`
}
</div>
</div>
`;
}
private _handleSelectedCard(ev) {
this._selectedCard =
ev.target.id === "add-card"
? this._config!.cards.length
: parseInt(ev.target.selected, 10);
}
private _handleConfigChanged(ev) {
ev.stopPropagation();
if (!this._config) {
return;
}
this._config.cards[this._selectedCard] = ev.detail.config;
fireEvent(this, "config-changed", { config: this._config });
}
private _handleCardPicked(ev) {
ev.stopPropagation();
if (!this._config) {
return;
}
const config = ev.detail.config;
this._config.cards.push(config);
fireEvent(this, "config-changed", { config: this._config });
}
private _handleDeleteCard() {
if (!this._config) {
return;
}
this._config.cards.splice(this._selectedCard, 1);
this._selectedCard = Math.max(0, this._selectedCard - 1);
fireEvent(this, "config-changed", { config: this._config });
}
private _handleMove(ev) {
if (!this._config) {
return;
}
const source = this._selectedCard;
const target = ev.target.id === "move-before" ? source - 1 : source + 1;
const card = this._config.cards.splice(this._selectedCard, 1)[0];
this._config.cards.splice(target, 0, card);
this._selectedCard = target;
fireEvent(this, "config-changed", { config: this._config });
}
static get styles(): CSSResult {
return css`
.toolbar {
display: flex;
--paper-tabs-selection-bar-color: var(--primary-color);
--paper-tab-ink: var(--primary-color);
}
paper-tabs {
display: flex;
font-size: 14px;
flex-grow: 1;
}
#add-card {
max-width: 32px;
padding: 0;
}
#card-options {
display: flex;
justify-content: flex-end;
width: 100%;
}
#editor {
border: 1px solid var(--divider-color);
padding: 12px;
}
@media (max-width: 450px) {
#editor {
margin: 0 -12px;
}
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-stack-card-editor": HuiStackCardEditor;
}
}