diff --git a/package.json b/package.json index f6b52875dd..8275958cd7 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,6 @@ "@material/mwc-textfield": "^0.27.0", "@material/mwc-top-app-bar-fixed": "^0.27.0", "@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0", - "@material/web": "^1.0.0-pre.1", "@mdi/js": "7.1.96", "@mdi/svg": "7.1.96", "@polymer/app-layout": "^3.1.0", diff --git a/src/components/ha-bar-slider.ts b/src/components/ha-bar-slider.ts index 9142b35e72..51bd09df6a 100644 --- a/src/components/ha-bar-slider.ts +++ b/src/components/ha-bar-slider.ts @@ -44,7 +44,7 @@ const getPercentageFromEvent = (e: HammerInput, vertical: boolean) => { @customElement("ha-bar-slider") export class HaBarSlider extends LitElement { - @property({ type: Boolean, reflect: true }) + @property({ type: Boolean }) public disabled = false; @property() @@ -245,16 +245,14 @@ export class HaBarSlider extends LitElement { >
${this.mode === "cursor" - ? this.value != null - ? html` -
- ` - : null + ? html` +
+ ` : html`
string | TemplateResult; - - @property({ type: Number }) - public value?: number; - - @property({ type: Number }) - public step = 1; - - @property({ type: Number }) - public min = 0; - - @property({ type: Number }) - public max = 100; - - @property({ type: Boolean, attribute: "show-handle" }) - public showHandle = false; - - @property() public label = ""; - - @property({ type: Boolean, attribute: "show-label" }) - public showLabel = false; - - @property({ type: Boolean, attribute: "show-value" }) - public showValue = false; - - protected render(): TemplateResult { - return html` -
- ${this.showValue - ? html` -

- ${this.valueFormatter - ? this.valueFormatter(this.value) - : this.value} -

- ` - : null} - - - ${this.showLabel ? html`

${this.label}

` : null} -
- `; - } - - static get styles(): CSSResultGroup { - return css` - .container { - display: flex; - align-items: center; - flex-direction: column; - } - ha-bar-slider { - height: var(--more-info-slider-bar-height, 330px); - --slider-bar-thickness: 100px; - --slider-bar-border-radius: 24px; - --slider-bar-color: var( - --more-info-slider-bar-color, - var(--primary-color) - ); - --slider-bar-background: var( - --more-info-slider-bar-background, - var(--disabled-color) - ); - --slider-bar-background-opacity: var( - --more-info-slider-bar-background-opacity, - 0.2 - ); - } - p { - font-weight: 500; - font-size: 14px; - line-height: 20px; - text-align: center; - margin: 16px 0; - } - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-more-info-bar-slider": HaTileSlider; - } -} diff --git a/src/dialogs/more-info/components/ha-more-info-light-brightness.ts b/src/dialogs/more-info/components/ha-more-info-light-brightness.ts deleted file mode 100644 index 83e40781ac..0000000000 --- a/src/dialogs/more-info/components/ha-more-info-light-brightness.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { styleMap } from "lit/directives/style-map"; -import { hsv2rgb, rgb2hex, rgb2hsv } from "../../../common/color/convert-color"; -import { computeStateDisplay } from "../../../common/entity/compute_state_display"; -import { stateActive } from "../../../common/entity/state_active"; -import { stateColorCss } from "../../../common/entity/state_color"; -import { blankBeforePercent } from "../../../common/translations/blank_before_percent"; -import { LightEntity } from "../../../data/light"; -import { HomeAssistant } from "../../../types"; -import "./ha-more-info-bar-slider"; - -@customElement("ha-more-info-light-brightness") -export class HaMoreInfoLightBrightness extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @property({ attribute: false }) public stateObj!: LightEntity; - - @state() value?: number; - - protected updated(changedProp: Map): void { - if (changedProp.has("stateObj")) { - this.value = - this.stateObj.attributes.brightness != null - ? Math.max( - Math.round((this.stateObj.attributes.brightness * 100) / 255), - 1 - ) - : undefined; - } - } - - private _valueChanged(ev: CustomEvent) { - const value = (ev.detail as any).value; - if (isNaN(value)) return; - - this.hass.callService("light", "turn_on", { - entity_id: this.stateObj!.entity_id, - brightness_pct: value, - }); - } - - private _sliderMoved(ev: CustomEvent) { - const value = (ev.detail as any).value; - if (isNaN(value)) return; - - this.value = value; - } - - private _valueFormatter = (value?: number) => { - if (value == null) - return computeStateDisplay( - this.hass.localize, - this.stateObj, - this.hass.locale, - this.hass.entities - ); - return `${Math.round(value)}${blankBeforePercent(this.hass!.locale)}%`; - }; - - protected render(): TemplateResult { - let color = stateColorCss(this.stateObj); - - if (this.stateObj.attributes.rgb_color) { - const hsvColor = rgb2hsv(this.stateObj.attributes.rgb_color); - - // Modify the real rgb color for better contrast - if (hsvColor[1] < 0.4) { - // Special case for very light color (e.g: white) - if (hsvColor[1] < 0.1) { - hsvColor[2] = 225; - } else { - hsvColor[1] = 0.4; - } - } - color = rgb2hex(hsv2rgb(hsvColor)); - } - - return html` - - - `; - } - - static get styles(): CSSResultGroup { - return css` - ha-more-info-bar-slider { - --more-info-slider-bar-height: 250px; - } - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-more-info-light-brightness": HaMoreInfoLightBrightness; - } -} diff --git a/src/dialogs/more-info/components/lights/dialog-light-color-picker.ts b/src/dialogs/more-info/components/lights/dialog-light-color-picker.ts deleted file mode 100644 index 535adf1c3b..0000000000 --- a/src/dialogs/more-info/components/lights/dialog-light-color-picker.ts +++ /dev/null @@ -1,618 +0,0 @@ -import "@material/mwc-button"; -import "@material/mwc-tab-bar/mwc-tab-bar"; -import "@material/mwc-tab/mwc-tab"; -import { mdiPalette } from "@mdi/js"; -import { - css, - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { fireEvent } from "../../../../common/dom/fire_event"; -import "../../../../components/ha-button-toggle-group"; -import "../../../../components/ha-color-picker"; -import "../../../../components/ha-dialog"; -import "../../../../components/ha-header-bar"; -import "../../../../components/ha-icon-button-prev"; -import "../../../../components/ha-labeled-slider"; -import { - getLightCurrentModeRgbColor, - LightColorMode, - LightEntity, - lightSupportsColor, - lightSupportsColorMode, -} from "../../../../data/light"; -import { haStyleDialog } from "../../../../resources/styles"; -import { HomeAssistant } from "../../../../types"; -import "../ha-more-info-bar-slider"; -import { LightColorPickerDialogParams } from "./show-dialog-light-color-picker"; - -type Mode = "color_temp" | "color" | "white"; - -@customElement("dialog-light-color-picker") -class DialogLightColorPicker extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @state() private _params?: LightColorPickerDialogParams; - - @state() private _ctSliderValue?: number; - - @state() private _cwSliderValue?: number; - - @state() private _wwSliderValue?: number; - - @state() private _wvSliderValue?: number; - - @state() private _colorBrightnessSliderValue?: number; - - @state() private _brightnessAdjusted?: number; - - @state() private _hueSegments = 24; - - @state() private _saturationSegments = 8; - - @state() private _colorPickerColor?: [number, number, number]; - - @state() private _mode?: Mode; - - @state() private _modes: Mode[] = []; - - get stateObj() { - return this._params - ? (this.hass.states[this._params.entityId] as LightEntity) - : undefined; - } - - public async showDialog(params: LightColorPickerDialogParams): Promise { - this._params = params; - - const supportsTemp = lightSupportsColorMode( - this.stateObj!, - LightColorMode.COLOR_TEMP - ); - const supportsWhite = lightSupportsColorMode( - this.stateObj!, - LightColorMode.WHITE - ); - - const modes: Mode[] = []; - modes.push("color"); - if (supportsTemp) { - modes.push("color_temp"); - } - if (supportsWhite) { - modes.push("white"); - } - - this._modes = modes; - this._mode = - this.stateObj!.attributes.color_mode === "color_temp" || - this.stateObj!.attributes.color_mode === "white" - ? this.stateObj!.attributes.color_mode - : "color"; - - this._updateSliderValues(); - await this.updateComplete; - } - - public closeDialog() { - this._params = undefined; - fireEvent(this, "dialog-closed", { dialog: this.localName }); - } - - protected render(): TemplateResult { - if (!this._params || !this.stateObj) { - return html``; - } - - const supportsTemp = lightSupportsColorMode( - this.stateObj, - LightColorMode.COLOR_TEMP - ); - - const supportsWhite = lightSupportsColorMode( - this.stateObj, - LightColorMode.WHITE - ); - - const supportsRgbww = lightSupportsColorMode( - this.stateObj, - LightColorMode.RGBWW - ); - - const supportsRgbw = - !supportsRgbww && - lightSupportsColorMode(this.stateObj, LightColorMode.RGBW); - - const supportsColor = - supportsRgbww || supportsRgbw || lightSupportsColor(this.stateObj); - - return html` - -
- - - - Change Color - -
-
- ${supportsColor && (supportsTemp || supportsWhite) - ? html` - - ${this._modes.map( - (value) => - html`` - )} - - ` - : ""} -
- ${supportsTemp && - ((!supportsColor && !supportsWhite) || - this._mode === LightColorMode.COLOR_TEMP) - ? html` - - - ` - : ""} - ${supportsColor && - ((!supportsTemp && !supportsWhite) || this._mode === "color") - ? html` -
- - - -
- - ${supportsRgbw || supportsRgbww - ? html`` - : ""} - ${supportsRgbw - ? html` - - ` - : ""} - ${supportsRgbww - ? html` - - - ` - : ""} - ` - : ""} -
-
-
- `; - } - - public _updateSliderValues() { - const stateObj = this.stateObj; - - if (stateObj?.state === "on") { - this._brightnessAdjusted = undefined; - if ( - stateObj.attributes.color_mode === LightColorMode.RGB && - !lightSupportsColorMode(stateObj, LightColorMode.RGBWW) && - !lightSupportsColorMode(stateObj, LightColorMode.RGBW) - ) { - const maxVal = Math.max(...stateObj.attributes.rgb_color!); - - if (maxVal < 255) { - this._brightnessAdjusted = maxVal; - } - } - this._ctSliderValue = stateObj.attributes.color_temp_kelvin; - this._wvSliderValue = - stateObj.attributes.color_mode === LightColorMode.RGBW - ? Math.round((stateObj.attributes.rgbw_color![3] * 100) / 255) - : undefined; - this._cwSliderValue = - stateObj.attributes.color_mode === LightColorMode.RGBWW - ? Math.round((stateObj.attributes.rgbww_color![3] * 100) / 255) - : undefined; - this._wwSliderValue = - stateObj.attributes.color_mode === LightColorMode.RGBWW - ? Math.round((stateObj.attributes.rgbww_color![4] * 100) / 255) - : undefined; - - const currentRgbColor = getLightCurrentModeRgbColor(stateObj); - - this._colorBrightnessSliderValue = currentRgbColor - ? Math.round((Math.max(...currentRgbColor.slice(0, 3)) * 100) / 255) - : undefined; - - this._colorPickerColor = currentRgbColor?.slice(0, 3) as [ - number, - number, - number - ]; - } else { - this._colorPickerColor = [0, 0, 0]; - this._ctSliderValue = undefined; - this._wvSliderValue = undefined; - this._cwSliderValue = undefined; - this._wwSliderValue = undefined; - } - } - - public willUpdate(changedProps: PropertyValues) { - super.willUpdate(changedProps); - - if (!changedProps.has("hass")) { - return; - } - - this._updateSliderValues(); - } - - private _handleTabChanged(ev: CustomEvent): void { - const newMode = this._modes[ev.detail.index]; - if (newMode === this._mode) { - return; - } - this._mode = newMode; - } - - private _ctSliderChanged(ev: CustomEvent) { - const ct = ev.detail.value; - - if (isNaN(ct)) { - return; - } - - this._ctSliderValue = ct; - - this.hass.callService("light", "turn_on", { - entity_id: this.stateObj!.entity_id, - color_temp_kelvin: ct, - }); - } - - private _wvSliderChanged(ev: CustomEvent) { - const target = ev.target as any; - let wv = Number(target.value); - const name = target.name; - - if (isNaN(wv)) { - return; - } - - if (name === "wv") { - this._wvSliderValue = wv; - } else if (name === "cw") { - this._cwSliderValue = wv; - } else if (name === "ww") { - this._wwSliderValue = wv; - } - - wv = Math.min(255, Math.round((wv * 255) / 100)); - - const rgb = getLightCurrentModeRgbColor(this.stateObj!); - - if (name === "wv") { - const rgbw_color = rgb || [0, 0, 0, 0]; - rgbw_color[3] = wv; - this.hass.callService("light", "turn_on", { - entity_id: this.stateObj!.entity_id, - rgbw_color, - }); - return; - } - - const rgbww_color = rgb || [0, 0, 0, 0, 0]; - while (rgbww_color.length < 5) { - rgbww_color.push(0); - } - rgbww_color[name === "cw" ? 3 : 4] = wv; - this.hass.callService("light", "turn_on", { - entity_id: this.stateObj!.entity_id, - rgbww_color, - }); - } - - private _colorBrightnessSliderChanged(ev: CustomEvent) { - const target = ev.target as any; - let value = Number(target.value); - - if (isNaN(value)) { - return; - } - - const oldValue = this._colorBrightnessSliderValue; - this._colorBrightnessSliderValue = value; - - value = (value * 255) / 100; - - const rgb = (getLightCurrentModeRgbColor(this.stateObj!)?.slice(0, 3) || [ - 255, 255, 255, - ]) as [number, number, number]; - - this._setRgbWColor( - this._adjustColorBrightness( - // first normalize the value - oldValue - ? this._adjustColorBrightness(rgb, (oldValue * 255) / 100, true) - : rgb, - value - ) - ); - } - - private _segmentClick() { - if (this._hueSegments === 24 && this._saturationSegments === 8) { - this._hueSegments = 0; - this._saturationSegments = 0; - } else { - this._hueSegments = 24; - this._saturationSegments = 8; - } - } - - private _adjustColorBrightness( - rgbColor: [number, number, number], - value?: number, - invert = false - ) { - if (value !== undefined && value !== 255) { - let ratio = value / 255; - if (invert) { - ratio = 1 / ratio; - } - rgbColor[0] = Math.min(255, Math.round(rgbColor[0] * ratio)); - rgbColor[1] = Math.min(255, Math.round(rgbColor[1] * ratio)); - rgbColor[2] = Math.min(255, Math.round(rgbColor[2] * ratio)); - } - return rgbColor; - } - - private _setRgbWColor(rgbColor: [number, number, number]) { - if (lightSupportsColorMode(this.stateObj!, LightColorMode.RGBWW)) { - const rgbww_color: [number, number, number, number, number] = this - .stateObj!.attributes.rgbww_color - ? [...this.stateObj!.attributes.rgbww_color] - : [0, 0, 0, 0, 0]; - this.hass.callService("light", "turn_on", { - entity_id: this.stateObj!.entity_id, - rgbww_color: rgbColor.concat(rgbww_color.slice(3)), - }); - } else if (lightSupportsColorMode(this.stateObj!, LightColorMode.RGBW)) { - const rgbw_color: [number, number, number, number] = this.stateObj! - .attributes.rgbw_color - ? [...this.stateObj!.attributes.rgbw_color] - : [0, 0, 0, 0]; - this.hass.callService("light", "turn_on", { - entity_id: this.stateObj!.entity_id, - rgbw_color: rgbColor.concat(rgbw_color.slice(3)), - }); - } - } - - /** - * Called when a new color has been picked. - * should be throttled with the 'throttle=' attribute of the color picker - */ - private _colorPicked( - ev: CustomEvent<{ - hs: { h: number; s: number }; - rgb: { r: number; g: number; b: number }; - }> - ) { - this._colorPickerColor = [ - ev.detail.rgb.r, - ev.detail.rgb.g, - ev.detail.rgb.b, - ]; - - if ( - lightSupportsColorMode(this.stateObj!, LightColorMode.RGBWW) || - lightSupportsColorMode(this.stateObj!, LightColorMode.RGBW) - ) { - this._setRgbWColor( - this._colorBrightnessSliderValue - ? this._adjustColorBrightness( - [ev.detail.rgb.r, ev.detail.rgb.g, ev.detail.rgb.b], - (this._colorBrightnessSliderValue * 255) / 100 - ) - : [ev.detail.rgb.r, ev.detail.rgb.g, ev.detail.rgb.b] - ); - } else if (lightSupportsColorMode(this.stateObj!, LightColorMode.RGB)) { - const rgb_color: [number, number, number] = [ - ev.detail.rgb.r, - ev.detail.rgb.g, - ev.detail.rgb.b, - ]; - if (this._brightnessAdjusted) { - const brightnessAdjust = (this._brightnessAdjusted / 255) * 100; - const brightnessPercentage = Math.round( - ((this.stateObj!.attributes.brightness || 0) * brightnessAdjust) / 255 - ); - this.hass.callService("light", "turn_on", { - entity_id: this.stateObj!.entity_id, - brightness_pct: brightnessPercentage, - rgb_color: this._adjustColorBrightness( - rgb_color, - this._brightnessAdjusted, - true - ), - }); - } else { - this.hass.callService("light", "turn_on", { - entity_id: this.stateObj!.entity_id, - rgb_color, - }); - } - } else { - this.hass.callService("light", "turn_on", { - entity_id: this.stateObj!.entity_id, - hs_color: [ev.detail.hs.h, ev.detail.hs.s * 100], - }); - } - } - - private _close(): void { - this._params = undefined; - } - - static get styles(): CSSResultGroup { - return [ - haStyleDialog, - css` - ha-dialog { - --dialog-content-padding: 0px; - } - ha-header-bar { - --mdc-theme-on-primary: var(--primary-text-color); - --mdc-theme-primary: var(--mdc-theme-surface); - } - .content { - display: flex; - flex-direction: column; - align-items: center; - padding: 16px; - } - - .content > * { - width: 100%; - } - - .segmentationContainer { - position: relative; - max-height: 500px; - display: flex; - justify-content: center; - } - - .segmentationButton { - position: absolute; - top: 5%; - left: 0; - color: var(--secondary-text-color); - } - - ha-color-picker { - --ha-color-picker-wheel-borderwidth: 5; - --ha-color-picker-wheel-bordercolor: white; - --ha-color-picker-wheel-shadow: none; - --ha-color-picker-marker-borderwidth: 2; - --ha-color-picker-marker-bordercolor: white; - } - - ha-more-info-bar-slider { - --more-info-slider-bar-height: 290px; - margin: 20px 0; - } - - .color_temp { - --more-info-slider-bar-background: -webkit-linear-gradient( - top, - rgb(166, 209, 255) 0%, - white 50%, - rgb(255, 160, 0) 100% - ); - --more-info-slider-bar-background-opacity: 1; - } - - hr { - border-color: var(--divider-color); - border-bottom: none; - margin: 16px 0; - } - `, - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "dialog-light-color-picker": DialogLightColorPicker; - } -} diff --git a/src/dialogs/more-info/components/lights/show-dialog-light-color-picker.ts b/src/dialogs/more-info/components/lights/show-dialog-light-color-picker.ts deleted file mode 100644 index 41d82429cb..0000000000 --- a/src/dialogs/more-info/components/lights/show-dialog-light-color-picker.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { fireEvent } from "../../../../common/dom/fire_event"; - -export interface LightColorPickerDialogParams { - entityId: string; -} - -export const loadLightColorPickerDialog = () => - import("./dialog-light-color-picker"); - -export const showLightColorPickerDialog = ( - element: HTMLElement, - dialogParams: LightColorPickerDialogParams -): void => { - fireEvent(element, "show-dialog", { - dialogTag: "dialog-light-color-picker", - dialogImport: loadLightColorPickerDialog, - dialogParams, - }); -}; diff --git a/src/dialogs/more-info/controls/more-info-light.ts b/src/dialogs/more-info/controls/more-info-light.ts index 32eca23af9..7ce94dabbf 100644 --- a/src/dialogs/more-info/controls/more-info-light.ts +++ b/src/dialogs/more-info/controls/more-info-light.ts @@ -1,24 +1,34 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/web/iconbutton/outlined-icon-button"; -import { mdiPalette, mdiShimmer } from "@mdi/js"; -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property } from "lit/decorators"; +import { mdiPalette } from "@mdi/js"; +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-attributes"; -import "../../../components/ha-button-menu"; +import "../../../components/ha-button-toggle-group"; +import "../../../components/ha-color-picker"; +import "../../../components/ha-icon-button"; +import "../../../components/ha-labeled-slider"; import "../../../components/ha-select"; import { + getLightCurrentModeRgbColor, LightColorMode, LightEntity, LightEntityFeature, - lightSupportsBrightness, + lightIsInColorMode, lightSupportsColor, lightSupportsColorMode, + lightSupportsBrightness, } from "../../../data/light"; import type { HomeAssistant } from "../../../types"; -import "../components/ha-more-info-light-brightness"; -import { showLightColorPickerDialog } from "../components/lights/show-dialog-light-color-picker"; @customElement("more-info-light") class MoreInfoLight extends LitElement { @@ -26,90 +36,198 @@ class MoreInfoLight extends LitElement { @property({ attribute: false }) public stateObj?: LightEntity; + @state() private _brightnessSliderValue = 0; + + @state() private _ctSliderValue?: number; + + @state() private _cwSliderValue?: number; + + @state() private _wwSliderValue?: number; + + @state() private _wvSliderValue?: number; + + @state() private _colorBrightnessSliderValue?: number; + + @state() private _brightnessAdjusted?: number; + + @state() private _hueSegments = 24; + + @state() private _saturationSegments = 8; + + @state() private _colorPickerColor?: [number, number, number]; + + @state() private _mode?: "color" | LightColorMode; + protected render(): TemplateResult { if (!this.hass || !this.stateObj) { return html``; } - const supportsColorTemp = lightSupportsColorMode( + const supportsTemp = lightSupportsColorMode( this.stateObj, LightColorMode.COLOR_TEMP ); - const supportsColor = lightSupportsColor(this.stateObj); - - const supportsBrightness = lightSupportsBrightness(this.stateObj); - - const supportsEffects = supportsFeature( + const supportsWhite = lightSupportsColorMode( this.stateObj, - LightEntityFeature.EFFECT + LightColorMode.WHITE ); + const supportsRgbww = lightSupportsColorMode( + this.stateObj, + LightColorMode.RGBWW + ); + + const supportsRgbw = + !supportsRgbww && + lightSupportsColorMode(this.stateObj, LightColorMode.RGBW); + + const supportsColor = + supportsRgbww || supportsRgbw || lightSupportsColor(this.stateObj); + return html`
- ${supportsBrightness + ${lightSupportsBrightness(this.stateObj) ? html` - - + ` : ""} - ${supportsColorTemp || supportsColor || supportsEffects + ${this.stateObj.state === "on" ? html` -
- ${supportsColorTemp || supportsColor - ? html` - ` : ""} + ${supportsColor && (supportsTemp || supportsWhite) + ? html`` + : ""} + ${supportsTemp && + ((!supportsColor && !supportsWhite) || + this._mode === LightColorMode.COLOR_TEMP) + ? html` + + ` + : ""} + ${supportsColor && + ((!supportsTemp && !supportsWhite) || this._mode === "color") + ? html` +
+ - - - ` - : null} - ${supportsEffects - ? html` - - + +
+ + ${supportsRgbw || supportsRgbww + ? html` - -
- ${this.stateObj.attributes.effect_list!.map( - (effect: string) => html` - - ${effect} - - ` - )} - - ` - : null} -
+ icon="hass:brightness-7" + max="100" + .value=${this._colorBrightnessSliderValue} + @change=${this._colorBrightnessSliderChanged} + pin + >` + : ""} + ${supportsRgbw + ? html` + + ` + : ""} + ${supportsRgbww + ? html` + + + ` + : ""} + ` + : ""} + ${supportsFeature(this.stateObj, LightEntityFeature.EFFECT) && + this.stateObj!.attributes.effect_list?.length + ? html` +
+ + ${this.stateObj.attributes.effect_list.map( + (effect: string) => html` + + ${effect} + + ` + )} + + ` + : ""} ` - : null} + : ""} { - showLightColorPickerDialog(this, { entityId: this.stateObj!.entity_id }); - }; + public willUpdate(changedProps: PropertyValues) { + super.willUpdate(changedProps); - private _handleEffectButton(ev) { - ev.stopPropagation(); - ev.preventDefault(); + if (!changedProps.has("stateObj")) { + return; + } + const stateObj = this.stateObj! as LightEntity; + const oldStateObj = changedProps.get("stateObj") as LightEntity | undefined; - const index = ev.detail.index; - const effect = this.stateObj!.attributes.effect_list![index]; + if (stateObj.state === "on") { + // Don't change tab when the color mode changes + if ( + oldStateObj?.entity_id !== stateObj.entity_id || + oldStateObj?.state !== stateObj.state + ) { + this._mode = lightIsInColorMode(this.stateObj!) + ? "color" + : this.stateObj!.attributes.color_mode; + } - if (!effect || this.stateObj!.attributes.effect === effect) { + let brightnessAdjust = 100; + this._brightnessAdjusted = undefined; + if ( + stateObj.attributes.color_mode === LightColorMode.RGB && + !lightSupportsColorMode(stateObj, LightColorMode.RGBWW) && + !lightSupportsColorMode(stateObj, LightColorMode.RGBW) + ) { + const maxVal = Math.max(...stateObj.attributes.rgb_color!); + if (maxVal < 255) { + this._brightnessAdjusted = maxVal; + brightnessAdjust = (this._brightnessAdjusted / 255) * 100; + } + } + this._brightnessSliderValue = Math.round( + ((stateObj.attributes.brightness || 0) * brightnessAdjust) / 255 + ); + this._ctSliderValue = stateObj.attributes.color_temp_kelvin; + this._wvSliderValue = + stateObj.attributes.color_mode === LightColorMode.RGBW + ? Math.round((stateObj.attributes.rgbw_color![3] * 100) / 255) + : undefined; + this._cwSliderValue = + stateObj.attributes.color_mode === LightColorMode.RGBWW + ? Math.round((stateObj.attributes.rgbww_color![3] * 100) / 255) + : undefined; + this._wwSliderValue = + stateObj.attributes.color_mode === LightColorMode.RGBWW + ? Math.round((stateObj.attributes.rgbww_color![4] * 100) / 255) + : undefined; + + const currentRgbColor = getLightCurrentModeRgbColor(stateObj); + + this._colorBrightnessSliderValue = currentRgbColor + ? Math.round((Math.max(...currentRgbColor.slice(0, 3)) * 100) / 255) + : undefined; + + this._colorPickerColor = currentRgbColor?.slice(0, 3) as [ + number, + number, + number + ]; + } else { + this._brightnessSliderValue = 0; + } + } + + private _toggleButtons = memoizeOne( + (supportsTemp: boolean, supportsWhite: boolean) => { + const modes = [{ label: "Color", value: "color" }]; + if (supportsTemp) { + modes.push({ label: "Temperature", value: LightColorMode.COLOR_TEMP }); + } + if (supportsWhite) { + modes.push({ label: "White", value: LightColorMode.WHITE }); + } + return modes; + } + ); + + private _modeChanged(ev: CustomEvent) { + this._mode = ev.detail.value; + } + + private _effectChanged(ev) { + const newVal = ev.target.value; + + if (!newVal || this.stateObj!.attributes.effect === newVal) { return; } this.hass.callService("light", "turn_on", { entity_id: this.stateObj!.entity_id, - effect, + effect: newVal, }); } + private _brightnessSliderChanged(ev: CustomEvent) { + const bri = Number((ev.target as any).value); + + if (isNaN(bri)) { + return; + } + + this._brightnessSliderValue = bri; + + if (this._mode === LightColorMode.WHITE) { + this.hass.callService("light", "turn_on", { + entity_id: this.stateObj!.entity_id, + white: Math.min(255, Math.round((bri * 255) / 100)), + }); + return; + } + + if (this._brightnessAdjusted) { + const rgb = + this.stateObj!.attributes.rgb_color || + ([0, 0, 0] as [number, number, number]); + + this.hass.callService("light", "turn_on", { + entity_id: this.stateObj!.entity_id, + brightness_pct: bri, + rgb_color: this._adjustColorBrightness( + rgb, + this._brightnessAdjusted, + true + ), + }); + return; + } + + this.hass.callService("light", "turn_on", { + entity_id: this.stateObj!.entity_id, + brightness_pct: bri, + }); + } + + private _ctSliderChanged(ev: CustomEvent) { + const ct = Number((ev.target as any).value); + + if (isNaN(ct)) { + return; + } + + this._ctSliderValue = ct; + + this.hass.callService("light", "turn_on", { + entity_id: this.stateObj!.entity_id, + color_temp_kelvin: ct, + }); + } + + private _wvSliderChanged(ev: CustomEvent) { + const target = ev.target as any; + let wv = Number(target.value); + const name = target.name; + + if (isNaN(wv)) { + return; + } + + if (name === "wv") { + this._wvSliderValue = wv; + } else if (name === "cw") { + this._cwSliderValue = wv; + } else if (name === "ww") { + this._wwSliderValue = wv; + } + + wv = Math.min(255, Math.round((wv * 255) / 100)); + + const rgb = getLightCurrentModeRgbColor(this.stateObj!); + + if (name === "wv") { + const rgbw_color = rgb || [0, 0, 0, 0]; + rgbw_color[3] = wv; + this.hass.callService("light", "turn_on", { + entity_id: this.stateObj!.entity_id, + rgbw_color, + }); + return; + } + + const rgbww_color = rgb || [0, 0, 0, 0, 0]; + while (rgbww_color.length < 5) { + rgbww_color.push(0); + } + rgbww_color[name === "cw" ? 3 : 4] = wv; + this.hass.callService("light", "turn_on", { + entity_id: this.stateObj!.entity_id, + rgbww_color, + }); + } + + private _colorBrightnessSliderChanged(ev: CustomEvent) { + const target = ev.target as any; + let value = Number(target.value); + + if (isNaN(value)) { + return; + } + + const oldValue = this._colorBrightnessSliderValue; + this._colorBrightnessSliderValue = value; + + value = (value * 255) / 100; + + const rgb = (getLightCurrentModeRgbColor(this.stateObj!)?.slice(0, 3) || [ + 255, 255, 255, + ]) as [number, number, number]; + + this._setRgbWColor( + this._adjustColorBrightness( + // first normalize the value + oldValue + ? this._adjustColorBrightness(rgb, (oldValue * 255) / 100, true) + : rgb, + value + ) + ); + } + + private _segmentClick() { + if (this._hueSegments === 24 && this._saturationSegments === 8) { + this._hueSegments = 0; + this._saturationSegments = 0; + } else { + this._hueSegments = 24; + this._saturationSegments = 8; + } + } + + private _adjustColorBrightness( + rgbColor: [number, number, number], + value?: number, + invert = false + ) { + if (value !== undefined && value !== 255) { + let ratio = value / 255; + if (invert) { + ratio = 1 / ratio; + } + rgbColor[0] = Math.min(255, Math.round(rgbColor[0] * ratio)); + rgbColor[1] = Math.min(255, Math.round(rgbColor[1] * ratio)); + rgbColor[2] = Math.min(255, Math.round(rgbColor[2] * ratio)); + } + return rgbColor; + } + + private _setRgbWColor(rgbColor: [number, number, number]) { + if (lightSupportsColorMode(this.stateObj!, LightColorMode.RGBWW)) { + const rgbww_color: [number, number, number, number, number] = this + .stateObj!.attributes.rgbww_color + ? [...this.stateObj!.attributes.rgbww_color] + : [0, 0, 0, 0, 0]; + this.hass.callService("light", "turn_on", { + entity_id: this.stateObj!.entity_id, + rgbww_color: rgbColor.concat(rgbww_color.slice(3)), + }); + } else if (lightSupportsColorMode(this.stateObj!, LightColorMode.RGBW)) { + const rgbw_color: [number, number, number, number] = this.stateObj! + .attributes.rgbw_color + ? [...this.stateObj!.attributes.rgbw_color] + : [0, 0, 0, 0]; + this.hass.callService("light", "turn_on", { + entity_id: this.stateObj!.entity_id, + rgbw_color: rgbColor.concat(rgbw_color.slice(3)), + }); + } + } + + /** + * Called when a new color has been picked. + * should be throttled with the 'throttle=' attribute of the color picker + */ + private _colorPicked( + ev: CustomEvent<{ + hs: { h: number; s: number }; + rgb: { r: number; g: number; b: number }; + }> + ) { + this._colorPickerColor = [ + ev.detail.rgb.r, + ev.detail.rgb.g, + ev.detail.rgb.b, + ]; + + if ( + lightSupportsColorMode(this.stateObj!, LightColorMode.RGBWW) || + lightSupportsColorMode(this.stateObj!, LightColorMode.RGBW) + ) { + this._setRgbWColor( + this._colorBrightnessSliderValue + ? this._adjustColorBrightness( + [ev.detail.rgb.r, ev.detail.rgb.g, ev.detail.rgb.b], + (this._colorBrightnessSliderValue * 255) / 100 + ) + : [ev.detail.rgb.r, ev.detail.rgb.g, ev.detail.rgb.b] + ); + } else if (lightSupportsColorMode(this.stateObj!, LightColorMode.RGB)) { + const rgb_color: [number, number, number] = [ + ev.detail.rgb.r, + ev.detail.rgb.g, + ev.detail.rgb.b, + ]; + if (this._brightnessAdjusted) { + this.hass.callService("light", "turn_on", { + entity_id: this.stateObj!.entity_id, + brightness_pct: this._brightnessSliderValue, + rgb_color: this._adjustColorBrightness( + rgb_color, + this._brightnessAdjusted, + true + ), + }); + } else { + this.hass.callService("light", "turn_on", { + entity_id: this.stateObj!.entity_id, + rgb_color, + }); + } + } else { + this.hass.callService("light", "turn_on", { + entity_id: this.stateObj!.entity_id, + hs_color: [ev.detail.hs.h, ev.detail.hs.s * 100], + }); + } + } + static get styles(): CSSResultGroup { return css` .content { @@ -152,18 +577,48 @@ class MoreInfoLight extends LitElement { width: 100%; } - .buttons { - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 12px; - } - .buttons > * { - margin: 4px; + .color_temp { + --ha-slider-background: -webkit-linear-gradient( + var(--float-end), + rgb(166, 209, 255) 0%, + white 50%, + rgb(255, 160, 0) 100% + ); + /* The color temp minimum value shouldn't be rendered differently. It's not "off". */ + --paper-slider-knob-start-border-color: var(--primary-color); + margin-bottom: 4px; } - ha-more-info-light-brightness { - margin-bottom: 16px; + .segmentationContainer { + position: relative; + max-height: 500px; + display: flex; + justify-content: center; + } + + ha-button-toggle-group { + margin-bottom: 8px; + } + + ha-color-picker { + --ha-color-picker-wheel-borderwidth: 5; + --ha-color-picker-wheel-bordercolor: white; + --ha-color-picker-wheel-shadow: none; + --ha-color-picker-marker-borderwidth: 2; + --ha-color-picker-marker-bordercolor: white; + } + + .segmentationButton { + position: absolute; + top: 5%; + left: 0; + color: var(--secondary-text-color); + } + + hr { + border-color: var(--divider-color); + border-bottom: none; + margin: 16px 0; } `; } diff --git a/src/dialogs/more-info/state_more_info_control.ts b/src/dialogs/more-info/state_more_info_control.ts index ad5d0b64e0..d4ebaed326 100644 --- a/src/dialogs/more-info/state_more_info_control.ts +++ b/src/dialogs/more-info/state_more_info_control.ts @@ -1,9 +1,9 @@ import type { HassEntity } from "home-assistant-js-websocket"; -import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { - DOMAINS_HIDE_DEFAULT_MORE_INFO, DOMAINS_WITH_MORE_INFO, + DOMAINS_HIDE_DEFAULT_MORE_INFO, } from "./const"; +import { computeStateDomain } from "../../common/entity/compute_state_domain"; const LAZY_LOADED_MORE_INFO_CONTROL = { alarm_control_panel: () => import("./controls/more-info-alarm_control_panel"), @@ -33,6 +33,7 @@ const LAZY_LOADED_MORE_INFO_CONTROL = { export const stateMoreInfoType = (stateObj: HassEntity): string => { const domain = computeStateDomain(stateObj); + return domainMoreInfoType(domain); }; diff --git a/src/translations/en.json b/src/translations/en.json index 772462f2a2..c063af0864 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -889,18 +889,6 @@ }, "zone": { "graph_unit": "People home" - }, - "light": { - "change_color": "Change color", - "select_effect": "Select effect", - "color_picker": { - "title": "Change color", - "mode": { - "color": "Color", - "color_temp": "Temperature", - "white": "White" - } - } } }, "entity_registry": { diff --git a/yarn.lock b/yarn.lock index f4446890ba..621c90a886 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3117,16 +3117,6 @@ __metadata: languageName: node linkType: hard -"@material/web@npm:^1.0.0-pre.1": - version: 1.0.0-pre.1 - resolution: "@material/web@npm:1.0.0-pre.1" - dependencies: - lit: ^2.3.0 - tslib: ^2.4.0 - checksum: 87716cb760020380f797feebb066a6e22ecd9d97b2fdb697977ca9af2b58b7422737a38cb0639cdb1124669d650ce3cfe1e77642738ef36556116ced57d82a1f - languageName: node - linkType: hard - "@mdi/js@npm:7.1.96": version: 7.1.96 resolution: "@mdi/js@npm:7.1.96" @@ -9503,7 +9493,6 @@ fsevents@^1.2.7: "@material/mwc-textfield": ^0.27.0 "@material/mwc-top-app-bar-fixed": ^0.27.0 "@material/top-app-bar": =14.0.0-canary.53b3cad2f.0 - "@material/web": ^1.0.0-pre.1 "@mdi/js": 7.1.96 "@mdi/svg": 7.1.96 "@octokit/auth-oauth-device": ^4.0.2 @@ -11225,7 +11214,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"lit@npm:^2.0.0, lit@npm:^2.0.0-rc.2, lit@npm:^2.2.1, lit@npm:^2.3.0, lit@npm:^2.5.0, lit@npm:^2.6.1": +"lit@npm:^2.0.0, lit@npm:^2.0.0-rc.2, lit@npm:^2.2.1, lit@npm:^2.5.0, lit@npm:^2.6.1": version: 2.6.1 resolution: "lit@npm:2.6.1" dependencies: