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/>
|
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 { 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 {
|
function parse_json_or_undefined<T>(str: string | undefined): T | undefined {
|
||||||
if (str === undefined) return 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(): [
|
export function useSettings(): [
|
||||||
Readonly<Settings>,
|
Readonly<Settings>,
|
||||||
<T extends keyof Settings>(key: T, value: Settings[T]) => void,
|
<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]) {
|
function updateField<T extends keyof Settings>(k: T, v: Settings[T]) {
|
||||||
const newValue = { ...parsed, [k]: v };
|
update({ ...parsed, [k]: v });
|
||||||
const json = JSON.stringify(newValue);
|
|
||||||
update(json);
|
|
||||||
}
|
}
|
||||||
return [parsed, updateField];
|
return [parsed, updateField];
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export { useLang } from "./useLang.js";
|
export { useLang } from "./useLang.js";
|
||||||
export { useLocalStorage } from "./useLocalStorage.js";
|
export { useLocalStorage, buildStorageKey } from "./useLocalStorage.js";
|
||||||
export { useMemoryStorage } from "./useMemoryStorage.js";
|
export { useMemoryStorage } from "./useMemoryStorage.js";
|
||||||
export {
|
export {
|
||||||
useNotifications,
|
useNotifications,
|
||||||
|
@ -14,7 +14,11 @@
|
|||||||
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
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 {
|
function getBrowserLang(): string | undefined {
|
||||||
if (typeof window === "undefined") return undefined;
|
if (typeof window === "undefined") return undefined;
|
||||||
@ -23,7 +27,9 @@ function getBrowserLang(): string | undefined {
|
|||||||
return 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);
|
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)
|
* @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 { useEffect, useState } from "preact/hooks";
|
||||||
import {
|
import {
|
||||||
ObservableMap,
|
ObservableMap,
|
||||||
@ -28,7 +28,26 @@ import {
|
|||||||
memoryMap,
|
memoryMap,
|
||||||
} from "../utils/observable.js";
|
} 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;
|
value?: Type;
|
||||||
update: (s: Type) => void;
|
update: (s: Type) => void;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
@ -50,59 +69,48 @@ const storage: ObservableMap<string, string> = (function buildStorage() {
|
|||||||
|
|
||||||
//with initial value
|
//with initial value
|
||||||
export function useLocalStorage<Type = string>(
|
export function useLocalStorage<Type = string>(
|
||||||
key: string,
|
key: StorageKey<Type>,
|
||||||
options?: {
|
defaultValue: Type,
|
||||||
defaultValue: Type;
|
): Required<StorageState<Type>>;
|
||||||
codec?: Codec<Type>;
|
|
||||||
},
|
|
||||||
): Required<LocalStorageState<Type>>;
|
|
||||||
//without initial value
|
//without initial value
|
||||||
export function useLocalStorage<Type = string>(
|
export function useLocalStorage<Type = string>(
|
||||||
key: string,
|
key: StorageKey<Type>,
|
||||||
options?: {
|
): StorageState<Type>;
|
||||||
codec?: Codec<Type>;
|
|
||||||
},
|
|
||||||
): LocalStorageState<Type>;
|
|
||||||
// impl
|
// impl
|
||||||
export function useLocalStorage<Type = string>(
|
export function useLocalStorage<Type = string>(
|
||||||
key: string,
|
key: StorageKey<Type>,
|
||||||
options?: {
|
defaultValue?: Type,
|
||||||
defaultValue?: Type;
|
): StorageState<Type> {
|
||||||
codec?: Codec<Type>;
|
|
||||||
},
|
|
||||||
): LocalStorageState<Type> {
|
|
||||||
function convert(updated: string | undefined): Type | undefined {
|
function convert(updated: string | undefined): Type | undefined {
|
||||||
if (updated === undefined) return options?.defaultValue; //optional
|
if (updated === undefined) return defaultValue; //optional
|
||||||
try {
|
try {
|
||||||
return !options?.codec
|
return key.codec.decode(JSON.parse(updated));
|
||||||
? (updated as Type)
|
|
||||||
: options.codec.decode(JSON.parse(updated));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//decode error
|
//decode error
|
||||||
return options?.defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const [storedValue, setStoredValue] = useState<Type | undefined>(
|
const [storedValue, setStoredValue] = useState<Type | undefined>(
|
||||||
(): Type | undefined => {
|
(): Type | undefined => {
|
||||||
const prev = storage.get(key);
|
const prev = storage.get(key.id);
|
||||||
return convert(prev);
|
return convert(prev);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return storage.onUpdate(key, () => {
|
return storage.onUpdate(key.id, () => {
|
||||||
const newValue = storage.get(key);
|
const newValue = storage.get(key.id);
|
||||||
setStoredValue(convert(newValue));
|
setStoredValue(convert(newValue));
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setValue = (value?: Type): void => {
|
const setValue = (value?: Type): void => {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
storage.delete(key);
|
storage.delete(key.id);
|
||||||
} else {
|
} else {
|
||||||
storage.set(
|
storage.set(
|
||||||
key,
|
key.id,
|
||||||
options?.codec ? JSON.stringify(value) : (value as string),
|
key.codec ? JSON.stringify(value) : (value as string),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -111,7 +119,7 @@ export function useLocalStorage<Type = string>(
|
|||||||
value: storedValue,
|
value: storedValue,
|
||||||
update: setValue,
|
update: setValue,
|
||||||
reset: () => {
|
reset: () => {
|
||||||
setValue(options?.defaultValue);
|
setValue(defaultValue);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -20,48 +20,44 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import {
|
import { ObservableMap, memoryMap } from "../utils/observable.js";
|
||||||
ObservableMap,
|
import { StorageKey, StorageState } from "./useLocalStorage.js";
|
||||||
browserStorageMap,
|
|
||||||
localStorageMap,
|
|
||||||
memoryMap,
|
|
||||||
} from "../utils/observable.js";
|
|
||||||
|
|
||||||
export interface LocalStorageState {
|
const storage: ObservableMap<string, any> = memoryMap<any>();
|
||||||
value?: string;
|
|
||||||
update: (s: string) => void;
|
|
||||||
reset: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const storage: ObservableMap<string, string> = memoryMap<string>();
|
//with initial value
|
||||||
|
export function useMemoryStorage<Type = string>(
|
||||||
export function useMemoryStorage(
|
key: StorageKey<Type>,
|
||||||
key: string,
|
defaultValue: Type,
|
||||||
initialValue: string,
|
): Required<StorageState<Type>>;
|
||||||
): Required<LocalStorageState>;
|
//with initial value
|
||||||
export function useMemoryStorage(key: string): LocalStorageState;
|
export function useMemoryStorage<Type = string>(
|
||||||
export function useMemoryStorage(
|
key: StorageKey<Type>,
|
||||||
key: string,
|
): StorageState<Type>;
|
||||||
initialValue?: string,
|
// impl
|
||||||
): LocalStorageState {
|
export function useMemoryStorage<Type = string>(
|
||||||
const [storedValue, setStoredValue] = useState<string | undefined>(
|
key: StorageKey<Type>,
|
||||||
(): string | undefined => {
|
defaultValue?: Type,
|
||||||
return storage.get(key) ?? initialValue;
|
): StorageState<Type> {
|
||||||
|
const [storedValue, setStoredValue] = useState<Type | undefined>(
|
||||||
|
(): Type | undefined => {
|
||||||
|
const prev = storage.get(key.id);
|
||||||
|
return prev;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return storage.onUpdate(key, () => {
|
return storage.onUpdate(key.id, () => {
|
||||||
const newValue = storage.get(key);
|
const newValue = storage.get(key.id);
|
||||||
setStoredValue(newValue ?? initialValue);
|
setStoredValue(newValue);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setValue = (value?: string): void => {
|
const setValue = (value?: Type): void => {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
storage.delete(key);
|
storage.delete(key.id);
|
||||||
} else {
|
} else {
|
||||||
storage.set(key, value);
|
storage.set(key.id, value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -69,7 +65,7 @@ export function useMemoryStorage(
|
|||||||
value: storedValue,
|
value: storedValue,
|
||||||
update: setValue,
|
update: setValue,
|
||||||
reset: () => {
|
reset: () => {
|
||||||
setValue(initialValue);
|
setValue(defaultValue);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user