From 706454736165a403f678f3b5572f416d21d20557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr> Date: Tue, 9 Jun 2020 23:00:00 +0200 Subject: [PATCH] [dome] JSON encoders --- ivette/src/dome/src/renderer/data/json.ts | 113 +++++++++++++++++++- ivette/src/dome/src/renderer/data/states.ts | 40 +++++-- 2 files changed, 139 insertions(+), 14 deletions(-) diff --git a/ivette/src/dome/src/renderer/data/json.ts b/ivette/src/dome/src/renderer/data/json.ts index b384fff7271..666a1ba172c 100644 --- a/ivette/src/dome/src/renderer/data/json.ts +++ b/ivette/src/dome/src/renderer/data/json.ts @@ -40,7 +40,7 @@ export function stringify(js: any) { } /** - Export JSON (or any data) as a string with indented content. + Export JSON (or any data) as a string with indentation. */ export function pretty(js: any) { return JSON.stringify(js, undefined, 2); @@ -189,6 +189,38 @@ export function jTry<A>(fn: Loose<A>, defaultValue?: A): Loose<A> { }; } +/** + Converts maps to dictionnaries. + */ +export function jMap<A>(fn: Loose<A>): Safe<Map<string, A>> { + return (js: json) => { + const m = new Map<string, A>(); + if (js !== null && typeof js === 'object' && !Array.isArray(js)) { + for (let k of Object.keys(js)) { + const v = fn(js[k]); + if (v !== undefined) m.set(k, v); + } + } + return m; + }; +} + +/** + Converts dictionnaries to maps. + */ +export function eMap<A>(fn: Encoder<A>): Encoder<Map<string, undefined | A>> { + return m => { + const js: json = {}; + m.forEach((v, k) => { + if (v !== undefined) { + const u = fn(v); + if (u !== undefined) js[k] = u; + } + }); + return js; + }; +} + /** Apply the decoder on each item of a JSON array, or return `[]` otherwize. Can be also applied on a _loose_ decoder, but you will get @@ -215,6 +247,22 @@ export function jList<A>(fn: Loose<A>): Safe<A[]> { }; } +/** + Exports all non-undefined elements. + */ +export function eList<A>(fn: Encoder<A>): Encoder<(A | undefined)[]> { + return m => { + const js: json[] = []; + m.forEach(v => { + if (v !== undefined) { + const u = fn(v); + if (u !== undefined) js.push(u); + } + }); + return js; + }; +} + /** Apply a pair of decoders to JSON pairs, or return `undefined`. */ export function jPair<A, B>( fa: Safe<A>, @@ -289,8 +337,13 @@ export function jObject<A>(fp: Props<A>): Loose<A> { const buffer = {} as A; for (var k of Object.keys(fp)) { const fn = fp[k as keyof A]; - const fv = fn(js[k]); - buffer[k as keyof A] = fv; + if (fn !== undefined) { + const fj = js[k]; + if (fj !== undefined) { + const fv = fn(fj); + if (fv !== undefined) buffer[k as keyof A] = fv; + } + } } return buffer; } @@ -298,6 +351,35 @@ export function jObject<A>(fp: Props<A>): Loose<A> { }; } +/** + Encoders for each property of object type `A`. +*/ +export type EProps<A> = { + [P in keyof A]?: Encoder<A[P]>; +} + +/** + Encode an object given the provided encoders by fields. + The exported JSON object has only original + fields with some specified encoder. + */ +export function eObject<A>(fp: EProps<A>): Encoder<A> { + return (m: A) => { + const js: json = {}; + for (var k of Object.keys(fp)) { + const fn = fp[k as keyof A]; + if (fn !== undefined) { + const fv = m[k as keyof A]; + if (fv !== undefined) { + const r = fn(fv); + if (r !== undefined) js[k] = r; + } + } + } + return js; + } +} + /** Type of dictionaries. */ export type dict<A> = { [key: string]: A }; @@ -310,12 +392,33 @@ export function jDictionary<A>(fn: Loose<A>): Safe<dict<A>> { const buffer: dict<A> = {}; if (js !== null && typeof js === 'object' && !Array.isArray(js)) { for (var k of Object.keys(js)) { - const fv = fn(js[k]); - if (fv) buffer[k] = fv; + const fd = js[k]; + if (fd !== undefined) { + const fv = fn(fd); + if (fv !== undefined) buffer[k] = fv; + } } } return buffer; }; } +/** + Encode a dictionary into JSON, dicarding all inconsistent entries. + If the dictionary contains no valid entry, still returns `{}`. +*/ +export function eDictionary<A>(fn: Encoder<A>): Encoder<dict<A>> { + return (d: dict<A>) => { + const js: json = {}; + for (var k of Object.keys(d)) { + const fv = d[k]; + if (fv !== undefined) { + const fv = fn(d[k]); + if (fv !== undefined) js[k] = fv; + } + } + return js; + }; +} + // -------------------------------------------------------------------------- diff --git a/ivette/src/dome/src/renderer/data/states.ts b/ivette/src/dome/src/renderer/data/states.ts index a552a336413..b530d1ce269 100644 --- a/ivette/src/dome/src/renderer/data/states.ts +++ b/ivette/src/dome/src/renderer/data/states.ts @@ -119,10 +119,12 @@ abstract class Settings<A> { private readonly role: symbol; protected readonly decoder: JSON.Safe<A>; + protected readonly encoder: JSON.Encoder<A>; - constructor(role: string, decoder: JSON.Safe<A>) { + constructor(role: string, decoder: JSON.Safe<A>, encoder: JSON.Encoder<A>) { this.role = Symbol(role); this.decoder = decoder; + this.encoder = encoder; } validateKey(k?: string): string | undefined { @@ -150,9 +152,13 @@ abstract class Settings<A> { return this.decoder(key ? this.loadData(key) : undefined) } + saveValue(key: string, value: A) { + this.saveData(key, this.encoder(value)); + } + } -export function useSettings<A extends JSON.json>( +export function useSettings<A>( S: Settings<A>, dataKey?: string, ): [A, (update: A) => void] { @@ -172,7 +178,7 @@ export function useSettings<A extends JSON.json>( const updateValue = React.useCallback((update: A) => { if (!isEqual(value, update)) { setValue(update); - if (theKey) S.saveData(theKey, update); + if (theKey) S.saveValue(theKey, update); } }, [S, theKey]); @@ -180,10 +186,10 @@ export function useSettings<A extends JSON.json>( } -export class WindowSettings<A> extends Settings<A> { +export class WindowSettingsData<A> extends Settings<A> { - constructor(role: string, decoder: JSON.Safe<A>) { - super(role, decoder); + constructor(role: string, decoder: JSON.Safe<A>, encoder: JSON.Encoder<A>) { + super(role, decoder, encoder); } event = Symbol('dome.settings'); @@ -192,10 +198,10 @@ export class WindowSettings<A> extends Settings<A> { } -export class GlobalSettings<A> extends Settings<A> { +export class GlobalSettingsData<A> extends Settings<A> { - constructor(role: string, decoder: JSON.Safe<A>) { - super(role, decoder); + constructor(role: string, decoder: JSON.Safe<A>, encoder: JSON.Encoder<A>) { + super(role, decoder, encoder); } event = Symbol('dome.globals'); @@ -204,4 +210,20 @@ export class GlobalSettings<A> extends Settings<A> { } +export class WindowSettings<A extends JSON.json> extends WindowSettingsData<A> { + + constructor(role: string, decoder: JSON.Safe<A>) { + super(role, decoder, JSON.identity); + } + +} + +export class GlobalSettings<A extends JSON.json> extends GlobalSettingsData<A> { + + constructor(role: string, decoder: JSON.Safe<A>) { + super(role, decoder, JSON.identity); + } + +} + // -------------------------------------------------------------------------- -- GitLab