From b065f002a4a12088587e9a515c239c4b40c6998c Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 4 Sep 2020 22:22:55 +0200 Subject: [PATCH] Allow local storage decorator to register as property (#6776) --- src/common/decorators/local-storage.ts | 113 ++++++++++++++++++++--- src/components/ha-form/ha-form-select.ts | 10 +- src/components/ha-sidebar.ts | 12 ++- 3 files changed, 114 insertions(+), 21 deletions(-) diff --git a/src/common/decorators/local-storage.ts b/src/common/decorators/local-storage.ts index 99cdebdf3d..d4034ae25f 100644 --- a/src/common/decorators/local-storage.ts +++ b/src/common/decorators/local-storage.ts @@ -1,7 +1,33 @@ +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { PropertyDeclaration, UpdatingElement } from "lit-element"; import type { ClassElement } from "../../types"; +type Callback = (oldValue: any, newValue: any) => void; + class Storage { - private _storage: any = {}; + constructor() { + window.addEventListener("storage", (ev: StorageEvent) => { + if (ev.key && this.hasKey(ev.key)) { + this._storage[ev.key] = ev.newValue + ? JSON.parse(ev.newValue) + : ev.newValue; + if (this._listeners[ev.key]) { + this._listeners[ev.key].forEach((listener) => + listener( + ev.oldValue ? JSON.parse(ev.oldValue) : ev.oldValue, + this._storage[ev.key!] + ) + ); + } + } + }); + } + + private _storage: { [storageKey: string]: any } = {}; + + private _listeners: { + [storageKey: string]: Callback[]; + } = {}; public addFromStorage(storageKey: any): void { if (!this._storage[storageKey]) { @@ -12,6 +38,30 @@ class Storage { } } + public subscribeChanges( + storageKey: string, + callback: Callback + ): UnsubscribeFunc { + if (this._listeners[storageKey]) { + this._listeners[storageKey].push(callback); + } else { + this._listeners[storageKey] = [callback]; + } + return () => { + this.unsubscribeChanges(storageKey, callback); + }; + } + + public unsubscribeChanges(storageKey: string, callback: Callback) { + if (!(storageKey in this._listeners)) { + return; + } + const index = this._listeners[storageKey].indexOf(callback); + if (index !== -1) { + this._listeners[storageKey].splice(index, 1); + } + } + public hasKey(storageKey: string): any { return storageKey in this._storage; } @@ -32,30 +82,49 @@ class Storage { const storage = new Storage(); -export const LocalStorage = (key?: string) => { - return (element: ClassElement, propName: string) => { - const storageKey = key || propName; - const initVal = element.initializer ? element.initializer() : undefined; +export const LocalStorage = ( + storageKey?: string, + property?: boolean, + propertyOptions?: PropertyDeclaration +): any => { + return (clsElement: ClassElement) => { + const key = String(clsElement.key); + storageKey = storageKey || String(clsElement.key); + const initVal = clsElement.initializer + ? clsElement.initializer() + : undefined; storage.addFromStorage(storageKey); + const subscribe = (el: UpdatingElement): UnsubscribeFunc => + storage.subscribeChanges(storageKey!, (oldValue) => { + el.requestUpdate(clsElement.key, oldValue); + }); + const getValue = (): any => { - return storage.hasKey(storageKey) - ? storage.getValue(storageKey) + return storage.hasKey(storageKey!) + ? storage.getValue(storageKey!) : initVal; }; - const setValue = (val: any) => { - storage.setValue(storageKey, val); + const setValue = (el: UpdatingElement, value: any) => { + let oldValue: unknown | undefined; + if (property) { + oldValue = getValue(); + } + storage.setValue(storageKey!, value); + if (property) { + el.requestUpdate(clsElement.key, oldValue); + } }; return { kind: "method", - placement: "own", - key: element.key, + placement: "prototype", + key: clsElement.key, descriptor: { - set(value) { - setValue(value); + set(this: UpdatingElement, value: unknown) { + setValue(this, value); }, get() { return getValue(); @@ -63,6 +132,24 @@ export const LocalStorage = (key?: string) => { enumerable: true, configurable: true, }, + finisher(cls: typeof UpdatingElement) { + if (property) { + const connectedCallback = cls.prototype.connectedCallback; + const disconnectedCallback = cls.prototype.disconnectedCallback; + cls.prototype.connectedCallback = function () { + connectedCallback.call(this); + this[`__unbsubLocalStorage${key}`] = subscribe(this); + }; + cls.prototype.disconnectedCallback = function () { + disconnectedCallback.call(this); + this[`__unbsubLocalStorage${key}`](); + }; + cls.createProperty(clsElement.key, { + noAccessor: true, + ...propertyOptions, + }); + } + }, }; }; }; diff --git a/src/components/ha-form/ha-form-select.ts b/src/components/ha-form/ha-form-select.ts index 2f3b82984c..359a06c5a8 100644 --- a/src/components/ha-form/ha-form-select.ts +++ b/src/components/ha-form/ha-form-select.ts @@ -1,4 +1,3 @@ -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; import { @@ -12,6 +11,7 @@ import { TemplateResult, } from "lit-element"; import { fireEvent } from "../../common/dom/fire_event"; +import "../ha-paper-dropdown-menu"; import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./ha-form"; @customElement("ha-form-select") @@ -24,7 +24,7 @@ export class HaFormSelect extends LitElement implements HaFormElement { @property() public suffix!: string; - @query("paper-dropdown-menu") private _input?: HTMLElement; + @query("ha-paper-dropdown-menu") private _input?: HTMLElement; public focus() { if (this._input) { @@ -34,7 +34,7 @@ export class HaFormSelect extends LitElement implements HaFormElement { protected render(): TemplateResult { return html` - + - + `; } @@ -74,7 +74,7 @@ export class HaFormSelect extends LitElement implements HaFormElement { static get styles(): CSSResult { return css` - paper-dropdown-menu { + ha-paper-dropdown-menu { display: block; } `; diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 9cfffb822c..12b1c17d39 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -191,11 +191,15 @@ class HaSidebar extends LitElement { private _recentKeydownActiveUntil = 0; // @ts-ignore - @LocalStorage("sidebarPanelOrder") + @LocalStorage("sidebarPanelOrder", true, { + attribute: false, + }) private _panelOrder: string[] = []; // @ts-ignore - @LocalStorage("sidebarHiddenPanels") + @LocalStorage("sidebarHiddenPanels", true, { + attribute: false, + }) private _hiddenPanels: string[] = []; private _sortable?; @@ -400,7 +404,9 @@ class HaSidebar extends LitElement { changedProps.has("_externalConfig") || changedProps.has("_notifications") || changedProps.has("_editMode") || - changedProps.has("_renderEmptySortable") + changedProps.has("_renderEmptySortable") || + changedProps.has("_hiddenPanels") || + (changedProps.has("_panelOrder") && !this._editMode) ) { return true; }