diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts index 40c2a678f0..91cc8a9efd 100644 --- a/src/panels/lovelace/ha-panel-lovelace.ts +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -324,6 +324,8 @@ export class LovelacePanel extends LitElement { urlPath: this.urlPath, editMode: this.lovelace ? this.lovelace.editMode : false, locale: this.hass!.locale, + configHistory: this.lovelace ? this.lovelace.configHistory : [config], + configHistoryIndex: this.lovelace ? this.lovelace.configHistoryIndex : 0, enableFullEditMode: () => { if (!editorLoaded) { editorLoaded = true; @@ -358,6 +360,8 @@ export class LovelacePanel extends LitElement { config: previousConfig, rawConfig: previousRawConfig, mode: previousMode, + configHistory: previousConfigHistory, + configHistoryIndex: previousConfigHistoryIndex, } = this.lovelace!; newConfig = this._checkLovelaceConfig(newConfig); let conf: LovelaceConfig; @@ -372,11 +376,22 @@ export class LovelacePanel extends LitElement { conf = newConfig; } try { + const newConfigHistory = [...previousConfigHistory]; + let newConfigHistoryIndex = previousConfigHistoryIndex; + + if (previousConfigHistoryIndex !== 0) { + newConfigHistory.splice(0, previousConfigHistoryIndex); + newConfigHistoryIndex = 0; + } + newConfigHistory.unshift(newConfig); + // Optimistic update this._updateLovelace({ config: conf, rawConfig: newConfig, mode: "storage", + configHistory: newConfigHistory, + configHistoryIndex: newConfigHistoryIndex, }); this._ignoreNextUpdateEvent = true; await saveConfig(this.hass!, urlPath, newConfig); @@ -388,6 +403,7 @@ export class LovelacePanel extends LitElement { config: previousConfig, rawConfig: previousRawConfig, mode: previousMode, + configHistory: previousConfigHistory, }); throw err; } @@ -427,6 +443,57 @@ export class LovelacePanel extends LitElement { throw err; } }, + restoreConfigFromHistory: async ( + configHistoryIndex: number + ): Promise => { + const { + configHistory, + configHistoryIndex: previousConfigHistoryIndex, + config: previousConfig, + rawConfig: previousRawConfig, + } = this.lovelace!; + + if ( + previousConfigHistoryIndex === configHistoryIndex || + configHistoryIndex < 0 || + configHistoryIndex >= configHistory.length + ) { + return; + } + + let conf: LovelaceConfig; + const newConfig = configHistory[configHistoryIndex]; + + if (newConfig.strategy) { + conf = await generateLovelaceDashboardStrategy({ + config: newConfig, + hass: this.hass!, + narrow: this.narrow, + }); + } else { + conf = newConfig; + } + try { + // Optimistic update + this._updateLovelace({ + config: conf, + rawConfig: newConfig, + configHistoryIndex: configHistoryIndex, + }); + this._ignoreNextUpdateEvent = true; + await saveConfig(this.hass!, urlPath, newConfig); + } catch (err: any) { + // eslint-disable-next-line + console.error(err); + // Rollback the optimistic update + this._updateLovelace({ + config: previousConfig, + rawConfig: previousRawConfig, + configHistoryIndex: previousConfigHistoryIndex, + }); + throw err; + } + }, }; } diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index e9423fb81a..1416e4b73d 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -12,8 +12,10 @@ import { mdiMagnify, mdiPencil, mdiPlus, + mdiRedo, mdiRefresh, mdiShape, + mdiUndo, mdiViewDashboard, } from "@mdi/js"; import "@polymer/paper-tabs/paper-tab"; @@ -106,12 +108,31 @@ class HUIRoot extends LitElement { ); } + private _undo() { + if (this.lovelace) { + this.lovelace.restoreConfigFromHistory( + this.lovelace.configHistoryIndex + 1 + ); + } + } + + private _redo() { + if (this.lovelace) { + this.lovelace.restoreConfigFromHistory( + this.lovelace.configHistoryIndex - 1 + ); + } + } + protected render(): TemplateResult { const views = this.lovelace?.config.views ?? []; const curViewConfig = typeof this._curView === "number" ? views[this._curView] : undefined; + const historyTotal = this.lovelace?.configHistory.length ?? 0 - 1; + const historyIndex = this.lovelace?.configHistoryIndex ?? 0; + return html`
+ = historyTotal} + @click=${this._undo} + > + void; saveConfig: (newConfig: LovelaceConfig) => Promise; deleteConfig: () => Promise; + configHistory: LovelaceConfig[]; + configHistoryIndex: number; + restoreConfigFromHistory: (index: number) => Promise; } export interface LovelaceBadge extends HTMLElement {