add UI editor

This commit is contained in:
Ian Richardson
2019-11-05 22:35:12 -06:00
parent 81c3e51342
commit d7c51bcbe6
11 changed files with 570 additions and 17 deletions
+3 -2
View File
@@ -3,6 +3,7 @@ resources:
type: module
views:
- cards:
- type: "custom:boilerplate-card"
name: Boilerplate card development
- type: 'custom:boilerplate-card'
name: Boilerplate Card Development
entity: sun.sun
test_gui: true
+3
View File
@@ -10,4 +10,7 @@ module.exports = {
sourceType: 'module', // Allows for the use of imports
experimentalDecorators: true,
},
rules: {
"@typescript-eslint/camelcase": 0
}
};
+1
View File
@@ -1,2 +1,3 @@
/node_modules/
/.rpt2_cache/
package-lock.json
+288 -5
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "boilerplate-card",
"version": "1.1.1",
"version": "1.1.3",
"description": "Lovelace boilerplate-card",
"keywords": [
"home-assistant",
+8 -1
View File
@@ -111,7 +111,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
if (this.held) {
fireEvent(element as HTMLElement, 'action', { action: 'hold' });
} else if (options.hasDoubleTap) {
if ((ev as MouseEvent).detail === 1) {
if ((ev as MouseEvent).detail === 1 || ev.type === 'keyup') {
this.dblClickTimeout = window.setTimeout(() => {
fireEvent(element as HTMLElement, 'action', { action: 'tap' });
}, 250);
@@ -126,9 +126,16 @@ class ActionHandler extends HTMLElement implements ActionHandler {
window.setTimeout(() => (this.cooldownEnd = false), 100);
};
const handleEnter = (ev: Event): void => {
if ((ev as KeyboardEvent).keyCode === 13) {
return clickEnd(ev);
}
};
element.addEventListener('touchstart', clickStart, { passive: true });
element.addEventListener('touchend', clickEnd);
element.addEventListener('touchcancel', clickEnd);
element.addEventListener('keyup', handleEnter);
// iOS 13 sends a complete normal touchstart-touchend series of events followed by a mousedown-click series.
// That might be a bug, but until it's fixed, this should make action-handler work.
+21 -3
View File
@@ -5,9 +5,13 @@ import {
hasAction,
ActionHandlerEvent,
handleAction,
LovelaceCardEditor,
getLovelace,
} from 'custom-card-helpers';
import { BoilerplateConfig } from './types';
import './editor';
import { BoilerplateCardConfig } from './types';
import { actionHandler } from './action-handler-directive';
import { CARD_VERSION } from './const';
@@ -21,16 +25,28 @@ console.info(
// TODO Name your custom element
@customElement('boilerplate-card')
export class BoilerplateCard extends LitElement {
public static async getConfigElement(): Promise<LovelaceCardEditor> {
return document.createElement('boilerplate-card-editor') as LovelaceCardEditor;
}
public static getStubConfig(): object {
return {};
}
// TODO Add any properities that should cause your element to re-render here
@property() public hass?: HomeAssistant;
@property() private _config?: BoilerplateConfig;
@property() private _config?: BoilerplateCardConfig;
public setConfig(config: BoilerplateConfig): void {
public setConfig(config: BoilerplateCardConfig): void {
// TODO Check for required fields and that they are of the proper format
if (!config || config.show_error) {
throw new Error('Invalid configuration');
}
if (config.test_gui) {
getLovelace().setEditMode(true);
}
this._config = {
name: 'Boilerplate',
...config,
@@ -64,6 +80,8 @@ export class BoilerplateCard extends LitElement {
hasDoubleTap: hasAction(this._config.double_tap_action),
repeat: this._config.hold_action ? this._config.hold_action.repeat : undefined,
})}
tabindex="0"
aria-label=${`Boilerplate: ${this._config.entity}`}
></ha-card>
`;
}
+1 -1
View File
@@ -1 +1 @@
export const CARD_VERSION = '1.1.2';
export const CARD_VERSION = '1.1.3';
+240 -1
View File
@@ -1 +1,240 @@
// TODO
import { LitElement, html, customElement, property, TemplateResult, CSSResult, css } from 'lit-element';
import { HomeAssistant, fireEvent, LovelaceCardEditor, ActionConfig } from 'custom-card-helpers';
import { BoilerplateCardConfig } from './types';
const options = [
{
icon: 'tune',
name: 'Required',
secondary: 'Required options for this card to function',
show: true,
},
{
icon: 'gesture-tap-hold',
name: 'Actions',
secondary: 'Perform actions based on tapping/clicking',
show: false,
},
{
icon: 'palette',
name: 'Appearance',
secondary: 'Customize the name, icon, etc',
show: false,
},
];
@customElement('boilerplate-card-editor')
export class BoilerplateCardEditor extends LitElement implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: BoilerplateCardConfig;
@property() private _toggle?: boolean;
public setConfig(config: BoilerplateCardConfig): void {
this._config = config;
}
get _name(): string {
if (this._config) {
return this._config.name || '';
}
return '';
}
get _entity(): string {
if (this._config) {
return this._config.entity || '';
}
return '';
}
get _show_warning(): boolean {
if (this._config) {
return this._config.show_warning || false;
}
return false;
}
get _show_error(): boolean {
if (this._config) {
return this._config.show_error || false;
}
return false;
}
get _tap_action(): ActionConfig {
if (this._config) {
return this._config.tap_aciton || { action: 'more-info' };
}
return { action: 'more-info' };
}
get _hold_action(): ActionConfig {
if (this._config) {
return this._config.hold_action || { action: 'none' };
}
return { action: 'none' };
}
get _double_tap_action(): ActionConfig {
if (this._config) {
return this._config.double_tap_action || { action: 'none' };
}
return { action: 'none' };
}
protected render(): TemplateResult | void {
if (!this.hass) {
return html``;
}
// You can restrict on domain type
const entities = Object.keys(this.hass.states).filter(eid => eid.substr(0, eid.indexOf('.')) === 'sun');
return html`
<div class="card-config">
<div class="option" @click=${this._toggleOption} .index=${0}>
<div class="row">
<ha-icon .icon=${`mdi:${options[0].icon}`}></ha-icon>
<div class="title">${options[0].name}</div>
</div>
<div class="secondary">${options[0].secondary}</div>
</div>
${options[0].show
? html`
<div class="values">
<paper-dropdown-menu
label="Entity (Required)"
@value-changed=${this._valueChanged}
.configValue=${'entity'}
>
<paper-listbox slot="dropdown-content" .selected=${entities.indexOf(this._entity)}>
${entities.map(entity => {
return html`
<paper-item>${entity}</paper-item>
`;
})}
</paper-listbox>
</paper-dropdown-menu>
</div>
`
: ''}
<div class="option" @click=${this._toggleOption} .index=${1}>
<div class="row">
<ha-icon .icon=${`mdi:${options[1].icon}`}></ha-icon>
<div class="title">${options[1].name}</div>
</div>
<div class="secondary">${options[1].secondary}</div>
</div>
${options[1].show
? html`
<div class="values">
<paper-item>Action Editors Coming Soon</paper-item>
</div>
`
: ''}
<div class="option" @click=${this._toggleOption} .index=${2}>
<div class="row">
<ha-icon .icon=${`mdi:${options[2].icon}`}></ha-icon>
<div class="title">${options[2].name}</div>
</div>
<div class="secondary">${options[2].secondary}</div>
</div>
${options[2].show
? html`
<div class="values">
<paper-input
label="Name (Optional)"
.value=${this._name}
.configValue=${'name'}
@value-changed=${this._valueChanged}
></paper-input>
<br />
<ha-switch
aria-label=${`Toggle warning ${this._show_warning ? 'off' : 'on'}`}
.checked=${this._show_warning !== false}
.configValue=${'show_warning'}
@change=${this._valueChanged}
>Show Warning?</ha-switch
>
<ha-switch
aria-label=${`Toggle error ${this._show_error ? 'off' : 'on'}`}
.checked=${this._show_error !== false}
.configValue=${'show_error'}
@change=${this._valueChanged}
>Show Error?</ha-switch
>
</div>
`
: ''}
</div>
`;
}
private _toggleOption(ev): void {
console.log(options[ev.target.index]);
const show = !options[ev.target.index].show;
options.forEach(option => (option.show = false));
options[ev.target.index].show = show;
this._toggle = !this._toggle;
}
private _valueChanged(ev): void {
if (!this._config || !this.hass) {
return;
}
const target = ev.target;
if (this[`_${target.configValue}`] === target.value) {
return;
}
if (target.configValue) {
if (target.value === '') {
delete this._config[target.configValue];
} else {
this._config = {
...this._config,
[target.configValue]: target.checked !== undefined ? target.checked : target.value,
};
}
}
fireEvent(this, 'config-changed', { config: this._config });
}
static get styles(): CSSResult {
return css`
.option {
padding: 4px 0px;
cursor: pointer;
}
.row {
display: flex;
margin-bottom: -14px;
pointer-events: none;
}
.title {
padding-left: 16px;
margin-top: -6px;
pointer-events: none;
}
.secondary {
padding-left: 40px;
color: var(--secondary-text-color);
pointer-events: none;
}
.values {
padding-left: 16px;
background: var(--secondary-background-color);
}
ha-switch {
padding-bottom: 8px;
}
`;
}
}
+2 -1
View File
@@ -1,11 +1,12 @@
import { ActionConfig } from 'custom-card-helpers';
// TODO Add your configuration elements here for type-checking
export interface BoilerplateConfig {
export interface BoilerplateCardConfig {
type: string;
name?: string;
show_warning?: boolean;
show_error?: boolean;
test_gui?: boolean;
entity?: string;
tap_aciton?: ActionConfig;
hold_action?: ActionConfig;