Light more info enhancement (#16673)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Paul Bottein 2023-05-30 20:41:12 +02:00 committed by GitHub
parent 7b350e31dd
commit 0771a780d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 632 additions and 202 deletions

View File

@ -0,0 +1,39 @@
diff --git a/modular/sortable.complete.esm.js b/modular/sortable.complete.esm.js
index 02e9f2d6bebeb430fe6e7c1cc3f9c3c9df051f14..bb8268b0844a1faa4108cc92c0be2a3dbaf23f83 100644
--- a/modular/sortable.complete.esm.js
+++ b/modular/sortable.complete.esm.js
@@ -1657,7 +1657,7 @@ Sortable.prototype =
target = parent; // store last element
}
/* jshint boss:true */
- while (parent = parent.parentNode);
+ while (parent = parent.parentNode || parent.getRootNode().host);
}
_unhideGhostForTarget();
diff --git a/modular/sortable.core.esm.js b/modular/sortable.core.esm.js
index b04c8b4634f7c6b4ef1aadbb48afe6564306dea9..39a107163c8c336ebd669b5ea8a936af87e1c1e7 100644
--- a/modular/sortable.core.esm.js
+++ b/modular/sortable.core.esm.js
@@ -1657,7 +1657,7 @@ Sortable.prototype =
target = parent; // store last element
}
/* jshint boss:true */
- while (parent = parent.parentNode);
+ while (parent = parent.parentNode || parent.getRootNode().host);
}
_unhideGhostForTarget();
diff --git a/modular/sortable.esm.js b/modular/sortable.esm.js
index 6ec7ed1bb557e21c2578200161e989c65d23150b..0a05475a22904472fac6c13f524c674da76584b0 100644
--- a/modular/sortable.esm.js
+++ b/modular/sortable.esm.js
@@ -1657,7 +1657,7 @@ Sortable.prototype =
target = parent; // store last element
}
/* jshint boss:true */
- while (parent = parent.parentNode);
+ while (parent = parent.parentNode || parent.getRootNode().host);
}
_unhideGhostForTarget();

View File

