From 7178d208d3b5280266ae5f6a7e8f44744c331f0b Mon Sep 17 00:00:00 2001 From: Zack Arnett Date: Mon, 29 Oct 2018 14:33:05 -0400 Subject: [PATCH 1/6] Light Card addition --- gallery/src/demos/demo-hui-light-card.js | 48 +++ src/panels/lovelace/cards/hui-light-card.ts | 312 ++++++++++++++++++ .../lovelace/common/create-card-element.js | 2 + src/types.ts | 9 + 4 files changed, 371 insertions(+) create mode 100644 gallery/src/demos/demo-hui-light-card.js create mode 100644 src/panels/lovelace/cards/hui-light-card.ts diff --git a/gallery/src/demos/demo-hui-light-card.js b/gallery/src/demos/demo-hui-light-card.js new file mode 100644 index 0000000000..8f5b2cca28 --- /dev/null +++ b/gallery/src/demos/demo-hui-light-card.js @@ -0,0 +1,48 @@ +import { html } from "@polymer/polymer/lib/utils/html-tag.js"; +import { PolymerElement } from "@polymer/polymer/polymer-element.js"; + +import getEntity from "../data/entity.js"; +import provideHass from "../data/provide_hass.js"; +import "../components/demo-cards.js"; + +const ENTITIES = [ + getEntity("light", "bed_light", "on", { + friendly_name: "Bed Light", + brightness: 130, + }), +]; + +const CONFIGS = [ + { + heading: "Basic example", + config: ` +- type: light + entity: light.bed_light + `, + }, +]; + +class DemoLightEntity extends PolymerElement { + static get template() { + return html` + + `; + } + + static get properties() { + return { + _configs: { + type: Object, + value: CONFIGS, + }, + }; + } + + ready() { + super.ready(); + const hass = provideHass(this.$.demos); + hass.addEntities(ENTITIES); + } +} + +customElements.define("demo-hui-light-card", DemoLightEntity); diff --git a/src/panels/lovelace/cards/hui-light-card.ts b/src/panels/lovelace/cards/hui-light-card.ts new file mode 100644 index 0000000000..2cb22acdf9 --- /dev/null +++ b/src/panels/lovelace/cards/hui-light-card.ts @@ -0,0 +1,312 @@ +import { html, LitElement, PropertyValues } from "@polymer/lit-element"; +import { fireEvent } from "../../../common/dom/fire_event.js"; +import { styleMap } from "lit-html/directives/styleMap.js"; +import computeStateName from "../../../common/entity/compute_state_name.js"; +import stateIcon from "../../../common/entity/state_icon.js"; +import { jQuery } from "../../../resources/jquery"; + +import "../../../components/ha-card.js"; +import "../../../components/ha-icon.js"; +import { roundSliderStyle } from "../../../resources/jquery.roundslider"; + +import { HomeAssistant, LightEntity } from "../../../types.js"; +import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; +import { LovelaceCard, LovelaceConfig } from "../types.js"; +import { longPress } from "../common/directives/long-press-directive"; + +const lightConfig = { + radius: 80, + step: 1, + circleShape: "pie", + startAngle: 315, + width: 5, + min: 1, + max: 100, + sliderType: "min-range", + lineCap: "round", + handleSize: "+12", + showTooltip: false, +}; + +interface Config extends LovelaceConfig { + entity: string; + name?: string; +} + +export class HuiLightCard extends hassLocalizeLitMixin(LitElement) + implements LovelaceCard { + public hass?: HomeAssistant; + private _config?: Config; + private _brightnessTimout?: NodeJS.Timer; + + static get properties() { + return { + hass: {}, + _config: {}, + }; + } + + public getCardSize() { + return 2; + } + + public setConfig(config: Config) { + if (!config.entity || config.entity.split(".")[0] !== "light") { + throw new Error("Specify an entity from within the light domain."); + } + + this._config = config; + } + + protected render() { + if (!this.hass || !this._config) { + return html``; + } + + const stateObj = this.hass.states[this._config!.entity] as LightEntity; + + return html` + ${this.renderStyle()} + + ${ + !stateObj + ? html` +
Entity not available: ${ + this._config.entity + }
` + : html` +
+
+
+ +
+
${this._config.name || + computeStateName(stateObj)}
+
+
+ ` + } + +
+ `; + } + + protected shouldUpdate(changedProps: PropertyValues) { + if (changedProps.get("hass")) { + return ( + (changedProps.get("hass") as any).states[this._config!.entity] !== + this.hass!.states[this._config!.entity] + ); + } + return (changedProps as unknown) as boolean; + } + + protected firstUpdated() { + const brightness = this.hass!.states[this._config!.entity].attributes + .brightness; + jQuery("#light", this.shadowRoot).roundSlider({ + ...lightConfig, + change: (value) => this._setBrightness(value), + drag: (value) => this._dragEvent(value), + start: () => this._showBrightness(), + stop: () => this._hideBrightness(), + }); + this.shadowRoot!.querySelector(".brightness")!.innerHTML = + (Math.round((brightness / 254) * 100) || 0) + "%"; + } + + protected updated() { + const attrs = this.hass!.states[this._config!.entity].attributes; + + jQuery("#light", this.shadowRoot).roundSlider({ + value: Math.round((attrs.brightness / 254) * 100) || 0, + }); + } + + private renderStyle() { + return html` + ${roundSliderStyle} + + `; + } + + private _dragEvent(e) { + this.shadowRoot!.querySelector(".brightness")!.innerHTML = e.value + "%"; + } + + private _showBrightness() { + clearTimeout(this._brightnessTimout); + this.shadowRoot!.querySelector(".brightness")!.classList.add( + "show_brightness" + ); + } + + private _hideBrightness() { + this._brightnessTimout = setTimeout(() => { + this.shadowRoot!.querySelector(".brightness")!.classList.remove( + "show_brightness" + ); + }, 500); + } + + private _setBrightness(e) { + this.hass!.callService("light", "turn_on", { + entity_id: this._config!.entity, + brightness_pct: e.value, + }); + } + + private _computeBrightness(stateObj: LightEntity) { + if (!stateObj.attributes.brightness) { + return ""; + } + const brightness = stateObj.attributes.brightness; + return `brightness(${(brightness + 245) / 5}%)`; + } + + private _computeColor(stateObj: LightEntity) { + if (!stateObj.attributes.hs_color) { + return ""; + } + const [hue, sat] = stateObj.attributes.hs_color; + if (sat <= 10) { + return ""; + } + return `hsl(${hue}, 100%, ${100 - sat / 2}%)`; + } + + private _handleClick(hold: boolean) { + const entityId = this._config!.entity; + + if (hold) { + fireEvent(this, "hass-more-info", { + entityId, + }); + return; + } + + this.hass!.callService("light", "toggle", { + entity_id: entityId, + }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-light-card": HuiLightCard; + } +} + +customElements.define("hui-light-card", HuiLightCard); diff --git a/src/panels/lovelace/common/create-card-element.js b/src/panels/lovelace/common/create-card-element.js index 3759bc814c..92db6f4a6f 100644 --- a/src/panels/lovelace/common/create-card-element.js +++ b/src/panels/lovelace/common/create-card-element.js @@ -10,6 +10,7 @@ import "../cards/hui-glance-card.ts"; import "../cards/hui-history-graph-card.js"; import "../cards/hui-horizontal-stack-card.ts"; import "../cards/hui-iframe-card.ts"; +import "../cards/hui-light-card"; import "../cards/hui-map-card.js"; import "../cards/hui-markdown-card.ts"; import "../cards/hui-media-control-card.js"; @@ -38,6 +39,7 @@ const CARD_TYPES = new Set([ "history-graph", "horizontal-stack", "iframe", + "light", "map", "markdown", "media-control", diff --git a/src/types.ts b/src/types.ts index 359425876a..b0d3fd2a88 100644 --- a/src/types.ts +++ b/src/types.ts @@ -111,3 +111,12 @@ export type ClimateEntity = HassEntityBase & { aux_heat?: "on" | "off"; }; }; + +export type LightEntity = HassEntityBase & { + attributes: HassEntityAttributeBase & { + min_mireds: number; + max_mireds: number; + friendly_name: string; + brightness: number; + }; +}; From bb4ce278b08fe8bd9b79672e684e422e16562ff2 Mon Sep 17 00:00:00 2001 From: Zack Arnett Date: Mon, 29 Oct 2018 18:07:57 -0400 Subject: [PATCH 2/6] Fixing Gallery and updating timeout type --- gallery/src/data/entity.js | 4 +++- src/panels/lovelace/cards/hui-light-card.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/gallery/src/data/entity.js b/gallery/src/data/entity.js index b2b90a50ca..d705322d81 100644 --- a/gallery/src/data/entity.js +++ b/gallery/src/data/entity.js @@ -55,7 +55,9 @@ export class LightEntity extends Entity { if (service === "turn_on") { // eslint-disable-next-line - const { brightness, hs_color } = data; + let { brightness, hs_color, brightness_pct } = data; + // eslint-disable-next-line + brightness = (255 * brightness_pct) / 100; this.update( "on", Object.assign(this.attributes, { diff --git a/src/panels/lovelace/cards/hui-light-card.ts b/src/panels/lovelace/cards/hui-light-card.ts index 2cb22acdf9..c3761b2724 100644 --- a/src/panels/lovelace/cards/hui-light-card.ts +++ b/src/panels/lovelace/cards/hui-light-card.ts @@ -37,7 +37,7 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) implements LovelaceCard { public hass?: HomeAssistant; private _config?: Config; - private _brightnessTimout?: NodeJS.Timer; + private _brightnessTimout?: number; static get properties() { return { @@ -254,7 +254,7 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) } private _hideBrightness() { - this._brightnessTimout = setTimeout(() => { + this._brightnessTimout = window.setTimeout(() => { this.shadowRoot!.querySelector(".brightness")!.classList.remove( "show_brightness" ); From 1fcf510278be91909bcc69bd8bfbc3a146128913 Mon Sep 17 00:00:00 2001 From: Zack Arnett Date: Mon, 29 Oct 2018 18:11:34 -0400 Subject: [PATCH 3/6] Adding Type to Returns --- src/panels/lovelace/cards/hui-light-card.ts | 201 ++++++++++---------- 1 file changed, 101 insertions(+), 100 deletions(-) diff --git a/src/panels/lovelace/cards/hui-light-card.ts b/src/panels/lovelace/cards/hui-light-card.ts index c3761b2724..cde25a7135 100644 --- a/src/panels/lovelace/cards/hui-light-card.ts +++ b/src/panels/lovelace/cards/hui-light-card.ts @@ -13,6 +13,7 @@ import { HomeAssistant, LightEntity } from "../../../types.js"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; import { LovelaceCard, LovelaceConfig } from "../types.js"; import { longPress } from "../common/directives/long-press-directive"; +import { TemplateResult } from "lit-html"; const lightConfig = { radius: 80, @@ -138,107 +139,107 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) }); } - private renderStyle() { + private renderStyle(): TemplateResult { return html` ${roundSliderStyle} - + } + #light .rs-path-color { + background-color: #d6d6d6; + } + #light .rs-handle { + background-color: #FFF; + padding: 7px; + border: 2px solid #d6d6d6; + } + #light .rs-handle.rs-focus { + border-color:var(--primary-color); + } + #light .rs-handle:after { + border-color: var(--primary-color); + background-color: var(--primary-color); + } + #light .rs-border { + border-color: transparent; + } + ha-icon { + margin: auto; + width: 76px; + height: 76px; + color: var(--paper-item-icon-color, #44739e); + cursor: pointer; + } + ha-icon[data-state=on] { + color: var(--paper-item-icon-active-color, #FDD835); + } + ha-icon[data-state=unavailable] { + color: var(--state-icon-unavailable-color); + } + .name { + padding-top: 40px; + font-size: 1.2rem; + } + .brightness { + font-size: 1.2rem; + position: absolute; + margin: 0 auto; + left: 50%; + top: 10%; + transform: translate(-50%); + opacity: 0; + transition: opacity .5s ease-in-out; + -moz-transition: opacity .5s ease-in-out; + -webkit-transition: opacity .5s ease-in-out; + cursor: pointer; + color: white; + text-shadow: + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000; + } + .show_brightness { + opacity: 1; + } + .not-found { + flex: 1; + background-color: yellow; + padding: 8px; + } + `; } @@ -268,7 +269,7 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) }); } - private _computeBrightness(stateObj: LightEntity) { + private _computeBrightness(stateObj: LightEntity): string { if (!stateObj.attributes.brightness) { return ""; } @@ -276,7 +277,7 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) return `brightness(${(brightness + 245) / 5}%)`; } - private _computeColor(stateObj: LightEntity) { + private _computeColor(stateObj: LightEntity): string { if (!stateObj.attributes.hs_color) { return ""; } From 410b66d40f70c1494390f8b6ac2ac6246f1ec154 Mon Sep 17 00:00:00 2001 From: Zack Arnett Date: Mon, 29 Oct 2018 18:15:35 -0400 Subject: [PATCH 4/6] Adding more types --- src/panels/lovelace/cards/hui-light-card.ts | 31 ++++++++++++--------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/panels/lovelace/cards/hui-light-card.ts b/src/panels/lovelace/cards/hui-light-card.ts index cde25a7135..83058920c9 100644 --- a/src/panels/lovelace/cards/hui-light-card.ts +++ b/src/panels/lovelace/cards/hui-light-card.ts @@ -1,4 +1,9 @@ -import { html, LitElement, PropertyValues } from "@polymer/lit-element"; +import { + html, + LitElement, + PropertyValues, + PropertyDeclarations, +} from "@polymer/lit-element"; import { fireEvent } from "../../../common/dom/fire_event.js"; import { styleMap } from "lit-html/directives/styleMap.js"; import computeStateName from "../../../common/entity/compute_state_name.js"; @@ -40,18 +45,18 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) private _config?: Config; private _brightnessTimout?: number; - static get properties() { + static get properties(): PropertyDeclarations { return { hass: {}, _config: {}, }; } - public getCardSize() { + public getCardSize(): number { return 2; } - public setConfig(config: Config) { + public setConfig(config: Config): void { if (!config.entity || config.entity.split(".")[0] !== "light") { throw new Error("Specify an entity from within the light domain."); } @@ -59,7 +64,7 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) this._config = config; } - protected render() { + protected render(): TemplateResult { if (!this.hass || !this._config) { return html``; } @@ -107,7 +112,7 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) `; } - protected shouldUpdate(changedProps: PropertyValues) { + protected shouldUpdate(changedProps: PropertyValues): boolean { if (changedProps.get("hass")) { return ( (changedProps.get("hass") as any).states[this._config!.entity] !== @@ -117,7 +122,7 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) return (changedProps as unknown) as boolean; } - protected firstUpdated() { + protected firstUpdated(): void { const brightness = this.hass!.states[this._config!.entity].attributes .brightness; jQuery("#light", this.shadowRoot).roundSlider({ @@ -131,7 +136,7 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) (Math.round((brightness / 254) * 100) || 0) + "%"; } - protected updated() { + protected updated(): void { const attrs = this.hass!.states[this._config!.entity].attributes; jQuery("#light", this.shadowRoot).roundSlider({ @@ -243,18 +248,18 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) `; } - private _dragEvent(e) { + private _dragEvent(e: any): void { this.shadowRoot!.querySelector(".brightness")!.innerHTML = e.value + "%"; } - private _showBrightness() { + private _showBrightness(): void { clearTimeout(this._brightnessTimout); this.shadowRoot!.querySelector(".brightness")!.classList.add( "show_brightness" ); } - private _hideBrightness() { + private _hideBrightness(): void { this._brightnessTimout = window.setTimeout(() => { this.shadowRoot!.querySelector(".brightness")!.classList.remove( "show_brightness" @@ -262,7 +267,7 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) }, 500); } - private _setBrightness(e) { + private _setBrightness(e: any): void { this.hass!.callService("light", "turn_on", { entity_id: this._config!.entity, brightness_pct: e.value, @@ -288,7 +293,7 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) return `hsl(${hue}, 100%, ${100 - sat / 2}%)`; } - private _handleClick(hold: boolean) { + private _handleClick(hold: boolean): void { const entityId = this._config!.entity; if (hold) { From f7458b8d41a661628b28d3d5329f5c984d4767d3 Mon Sep 17 00:00:00 2001 From: Zack Arnett Date: Mon, 29 Oct 2018 20:12:20 -0400 Subject: [PATCH 5/6] Add Hs_color to types --- src/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types.ts b/src/types.ts index b0d3fd2a88..5a3e209cfa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -118,5 +118,6 @@ export type LightEntity = HassEntityBase & { max_mireds: number; friendly_name: string; brightness: number; + hs_color: string[]; }; }; From a5304115f0d29860eed3fb7360d6c01519b76b4f Mon Sep 17 00:00:00 2001 From: Zack Arnett Date: Mon, 29 Oct 2018 22:24:23 -0400 Subject: [PATCH 6/6] UPdate Type for color --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 5a3e209cfa..6cc016d2ee 100644 --- a/src/types.ts +++ b/src/types.ts @@ -118,6 +118,6 @@ export type LightEntity = HassEntityBase & { max_mireds: number; friendly_name: string; brightness: number; - hs_color: string[]; + hs_color: number[]; }; };