mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-14 20:32:09 +00:00
Compare commits
1 Commits
dev
...
dirty-state-7
| Author | SHA1 | Date | |
|---|---|---|---|
| 8dedee6e70 |
@@ -6,6 +6,9 @@ export const PreventUnsavedMixin = <T extends Constructor<LitElement>>(
|
||||
superClass: T
|
||||
) =>
|
||||
class extends superClass {
|
||||
/** Provided by `DirtyStateProviderMixin` or overridden by the consuming class. */
|
||||
declare isDirtyState: boolean;
|
||||
|
||||
private _handleClick = async (e: MouseEvent) => {
|
||||
// get the right target, otherwise the composedPath would return <home-assistant> in the new event
|
||||
const target = e.composedPath()[0];
|
||||
@@ -33,7 +36,7 @@ export const PreventUnsavedMixin = <T extends Constructor<LitElement>>(
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (this.isDirty) {
|
||||
if (this.isDirtyState) {
|
||||
window.addEventListener("click", this._handleClick, true);
|
||||
window.addEventListener("beforeunload", this._handleUnload);
|
||||
} else {
|
||||
@@ -47,11 +50,6 @@ export const PreventUnsavedMixin = <T extends Constructor<LitElement>>(
|
||||
this._removeListeners();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/class-literal-property-style
|
||||
protected get isDirty(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected async promptDiscardChanges(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,55 @@
|
||||
import { consume } from "@lit/context";
|
||||
import type { LitElement } from "lit";
|
||||
import { state } from "lit/decorators";
|
||||
import { closestWithProperty } from "../../../common/dom/ancestors-with-property";
|
||||
import type { DirtyStateContext } from "../../../data/context/dirty-state";
|
||||
import { dirtyStateContext } from "../../../data/context/dirty-state";
|
||||
import type { ShowToastParams } from "../../../managers/notification-manager";
|
||||
import { showToast } from "../../../util/toast";
|
||||
|
||||
export const EDITOR_SAVE_FAB_TOAST_BOTTOM_OFFSET = 60;
|
||||
|
||||
function editorSaveFabVisibleFrom(el: HTMLElement): boolean {
|
||||
if (
|
||||
el.localName === "ha-automation-editor" ||
|
||||
el.localName === "ha-script-editor"
|
||||
) {
|
||||
return Boolean((el as { dirty?: boolean }).dirty);
|
||||
}
|
||||
const holder = closestWithProperty(el, "dirty", false) as
|
||||
| (HTMLElement & { dirty?: boolean })
|
||||
| null;
|
||||
return Boolean(holder?.dirty);
|
||||
}
|
||||
/**
|
||||
* Mixin that consumes `dirtyStateContext` and exposes `editorDirty` for use
|
||||
* in determining toast offset positioning.
|
||||
*/
|
||||
export const EditorToastDirtyConsumerMixin = <
|
||||
Base extends abstract new (...args: any[]) => LitElement,
|
||||
>(
|
||||
superClass: Base
|
||||
) => {
|
||||
abstract class EditorToastDirtyConsumer extends superClass {
|
||||
@state()
|
||||
@consume({ context: dirtyStateContext, subscribe: true })
|
||||
private _dirtyCtx?: DirtyStateContext;
|
||||
|
||||
protected get editorDirty(): boolean {
|
||||
return Boolean(this._dirtyCtx?.isDirty);
|
||||
}
|
||||
|
||||
protected showEditorToast(params: ShowToastParams): void {
|
||||
const offset = this.editorDirty
|
||||
? EDITOR_SAVE_FAB_TOAST_BOTTOM_OFFSET
|
||||
: undefined;
|
||||
showToast(this, {
|
||||
...params,
|
||||
...(offset !== undefined ? { bottomOffset: offset } : {}),
|
||||
dismissable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
return EditorToastDirtyConsumer as unknown as Base &
|
||||
(abstract new (...args: any[]) => {
|
||||
editorDirty: boolean;
|
||||
showEditorToast(params: ShowToastParams): void;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Standalone function for callers that haven't adopted `EditorToastDirtyConsumerMixin`.
|
||||
* Falls back to DOM traversal for dirty detection.
|
||||
* @deprecated Use `EditorToastDirtyConsumerMixin` instead.
|
||||
*/
|
||||
export function showEditorToast(
|
||||
el: HTMLElement,
|
||||
params: ShowToastParams
|
||||
@@ -30,3 +63,17 @@ export function showEditorToast(
|
||||
dismissable: true,
|
||||
});
|
||||
}
|
||||
|
||||
/** @deprecated Use `EditorToastDirtyConsumerMixin` to consume dirty state from context. */
|
||||
function editorSaveFabVisibleFrom(el: HTMLElement): boolean {
|
||||
if (
|
||||
el.localName === "ha-automation-editor" ||
|
||||
el.localName === "ha-script-editor"
|
||||
) {
|
||||
return Boolean((el as { dirty?: boolean }).dirty);
|
||||
}
|
||||
const holder = closestWithProperty(el, "dirty", false) as
|
||||
| (HTMLElement & { dirty?: boolean })
|
||||
| null;
|
||||
return Boolean(holder?.dirty);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
showConfirmationDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
|
||||
import type { Constructor, HomeAssistant, Route } from "../../../types";
|
||||
import type { EntityRegistryUpdate } from "./automation-save-dialog/show-dialog-automation-save";
|
||||
|
||||
@@ -87,7 +88,9 @@ export interface EditorDomainHooks<TConfig> {
|
||||
export const AutomationScriptEditorMixin = <TConfig extends BaseEditorConfig>(
|
||||
superClass: Constructor<LitElement>
|
||||
) => {
|
||||
class AutomationScriptEditorClass extends superClass {
|
||||
class AutomationScriptEditorClass extends DirtyStateProviderMixin<boolean>()(
|
||||
superClass
|
||||
) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||
@@ -144,8 +147,16 @@ export const AutomationScriptEditorMixin = <TConfig extends BaseEditorConfig>(
|
||||
|
||||
private _relatedContextAreaId?: string;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._initDirtyTracking({ type: "shallow" }, false);
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
if (changedProps.has("dirty")) {
|
||||
this._updateDirtyState(this.dirty);
|
||||
}
|
||||
if (
|
||||
changedProps.has("currentEntityId") ||
|
||||
changedProps.has("entityRegistry")
|
||||
@@ -237,10 +248,6 @@ export const AutomationScriptEditorMixin = <TConfig extends BaseEditorConfig>(
|
||||
}
|
||||
};
|
||||
|
||||
protected get isDirty() {
|
||||
return this.dirty;
|
||||
}
|
||||
|
||||
protected async promptDiscardChanges() {
|
||||
return this.confirmUnsavedChanged();
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
const IP_VERSIONS = ["ipv4", "ipv6"];
|
||||
@@ -57,15 +58,15 @@ const PREDEFINED_DNS = {
|
||||
};
|
||||
|
||||
@customElement("supervisor-network")
|
||||
export class HassioNetwork extends LitElement {
|
||||
export class HassioNetwork extends DirtyStateProviderMixin<NetworkInterface>()(
|
||||
LitElement
|
||||
) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _accessPoints: AccessPoint[] = [];
|
||||
|
||||
@state() private _curTabIndex = 0;
|
||||
|
||||
@state() private _dirty = false;
|
||||
|
||||
@state() private _interface?: NetworkInterface;
|
||||
|
||||
@state() private _interfaces!: NetworkInterface[];
|
||||
@@ -88,6 +89,7 @@ export class HassioNetwork extends LitElement {
|
||||
a.primary > b.primary ? -1 : 1
|
||||
);
|
||||
this._interface = { ...this._interfaces[this._curTabIndex] };
|
||||
this._initDirtyTracking({ type: "deep" }, this._interface);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -230,7 +232,7 @@ export class HassioNetwork extends LitElement {
|
||||
? this._renderIPConfiguration(version)
|
||||
: nothing
|
||||
)}
|
||||
${this._dirty
|
||||
${this.isDirtyState
|
||||
? html`<ha-alert alert-type="warning">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.network.supervisor.warning"
|
||||
@@ -242,7 +244,7 @@ export class HassioNetwork extends LitElement {
|
||||
<ha-button
|
||||
.loading=${this._processing}
|
||||
@click=${this._updateNetwork}
|
||||
.disabled=${!this._dirty}
|
||||
.disabled=${!this.isDirtyState}
|
||||
>
|
||||
${this.hass.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
@@ -254,12 +256,17 @@ export class HassioNetwork extends LitElement {
|
||||
|
||||
private _selectAP(event) {
|
||||
this._wifiConfiguration = event.currentTarget.ap;
|
||||
let iface = this._interface!;
|
||||
IP_VERSIONS.forEach((version) => {
|
||||
if (this._interface![version]!.method === "disabled") {
|
||||
this._interface![version]!.method = "auto";
|
||||
if (iface[version]!.method === "disabled") {
|
||||
iface = {
|
||||
...iface,
|
||||
[version]: { ...iface[version], method: "auto" },
|
||||
};
|
||||
}
|
||||
});
|
||||
this._dirty = true;
|
||||
this._interface = iface;
|
||||
this._updateDirtyState(this._interface);
|
||||
}
|
||||
|
||||
private async _scanForAP() {
|
||||
@@ -555,7 +562,7 @@ export class HassioNetwork extends LitElement {
|
||||
this._interface!.interface,
|
||||
interfaceOptions
|
||||
);
|
||||
this._dirty = false;
|
||||
this._markDirtyStateClean();
|
||||
await this._fetchNetworkInfo();
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
@@ -571,32 +578,38 @@ export class HassioNetwork extends LitElement {
|
||||
|
||||
private async _clear() {
|
||||
await this._fetchNetworkInfo();
|
||||
this._interface!.ipv4!.method = "auto";
|
||||
this._interface!.ipv4!.nameservers = [];
|
||||
this._interface!.ipv6!.method = "auto";
|
||||
this._interface!.ipv6!.nameservers = [];
|
||||
// removing the connection will disable the interface
|
||||
// this is the only way to forget the wifi network right now
|
||||
this._interface!.wifi = null;
|
||||
this._interface = {
|
||||
...this._interface!,
|
||||
ipv4: {
|
||||
...this._interface!.ipv4!,
|
||||
method: "auto",
|
||||
nameservers: [],
|
||||
},
|
||||
ipv6: {
|
||||
...this._interface!.ipv6!,
|
||||
method: "auto",
|
||||
nameservers: [],
|
||||
},
|
||||
wifi: null,
|
||||
};
|
||||
this._wifiConfiguration = undefined;
|
||||
this._dirty = true;
|
||||
this.requestUpdate("_interface");
|
||||
this._updateDirtyState(this._interface);
|
||||
}
|
||||
|
||||
private async _handleTabActivated(ev: CustomEvent): Promise<void> {
|
||||
if (this._dirty) {
|
||||
if (this.isDirtyState) {
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize("ui.panel.config.network.supervisor.unsaved"),
|
||||
confirmText: this.hass.localize("ui.common.yes"),
|
||||
dismissText: this.hass.localize("ui.common.no"),
|
||||
});
|
||||
if (!confirm) {
|
||||
this.requestUpdate("_interface");
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._curTabIndex = Number(ev.detail.name);
|
||||
this._interface = { ...this._interfaces[this._curTabIndex] };
|
||||
this._initDirtyTracking({ type: "deep" }, this._interface);
|
||||
}
|
||||
|
||||
private _handleRadioValueChanged(ev: Event): void {
|
||||
@@ -611,18 +624,19 @@ export class HassioNetwork extends LitElement {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this._dirty = true;
|
||||
|
||||
this._interface[version]!.method = value;
|
||||
this.requestUpdate("_interface");
|
||||
this._interface = {
|
||||
...this._interface,
|
||||
[version]: { ...this._interface[version], method: value },
|
||||
};
|
||||
this._updateDirtyState(this._interface);
|
||||
}
|
||||
|
||||
private _handleRadioValueChangedAp(ev: Event): void {
|
||||
const source = ev.currentTarget as HaRadioGroup;
|
||||
const value = source.value as "open" | "wep" | "wpa-psk";
|
||||
this._wifiConfiguration!.auth = value;
|
||||
this._dirty = true;
|
||||
this.requestUpdate("_wifiConfiguration");
|
||||
this._wifiConfiguration = { ...this._wifiConfiguration!, auth: value };
|
||||
this._updateDirtyState(this._interface!);
|
||||
}
|
||||
|
||||
private _handleInputValueChanged(ev: Event): void {
|
||||
@@ -636,35 +650,50 @@ export class HassioNetwork extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
this._dirty = true;
|
||||
const versionData = this._interface[version];
|
||||
if (id === "address") {
|
||||
const index = (ev.target as any).index as number;
|
||||
const { mask: oldMask } = parseAddress(
|
||||
this._interface[version].address![index]
|
||||
);
|
||||
const { mask: oldMask } = parseAddress(versionData.address![index]);
|
||||
const { mask } = parseAddress(value);
|
||||
this._interface[version].address![index] = formatAddress(
|
||||
value,
|
||||
mask || oldMask || ""
|
||||
);
|
||||
this.requestUpdate("_interface");
|
||||
const newAddress = [...versionData.address!];
|
||||
newAddress[index] = formatAddress(value, mask || oldMask || "");
|
||||
this._interface = {
|
||||
...this._interface,
|
||||
[version]: { ...versionData, address: newAddress },
|
||||
};
|
||||
} else if (id === "netmask") {
|
||||
const index = (ev.target as any).index as number;
|
||||
const { ip } = parseAddress(this._interface[version].address![index]);
|
||||
this._interface[version].address![index] = formatAddress(ip, value);
|
||||
this.requestUpdate("_interface");
|
||||
const { ip } = parseAddress(versionData.address![index]);
|
||||
const newAddress = [...versionData.address!];
|
||||
newAddress[index] = formatAddress(ip, value);
|
||||
this._interface = {
|
||||
...this._interface,
|
||||
[version]: { ...versionData, address: newAddress },
|
||||
};
|
||||
} else if (id === "prefix") {
|
||||
const index = (ev.target as any).index as number;
|
||||
const { ip } = parseAddress(this._interface[version].address![index]);
|
||||
this._interface[version].address![index] = `${ip}/${value}`;
|
||||
this.requestUpdate("_interface");
|
||||
const { ip } = parseAddress(versionData.address![index]);
|
||||
const newAddress = [...versionData.address!];
|
||||
newAddress[index] = `${ip}/${value}`;
|
||||
this._interface = {
|
||||
...this._interface,
|
||||
[version]: { ...versionData, address: newAddress },
|
||||
};
|
||||
} else if (id === "nameserver") {
|
||||
const index = (ev.target as any).index as number;
|
||||
this._interface[version].nameservers![index] = value;
|
||||
this.requestUpdate("_interface");
|
||||
const newNameservers = [...versionData.nameservers!];
|
||||
newNameservers[index] = value;
|
||||
this._interface = {
|
||||
...this._interface,
|
||||
[version]: { ...versionData, nameservers: newNameservers },
|
||||
};
|
||||
} else {
|
||||
this._interface[version][id] = value;
|
||||
this._interface = {
|
||||
...this._interface,
|
||||
[version]: { ...versionData, [id]: value },
|
||||
};
|
||||
}
|
||||
this._updateDirtyState(this._interface);
|
||||
}
|
||||
|
||||
private _handleInputValueChangedWifi(ev: Event): void {
|
||||
@@ -680,26 +709,35 @@ export class HassioNetwork extends LitElement {
|
||||
source.reportValidity();
|
||||
return;
|
||||
}
|
||||
this._dirty = true;
|
||||
this._wifiConfiguration![id] = value;
|
||||
this._wifiConfiguration = { ...this._wifiConfiguration, [id]: value };
|
||||
this._updateDirtyState(this._interface!);
|
||||
}
|
||||
|
||||
private _addAddress(ev: Event): void {
|
||||
const version = (ev.target as any).version as "ipv4" | "ipv6";
|
||||
this._interface![version]!.address!.push(
|
||||
version === "ipv4" ? "0.0.0.0/24" : "::/64"
|
||||
);
|
||||
this._dirty = true;
|
||||
this.requestUpdate("_interface");
|
||||
const newAddr = version === "ipv4" ? "0.0.0.0/24" : "::/64";
|
||||
this._interface = {
|
||||
...this._interface!,
|
||||
[version]: {
|
||||
...this._interface![version],
|
||||
address: [...this._interface![version]!.address!, newAddr],
|
||||
},
|
||||
};
|
||||
this._updateDirtyState(this._interface);
|
||||
}
|
||||
|
||||
private _removeAddress(ev: Event): void {
|
||||
const source = ev.target as any;
|
||||
const index = source.index as number;
|
||||
const version = source.version as "ipv4" | "ipv6";
|
||||
this._interface![version]!.address!.splice(index, 1);
|
||||
this._dirty = true;
|
||||
this.requestUpdate("_interface");
|
||||
const newAddress = this._interface![version]!.address!.filter(
|
||||
(_, i) => i !== index
|
||||
);
|
||||
this._interface = {
|
||||
...this._interface!,
|
||||
[version]: { ...this._interface![version], address: newAddress },
|
||||
};
|
||||
this._updateDirtyState(this._interface);
|
||||
}
|
||||
|
||||
private _handleDNSMenuOpened() {
|
||||
@@ -711,30 +749,41 @@ export class HassioNetwork extends LitElement {
|
||||
}
|
||||
|
||||
private _addPredefinedDNS(version: "ipv4" | "ipv6", addresses: string[]) {
|
||||
if (!this._interface![version]!.nameservers) {
|
||||
this._interface![version]!.nameservers = [];
|
||||
}
|
||||
this._interface![version]!.nameservers!.push(...addresses);
|
||||
this._dirty = true;
|
||||
this.requestUpdate("_interface");
|
||||
const existing = this._interface![version]!.nameservers || [];
|
||||
this._interface = {
|
||||
...this._interface!,
|
||||
[version]: {
|
||||
...this._interface![version],
|
||||
nameservers: [...existing, ...addresses],
|
||||
},
|
||||
};
|
||||
this._updateDirtyState(this._interface);
|
||||
}
|
||||
|
||||
private _addCustomDNS(version: "ipv4" | "ipv6") {
|
||||
if (!this._interface![version]!.nameservers) {
|
||||
this._interface![version]!.nameservers = [];
|
||||
}
|
||||
this._interface![version]!.nameservers!.push("");
|
||||
this._dirty = true;
|
||||
this.requestUpdate("_interface");
|
||||
const existing = this._interface![version]!.nameservers || [];
|
||||
this._interface = {
|
||||
...this._interface!,
|
||||
[version]: {
|
||||
...this._interface![version],
|
||||
nameservers: [...existing, ""],
|
||||
},
|
||||
};
|
||||
this._updateDirtyState(this._interface);
|
||||
}
|
||||
|
||||
private _removeNameserver(ev: Event): void {
|
||||
const source = ev.target as any;
|
||||
const index = source.index as number;
|
||||
const version = source.version as "ipv4" | "ipv6";
|
||||
this._interface![version]!.nameservers!.splice(index, 1);
|
||||
this._dirty = true;
|
||||
this.requestUpdate("_interface");
|
||||
const newNameservers = this._interface![version]!.nameservers!.filter(
|
||||
(_, i) => i !== index
|
||||
);
|
||||
this._interface = {
|
||||
...this._interface!,
|
||||
[version]: { ...this._interface![version], nameservers: newNameservers },
|
||||
};
|
||||
this._updateDirtyState(this._interface);
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
|
||||
|
||||
@@ -76,6 +76,7 @@ import {
|
||||
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
|
||||
import { PreventUnsavedMixin } from "../../../mixins/prevent-unsaved-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
@@ -97,8 +98,8 @@ interface DeviceEntities {
|
||||
type DeviceEntitiesLookup = Record<string, string[]>;
|
||||
|
||||
@customElement("ha-scene-editor")
|
||||
export class HaSceneEditor extends PreventUnsavedMixin(
|
||||
KeyboardShortcutMixin(LitElement)
|
||||
export class HaSceneEditor extends DirtyStateProviderMixin<boolean>()(
|
||||
PreventUnsavedMixin(KeyboardShortcutMixin(LitElement))
|
||||
) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -211,6 +212,7 @@ export class HaSceneEditor extends PreventUnsavedMixin(
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._initDirtyTracking({ type: "shallow" }, false);
|
||||
if (!this.sceneId) {
|
||||
this._mode = "live";
|
||||
this._subscribeEvents();
|
||||
@@ -603,6 +605,10 @@ export class HaSceneEditor extends PreventUnsavedMixin(
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (changedProps.has("_dirty")) {
|
||||
this._updateDirtyState(this._dirty);
|
||||
}
|
||||
|
||||
if (
|
||||
this._entityRegCreated &&
|
||||
this._newSceneId &&
|
||||
@@ -1322,10 +1328,6 @@ export class HaSceneEditor extends PreventUnsavedMixin(
|
||||
});
|
||||
}
|
||||
|
||||
protected get isDirty() {
|
||||
return this._dirty;
|
||||
}
|
||||
|
||||
protected async promptDiscardChanges() {
|
||||
return this._confirmUnsavedChanged();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../dialogs/generic/show-dialog-box";
|
||||
import { DirtyStateProviderMixin } from "../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { Lovelace } from "./types";
|
||||
@@ -33,7 +34,9 @@ const strategyStruct = type({
|
||||
});
|
||||
|
||||
@customElement("hui-editor")
|
||||
class LovelaceFullConfigEditor extends LitElement {
|
||||
class LovelaceFullConfigEditor extends DirtyStateProviderMixin<boolean>()(
|
||||
LitElement
|
||||
) {
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -44,8 +47,6 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
|
||||
@state() private _saving?: boolean;
|
||||
|
||||
@state() private _changed?: boolean;
|
||||
|
||||
private _config?: LovelaceRawConfig;
|
||||
|
||||
private _yamlError?: string;
|
||||
@@ -66,10 +67,10 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
slot="actionItems"
|
||||
class="save-button
|
||||
${classMap({
|
||||
saved: this._saving === false || this._changed === true,
|
||||
saved: this._saving === false || this.isDirtyState,
|
||||
})}"
|
||||
>
|
||||
${this._changed
|
||||
${this.isDirtyState
|
||||
? this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.raw_editor.unsaved_changes"
|
||||
)
|
||||
@@ -78,7 +79,7 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
<ha-button
|
||||
slot="actionItems"
|
||||
@click=${this._handleSave}
|
||||
.disabled=${!this._changed}
|
||||
.disabled=${!this.isDirtyState}
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.raw_editor.save"
|
||||
)}</ha-button
|
||||
@@ -96,6 +97,14 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._initDirtyTracking(
|
||||
{ type: "custom", compare: (a, b) => a === b },
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProps);
|
||||
this.yamlEditor.setValue(this.lovelace!.rawConfig);
|
||||
@@ -158,17 +167,18 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
private _yamlChanged(ev: CustomEvent) {
|
||||
this._config = ev.detail.isValid ? ev.detail.value : undefined;
|
||||
this._yamlError = ev.detail.errorMsg;
|
||||
this._changed = undoDepth(this.yamlEditor.codemirror!.state) > 0;
|
||||
if (this._changed && !window.onbeforeunload) {
|
||||
const changed = undoDepth(this.yamlEditor.codemirror!.state) > 0;
|
||||
this._updateDirtyState(changed);
|
||||
if (changed && !window.onbeforeunload) {
|
||||
window.onbeforeunload = () => true;
|
||||
} else if (!this._changed && window.onbeforeunload) {
|
||||
} else if (!changed && window.onbeforeunload) {
|
||||
window.onbeforeunload = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async _closeEditor() {
|
||||
if (
|
||||
this._changed &&
|
||||
this.isDirtyState &&
|
||||
!(await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.lovelace.editor.raw_editor.confirm_unsaved_changes"
|
||||
@@ -279,7 +289,7 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
});
|
||||
}
|
||||
window.onbeforeunload = null;
|
||||
this._changed = false;
|
||||
this._markDirtyStateClean();
|
||||
this._saving = false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user