aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-04-14 13:07:23 -0300
committerSebastian <sebasjm@gmail.com>2023-04-14 14:16:24 -0300
commitc3e1a0bb519bf5012781891c15c433841203bce2 (patch)
tree621348beccb0b38cf8069db1374debc96a076b4a /packages
parent665adb69f065f5d371c7ce71b0bdd32c23a600a4 (diff)
downloadwallet-core-c3e1a0bb519bf5012781891c15c433841203bce2.tar.xz
observable memory impl
Diffstat (limited to 'packages')
-rw-r--r--packages/web-util/src/context/translation.ts6
-rw-r--r--packages/web-util/src/hooks/index.ts2
-rw-r--r--packages/web-util/src/hooks/useLang.ts9
-rw-r--r--packages/web-util/src/hooks/useLocalStorage.ts105
-rw-r--r--packages/web-util/src/index.browser.ts1
-rw-r--r--packages/web-util/src/utils/observable.ts181
6 files changed, 223 insertions, 81 deletions
diff --git a/packages/web-util/src/context/translation.ts b/packages/web-util/src/context/translation.ts
index 3b79e31d3..53ca87f9d 100644
--- a/packages/web-util/src/context/translation.ts
+++ b/packages/web-util/src/context/translation.ts
@@ -26,7 +26,6 @@ interface Type {
supportedLang: { [id in keyof typeof supportedLang]: string };
changeLanguage: (l: string) => void;
i18n: InternationalizationAPI;
- isSaved: boolean;
}
const supportedLang = {
@@ -46,7 +45,6 @@ const initial = {
// do not change anything
},
i18n,
- isSaved: false,
};
const Context = createContext<Type>(initial);
@@ -64,7 +62,7 @@ export const TranslationProvider = ({
forceLang,
source,
}: Props): VNode => {
- const [lang, changeLanguage, isSaved] = useLang(initial);
+ const { value: lang, update: changeLanguage } = useLang(initial);
useEffect(() => {
if (forceLang) {
changeLanguage(forceLang);
@@ -80,7 +78,7 @@ export const TranslationProvider = ({
}
return h(Context.Provider, {
- value: { lang, changeLanguage, supportedLang, i18n, isSaved },
+ value: { lang, changeLanguage, supportedLang, i18n },
children,
});
};
diff --git a/packages/web-util/src/hooks/index.ts b/packages/web-util/src/hooks/index.ts
index 393a6fcbb..e5cb54e21 100644
--- a/packages/web-util/src/hooks/index.ts
+++ b/packages/web-util/src/hooks/index.ts
@@ -1,5 +1,5 @@
export { useLang } from "./useLang.js";
-export { useLocalStorage, useNotNullLocalStorage } from "./useLocalStorage.js";
+export { useLocalStorage } from "./useLocalStorage.js";
export {
useAsyncAsHook,
HookError,
diff --git a/packages/web-util/src/hooks/useLang.ts b/packages/web-util/src/hooks/useLang.ts
index 5b02c5255..9888cc51a 100644
--- a/packages/web-util/src/hooks/useLang.ts
+++ b/packages/web-util/src/hooks/useLang.ts
@@ -14,17 +14,16 @@
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { useNotNullLocalStorage } from "./useLocalStorage.js";
+import { LocalStorageState, useLocalStorage } from "./useLocalStorage.js";
function getBrowserLang(): string | undefined {
+ if (typeof window === "undefined") return undefined;
if (window.navigator.languages) return window.navigator.languages[0];
if (window.navigator.language) return window.navigator.language;
return undefined;
}
-export function useLang(
- initial?: string,
-): [string, (s: string) => void, boolean] {
+export function useLang(initial?: string): Required<LocalStorageState> {
const defaultLang = (getBrowserLang() || initial || "en").substring(0, 2);
- return useNotNullLocalStorage("lang-preference", defaultLang);
+ return useLocalStorage("lang-preference", defaultLang);
}
diff --git a/packages/web-util/src/hooks/useLocalStorage.ts b/packages/web-util/src/hooks/useLocalStorage.ts
index ab786db13..264919d37 100644
--- a/packages/web-util/src/hooks/useLocalStorage.ts
+++ b/packages/web-util/src/hooks/useLocalStorage.ts
@@ -19,92 +19,55 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { StateUpdater, useEffect, useState } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
+import { localStorageMap, memoryMap } from "../utils/observable.js";
+export interface LocalStorageState {
+ value?: string;
+ update: (s: string) => void;
+ reset: () => void;
+}
+
+const supportLocalStorage = typeof window !== "undefined";
+
+const storage = supportLocalStorage ? localStorageMap() : memoryMap<string>();
+
+export function useLocalStorage(
+ key: string,
+ initialValue: string,
+): Required<LocalStorageState>;
+export function useLocalStorage(key: string): LocalStorageState;
export function useLocalStorage(
key: string,
initialValue?: string,
-): [string | undefined, StateUpdater<string | undefined>] {
+): LocalStorageState {
const [storedValue, setStoredValue] = useState<string | undefined>(
(): string | undefined => {
- return typeof window !== "undefined"
- ? window.localStorage.getItem(key) || initialValue
- : initialValue;
+ return storage.get(key) ?? initialValue;
},
);
useEffect(() => {
- const listener = buildListenerForKey(key, (newValue) => {
+ return storage.onUpdate(key, () => {
+ const newValue = storage.get(key);
+ console.log("new value", key, newValue);
setStoredValue(newValue ?? initialValue);
});
- window.addEventListener("storage", listener);
- return () => {
- window.removeEventListener("storage", listener);
- };
}, []);
- const setValue = (
- value?: string | ((val?: string) => string | undefined),
- ): void => {
- setStoredValue((p) => {
- const toStore = value instanceof Function ? value(p) : value;
- if (typeof window !== "undefined") {
- if (!toStore) {
- window.localStorage.removeItem(key);
- } else {
- window.localStorage.setItem(key, toStore);
- }
- }
- return toStore;
- });
- };
-
- return [storedValue, setValue];
-}
-
-function buildListenerForKey(
- key: string,
- onUpdate: (newValue: string | undefined) => void,
-): () => void {
- return function listenKeyChange() {
- const value = window.localStorage.getItem(key);
- onUpdate(value ?? undefined);
- };
-}
-
-//TODO: merge with the above function
-export function useNotNullLocalStorage(
- key: string,
- initialValue: string,
-): [string, StateUpdater<string>, boolean] {
- const [storedValue, setStoredValue] = useState<string>((): string => {
- return typeof window !== "undefined"
- ? window.localStorage.getItem(key) || initialValue
- : initialValue;
- });
-
- useEffect(() => {
- const listener = buildListenerForKey(key, (newValue) => {
- setStoredValue(newValue ?? initialValue);
- });
- window.addEventListener("storage", listener);
- return () => {
- window.removeEventListener("storage", listener);
- };
- });
-
- const setValue = (value: string | ((val: string) => string)): void => {
- const valueToStore = value instanceof Function ? value(storedValue) : value;
- setStoredValue(valueToStore);
- if (typeof window !== "undefined") {
- if (!valueToStore) {
- window.localStorage.removeItem(key);
- } else {
- window.localStorage.setItem(key, valueToStore);
- }
+ const setValue = (value?: string): void => {
+ if (!value) {
+ storage.delete(key);
+ } else {
+ storage.set(key, value);
}
};
- const isSaved = window.localStorage.getItem(key) !== null;
- return [storedValue, setValue, isSaved];
+ return {
+ value: storedValue,
+ update: setValue,
+ reset: () => {
+ setValue(initialValue);
+ },
+ };
}
diff --git a/packages/web-util/src/index.browser.ts b/packages/web-util/src/index.browser.ts
index 2ae3f2a0b..b1df2f96e 100644
--- a/packages/web-util/src/index.browser.ts
+++ b/packages/web-util/src/index.browser.ts
@@ -1,5 +1,6 @@
export * from "./hooks/index.js";
export * from "./utils/request.js";
+export * from "./utils/observable.js";
export * from "./context/index.js";
export * from "./components/index.js";
export * as tests from "./tests/index.js";
diff --git a/packages/web-util/src/utils/observable.ts b/packages/web-util/src/utils/observable.ts
new file mode 100644
index 000000000..dfa434635
--- /dev/null
+++ b/packages/web-util/src/utils/observable.ts
@@ -0,0 +1,181 @@
+export type ObservableMap<K, V> = Map<K, V> & {
+ onUpdate: (key: string, callback: () => void) => () => void;
+};
+
+const UPDATE_EVENT_NAME = "update";
+
+//FIXME: allow different type for different properties
+export function memoryMap<T>(): ObservableMap<string, T> {
+ const obs = new EventTarget();
+ const theMap = new Map<string, T>();
+ const theMemoryMap: ObservableMap<string, T> = {
+ onUpdate: (key, handler) => {
+ //@ts-ignore
+ theMemoryMap.size = theMap.length;
+ obs.addEventListener(`update-${key}`, handler);
+ obs.addEventListener(`clear`, handler);
+ return () => {
+ obs.removeEventListener(`update-${key}`, handler);
+ obs.removeEventListener(`clear`, handler);
+ };
+ },
+ delete: (key: string) => {
+ const result = theMap.delete(key);
+ obs.dispatchEvent(new Event(`update-${key}`));
+ return result;
+ },
+ set: (key: string, value: T) => {
+ theMap.set(key, value);
+ obs.dispatchEvent(new Event(`update-${key}`));
+ return theMemoryMap;
+ },
+ clear: () => {
+ theMap.clear();
+ obs.dispatchEvent(new Event(`clear`));
+ },
+ entries: theMap.entries.bind(theMap),
+ forEach: theMap.forEach.bind(theMap),
+ get: theMap.get.bind(theMap),
+ has: theMap.has.bind(theMap),
+ keys: theMap.keys.bind(theMap),
+ size: theMap.size,
+ values: theMap.values.bind(theMap),
+ [Symbol.iterator]: theMap[Symbol.iterator],
+ [Symbol.toStringTag]: "theMemoryMap",
+ };
+ return theMemoryMap;
+}
+
+export function localStorageMap(): ObservableMap<string, string> {
+ const obs = new EventTarget();
+ const theLocalStorageMap: ObservableMap<string, string> = {
+ onUpdate: (key, handler) => {
+ //@ts-ignore
+ theLocalStorageMap.size = localStorage.length;
+ obs.addEventListener(`update-${key}`, handler);
+ obs.addEventListener(`clear`, handler);
+ function handleStorageEvent(ev: StorageEvent) {
+ if (ev.key === null || ev.key === key) {
+ handler();
+ }
+ }
+ window.addEventListener("storage", handleStorageEvent);
+ return () => {
+ window.removeEventListener("storage", handleStorageEvent);
+ obs.removeEventListener(`update-${key}`, handler);
+ obs.removeEventListener(`clear`, handler);
+ };
+ },
+ delete: (key: string) => {
+ const exists = localStorage.getItem(key) !== null;
+ localStorage.removeItem(key);
+ obs.dispatchEvent(new Event(`update-${key}`));
+ return exists;
+ },
+ set: (key: string, v: string) => {
+ localStorage.setItem(key, v);
+ obs.dispatchEvent(new Event(`update-${key}`));
+ return theLocalStorageMap;
+ },
+ clear: () => {
+ localStorage.clear();
+ obs.dispatchEvent(new Event(`clear`));
+ },
+ entries: (): IterableIterator<[string, string]> => {
+ let index = 0;
+ const total = localStorage.length;
+ return {
+ next() {
+ const key = localStorage.key(index);
+ if (key === null) {
+ //we are going from 0 until last, this should not happen
+ throw Error("key cant be null");
+ }
+ const item = localStorage.getItem(key);
+ if (item === null) {
+ //the key exist, this should not happen
+ throw Error("value cant be null");
+ }
+ if (index == total) return { done: true, value: [key, item] };
+ index = index + 1;
+ return { done: false, value: [key, item] };
+ },
+ [Symbol.iterator]() {
+ return this;
+ },
+ };
+ },
+ forEach: (cb) => {
+ for (let index = 0; index < localStorage.length; index++) {
+ const key = localStorage.key(index);
+ if (key === null) {
+ //we are going from 0 until last, this should not happen
+ throw Error("key cant be null");
+ }
+ const item = localStorage.getItem(key);
+ if (item === null) {
+ //the key exist, this should not happen
+ throw Error("value cant be null");
+ }
+ cb(key, item, theLocalStorageMap);
+ }
+ },
+ get: (key: string) => {
+ const item = localStorage.getItem(key);
+ if (item === null) return undefined;
+ return item;
+ },
+ has: (key: string) => {
+ return localStorage.getItem(key) === null;
+ },
+ keys: () => {
+ let index = 0;
+ const total = localStorage.length;
+ return {
+ next() {
+ const key = localStorage.key(index);
+ if (key === null) {
+ //we are going from 0 until last, this should not happen
+ throw Error("key cant be null");
+ }
+ if (index == total) return { done: true, value: key };
+ index = index + 1;
+ return { done: false, value: key };
+ },
+ [Symbol.iterator]() {
+ return this;
+ },
+ };
+ },
+ size: localStorage.length,
+ values: () => {
+ let index = 0;
+ const total = localStorage.length;
+ return {
+ next() {
+ const key = localStorage.key(index);
+ if (key === null) {
+ //we are going from 0 until last, this should not happen
+ throw Error("key cant be null");
+ }
+ const item = localStorage.getItem(key);
+ if (item === null) {
+ //the key exist, this should not happen
+ throw Error("value cant be null");
+ }
+ if (index == total) return { done: true, value: item };
+ index = index + 1;
+ return { done: false, value: item };
+ },
+ [Symbol.iterator]() {
+ return this;
+ },
+ };
+ },
+ [Symbol.iterator]: function (): IterableIterator<[string, string]> {
+ return theLocalStorageMap.entries();
+ },
+ [Symbol.toStringTag]: "theLocalStorageMap",
+ };
+ return theLocalStorageMap;
+}