Compare commits

...

4 Commits

Author SHA1 Message Date
Aidan Timson 8e1474d717 Slightly better message 2026-05-13 12:25:09 +01:00
Aidan Timson 6172c9db23 Use helper instead 2026-05-13 12:24:38 +01:00
Aidan Timson ad36772c74 Show device editor tip on name change when device exists 2026-05-13 12:15:33 +01:00
Aidan Timson f732de574b Disable update button when state is clean 2026-05-13 11:41:31 +01:00
5 changed files with 132 additions and 3 deletions
+33 -1
View File
@@ -1,3 +1,4 @@
import "@home-assistant/webawesome/dist/components/popup/popup";
import { mdiLightbulbOutline } from "@mdi/js";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
@@ -9,18 +10,41 @@ import "./ha-svg-icon";
class HaTip extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
/**
* When set, renders the tip inside a popup anchored to the given element
* instead of inline. Does not steal focus.
*/
@property({ attribute: false }) public popoverAnchor?: Element;
public render() {
if (!this.hass) {
return nothing;
}
return html`
const content = html`
<ha-svg-icon .path=${mdiLightbulbOutline}></ha-svg-icon>
<span class="prefix"
>${this.hass.localize("ui.panel.config.tips.tip")}</span
>
<span class="text"><slot></slot></span>
`;
if (this.popoverAnchor) {
return html`
<wa-popup
active
.anchor=${this.popoverAnchor}
placement="top-start"
distance="4"
flip
shift
>
<div class="popup-content">${content}</div>
</wa-popup>
`;
}
return content;
}
static styles = css`
@@ -40,6 +64,14 @@ class HaTip extends LitElement {
.prefix {
font-weight: var(--ha-font-weight-medium);
}
.popup-content {
padding: var(--ha-space-2) var(--ha-space-3);
background: var(--card-background-color);
border-radius: var(--ha-border-radius-xl);
box-shadow: var(--wa-shadow-m);
color: var(--primary-text-color);
}
`;
}
@@ -39,11 +39,15 @@ export class EntitySettingsHelperTab extends LitElement {
@state() private _submitting = false;
@state() private _dirty = false;
@state() private _componentLoaded?: boolean;
@query("entity-registry-settings-editor")
private _registryEditor?: EntityRegistrySettingsEditor;
private _originalItemJson?: string;
protected firstUpdated(changedProperties: PropertyValues<this>) {
super.firstUpdated(changedProperties);
this._componentLoaded = isComponentLoaded(
@@ -120,7 +124,9 @@ export class EntitySettingsHelperTab extends LitElement {
</ha-button>
<ha-button
@click=${this._updateItem}
.disabled=${!!this._submitting || !!(this._item && !this._item.name)}
.disabled=${!this._dirty ||
!!this._submitting ||
!!(this._item && !this._item.name)}
>
${this.hass.localize("ui.dialogs.entity_registry.editor.update")}
</ha-button>
@@ -128,8 +134,18 @@ export class EntitySettingsHelperTab extends LitElement {
`;
}
private get _isHelperDirty(): boolean {
if (!this._item || !this._originalItemJson) return false;
return JSON.stringify(this._item) !== this._originalItemJson;
}
private _updateDirty() {
this._dirty = (this._registryEditor?.dirty ?? false) || this._isHelperDirty;
}
private _entityRegistryChanged() {
this._error = undefined;
this._updateDirty();
}
private _valueChanged(ev: CustomEvent): void {
@@ -138,11 +154,15 @@ export class EntitySettingsHelperTab extends LitElement {
}
this._error = undefined;
this._item = ev.detail.value;
this._updateDirty();
}
private async _getItem() {
const items = await HELPERS_CRUD[this.entry.platform].fetch(this.hass!);
this._item = items.find((item) => item.id === this.entry.unique_id) || null;
this._originalItemJson = this._item
? JSON.stringify(this._item)
: undefined;
}
private async _updateItem(): Promise<void> {
@@ -208,6 +208,34 @@ export class EntityRegistrySettingsEditor extends LitElement {
private _deviceClassOptions?: string[][];
private _initialStateJson!: string;
private _lastDirty = false;
private _currentState() {
return {
name: this._name.trim() || null,
icon: this._icon.trim() || null,
entityId: this._entityId.trim(),
areaId: this._areaId ?? null,
labels: this._labels ?? [],
deviceClass: this._deviceClass,
disabledBy: this._disabledBy,
hiddenBy: this._hiddenBy,
unitOfMeasurement: this._unit_of_measurement,
precision: this._precision,
defaultCode: this._defaultCode,
calendarColor: this._calendarColor ?? null,
precipitationUnit: this._precipitation_unit,
pressureUnit: this._pressure_unit,
temperatureUnit: this._temperature_unit,
visibilityUnit: this._visibility_unit,
windSpeedUnit: this._wind_speed_unit,
switchAsDomain: this._switchAsDomain,
switchAsInvert: this._switchAsInvert,
};
}
protected willUpdate(changedProperties: PropertyValues<this>) {
super.willUpdate(changedProperties);
if (
@@ -274,6 +302,9 @@ export class EntityRegistrySettingsEditor extends LitElement {
this._wind_speed_unit = stateObj?.attributes?.wind_speed_unit;
}
this._initialStateJson = JSON.stringify(this._currentState());
this._lastDirty = false;
const deviceClasses: string[][] = OVERRIDE_DEVICE_CLASSES[domain];
if (!deviceClasses || this._hideDeviceClassOverride(domain)) {
@@ -372,6 +403,16 @@ export class EntityRegistrySettingsEditor extends LitElement {
this._switchAsDomain = "switch";
this._switchAsInvert = false;
}
this._initialStateJson = JSON.stringify(this._currentState());
this._lastDirty = false;
}
if (this._initialStateJson) {
const dirty = this.dirty;
if (dirty !== this._lastDirty) {
this._lastDirty = dirty;
fireEvent(this, "change");
}
}
}
@@ -407,6 +448,23 @@ export class EntityRegistrySettingsEditor extends LitElement {
.disabled=${this.disabled}
@input=${this._nameChanged}
>
${this._device
? html`<span slot="hint"
>${this.hass.localize(
"ui.dialogs.entity_registry.editor.device_name_tip",
{
link: html`<button
class="link"
@click=${this._resetNameAndOpenDeviceSettings}
>
${this.hass.localize(
"ui.dialogs.entity_registry.editor.open_device_settings"
)}
</button>`,
}
)}</span
>`
: nothing}
</ha-input>`}
${this.hideIcon
? nothing
@@ -1060,6 +1118,10 @@ export class EntityRegistrySettingsEditor extends LitElement {
`;
}
public get dirty(): boolean {
return JSON.stringify(this._currentState()) !== this._initialStateJson;
}
public async updateEntry(): Promise<{
close: boolean;
entry: ExtEntityRegistryEntry;
@@ -1518,6 +1580,13 @@ export class EntityRegistrySettingsEditor extends LitElement {
}
}
private _resetNameAndOpenDeviceSettings() {
this._name = this.entry.name || "";
fireEvent(this, "change");
this._openDeviceSettings();
}
private _openDeviceSettings() {
showDeviceRegistryDetailDialog(this, {
device: this._device!,
@@ -44,6 +44,8 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
@state() private _submitting?: boolean;
@state() private _dirty = false;
@query("entity-registry-settings-editor")
private _registryEditor?: EntityRegistrySettingsEditor;
@@ -144,7 +146,11 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
>
${this.hass.localize("ui.dialogs.entity_registry.editor.delete")}
</ha-button>
<ha-button @click=${this._updateEntry} .loading=${!!this._submitting}>
<ha-button
@click=${this._updateEntry}
.disabled=${!this._dirty || !!this._submitting}
.loading=${!!this._submitting}
>
${this.hass.localize("ui.dialogs.entity_registry.editor.update")}
</ha-button>
</div>
@@ -153,6 +159,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
private _entityRegistryChanged() {
this._error = undefined;
this._dirty = this._registryEditor?.dirty ?? false;
}
private _openDeviceSettings() {
+1
View File
@@ -1958,6 +1958,7 @@
"entity_disabled": "This entity is disabled.",
"enable_entity": "Enable",
"open_device_settings": "Open device settings",
"device_name_tip": "Consider renaming the device instead to update all its entities at once. {link}",
"switch_as_x_confirm": "This switch will be hidden and a new {domain} will be added. Your existing configurations using the switch will continue to work.",
"switch_as_x_remove_confirm": "This {domain} will be removed and the original switch will be visible again. Your existing configurations using the {domain} will no longer work!",
"switch_as_x_change_confirm": "This {domain_1} will be removed and will be replaced by a new {domain_2}. Your existing configurations using the {domain_1} will no longer work!",