@ -248,7 +248,8 @@
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
"resolutions": {
"@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@material/mwc-button@^0.25.3": "^0.27.0"
"@material/mwc-button@^0.25.3": "^0.27.0",
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch"
},
"prettier": {
"trailingComma": "es5",

View File

@ -24,6 +24,11 @@ export class HaOutlinedIconButton extends IconButton {
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-sys-color-on-surface-rgb: var(--rgb-secondary-text-color);
}
:host([no-ripple]) .outlined {
--md-ripple-focus-opacity: 0;
--md-ripple-hover-opacity: 0;
--md-ripple-pressed-opacity: 0;
}
button {
/* Fix md-outlined-icon-button padding for iOS */
padding: 0;

View File

@ -76,7 +76,7 @@ export interface SensorEntityOptions {
}
export interface LightEntityOptions {
favorites_colors?: LightColor[];
favorite_colors?: LightColor[];
}
export interface NumberEntityOptions {

View File

@ -107,7 +107,7 @@ export type LightColor =
rgbww_color: [number, number, number, number, number];
};
const FAVORITE_COLOR_COUNT = 6;
const FAVORITE_COLOR_COUNT = 8;
export const computeDefaultFavoriteColors = (
stateObj: LightEntity
@ -119,8 +119,6 @@ export const computeDefaultFavoriteColors = (
LightColorMode.COLOR_TEMP
);
const supportsWhite = lightSupportsColorMode(stateObj, LightColorMode.WHITE);
const supportsColor = lightSupportsColor(stateObj);
const colorPerMode =
@ -149,9 +147,5 @@ export const computeDefaultFavoriteColors = (
}
}
// Remove last color by white mode if supported
if (supportsWhite) {
colors.pop();
}
return colors;
};

View File

@ -33,6 +33,7 @@ class DialogLightColorFavorite extends LitElement {
public closeDialog(): void {
this._dialogParams = undefined;
this._entry = undefined;
this._color = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@ -59,10 +60,13 @@ class DialogLightColorFavorite extends LitElement {
return nothing;
}
const title = this.hass.localize("ui.dialogs.light-color-favorite.title");
return html`
<ha-dialog open @closed=${this._cancel} .heading=${title} flexContent>
<ha-dialog
open
@closed=${this._cancel}
.heading=${this._dialogParams?.title ?? ""}
flexContent
>
<ha-dialog-header slot="heading">
<ha-icon-button
slot="navigationIcon"
@ -70,7 +74,7 @@ class DialogLightColorFavorite extends LitElement {
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
></ha-icon-button>
<span slot="title">${title}</span>
<span slot="title">${this._dialogParams?.title}</span>
</ha-dialog-header>
<light-color-picker
.hass=${this.hass}

View File

@ -1,5 +1,4 @@
import { mdiPencil } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map";
@ -28,31 +27,32 @@ class MoreInfoViewLightColorPicker extends LitElement {
@property() color!: LightColor;
@property() editMode?: boolean;
@query("ha-outlined-icon-button", true)
private _button?: HaOutlinedIconButton;
private get _rgbColor(): [number, number, number] {
if ("hs_color" in this.color) {
return hs2rgb([this.color.hs_color[0], this.color.hs_color[1] / 100]);
}
if ("color_temp_kelvin" in this.color) {
return temperature2rgb(this.color.color_temp_kelvin);
}
if ("rgb_color" in this.color) {
return this.color.rgb_color;
}
if ("rgbw_color" in this.color) {
return rgbw2rgb(this.color.rgbw_color);
}
if ("rgbww_color" in this.color) {
return rgbww2rgb(
this.color.rgbww_color,
this.stateObj?.attributes.min_color_temp_kelvin,
this.stateObj?.attributes.max_color_temp_kelvin
);
if (this.color) {
if ("hs_color" in this.color) {
return hs2rgb([this.color.hs_color[0], this.color.hs_color[1] / 100]);
}
if ("color_temp_kelvin" in this.color) {
return temperature2rgb(this.color.color_temp_kelvin);
}
if ("rgb_color" in this.color) {
return this.color.rgb_color;
}
if ("rgbw_color" in this.color) {
return rgbw2rgb(this.color.rgbw_color);
}
if ("rgbww_color" in this.color) {
return rgbww2rgb(
this.color.rgbww_color,
this.stateObj?.attributes.min_color_temp_kelvin,
this.stateObj?.attributes.max_color_temp_kelvin
);
}
}
return [255, 255, 255];
}
@ -67,6 +67,7 @@ class MoreInfoViewLightColorPicker extends LitElement {
return html`
<ha-outlined-icon-button
no-ripple
.disabled=${this.disabled}
title=${ifDefined(this.label)}
aria-label=${ifDefined(this.label)}
@ -75,17 +76,16 @@ class MoreInfoViewLightColorPicker extends LitElement {
"--icon-color": hexIconColor,
"--rgb-icon-color": rgbIconColor,
})}
>
${this.editMode
? html`<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>`
: nothing}
</ha-outlined-icon-button>
></ha-outlined-icon-button>
`;
}
static get styles(): CSSResultGroup {
return [
css`
:host {
display: block;
}
ha-outlined-icon-button {
--ha-icon-display: block;
--md-sys-color-on-surface: var(
@ -101,6 +101,9 @@ class MoreInfoViewLightColorPicker extends LitElement {
var(--rgb-secondary-text-color)
);
--md-sys-color-outline: var(--divider-color);
--md-ripple-focus-color: 0;
--md-ripple-hover-opacity: 0;
--md-ripple-pressed-opacity: 0;
border-radius: 9999px;
}
:host([disabled]) {

View File

@ -0,0 +1,377 @@
import { mdiCheck, mdiMinus, mdiPlus } from "@mdi/js";
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import type { SortableEvent } from "sortablejs";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-control-slider";
import { UNAVAILABLE } from "../../../../data/entity";
import {
ExtEntityRegistryEntry,
updateEntityRegistryEntry,
} from "../../../../data/entity_registry";
import {
computeDefaultFavoriteColors,
LightColor,
LightEntity,
} from "../../../../data/light";
import { actionHandler } from "../../../../panels/lovelace/common/directives/action-handler-directive";
import {
loadSortable,
SortableInstance,
} from "../../../../resources/sortable.ondemand";
import { HomeAssistant } from "../../../../types";
import { showConfirmationDialog } from "../../../generic/show-dialog-box";
import "./ha-favorite-color-button";
import { showLightColorFavoriteDialog } from "./show-dialog-light-color-favorite";
@customElement("ha-more-info-light-favorite-colors")
export class HaMoreInfoLightFavoriteColors extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: LightEntity;
@property({ attribute: false }) public entry?: ExtEntityRegistryEntry | null;
@property({ attribute: false }) public editMode?: boolean;
@state() private _favoriteColors: LightColor[] = [];
private _sortable?: SortableInstance;
protected updated(changedProps: PropertyValues): void {
if (changedProps.has("editMode")) {
if (this.editMode) {
this._createSortable();
} else {
this._destroySortable();
}
}
if (changedProps.has("entry")) {
if (this.entry) {
if (this.entry.options?.light?.favorite_colors) {
this._favoriteColors = this.entry.options.light.favorite_colors;
} else if (this.stateObj) {
this._favoriteColors = computeDefaultFavoriteColors(this.stateObj);
}
}
}
}
private async _createSortable() {
const Sortable = await loadSortable();
this._sortable = new Sortable(
this.shadowRoot!.querySelector(".container")!,
{
animation: 150,
fallbackClass: "sortable-fallback",
draggable: ".color",
onChoose: (evt: SortableEvent) => {
(evt.item as any).placeholder =
document.createComment("sort-placeholder");
evt.item.after((evt.item as any).placeholder);
},
onEnd: (evt: SortableEvent) => {
// put back in original location
if ((evt.item as any).placeholder) {
(evt.item as any).placeholder.replaceWith(evt.item);
delete (evt.item as any).placeholder;
}
this._dragged(evt);
},
}
);
}
private _dragged(ev: SortableEvent): void {
if (ev.oldIndex === ev.newIndex) return;
this._move(ev.oldIndex!, ev.newIndex!);
}
private _move(index: number, newIndex: number) {
const favoriteColors = this._favoriteColors.concat();
const action = favoriteColors.splice(index, 1)[0];
favoriteColors.splice(newIndex, 0, action);
this._favoriteColors = favoriteColors;
this._save(favoriteColors);
}
private _destroySortable() {
this._sortable?.destroy();
this._sortable = undefined;
}
private _apply = (index: number) => {
const favorite = this._favoriteColors[index];
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
...favorite,
});
};
private async _save(newFavoriteColors: LightColor[]) {
const result = await updateEntityRegistryEntry(
this.hass,
this.entry!.entity_id,
{
options_domain: "light",
options: {
favorite_colors: newFavoriteColors,
},
}
);
fireEvent(this, "entity-entry-updated", result.entity_entry);
}
private _add = async () => {
const color = await showLightColorFavoriteDialog(this, {
entry: this.entry!,
title: this.hass.localize(
"ui.dialogs.more_info_control.light.favorite_color.add_title"
),
});
if (color) {
const newFavoriteColors = [...this._favoriteColors, color];
this._save(newFavoriteColors);
}
};
private _edit = async (index) => {
// Make sure the current favorite color is set
await this._apply(index);
const color = await showLightColorFavoriteDialog(this, {
entry: this.entry!,
title: this.hass.localize(
"ui.dialogs.more_info_control.light.favorite_color.edit_title"
),
});
if (color) {
const newFavoriteColors = [...this._favoriteColors];
newFavoriteColors[index] = color;
this._save(newFavoriteColors);
} else {
this._apply(index);
}
};
private _remove = async (index) => {
const confirm = await showConfirmationDialog(this, {
destructive: true,
title: this.hass.localize(
`ui.dialogs.more_info_control.light.favorite_color.remove_confirm_title`
),
text: this.hass.localize(
`ui.dialogs.more_info_control.light.favorite_color.remove_confirm_text`
),
confirmText: this.hass.localize(
`ui.dialogs.more_info_control.light.favorite_color.remove_confirm_action`
),
});
if (!confirm) {
return;
}
const newFavoriteColors = this._favoriteColors.filter(
(_, i) => index !== i
);
this._save(newFavoriteColors);
};
private _handleDeleteButton = (ev) => {
ev.stopPropagation();
const index = ev.target.index;
this._remove(index);
};
private _handleAddButton = (ev) => {
ev.stopPropagation();
this._add();
};
private _handleColorAction = (ev) => {
ev.stopPropagation();
if (ev.detail.action === "hold" && this.hass.user?.is_admin) {
fireEvent(this, "toggle-edit-mode", true);
return;
}
const index = ev.target.index;
if (this.editMode) {
this._edit(index);
return;
}
this._apply(index);
};
private _exitEditMode = (ev) => {
ev.stopPropagation();
fireEvent(this, "toggle-edit-mode", false);
};
protected render(): TemplateResult {
return html`
<div class="container">
${this._favoriteColors.map(
(color, index) => html`
<div class="color">
<div
class="color-bubble ${classMap({
shake: !!this.editMode,
})}"
>
<ha-favorite-color-button
.label=${this.hass.localize(
`ui.dialogs.more_info_control.light.favorite_color.${
this.editMode ? "edit" : "set"
}`,
{ number: index }
)}
.disabled=${this.stateObj!.state === UNAVAILABLE}
.color=${color}
.index=${index}
.actionHandler=${actionHandler({
hasHold: !this.editMode && this.hass.user?.is_admin,
disabled: this.stateObj!.state === UNAVAILABLE,
})}
@action=${this._handleColorAction}
>
</ha-favorite-color-button>
${this.editMode
? html`
<button
@click=${this._handleDeleteButton}
class="delete"
.index=${index}
aria-label=${this.hass.localize(
`ui.dialogs.more_info_control.light.favorite_color.remove`,
{ number: index }
)}
.title=${this.hass.localize(
`ui.dialogs.more_info_control.light.favorite_color.remove`,
{ number: index }
)}
>
<ha-svg-icon .path=${mdiMinus}></ha-svg-icon>
</button>
`
: nothing}
</div>
</div>
`
)}
${this.editMode
? html`
<ha-outlined-icon-button
class="button"
@click=${this._handleAddButton}
>
<ha-svg-icon .path=${mdiPlus}></ha-svg-icon>
</ha-outlined-icon-button>
<ha-outlined-icon-button
@click=${this._exitEditMode}
class="button"
>
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
</ha-outlined-icon-button>
`
: nothing}
</div>
`;
}
static get styles(): CSSResultGroup {
return css`
.container {
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
flex-wrap: wrap;
max-width: 250px;
user-select: none;
}
.container > * {
margin: 8px;
}
.color {
display: block;
}
.color .color-bubble.shake {
position: relative;
display: block;
animation: shake 0.45s linear infinite;
}
.color:nth-child(3n + 1) .color-bubble.shake {
animation-delay: 0.15s;
}
.color:nth-child(3n + 2) .color-bubble.shake {
animation-delay: 0.3s;
}
.sortable-ghost {
opacity: 0.4;
}
.sortable-fallback {
display: none;
}
@keyframes shake {
0% {
transform: rotateZ(0deg) translateX(-1px) translateY(0) scale(1);
}
20% {
transform: rotateZ(-3deg) translateX(0) translateY();
}
40% {
transform: rotateZ(0deg) translateX(1px) translateY(0);
}
60% {
transform: rotateZ(3deg) translateX(0) translateY(0);
}
100% {
transform: rotateZ(0deg) translateX(-1px) translateY(0);
}
}
.delete {
position: absolute;
top: -6px;
right: -6px;
width: 20px;
height: 20px;
outline: none;
background-color: var(--secondary-background-color);
padding: 0;
border-radius: 10px;
border: none;
cursor: pointer;
display: block;
}
.delete {
--mdc-icon-size: 12px;
color: var(--primary-text-color);
}
.delete * {
pointer-events: none;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-more-info-light-favorite-colors": HaMoreInfoLightFavoriteColors;
}
}

View File

@ -16,7 +16,11 @@ class MoreInfoViewLightColorPicker extends LitElement {
}
return html`
<light-color-picker .hass=${this.hass} entityId=${this.params.entityId}>
<light-color-picker
.hass=${this.hass}
.entityId=${this.params.entityId}
.defaultMode=${this.params.defaultMode}
>
</light-color-picker>
`;
}

View File

@ -42,6 +42,8 @@ class LightColorPicker extends LitElement {
@property() public entityId!: string;
@property() public defaultMode!: Mode;
@state() private _cwSliderValue?: number;
@state() private _wwSliderValue?: number;
@ -274,11 +276,13 @@ class LightColorPicker extends LitElement {
}
this._modes = modes;
this._mode = this.stateObj!.attributes.color_mode
? this.stateObj!.attributes.color_mode === LightColorMode.COLOR_TEMP
? LightColorMode.COLOR_TEMP
: "color"
: this._modes[0];
this._mode =
this.defaultMode ??
(this.stateObj!.attributes.color_mode
? this.stateObj!.attributes.color_mode === LightColorMode.COLOR_TEMP
? LightColorMode.COLOR_TEMP
: "color"
: this._modes[0]);
}
this._updateSliderValues();

View File

@ -4,6 +4,7 @@ import { LightColor } from "../../../../data/light";
export interface LightColorFavoriteDialogParams {
entry: ExtEntityRegistryEntry;
title: string;
submit?: (color?: LightColor) => void;
cancel?: () => void;
}

View File

@ -2,6 +2,7 @@ import { fireEvent } from "../../../../common/dom/fire_event";
export interface LightColorPickerViewParams {
entityId: string;
defaultMode: "color" | "color_temp";
}
export const loadLightColorPickerView = () =>

View File

@ -15,8 +15,6 @@ import {
PropertyValues,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import {
computeAttributeNameDisplay,
@ -29,15 +27,10 @@ import "../../../components/ha-button-menu";
import "../../../components/ha-outlined-button";
import "../../../components/ha-outlined-icon-button";
import "../../../components/ha-select";
import { ON, UNAVAILABLE } from "../../../data/entity";
import {
ExtEntityRegistryEntry,
updateEntityRegistryEntry,
} from "../../../data/entity_registry";
import { UNAVAILABLE } from "../../../data/entity";
import { ExtEntityRegistryEntry } from "../../../data/entity_registry";
import { forwardHaptic } from "../../../data/haptics";
import {
computeDefaultFavoriteColors,
LightColor,
LightColorMode,
LightEntity,
LightEntityFeature,
@ -51,7 +44,7 @@ import "../components/ha-more-info-state-header";
import "../components/ha-more-info-toggle";
import "../components/lights/ha-favorite-color-button";
import "../components/lights/ha-more-info-light-brightness";
import { showLightColorFavoriteDialog } from "../components/lights/show-dialog-light-color-favorite";
import "../components/lights/ha-more-info-light-favorite-colors";
import { showLightColorPickerView } from "../components/lights/show-view-light-color-picker";
@customElement("more-info-light")
@ -62,12 +55,12 @@ class MoreInfoLight extends LitElement {
@property({ attribute: false }) public entry?: ExtEntityRegistryEntry | null;
@property({ attribute: false }) public editMode?: boolean;
@state() private _effect?: string;
@state() private _selectedBrightness?: number;
@state() private _focusedFavoriteIndex?: number;
private _brightnessChanged(ev) {
const value = (ev.detail as any).value;
if (isNaN(value)) return;
@ -80,25 +73,9 @@ class MoreInfoLight extends LitElement {
? Math.round((this.stateObj.attributes.brightness * 100) / 255)
: undefined;
this._effect = this.stateObj?.attributes.effect;
if (this.stateObj?.state !== ON) {
this._focusedFavoriteIndex = undefined;
}
}
}
private get _favoriteColors(): LightColor[] {
if (this.entry) {
if (this.entry.options?.light?.favorites_colors) {
return this.entry.options.light.favorites_colors;
}
if (this.stateObj) {
return computeDefaultFavoriteColors(this.stateObj);
}
}
return [];
}
protected render() {
if (!this.hass || !this.stateObj) {
return nothing;
@ -155,74 +132,87 @@ class MoreInfoLight extends LitElement {
`}
${supportsColorTemp || supportsColor || supportsBrightness
? html`
<div class="buttons">
${supportsBrightness
? html`<ha-outlined-icon-button
.disabled=${this.stateObj!.state === UNAVAILABLE}
.title=${this.hass.localize(
"ui.dialogs.more_info_control.light.toggle"
)}
.ariaLabel=${this.hass.localize(
"ui.dialogs.more_info_control.light.toggle"
)}
@click=${this._toggle}
>
<ha-svg-icon .path=${mdiPower}></ha-svg-icon>
</ha-outlined-icon-button>`
: nothing}
${supportsColor || supportsColorTemp
? html`<ha-outlined-icon-button
class=${classMap({
"color-rgb-mode": supportsColor,
"color-temp-mode": !supportsColor,
})}
.disabled=${this.stateObj!.state === UNAVAILABLE}
.title=${this.hass.localize(
"ui.dialogs.more_info_control.light.change_color"
)}
.ariaLabel=${this.hass.localize(
"ui.dialogs.more_info_control.light.change_color"
)}
@click=${this._showLightColorPickerView}
>
</ha-outlined-icon-button>`
: nothing}
${supportsWhite
<div>
<div class="buttons">
${supportsBrightness
? html`
<ha-outlined-icon-button
.disabled=${this.stateObj!.state === UNAVAILABLE}
.title=${this.hass.localize(
"ui.dialogs.more_info_control.light.toggle"
)}
.ariaLabel=${this.hass.localize(
"ui.dialogs.more_info_control.light.toggle"
)}
@click=${this._toggle}
>
<ha-svg-icon .path=${mdiPower}></ha-svg-icon>
</ha-outlined-icon-button>
`
: nothing}
${supportsColor
? html`
<ha-outlined-icon-button
class="color-mode"
.disabled=${this.stateObj!.state === UNAVAILABLE}
.title=${this.hass.localize(
"ui.dialogs.more_info_control.light.change_color"
)}
.ariaLabel=${this.hass.localize(
"ui.dialogs.more_info_control.light.change_color"
)}
.mode=${"color"}
@click=${this._showLightColorPickerView}
>
</ha-outlined-icon-button>
`
: nothing}
${supportsColorTemp
? html`
<ha-outlined-icon-button
class="color-temp-mode"
.disabled=${this.stateObj!.state === UNAVAILABLE}
.title=${this.hass.localize(
"ui.dialogs.more_info_control.light.change_color"
)}
.ariaLabel=${this.hass.localize(
"ui.dialogs.more_info_control.light.change_color"
)}
.mode=${"color_temp"}
@click=${this._showLightColorPickerView}
>
</ha-outlined-icon-button>
`
: nothing}
${supportsWhite
? html`
<ha-outlined-icon-button
.disabled=${this.stateObj!.state === UNAVAILABLE}
.title=${this.hass.localize(
"ui.dialogs.more_info_control.light.set_white"
)}
.ariaLabel=${this.hass.localize(
"ui.dialogs.more_info_control.light.set_white"
)}
@click=${this._setWhite}
>
<ha-svg-icon .path=${mdiFileWordBox}></ha-svg-icon>
</ha-outlined-icon-button>
`
: nothing}
</div>
${this.editMode ||
(this.entry?.options?.light?.favorite_colors?.length ?? 0) > 0
? html`
<ha-outlined-icon-button
.disabled=${this.stateObj!.state === UNAVAILABLE}
.title=${this.hass.localize(
"ui.dialogs.more_info_control.light.set_white"
)}
.ariaLabel=${this.hass.localize(
"ui.dialogs.more_info_control.light.set_white"
)}
@click=${this._setWhite}
<ha-more-info-light-favorite-colors
.hass=${this.hass}
.stateObj=${this.stateObj}
.entry=${this.entry}
.editMode=${this.editMode}
>
<ha-svg-icon .path=${mdiFileWordBox}></ha-svg-icon>
</ha-outlined-icon-button>
</ha-more-info-light-favorite-colors>
`
: nothing}
${this._favoriteColors.map((color, index) => {
const editMode = this._focusedFavoriteIndex === index;
return html`
<ha-favorite-color-button
.label=${this.hass.localize(
`ui.dialogs.more_info_control.light.favorite_color.${
editMode ? "edit" : "set"
}`,
{ number: index }
)}
.disabled=${this.stateObj!.state === UNAVAILABLE}
.color=${color}
.index=${index}
@click=${this._handleFavoriteButton}
@blur=${this._removeFocus}
.editMode=${editMode}
>
</ha-favorite-color-button>
`;
})}
</div>
`
: nothing}
@ -285,10 +275,6 @@ class MoreInfoLight extends LitElement {
`;
}
private _removeFocus = () => {
this._focusedFavoriteIndex = undefined;
};
private _toggle = () => {
const service = this.stateObj?.state === "on" ? "turn_off" : "turn_on";
forwardHaptic("light");
@ -297,7 +283,7 @@ class MoreInfoLight extends LitElement {
});
};
private _showLightColorPickerView = () => {
private _showLightColorPickerView = (ev) => {
showLightColorPickerView(
this,
this.hass.localize(
@ -305,48 +291,11 @@ class MoreInfoLight extends LitElement {
),
{
entityId: this.stateObj!.entity_id,
defaultMode: ev.target.mode,
}
);
};
private _editFavoriteColor = async (index) => {
// Make sure the current favorite color is set
this._applyFavoriteColor(index);
const color = await showLightColorFavoriteDialog(this, {
entry: this.entry!,
});
if (color) {
const newFavoriteColors = [...this._favoriteColors];
newFavoriteColors[index] = color;
const result = await updateEntityRegistryEntry(
this.hass,
this.entry!.entity_id,
{
options_domain: "light",
options: {
favorites_colors: newFavoriteColors,
},
}
);
fireEvent(this, "entity-entry-updated", result.entity_entry);
} else {
this._applyFavoriteColor(index);
}
this._focusedFavoriteIndex = index;
};
private _applyFavoriteColor = (index: number) => {
const favorite = this._favoriteColors[index];
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
...favorite,
});
};
private _setWhite = () => {
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
@ -354,19 +303,6 @@ class MoreInfoLight extends LitElement {
});
};
private _handleFavoriteButton = (ev) => {
ev.stopPropagation();
const index = ev.target.index;
if (this._focusedFavoriteIndex === index) {
this._editFavoriteColor(index);
return;
}
if (this.hass.user?.is_admin) {
this._focusedFavoriteIndex = index;
}
this._applyFavoriteColor(index);
};
private _handleEffectButton(ev) {
ev.stopPropagation();
ev.preventDefault();
@ -391,12 +327,12 @@ class MoreInfoLight extends LitElement {
flex-wrap: wrap;
max-width: 250px;
}
.color-rgb-mode,
.color-mode,
.color-temp-mode {
border-radius: 9999px;
--md-sys-color-outline: var(--divider-color);
}
.color-rgb-mode {
.color-mode {
background-image: url("/static/images/color_wheel.png");
background-size: cover;
}

View File

@ -5,6 +5,8 @@ import {
mdiDevices,
mdiDotsVertical,
mdiInformationOutline,
mdiPencil,
mdiPencilOff,
mdiPencilOutline,
} from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
@ -67,6 +69,9 @@ declare global {
interface HASSDomEvents {
"show-child-view": ChildView;
}
interface HASSDomEvents {
"toggle-edit-mode": boolean;
}
}
@customElement("ha-more-info-dialog")
@ -83,6 +88,8 @@ export class MoreInfoDialog extends LitElement {
@state() private _entry?: ExtEntityRegistryEntry | null;
@state() private _infoEditMode = false;
public showDialog(params: MoreInfoDialogParams) {
this._entityId = params.entityId;
if (!this._entityId) {
@ -218,6 +225,15 @@ export class MoreInfoDialog extends LitElement {
this.closeDialog();
}
private _toggleInfoEditMode(ev) {
if (!shouldHandleRequestSelectedEvent(ev)) return;
this._infoEditMode = !this._infoEditMode;
}
private _handleToggleInfoEditModeEvent(ev) {
this._infoEditMode = ev.detail;
}
private _goToRelated(ev): void {
if (!shouldHandleRequestSelectedEvent(ev)) return;
this.setView("related");
@ -342,6 +358,28 @@ export class MoreInfoDialog extends LitElement {
</ha-list-item>
`
: nothing}
${this._entry && domain === "light"
? html`
<ha-list-item
graphic="icon"
@request-selected=${this._toggleInfoEditMode}
>
${this._infoEditMode
? this.hass.localize(
`ui.dialogs.more_info_control.exit_edit_mode`
)
: this.hass.localize(
`ui.dialogs.more_info_control.${domain}.edit_mode`
)}
<ha-svg-icon
slot="graphic"
.path=${this._infoEditMode
? mdiPencilOff
: mdiPencil}
></ha-svg-icon>
</ha-list-item>
`
: nothing}
<ha-list-item
graphic="icon"
@request-selected=${this._goToRelated}
@ -366,6 +404,7 @@ export class MoreInfoDialog extends LitElement {
dialogInitialFocus
@show-child-view=${this._showChildView}
@entity-entry-updated=${this._entryUpdated}
@toggle-edit-mode=${this._handleToggleInfoEditModeEvent}
>
${this._childView
? html`
@ -385,6 +424,7 @@ export class MoreInfoDialog extends LitElement {
.hass=${this.hass}
.entityId=${this._entityId}
.entry=${this._entry}
.editMode=${this._infoEditMode}
></ha-more-info-info>
`
: this._currView === "history"
@ -428,6 +468,7 @@ export class MoreInfoDialog extends LitElement {
super.updated(changedProps);
if (changedProps.has("_currView")) {
this._childView = undefined;
this._infoEditMode = false;
}
}

View File

@ -19,9 +19,11 @@ import "./more-info-content";
export class MoreInfoInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public entityId!: string;
@property({ attribute: false }) public entityId!: string;
@property() public entry?: ExtEntityRegistryEntry | null;
@property({ attribute: false }) public entry?: ExtEntityRegistryEntry | null;
@property({ attribute: false }) public editMode?: boolean;
protected render() {
const entityId = this.entityId;
@ -78,6 +80,7 @@ export class MoreInfoInfo extends LitElement {
.stateObj=${stateObj}
.hass=${this.hass}
.entry=${this.entry}
.editMode=${this.editMode}
></more-info-content>
</div>
</div>

View File

@ -14,6 +14,8 @@ class MoreInfoContent extends ReactiveElement {
@property({ attribute: false }) public entry?: ExtEntityRegistryEntry | null;
@property({ attribute: false }) public editMode?: boolean;
private _detachedChild?: ChildNode;
protected createRenderRoot() {
@ -26,6 +28,7 @@ class MoreInfoContent extends ReactiveElement {
const stateObj = this.stateObj;
const entry = this.entry;
const hass = this.hass;
const editMode = this.editMode;
if (!stateObj || !hass) {
if (this.lastChild) {
@ -59,6 +62,7 @@ class MoreInfoContent extends ReactiveElement {
hass,
stateObj,
entry,
editMode,
});
}
}

View File

@ -869,6 +869,7 @@
"turn_on": "Turn on",
"turn_off": "Turn off",
"toggle": "Toggle",
"exit_edit_mode": "Exit edit mode",
"script": {
"last_action": "Last action",
"last_triggered": "Last triggered"
@ -926,6 +927,7 @@
"graph_unit": "People in zone"
},
"light": {
"edit_mode": "Edit favorite colors",
"toggle": "Toggle",
"change_color": "Change color",
"set_white": "Set white",
@ -940,7 +942,14 @@
},
"favorite_color": {
"set": "Set favorite color {number}",
"edit": "Edit favorite color {number}"
"edit": "Edit favorite color {number}",
"remove": "Remove favorite color {number}",
"remove_confirm_title": "Remove favorite color?",
"remove_confirm_text": "This favorite color will be permanently removed.",
"remove_confirm_action": "Remove",
"add": "Add new favorite color",
"edit_title": "Edit favorite color",
"add_title": "Add favorite color"
}
},
"fan": {
@ -1336,9 +1345,6 @@
"title": "Enter code",
"input_label": "Code"
},
"light-color-favorite": {
"title": "Edit favorite color"
},
"tts-try": {
"header": "Try text-to-speech",
"message": "Message",

View File

@ -14473,6 +14473,13 @@ __metadata:
languageName: node
linkType: hard
"sortablejs@patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch::locator=home-assistant-frontend%40workspace%3A.":
version: 1.15.0
resolution: "sortablejs@patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch::version=1.15.0&hash=29e891&locator=home-assistant-frontend%40workspace%3A."
checksum: 9a5c899bc4ec1a99732a44511ef7188ae0aba59b98bce66b952590a33a12e0779cebe8664a5565815facfeb0a32072418a2447eb690a5d3c234981126462da7e
languageName: node
linkType: hard
"source-list-map@npm:^2.0.1":
version: 2.0.1
resolution: "source-list-map@npm:2.0.1"