Refactor Storage decorator (#16987)

This commit is contained in:
Bram Kragten 2023-06-21 17:22:10 +02:00 committed by GitHub
parent 752bc192cd
commit 7faa165558
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 187 additions and 79 deletions

View File

@ -1,13 +1,15 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { PropertyDeclaration, ReactiveElement } from "lit";
import { ReactiveElement } from "lit";
import { InternalPropertyDeclaration } from "lit/decorators";
import type { ClassElement } from "../../types";
type Callback = (oldValue: any, newValue: any) => void;
class Storage {
constructor(subscribe = true, storage = window.localStorage) {
class StorageClass {
constructor(storage = window.localStorage) {
this.storage = storage;
if (!subscribe) {
if (storage !== window.localStorage) {
// storage events only work for localStorage
return;
}
window.addEventListener("storage", (ev: StorageEvent) => {
@ -77,6 +79,7 @@ class Storage {
}
public setValue(storageKey: string, value: any): any {
const oldValue = this._storage[storageKey];
this._storage[storageKey] = value;
try {
if (value === undefined) {
@ -86,49 +89,68 @@ class Storage {
}
} catch (err: any) {
// Safari in private mode doesn't allow localstorage
} finally {
if (this._listeners[storageKey]) {
this._listeners[storageKey].forEach((listener) =>
listener(oldValue, value)
);
}
}
}
}
const subscribeStorage = new Storage();
const storages: Record<string, StorageClass> = {};
export const LocalStorage =
(
storageKey?: string,
property?: boolean,
subscribe = true,
storageType?: globalThis.Storage,
propertyOptions?: PropertyDeclaration
): any =>
export const storage =
(options: {
key?: string;
storage?: "localStorage" | "sessionStorage";
subscribe?: boolean;
state?: boolean;
stateOptions?: InternalPropertyDeclaration;
}): any =>
(clsElement: ClassElement) => {
const storage =
subscribe && !storageType
? subscribeStorage
: new Storage(subscribe, storageType);
const storageName = options.storage || "localStorage";
let storageInstance: StorageClass;
if (storageName && storageName in storages) {
storageInstance = storages[storageName];
} else {
storageInstance = new StorageClass(window[storageName]);
storages[storageName] = storageInstance;
}
const key = String(clsElement.key);
storageKey = storageKey || String(clsElement.key);
const storageKey = options.key || String(clsElement.key);
const initVal = clsElement.initializer
? clsElement.initializer()
: undefined;
storage.addFromStorage(storageKey);
storageInstance.addFromStorage(storageKey);
const subscribeChanges = (el: ReactiveElement): UnsubscribeFunc =>
storage.subscribeChanges(storageKey!, (oldValue) => {
el.requestUpdate(clsElement.key, oldValue);
});
const subscribeChanges =
options.subscribe !== false
? (el: ReactiveElement): UnsubscribeFunc =>
storageInstance.subscribeChanges(
storageKey!,
(oldValue, _newValue) => {
el.requestUpdate(clsElement.key, oldValue);
}
)
: undefined;
const getValue = (): any =>
storage.hasKey(storageKey!) ? storage.getValue(storageKey!) : initVal;
storageInstance.hasKey(storageKey!)
? storageInstance.getValue(storageKey!)
: initVal;
const setValue = (el: ReactiveElement, value: any) => {
let oldValue: unknown | undefined;
if (property) {
if (options.state) {
oldValue = getValue();
}
storage.setValue(storageKey!, value);
if (property) {
storageInstance.setValue(storageKey!, value);
if (options.state) {
el.requestUpdate(clsElement.key, oldValue);
}
};
@ -148,22 +170,23 @@ export const LocalStorage =
configurable: true,
},
finisher(cls: typeof ReactiveElement) {
if (property && subscribe) {
if (options.state && options.subscribe) {
const connectedCallback = cls.prototype.connectedCallback;
const disconnectedCallback = cls.prototype.disconnectedCallback;
cls.prototype.connectedCallback = function () {
connectedCallback.call(this);
this[`__unbsubLocalStorage${key}`] = subscribeChanges(this);
this[`__unbsubLocalStorage${key}`] = subscribeChanges?.(this);
};
cls.prototype.disconnectedCallback = function () {
disconnectedCallback.call(this);
this[`__unbsubLocalStorage${key}`]();
this[`__unbsubLocalStorage${key}`]?.();
this[`__unbsubLocalStorage${key}`] = undefined;
};
}
if (property) {
if (options.state) {
cls.createProperty(clsElement.key, {
noAccessor: true,
...propertyOptions,
...options.stateOptions,
});
}
},

View File

@ -23,19 +23,19 @@ import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
css,
CSSResult,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
css,
html,
nothing,
} from "lit";
import { customElement, eventOptions, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { guard } from "lit/directives/guard";
import memoizeOne from "memoize-one";
import { LocalStorage } from "../common/decorators/local-storage";
import { storage } from "../common/decorators/storage";
import { fireEvent } from "../common/dom/fire_event";
import { toggleAttribute } from "../common/dom/toggle_attribute";
import { stringCompare } from "../common/string/compare";
@ -47,10 +47,10 @@ import {
subscribeNotifications,
} from "../data/persistent_notification";
import { subscribeRepairsIssueRegistry } from "../data/repairs";
import { updateCanInstall, UpdateEntity } from "../data/update";
import { UpdateEntity, updateCanInstall } from "../data/update";
import { SubscribeMixin } from "../mixins/subscribe-mixin";
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
import { loadSortable, SortableInstance } from "../resources/sortable.ondemand";
import { SortableInstance, loadSortable } from "../resources/sortable.ondemand";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant, PanelInfo, Route } from "../types";
import "./ha-icon";
@ -214,15 +214,17 @@ class HaSidebar extends SubscribeMixin(LitElement) {
private sortableStyleLoaded = false;
// @ts-ignore
@LocalStorage("sidebarPanelOrder", true, {
attribute: false,
@storage({
key: "sidebarPanelOrder",
state: true,
subscribe: true,
})
private _panelOrder: string[] = [];
// @ts-ignore
@LocalStorage("sidebarHiddenPanels", true, {
attribute: false,
@storage({
key: "sidebarHiddenPanels",
state: true,
subscribe: true,
})
private _hiddenPanels: string[] = [];

View File

@ -1,7 +1,7 @@
import "@material/mwc-list/mwc-list-item";
import { css, html, LitElement, nothing, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { LocalStorage } from "../../common/decorators/local-storage";
import { storage } from "../../common/decorators/storage";
import { fireEvent } from "../../common/dom/fire_event";
import {
MediaPlayerBrowseAction,
@ -43,7 +43,12 @@ class BrowseMediaTTS extends LitElement {
@state() private _provider?: TTSEngine;
@LocalStorage("TtsMessage", true, false) private _message!: string;
@storage({
key: "TtsMessage",
state: true,
subscribe: false,
})
private _message!: string;
protected render() {
return html`<ha-card>

View File

@ -1,7 +1,7 @@
import { mdiPlayCircleOutline } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { LocalStorage } from "../../common/decorators/local-storage";
import { storage } from "../../common/decorators/storage";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-button";
import { createCloseHeading } from "../../components/ha-dialog";
@ -25,10 +25,12 @@ export class TTSTryDialog extends LitElement {
@query("#message") private _messageInput?: HaTextArea;
@LocalStorage("ttsTryMessages", false, false) private _messages?: Record<
string,
string
>;
@storage({
key: "ttsTryMessages",
state: false,
subscribe: false,
})
private _messages?: Record<string, string>;
public showDialog(params: TTSTryDialogParams) {
this._params = params;

View File

@ -18,7 +18,7 @@ import {
TemplateResult,
} from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { LocalStorage } from "../../common/decorators/local-storage";
import { storage } from "../../common/decorators/storage";
import { fireEvent } from "../../common/dom/fire_event";
import { stopPropagation } from "../../common/dom/stop_propagation";
import "../../components/ha-button";
@ -57,7 +57,12 @@ export class HaVoiceCommandDialog extends LitElement {
@state() private _opened = false;
@LocalStorage("AssistPipelineId", true, false) private _pipelineId?: string;
@storage({
key: "AssistPipelineId",
state: true,
subscribe: false,
})
private _pipelineId?: string;
@state() private _pipeline?: AssistPipeline;

View File

@ -11,7 +11,7 @@ import {
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { LocalStorage } from "../../common/decorators/local-storage";
import { storage } from "../../common/decorators/storage";
import { HASSDomEvent } from "../../common/dom/fire_event";
import { computeStateName } from "../../common/entity/compute_state_name";
import "../../components/ha-card";
@ -41,7 +41,10 @@ class PanelCalendar extends LitElement {
@state() private _error?: string = undefined;
@LocalStorage("deSelectedCalendars", true)
@storage({
key: "deSelectedCalendars",
state: true,
})
private _deSelectedCalendars: string[] = [];
private _start?: Date;

View File

@ -20,7 +20,7 @@ import { documentationUrl } from "../../../util/documentation-url";
import "./action/ha-automation-action";
import "./condition/ha-automation-condition";
import "./trigger/ha-automation-trigger";
import { LocalStorage } from "../../../common/decorators/local-storage";
import { storage } from "../../../common/decorators/storage";
@customElement("manual-automation-editor")
export class HaManualAutomationEditor extends LitElement {
@ -36,7 +36,12 @@ export class HaManualAutomationEditor extends LitElement {
@property({ attribute: false }) public stateObj?: HassEntity;
@LocalStorage("automationClipboard", true, false, window.sessionStorage)
@storage({
key: "automationClipboard",
state: true,
subscribe: false,
storage: "sessionStorage",
})
private _clipboard: Clipboard = {};
protected render() {

View File

@ -3,7 +3,7 @@ import "@material/mwc-list/mwc-list-item";
import { mdiPlayCircleOutline, mdiRobot } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { LocalStorage } from "../../../../common/decorators/local-storage";
import { storage } from "../../../../common/decorators/storage";
import { fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import { computeStateDomain } from "../../../../common/entity/compute_state_domain";
@ -31,9 +31,19 @@ export class DialogTryTts extends LitElement {
@query("#message") private _messageInput?: HaTextArea;
@LocalStorage("cloudTtsTryMessage", false, false) private _message!: string;
@storage({
key: "cloudTtsTryMessage",
state: false,
subscribe: false,
})
private _message!: string;
@LocalStorage("cloudTtsTryTarget", false, false) private _target!: string;
@storage({
key: "cloudTtsTryTarget",
state: false,
subscribe: false,
})
private _target!: string;
public showDialog(params: TryTtsDialogParams) {
this._params = params;

View File

@ -1,7 +1,7 @@
import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { LocalStorage } from "../../../../../common/decorators/local-storage";
import { storage } from "../../../../../common/decorators/storage";
import "../../../../../components/ha-card";
import "../../../../../components/ha-code-editor";
import "../../../../../components/ha-formfield";
@ -21,19 +21,39 @@ class HaPanelDevMqtt extends LitElement {
@property({ type: Boolean }) public narrow!: boolean;
@LocalStorage("panel-dev-mqtt-topic-ls", true, false)
@storage({
key: "panel-dev-mqtt-topic-ls",
state: true,
subscribe: false,
})
private _topic = "";
@LocalStorage("panel-dev-mqtt-payload-ls", true, false)
@storage({
key: "panel-dev-mqtt-payload-ls",
state: true,
subscribe: false,
})
private _payload = "";
@LocalStorage("panel-dev-mqtt-qos-ls", true, false)
@storage({
key: "panel-dev-mqtt-qos-ls",
state: true,
subscribe: false,
})
private _qos = "0";
@LocalStorage("panel-dev-mqtt-retain-ls", true, false)
@storage({
key: "panel-dev-mqtt-retain-ls",
state: true,
subscribe: false,
})
private _retain = false;
@LocalStorage("panel-dev-mqtt-allow-template-ls", true, false)
@storage({
key: "panel-dev-mqtt-allow-template-ls",
state: true,
subscribe: false,
})
private _allowTemplate = false;
protected render(): TemplateResult {

View File

@ -8,7 +8,7 @@ import { formatTime } from "../../../../../common/datetime/format_time";
import { MQTTMessage, subscribeMQTTTopic } from "../../../../../data/mqtt";
import { HomeAssistant } from "../../../../../types";
import "@material/mwc-list/mwc-list-item";
import { LocalStorage } from "../../../../../common/decorators/local-storage";
import { storage } from "../../../../../common/decorators/storage";
import "../../../../../components/ha-formfield";
import "../../../../../components/ha-switch";
@ -18,13 +18,25 @@ const qosLevel = ["0", "1", "2"];
class MqttSubscribeCard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@LocalStorage("panel-dev-mqtt-topic-subscribe", true, false)
@storage({
key: "panel-dev-mqtt-topic-subscribe",
state: true,
subscribe: false,
})
private _topic = "";
@LocalStorage("panel-dev-mqtt-qos-subscribe", true, false)
@storage({
key: "panel-dev-mqtt-qos-subscribe",
state: true,
subscribe: false,
})
private _qos = "0";
@LocalStorage("panel-dev-mqtt-json-format", true, false)
@storage({
key: "panel-dev-mqtt-json-format",
state: true,
subscribe: false,
})
private _json_format = false;
@state() private _subscribed?: () => void;

View File

@ -3,7 +3,7 @@ import { mdiHelpCircle } from "@mdi/js";
import deepClone from "deep-clone-simple";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { LocalStorage } from "../../../common/decorators/local-storage";
import { storage } from "../../../common/decorators/storage";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
@ -26,7 +26,12 @@ export class HaManualScriptEditor extends LitElement {
@property({ attribute: false }) public config!: ScriptConfig;
@LocalStorage("automationClipboard", true, false, window.sessionStorage)
@storage({
key: "automationClipboard",
state: true,
subscribe: false,
storage: "sessionStorage",
})
private _clipboard: Clipboard = {};
protected render() {

View File

@ -4,7 +4,7 @@ import { load } from "js-yaml";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { LocalStorage } from "../../../common/decorators/local-storage";
import { storage } from "../../../common/decorators/storage";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeObjectId } from "../../../common/entity/compute_object_id";
import { hasTemplate } from "../../../common/string/has-template";
@ -38,10 +38,18 @@ class HaPanelDevService extends LitElement {
@state() private _uiAvailable = true;
@LocalStorage("panel-dev-service-state-service-data", true, false)
@storage({
key: "panel-dev-service-state-service-data",
state: true,
subscribe: false,
})
private _serviceData?: ServiceAction = { service: "", target: {}, data: {} };
@LocalStorage("panel-dev-service-state-yaml-mode", true, false)
@storage({
key: "panel-dev-service-state-yaml-mode",
state: true,
subscribe: false,
})
private _yamlMode = false;
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;

View File

@ -7,7 +7,7 @@ import {
import { css, html, LitElement, PropertyValues } from "lit";
import { property, query, state } from "lit/decorators";
import { ensureArray } from "../../common/array/ensure-array";
import { LocalStorage } from "../../common/decorators/local-storage";
import { storage } from "../../common/decorators/storage";
import { navigate } from "../../common/navigate";
import { constructUrlCurrentPath } from "../../common/url/construct-url";
import {
@ -58,7 +58,11 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
@state() private _endDate: Date;
@LocalStorage("historyPickedValue", true, false)
@storage({
key: "historyPickedValue",
state: true,
subscribe: false,
})
private _targetPickerValue?: HassServiceTarget;
@state() private _isLoading = false;

View File

@ -9,7 +9,7 @@ import {
TemplateResult,
} from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { LocalStorage } from "../../common/decorators/local-storage";
import { storage } from "../../common/decorators/storage";
import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event";
import { navigate } from "../../common/navigate";
import "../../components/ha-menu-button";
@ -71,7 +71,11 @@ class PanelMediaBrowser extends LitElement {
},
];
@LocalStorage("mediaBrowseEntityId", true, false)
@storage({
key: "mediaBrowseEntityId",
state: true,
subscribe: false,
})
private _entityId = BROWSER_PLAYER;
@query("ha-media-player-browse") private _browser!: HaMediaPlayerBrowse;