mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Refactor Storage decorator (#16987)
This commit is contained in:
parent
752bc192cd
commit
7faa165558
@ -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,
|
||||
});
|
||||
}
|
||||
},
|
@ -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[] = [];
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user