use buildKeyStorage to prevent different type for same key
This commit is contained in:
parent
62cec67983
commit
be27647ff7
@ -14,8 +14,13 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { useLocalStorage } from "@gnu-taler/web-util/browser";
|
||||
import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
|
||||
import { Settings, defaultSettings } from "../platform/api.js";
|
||||
import {
|
||||
Codec,
|
||||
buildCodecForObject,
|
||||
codecForBoolean,
|
||||
} from "@gnu-taler/taler-util";
|
||||
|
||||
function parse_json_or_undefined<T>(str: string | undefined): T | undefined {
|
||||
if (str === undefined) return undefined;
|
||||
@ -26,17 +31,30 @@ function parse_json_or_undefined<T>(str: string | undefined): T | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
export const codecForSettings = (): Codec<Settings> =>
|
||||
buildCodecForObject<Settings>()
|
||||
.property("walletAllowHttp", codecForBoolean())
|
||||
.property("walletBatchWithdrawal", codecForBoolean())
|
||||
.property("injectTalerSupport", codecForBoolean())
|
||||
.property("advanceMode", codecForBoolean())
|
||||
.property("backup", codecForBoolean())
|
||||
.property("langSelector", codecForBoolean())
|
||||
.property("showJsonOnError", codecForBoolean())
|
||||
.property("extendedAccountTypes", codecForBoolean())
|
||||
.property("deleteActiveTransactions", codecForBoolean())
|
||||
.build("Settings");
|
||||
|
||||
const SETTINGS_KEY = buildStorageKey("wallet-settings", codecForSettings());
|
||||
|
||||
export function useSettings(): [
|
||||
Readonly<Settings>,
|
||||
<T extends keyof Settings>(key: T, value: Settings[T]) => void,
|
||||
] {
|
||||
const { value, update } = useLocalStorage("wallet-settings");
|
||||
const { value, update } = useLocalStorage(SETTINGS_KEY);
|
||||
|
||||
const parsed: Settings = parse_json_or_undefined(value) ?? defaultSettings;
|
||||
const parsed: Settings = value ?? defaultSettings;
|
||||
function updateField<T extends keyof Settings>(k: T, v: Settings[T]) {
|
||||
const newValue = { ...parsed, [k]: v };
|
||||
const json = JSON.stringify(newValue);
|
||||
update(json);
|
||||
update({ ...parsed, [k]: v });
|
||||
}
|
||||
return [parsed, updateField];
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export { useLang } from "./useLang.js";
|
||||
export { useLocalStorage } from "./useLocalStorage.js";
|
||||
export { useLocalStorage, buildStorageKey } from "./useLocalStorage.js";
|
||||
export { useMemoryStorage } from "./useMemoryStorage.js";
|
||||
export {
|
||||
useNotifications,
|
||||
|
@ -14,7 +14,11 @@
|
||||
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { LocalStorageState, useLocalStorage } from "./useLocalStorage.js";
|
||||
import {
|
||||
StorageState,
|
||||
buildStorageKey,
|
||||
useLocalStorage,
|
||||
} from "./useLocalStorage.js";
|
||||
|
||||
function getBrowserLang(): string | undefined {
|
||||
if (typeof window === "undefined") return undefined;
|
||||
@ -23,7 +27,9 @@ function getBrowserLang(): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function useLang(initial?: string): Required<LocalStorageState> {
|
||||
const langPreferenceKey = buildStorageKey("lang-preference");
|
||||
|
||||
export function useLang(initial?: string): Required<StorageState> {
|
||||
const defaultValue = (getBrowserLang() || initial || "en").substring(0, 2);
|
||||
return useLocalStorage("lang-preference", { defaultValue: defaultValue });
|
||||
return useLocalStorage(langPreferenceKey, defaultValue);
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { Codec } from "@gnu-taler/taler-util";
|
||||
import { Codec, codecForString } from "@gnu-taler/taler-util";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import {
|
||||
ObservableMap,
|
||||
@ -28,7 +28,26 @@ import {
|
||||
memoryMap,
|
||||
} from "../utils/observable.js";
|
||||
|
||||
export interface LocalStorageState<Type = string> {
|
||||
declare const opaque_StorageKey: unique symbol;
|
||||
|
||||
export type StorageKey<Key> = {
|
||||
id: string;
|
||||
[opaque_StorageKey]: true;
|
||||
codec: Codec<Key>;
|
||||
};
|
||||
|
||||
export function buildStorageKey<Key = string>(
|
||||
name: string,
|
||||
codec?: Codec<Key>,
|
||||
): StorageKey<Key> {
|
||||
return {
|
||||
id: name,
|
||||
codec: codec ?? (codecForString() as Codec<Key>),
|
||||
[opaque_StorageKey]: true,
|
||||
};
|
||||
}
|
||||
|
||||
export interface StorageState<Type = string> {
|
||||
value?: Type;
|
||||
update: (s: Type) => void;
|
||||
reset: () => void;
|
||||
@ -50,59 +69,48 @@ const storage: ObservableMap<string, string> = (function buildStorage() {
|
||||
|
||||
//with initial value
|
||||
export function useLocalStorage<Type = string>(
|
||||
key: string,
|
||||
options?: {
|
||||
defaultValue: Type;
|
||||
codec?: Codec<Type>;
|
||||
},
|
||||
): Required<LocalStorageState<Type>>;
|
||||
key: StorageKey<Type>,
|
||||
defaultValue: Type,
|
||||
): Required<StorageState<Type>>;
|
||||
//without initial value
|
||||
export function useLocalStorage<Type = string>(
|
||||
key: string,
|
||||
options?: {
|
||||
codec?: Codec<Type>;
|
||||
},
|
||||
): LocalStorageState<Type>;
|
||||
key: StorageKey<Type>,
|
||||
): StorageState<Type>;
|
||||
// impl
|
||||
export function useLocalStorage<Type = string>(
|
||||
key: string,
|
||||
options?: {
|
||||
defaultValue?: Type;
|
||||
codec?: Codec<Type>;
|
||||
},
|
||||
): LocalStorageState<Type> {
|
||||
key: StorageKey<Type>,
|
||||
defaultValue?: Type,
|
||||
): StorageState<Type> {
|
||||
function convert(updated: string | undefined): Type | undefined {
|
||||
if (updated === undefined) return options?.defaultValue; //optional
|
||||
if (updated === undefined) return defaultValue; //optional
|
||||
try {
|
||||
return !options?.codec
|
||||
? (updated as Type)
|
||||
: options.codec.decode(JSON.parse(updated));
|
||||
return key.codec.decode(JSON.parse(updated));
|
||||
} catch (e) {
|
||||
//decode error
|
||||
return options?.defaultValue;
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
const [storedValue, setStoredValue] = useState<Type | undefined>(
|
||||
(): Type | undefined => {
|
||||
const prev = storage.get(key);
|
||||
const prev = storage.get(key.id);
|
||||
return convert(prev);
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return storage.onUpdate(key, () => {
|
||||
const newValue = storage.get(key);
|
||||
return storage.onUpdate(key.id, () => {
|
||||
const newValue = storage.get(key.id);
|
||||
setStoredValue(convert(newValue));
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setValue = (value?: Type): void => {
|
||||
if (value === undefined) {
|
||||
storage.delete(key);
|
||||
storage.delete(key.id);
|
||||
} else {
|
||||
storage.set(
|
||||
key,
|
||||
options?.codec ? JSON.stringify(value) : (value as string),
|
||||
key.id,
|
||||
key.codec ? JSON.stringify(value) : (value as string),
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -111,7 +119,7 @@ export function useLocalStorage<Type = string>(
|
||||
value: storedValue,
|
||||
update: setValue,
|
||||
reset: () => {
|
||||
setValue(options?.defaultValue);
|
||||
setValue(defaultValue);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -20,48 +20,44 @@
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import {
|
||||
ObservableMap,
|
||||
browserStorageMap,
|
||||
localStorageMap,
|
||||
memoryMap,
|
||||
} from "../utils/observable.js";
|
||||
import { ObservableMap, memoryMap } from "../utils/observable.js";
|
||||
import { StorageKey, StorageState } from "./useLocalStorage.js";
|
||||
|
||||
export interface LocalStorageState {
|
||||
value?: string;
|
||||
update: (s: string) => void;
|
||||
reset: () => void;
|
||||
}
|
||||
const storage: ObservableMap<string, any> = memoryMap<any>();
|
||||
|
||||
const storage: ObservableMap<string, string> = memoryMap<string>();
|
||||
|
||||
export function useMemoryStorage(
|
||||
key: string,
|
||||
initialValue: string,
|
||||
): Required<LocalStorageState>;
|
||||
export function useMemoryStorage(key: string): LocalStorageState;
|
||||
export function useMemoryStorage(
|
||||
key: string,
|
||||
initialValue?: string,
|
||||
): LocalStorageState {
|
||||
const [storedValue, setStoredValue] = useState<string | undefined>(
|
||||
(): string | undefined => {
|
||||
return storage.get(key) ?? initialValue;
|
||||
//with initial value
|
||||
export function useMemoryStorage<Type = string>(
|
||||
key: StorageKey<Type>,
|
||||
defaultValue: Type,
|
||||
): Required<StorageState<Type>>;
|
||||
//with initial value
|
||||
export function useMemoryStorage<Type = string>(
|
||||
key: StorageKey<Type>,
|
||||
): StorageState<Type>;
|
||||
// impl
|
||||
export function useMemoryStorage<Type = string>(
|
||||
key: StorageKey<Type>,
|
||||
defaultValue?: Type,
|
||||
): StorageState<Type> {
|
||||
const [storedValue, setStoredValue] = useState<Type | undefined>(
|
||||
(): Type | undefined => {
|
||||
const prev = storage.get(key.id);
|
||||
return prev;
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return storage.onUpdate(key, () => {
|
||||
const newValue = storage.get(key);
|
||||
setStoredValue(newValue ?? initialValue);
|
||||
return storage.onUpdate(key.id, () => {
|
||||
const newValue = storage.get(key.id);
|
||||
setStoredValue(newValue);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setValue = (value?: string): void => {
|
||||
const setValue = (value?: Type): void => {
|
||||
if (value === undefined) {
|
||||
storage.delete(key);
|
||||
storage.delete(key.id);
|
||||
} else {
|
||||
storage.set(key, value);
|
||||
storage.set(key.id, value);
|
||||
}
|
||||
};
|
||||
|
||||
@ -69,7 +65,7 @@ export function useMemoryStorage(
|
||||
value: storedValue,
|
||||
update: setValue,
|
||||
reset: () => {
|
||||
setValue(initialValue);
|
||||
setValue(defaultValue);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user