UI Editor for picture card (#2240)

* UI Editor for `picture` card

This is a WIP.
* How should I handle service data? It's kind of freeform and I don't really have a good idea on what I should do.
* in action-editor I have two issues for `_navigation_path` and `_service` have TS errors saying the property doesn't exist on `ToggleActionConfig`. Not sure why that is the only type it is looking at. Should I be checking the type somewhere?

* Remove `id`

* Cleanup. Service-data still WIP

* Could use some help on service_data

* Perhaps a better/more structured method?

* Revert "Perhaps a better/more structured method?"

This reverts commit 1e1a1e44c16a18c5ffc380347cffd01e7fad52f9.

* Just playing around

* MVP doesn't include service data

* Address review comments

* Address review comments

* Name chunk and remove when unused

* Remove `more-info` action option

* Address review comments
This commit is contained in:
Ian Richardson 2018-12-18 10:25:56 -06:00 committed by Paulus Schoutsen
parent 5e1cd389b3
commit 4c5d3138c1
4 changed files with 284 additions and 6 deletions

View File

@ -2,7 +2,7 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import "../../../components/ha-card";
import { LovelaceCard } from "../types";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig, ActionConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { TemplateResult } from "lit-html";
@ -10,20 +10,31 @@ import { classMap } from "lit-html/directives/classMap";
import { handleClick } from "../common/handle-click";
import { longPress } from "../common/directives/long-press-directive";
interface Config extends LovelaceCardConfig {
export interface Config extends LovelaceCardConfig {
image?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
}
export class HuiPictureCard extends LitElement implements LovelaceCard {
public static async getConfigElement(): Promise<LovelaceCardEditor> {
await import(/* webpackChunkName: "hui-picture-card-editor" */ "../editor/config-elements/hui-picture-card-editor");
return document.createElement("hui-picture-card-editor");
}
public static getStubConfig(): object {
return {
image:
"https://www.home-assistant.io/images/merchandise/shirt-frontpage.png",
tap_action: { action: "none" },
hold_action: { action: "none" },
};
}
public hass?: HomeAssistant;
protected _config?: Config;
static get properties(): PropertyDeclarations {
return {
_config: {},
};
return { _config: {} };
}
public getCardSize(): number {

View File

@ -0,0 +1,132 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import "@polymer/paper-input/paper-textarea";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import "../../../components/ha-service-picker";
import { HomeAssistant } from "../../../types";
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
import { EditorTarget } from "../editor/types";
import {
ActionConfig,
NavigateActionConfig,
CallServiceActionConfig,
} from "../../../data/lovelace";
declare global {
// for fire event
interface HASSDomEvents {
"action-changed": undefined;
}
// for add event listener
interface HTMLElementEventMap {
"action-changed": HASSDomEvent<undefined>;
}
}
export class HuiActionEditor extends LitElement {
public config?: ActionConfig;
public label?: string;
public actions?: string[];
protected hass?: HomeAssistant;
static get properties(): PropertyDeclarations {
return { hass: {}, config: {} };
}
get _action(): string {
return this.config!.action || "";
}
get _navigation_path(): string {
const config = this.config! as NavigateActionConfig;
return config.navigation_path || "";
}
get _service(): string {
const config = this.config! as CallServiceActionConfig;
return config.service || "";
}
protected render(): TemplateResult {
if (!this.hass || !this.actions) {
return html``;
}
return html`
<paper-dropdown-menu
.label="${this.label}"
.configValue="${"action"}"
@value-changed="${this._valueChanged}"
>
<paper-listbox
slot="dropdown-content"
.selected="${this.actions.indexOf(this._action)}"
>
${
this.actions.map((action) => {
return html`
<paper-item>${action}</paper-item>
`;
})
}
</paper-listbox>
</paper-dropdown-menu>
${
this._action === "navigate"
? html`
<paper-input
label="Navigation Path"
.value="${this._navigation_path}"
.configValue="${"navigation_path"}"
@value-changed="${this._valueChanged}"
></paper-input>
`
: ""
}
${
this.config && this.config.action === "call-service"
? html`
<ha-service-picker
.hass="${this.hass}"
.value="${this._service}"
.configValue="${"service"}"
@value-changed="${this._valueChanged}"
></ha-service-picker>
<h3>Toggle Editor to input Service Data</h3>
`
: ""
}
`;
}
private _valueChanged(ev: Event): void {
if (!this.hass) {
return;
}
const target = ev.target! as EditorTarget;
if (
this.config &&
this.config[this[`${target.configValue}`]] === target.value
) {
return;
}
if (target.configValue === "action") {
this.config = { action: "none" };
}
if (target.configValue) {
this.config = { ...this.config!, [target.configValue!]: target.value };
fireEvent(this, "action-changed");
}
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-action-editor": HuiActionEditor;
}
}
customElements.define("hui-action-editor", HuiActionEditor);

View File

@ -0,0 +1,122 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import "@polymer/paper-input/paper-input";
import { struct } from "../../common/structs/struct";
import {
EntitiesEditorEvent,
EditorTarget,
actionConfigStruct,
} from "../types";
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Config } from "../../cards/hui-picture-card";
import { configElementStyle } from "./config-elements-style";
import { ActionConfig } from "../../../../data/lovelace";
import "../../components/hui-action-editor";
const cardConfigStruct = struct({
type: "string",
image: "string?",
tap_action: actionConfigStruct,
hold_action: actionConfigStruct,
});
export class HuiPictureCardEditor extends hassLocalizeLitMixin(LitElement)
implements LovelaceCardEditor {
public hass?: HomeAssistant;
private _config?: Config;
public setConfig(config: Config): void {
config = cardConfigStruct(config);
this._config = config;
}
static get properties(): PropertyDeclarations {
return { hass: {}, _config: {} };
}
get _image(): string {
return this._config!.image || "";
}
get _tap_action(): ActionConfig {
return this._config!.tap_action || { action: "more-info" };
}
get _hold_action(): ActionConfig {
return this._config!.hold_action || { action: "none" };
}
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
${configElementStyle}
<div class="card-config">
<paper-input
label="Image Url"
.value="${this._image}"
.configValue="${"image"}"
@value-changed="${this._valueChanged}"
></paper-input>
<div class="side-by-side">
<hui-action-editor
label="Tap Action"
.hass="${this.hass}"
.config="${this._tap_action}"
.actions="${["navigate", "call-service", "none"]}"
.configValue="${"tap_action"}"
@action-changed="${this._valueChanged}"
></hui-action-editor>
<hui-action-editor
label=Hold Action"
.hass="${this.hass}"
.config="${this._hold_action}"
.actions="${["navigate", "call-service", "none"]}"
.configValue="${"hold_action"}"
@action-changed="${this._valueChanged}"
></hui-action-editor>
</div>
</div>
`;
}
private _valueChanged(ev: EntitiesEditorEvent): void {
if (!this._config || !this.hass) {
return;
}
const target = ev.target! as EditorTarget;
if (
this[`_${target.configValue}`] === target.value ||
this[`_${target.configValue}`] === target.config
) {
return;
}
if (target.configValue) {
if (target.value === "") {
delete this._config[target.configValue!];
} else {
this._config = {
...this._config,
[target.configValue!]: target.value ? target.value : target.config,
};
}
}
fireEvent(this, "config-changed", { config: this._config });
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-picture-card-editor": HuiPictureCardEditor;
}
}
customElements.define("hui-picture-card-editor", HuiPictureCardEditor);

View File

@ -1,6 +1,11 @@
import { LovelaceCardConfig, LovelaceViewConfig } from "../../../data/lovelace";
import {
LovelaceCardConfig,
LovelaceViewConfig,
ActionConfig,
} from "../../../data/lovelace";
import { EntityConfig } from "../entity-rows/types";
import { InputType } from "zlib";
import { struct } from "../common/structs/struct";
export interface YamlChangedEvent extends Event {
detail: {
@ -37,8 +42,16 @@ export interface EditorTarget extends EventTarget {
checked?: boolean;
configValue?: string;
type?: InputType;
config: ActionConfig;
}
export interface CardPickTarget extends EventTarget {
type: string;
}
export const actionConfigStruct = struct({
action: "string",
navigation_path: "string?",
service: "string?",
service_data: "object?",
});