-
Notifications
You must be signed in to change notification settings - Fork 3.6k
LoveLace Light Card #1874
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
LoveLace Light Card #1874
Changes from all commits
7178d20
bb4ce27
1fcf510
410b66d
f7458b8
a530411
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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` | ||
| <demo-cards id='demos' configs="[[_configs]]"></demo-cards> | ||
| `; | ||
| } | ||
|
|
||
| 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); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,318 @@ | ||
| 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"; | ||
| 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"; | ||
| import { TemplateResult } from "lit-html"; | ||
|
|
||
| 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?: number; | ||
|
|
||
| static get properties(): PropertyDeclarations { | ||
| return { | ||
| hass: {}, | ||
| _config: {}, | ||
| }; | ||
| } | ||
|
|
||
| public getCardSize(): number { | ||
| return 2; | ||
| } | ||
|
|
||
| public setConfig(config: Config): void { | ||
| if (!config.entity || config.entity.split(".")[0] !== "light") { | ||
| throw new Error("Specify an entity from within the light domain."); | ||
| } | ||
|
|
||
| this._config = config; | ||
| } | ||
|
|
||
| protected render(): TemplateResult { | ||
| if (!this.hass || !this._config) { | ||
| return html``; | ||
| } | ||
|
|
||
| const stateObj = this.hass.states[this._config!.entity] as LightEntity; | ||
|
|
||
| return html` | ||
| ${this.renderStyle()} | ||
| <ha-card> | ||
| ${ | ||
| !stateObj | ||
| ? html` | ||
| <div class="not-found">Entity not available: ${ | ||
| this._config.entity | ||
| }</div>` | ||
| : html` | ||
| <div id="light"></div> | ||
| <div id="tooltip"> | ||
| <div class="icon-state"> | ||
| <ha-icon | ||
| data-state="${stateObj.state}" | ||
| .icon="${stateIcon(stateObj)}" | ||
| style="${styleMap({ | ||
| filter: this._computeBrightness(stateObj), | ||
| color: this._computeColor(stateObj), | ||
| })}" | ||
| @ha-click="${() => this._handleClick(false)}" | ||
| @ha-hold="${() => this._handleClick(true)}" | ||
| .longPress="${longPress()}" | ||
| ></ha-icon> | ||
| <div | ||
| class="brightness" | ||
| @ha-click="${() => this._handleClick(false)}" | ||
| @ha-hold="${() => this._handleClick(true)}" | ||
| .longPress="${longPress()}" | ||
| ></div> | ||
| <div class="name">${this._config.name || | ||
| computeStateName(stateObj)}</div> | ||
| </div> | ||
| </div> | ||
| ` | ||
| } | ||
|
|
||
| </ha-card> | ||
| `; | ||
| } | ||
|
|
||
| protected shouldUpdate(changedProps: PropertyValues): boolean { | ||
| 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(): void { | ||
| 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(): void { | ||
| const attrs = this.hass!.states[this._config!.entity].attributes; | ||
|
|
||
| jQuery("#light", this.shadowRoot).roundSlider({ | ||
| value: Math.round((attrs.brightness / 254) * 100) || 0, | ||
| }); | ||
| } | ||
|
|
||
| private renderStyle(): TemplateResult { | ||
| return html` | ||
| ${roundSliderStyle} | ||
| <style> | ||
| :host { | ||
| display: block; | ||
| } | ||
| ha-card { | ||
| position: relative; | ||
| overflow: hidden; | ||
| } | ||
| #tooltip { | ||
| position: absolute; | ||
| top: 0; | ||
| left: 0; | ||
| right: 0; | ||
| height: 100%; | ||
| text-align: center; | ||
| z-index: 15; | ||
| } | ||
| .icon-state { | ||
| display: block; | ||
| margin: auto; | ||
| width: 100%; | ||
| height: 100%; | ||
| transform: translate(0,25%); | ||
| } | ||
| #light { | ||
| margin: 0 auto; | ||
| padding-top: 16px; | ||
| padding-bottom: 16px; | ||
| } | ||
| #light .rs-bar.rs-transition.rs-first, .rs-bar.rs-transition.rs-second{ | ||
| z-index: 20 !important; | ||
| } | ||
| #light .rs-range-color { | ||
| background-color: var(--primary-color); | ||
| } | ||
| #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; | ||
| } | ||
| </style> | ||
| `; | ||
| } | ||
|
|
||
| private _dragEvent(e: any): void { | ||
| this.shadowRoot!.querySelector(".brightness")!.innerHTML = e.value + "%"; | ||
| } | ||
|
|
||
| private _showBrightness(): void { | ||
| clearTimeout(this._brightnessTimout); | ||
| this.shadowRoot!.querySelector(".brightness")!.classList.add( | ||
| "show_brightness" | ||
| ); | ||
| } | ||
|
|
||
| private _hideBrightness(): void { | ||
| this._brightnessTimout = window.setTimeout(() => { | ||
| this.shadowRoot!.querySelector(".brightness")!.classList.remove( | ||
| "show_brightness" | ||
| ); | ||
| }, 500); | ||
| } | ||
|
|
||
| private _setBrightness(e: any): void { | ||
| this.hass!.callService("light", "turn_on", { | ||
| entity_id: this._config!.entity, | ||
| brightness_pct: e.value, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now I know why the gallery is flaky. We don't have support for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How can I add that?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where did you look?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need to update the logic here https://github.com/home-assistant/home-assistant-polymer/blob/dev/gallery/src/data/entity.js#L56-L65 |
||
| }); | ||
| } | ||
|
|
||
| private _computeBrightness(stateObj: LightEntity): string { | ||
| if (!stateObj.attributes.brightness) { | ||
| return ""; | ||
| } | ||
| const brightness = stateObj.attributes.brightness; | ||
| return `brightness(${(brightness + 245) / 5}%)`; | ||
| } | ||
|
|
||
| private _computeColor(stateObj: LightEntity): string { | ||
| 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): void { | ||
| 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); | ||
Uh oh!
There was an error while loading. Please reload this page.