mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-25 22:07:20 +00:00
commit
226203143b
@ -55,7 +55,9 @@ export class LightEntity extends Entity {
|
|||||||
|
|
||||||
if (service === "turn_on") {
|
if (service === "turn_on") {
|
||||||
// eslint-disable-next-line
|
// 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(
|
this.update(
|
||||||
"on",
|
"on",
|
||||||
Object.assign(this.attributes, {
|
Object.assign(this.attributes, {
|
||||||
|
48
gallery/src/demos/demo-hui-light-card.js
Normal file
48
gallery/src/demos/demo-hui-light-card.js
Normal 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);
|
318
src/panels/lovelace/cards/hui-light-card.ts
Normal file
318
src/panels/lovelace/cards/hui-light-card.ts
Normal 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);
|
@ -10,6 +10,7 @@ import "../cards/hui-glance-card.ts";
|
|||||||
import "../cards/hui-history-graph-card.js";
|
import "../cards/hui-history-graph-card.js";
|
||||||
import "../cards/hui-horizontal-stack-card.ts";
|
import "../cards/hui-horizontal-stack-card.ts";
|
||||||
import "../cards/hui-iframe-card.ts";
|
import "../cards/hui-iframe-card.ts";
|
||||||
|
import "../cards/hui-light-card";
|
||||||
import "../cards/hui-map-card.js";
|
import "../cards/hui-map-card.js";
|
||||||
import "../cards/hui-markdown-card.ts";
|
import "../cards/hui-markdown-card.ts";
|
||||||
import "../cards/hui-media-control-card.js";
|
import "../cards/hui-media-control-card.js";
|
||||||
@ -38,6 +39,7 @@ const CARD_TYPES = new Set([
|
|||||||
"history-graph",
|
"history-graph",
|
||||||
"horizontal-stack",
|
"horizontal-stack",
|
||||||
"iframe",
|
"iframe",
|
||||||
|
"light",
|
||||||
"map",
|
"map",
|
||||||
"markdown",
|
"markdown",
|
||||||
"media-control",
|
"media-control",
|
||||||
|
10
src/types.ts
10
src/types.ts
@ -111,3 +111,13 @@ export type ClimateEntity = HassEntityBase & {
|
|||||||
aux_heat?: "on" | "off";
|
aux_heat?: "on" | "off";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type LightEntity = HassEntityBase & {
|
||||||
|
attributes: HassEntityAttributeBase & {
|
||||||
|
min_mireds: number;
|
||||||
|
max_mireds: number;
|
||||||
|
friendly_name: string;
|
||||||
|
brightness: number;
|
||||||
|
hs_color: number[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user