Allow local storage decorator to register as property (#6776)

This commit is contained in:
Bram Kragten 2020-09-04 22:22:55 +02:00 committed by GitHub
parent 349a5f52b1
commit b065f002a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 114 additions and 21 deletions

View File

@ -1,7 +1,33 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { PropertyDeclaration, UpdatingElement } from "lit-element";
import type { ClassElement } from "../../types"; import type { ClassElement } from "../../types";
type Callback = (oldValue: any, newValue: any) => void;
class Storage { 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 { public addFromStorage(storageKey: any): void {
if (!this._storage[storageKey]) { 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 { public hasKey(storageKey: string): any {
return storageKey in this._storage; return storageKey in this._storage;
} }
@ -32,30 +82,49 @@ class Storage {
const storage = new Storage(); const storage = new Storage();
export const LocalStorage = (key?: string) => { export const LocalStorage = (
return (element: ClassElement, propName: string) => { storageKey?: string,
const storageKey = key || propName; property?: boolean,
const initVal = element.initializer ? element.initializer() : undefined; 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); storage.addFromStorage(storageKey);
const subscribe = (el: UpdatingElement): UnsubscribeFunc =>
storage.subscribeChanges(storageKey!, (oldValue) => {
el.requestUpdate(clsElement.key, oldValue);
});
const getValue = (): any => { const getValue = (): any => {
return storage.hasKey(storageKey) return storage.hasKey(storageKey!)
? storage.getValue(storageKey) ? storage.getValue(storageKey!)
: initVal; : initVal;
}; };
const setValue = (val: any) => { const setValue = (el: UpdatingElement, value: any) => {
storage.setValue(storageKey, val); let oldValue: unknown | undefined;
if (property) {
oldValue = getValue();
}
storage.setValue(storageKey!, value);
if (property) {
el.requestUpdate(clsElement.key, oldValue);
}
}; };
return { return {
kind: "method", kind: "method",
placement: "own", placement: "prototype",
key: element.key, key: clsElement.key,
descriptor: { descriptor: {
set(value) { set(this: UpdatingElement, value: unknown) {
setValue(value); setValue(this, value);
}, },
get() { get() {
return getValue(); return getValue();
@ -63,6 +132,24 @@ export const LocalStorage = (key?: string) => {
enumerable: true, enumerable: true,
configurable: 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,
});
}
},
}; };
}; };
}; };

View File

@ -1,4 +1,3 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
@ -12,6 +11,7 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import "../ha-paper-dropdown-menu";
import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./ha-form"; import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./ha-form";
@customElement("ha-form-select") @customElement("ha-form-select")
@ -24,7 +24,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
@property() public suffix!: string; @property() public suffix!: string;
@query("paper-dropdown-menu") private _input?: HTMLElement; @query("ha-paper-dropdown-menu") private _input?: HTMLElement;
public focus() { public focus() {
if (this._input) { if (this._input) {
@ -34,7 +34,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<paper-dropdown-menu .label=${this.label}> <ha-paper-dropdown-menu .label=${this.label}>
<paper-listbox <paper-listbox
slot="dropdown-content" slot="dropdown-content"
attr-for-selected="item-value" attr-for-selected="item-value"
@ -51,7 +51,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
` `
)} )}
</paper-listbox> </paper-listbox>
</paper-dropdown-menu> </ha-paper-dropdown-menu>
`; `;
} }
@ -74,7 +74,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
paper-dropdown-menu { ha-paper-dropdown-menu {
display: block; display: block;
} }
`; `;

View File

@ -191,11 +191,15 @@ class HaSidebar extends LitElement {
private _recentKeydownActiveUntil = 0; private _recentKeydownActiveUntil = 0;
// @ts-ignore // @ts-ignore
@LocalStorage("sidebarPanelOrder") @LocalStorage("sidebarPanelOrder", true, {
attribute: false,
})
private _panelOrder: string[] = []; private _panelOrder: string[] = [];
// @ts-ignore // @ts-ignore
@LocalStorage("sidebarHiddenPanels") @LocalStorage("sidebarHiddenPanels", true, {
attribute: false,
})
private _hiddenPanels: string[] = []; private _hiddenPanels: string[] = [];
private _sortable?; private _sortable?;
@ -400,7 +404,9 @@ class HaSidebar extends LitElement {
changedProps.has("_externalConfig") || changedProps.has("_externalConfig") ||
changedProps.has("_notifications") || changedProps.has("_notifications") ||
changedProps.has("_editMode") || changedProps.has("_editMode") ||
changedProps.has("_renderEmptySortable") changedProps.has("_renderEmptySortable") ||
changedProps.has("_hiddenPanels") ||
(changedProps.has("_panelOrder") && !this._editMode)
) { ) {
return true; return true;
} }