Merge pull request #1874 from zsarnett/light-card

LoveLace Light Card
This commit is contained in:
Zack Arnett 2018-10-29 23:39:12 -04:00 committed by GitHub
commit 226203143b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 381 additions and 1 deletions

View File

@ -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, {

View File

@ -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);

View File

@ -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,
});
}
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);

View File

@ -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",

View File

@ -111,3 +111,13 @@ 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;
hs_color: number[];
};
};