ballerina-core 1.0.30 → 1.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/main.ts +2 -0
- package/package.json +1 -1
- package/src/async/domains/mirroring/domains/entity/domains/loaded-collection-entity/state.ts +2 -1
- package/src/async/domains/mirroring/domains/entity/domains/loaded-entity/state.ts +2 -1
- package/src/debounced/coroutines/debounce.ts +72 -36
- package/src/debounced/state.ts +5 -4
- package/src/forms/domains/launcher/domains/edit/coroutines/runner.ts +1 -1
- package/src/forms/domains/launcher/domains/edit/state.ts +2 -2
- package/src/forms/domains/parser/domains/built-ins/state.ts +111 -33
- package/src/forms/domains/parser/domains/types/state.ts +17 -5
- package/src/forms/domains/parser/domains/validator/state.ts +121 -70
- package/src/forms/domains/parser/state.tsx +83 -15
- package/src/forms/domains/primitives/domains/map/state.ts +55 -0
- package/src/forms/domains/primitives/domains/map/template.tsx +114 -0
package/main.ts
CHANGED
|
@@ -91,6 +91,8 @@ export * from "./src/forms/domains/primitives/domains/searchable-infinite-stream
|
|
|
91
91
|
export * from "./src/forms/domains/primitives/domains/searchable-infinite-stream-multiselect/template"
|
|
92
92
|
export * from "./src/forms/domains/primitives/domains/list/state"
|
|
93
93
|
export * from "./src/forms/domains/primitives/domains/list/template"
|
|
94
|
+
export * from "./src/forms/domains/primitives/domains/map/state"
|
|
95
|
+
export * from "./src/forms/domains/primitives/domains/map/template"
|
|
94
96
|
export * from "./src/forms/domains/singleton/domains/mapping/state"
|
|
95
97
|
export * from "./src/forms/domains/singleton/domains/mapping/template"
|
|
96
98
|
export * from "./src/forms/domains/parser/state"
|
package/package.json
CHANGED
package/src/async/domains/mirroring/domains/entity/domains/loaded-collection-entity/state.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Sum } from "../../../../../../../../main";
|
|
1
2
|
import { DebouncedStatus, DirtyStatus } from "../../../../../../../debounced/state";
|
|
2
3
|
import { AsyncState } from "../../../../../../state";
|
|
3
4
|
import { CollectionEntity } from "../../../collection/state";
|
|
@@ -7,7 +8,7 @@ export type LoadedCollectionEntity<E> = LoadedEntity<E> & {
|
|
|
7
8
|
isRemoved:boolean;
|
|
8
9
|
lastUpdated: number;
|
|
9
10
|
dirty: DirtyStatus;
|
|
10
|
-
status: DebouncedStatus
|
|
11
|
+
status: Sum<DebouncedStatus, "debug off">;
|
|
11
12
|
};
|
|
12
13
|
export const LoadedCollectionEntity = {
|
|
13
14
|
Default:{
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Sum } from "../../../../../../../../main";
|
|
1
2
|
import { DebouncedStatus, DirtyStatus } from "../../../../../../../debounced/state";
|
|
2
3
|
import { AsyncState } from "../../../../../../state";
|
|
3
4
|
import { Entity } from "../../state";
|
|
@@ -10,7 +11,7 @@ export type LoadedEntity<E> = {
|
|
|
10
11
|
synchronizationErrors:Array<any>;
|
|
11
12
|
lastUpdated: number;
|
|
12
13
|
dirty: DirtyStatus;
|
|
13
|
-
status: DebouncedStatus
|
|
14
|
+
status: Sum<DebouncedStatus, "debug off">;
|
|
14
15
|
};
|
|
15
16
|
export const LoadedEntity = {
|
|
16
17
|
Default:{
|
|
@@ -26,11 +26,18 @@ export const Debounce = <value, context = Unit>(
|
|
|
26
26
|
Co.SetState(
|
|
27
27
|
updaters.Core.dirty(replaceWith<DirtyStatus>("dirty but being processed"))
|
|
28
28
|
),
|
|
29
|
-
Co.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
Co.GetState().then((current) => {
|
|
30
|
+
if (current.status.kind == "l") {
|
|
31
|
+
return Co.SetState(
|
|
32
|
+
updaters.Core.status(
|
|
33
|
+
replaceWith<DebouncedStatus>(
|
|
34
|
+
"just detected dirty, starting processing"
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
else return Co.Do(() => {});
|
|
40
|
+
}),
|
|
34
41
|
Co.Any([
|
|
35
42
|
// shortcircuit the validation if it takes longer than the whole cycle in the presence of an underwater update of the field
|
|
36
43
|
Co.Seq([
|
|
@@ -40,22 +47,36 @@ export const Debounce = <value, context = Unit>(
|
|
|
40
47
|
Date.now() - current.lastUpdated <= debounceDurationInMs,
|
|
41
48
|
Co.Wait(debounceDurationInMs / 5)
|
|
42
49
|
),
|
|
43
|
-
Co.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
Co.GetState().then((current) => {
|
|
51
|
+
if (current.status.kind == "l") {
|
|
52
|
+
return Co.SetState(
|
|
53
|
+
updaters.Core.status(
|
|
54
|
+
replaceWith<DebouncedStatus>(
|
|
55
|
+
"processing shortcircuited"
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
else return Co.Do(() => {});
|
|
61
|
+
}),
|
|
48
62
|
Co.Wait(debounceDurationInMs / 2),
|
|
49
63
|
]),
|
|
50
64
|
k
|
|
51
65
|
.embed((_: Debounced<value & context>) => _, updaters.Core.value)
|
|
52
66
|
.then((apiResult) => {
|
|
53
67
|
return Co.Seq([
|
|
54
|
-
Co.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
Co.GetState().then((current) => {
|
|
69
|
+
if (current.status.kind == "l") {
|
|
70
|
+
return Co.SetState(
|
|
71
|
+
updaters.Core.status(
|
|
72
|
+
replaceWith<DebouncedStatus>(
|
|
73
|
+
"processing finished"
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
else return Co.Do(() => {});
|
|
79
|
+
}),
|
|
59
80
|
// Co.Wait(250)
|
|
60
81
|
]).then(() => {
|
|
61
82
|
return Co.GetState().then((current) => {
|
|
@@ -64,13 +85,18 @@ export const Debounce = <value, context = Unit>(
|
|
|
64
85
|
// maybe a new change has already reset dirty, in that case we need to start all over again
|
|
65
86
|
if (current.dirty == "dirty but being processed") {
|
|
66
87
|
return Co.Seq([
|
|
67
|
-
Co.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
88
|
+
Co.GetState().then((current) => {
|
|
89
|
+
if (current.status.kind == "l") {
|
|
90
|
+
return Co.SetState(
|
|
91
|
+
updaters.Core.status(
|
|
92
|
+
replaceWith<DebouncedStatus>(
|
|
93
|
+
"state was still dirty but being processed, resetting to not dirty"
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
else return Co.Do(() => {});
|
|
99
|
+
}),
|
|
74
100
|
// use UpdateState to make sure that we look up the state at the last possible moment to account for delays
|
|
75
101
|
Co.UpdateState((state) =>
|
|
76
102
|
state.dirty == "dirty but being processed"
|
|
@@ -82,13 +108,18 @@ export const Debounce = <value, context = Unit>(
|
|
|
82
108
|
]);
|
|
83
109
|
} else {
|
|
84
110
|
return Co.Seq([
|
|
85
|
-
Co.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
111
|
+
Co.GetState().then((current) => {
|
|
112
|
+
if (current.status.kind == "l") {
|
|
113
|
+
return Co.SetState(
|
|
114
|
+
updaters.Core.status(
|
|
115
|
+
replaceWith<DebouncedStatus>(
|
|
116
|
+
"state was changed underwater back to dirty, leaving the dirty flag alone"
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
else return Co.Do(() => {});
|
|
122
|
+
}),
|
|
92
123
|
Co.SetState(
|
|
93
124
|
updaters.Core.dirty(replaceWith<DirtyStatus>("dirty"))
|
|
94
125
|
),
|
|
@@ -97,13 +128,18 @@ export const Debounce = <value, context = Unit>(
|
|
|
97
128
|
}
|
|
98
129
|
} else {
|
|
99
130
|
return Co.Seq([
|
|
100
|
-
Co.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
131
|
+
Co.GetState().then((current) => {
|
|
132
|
+
if (current.status.kind == "l") {
|
|
133
|
+
return Co.SetState(
|
|
134
|
+
updaters.Core.status(
|
|
135
|
+
replaceWith<DebouncedStatus>(
|
|
136
|
+
"inner call failed with transient failure"
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
else return Co.Do(() => {});
|
|
142
|
+
}),
|
|
107
143
|
Co.SetState(
|
|
108
144
|
updaters.Core.dirty(replaceWith<DirtyStatus>("dirty"))
|
|
109
145
|
),
|
package/src/debounced/state.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Sum } from "../../main";
|
|
1
2
|
import { replaceWith } from "../fun/domains/updater/domains/replaceWith/state";
|
|
2
3
|
import { BasicUpdater, Updater } from "../fun/domains/updater/state";
|
|
3
4
|
|
|
@@ -7,19 +8,19 @@ export type DebouncedStatus = "waiting for dirty" | "just detected dirty, starti
|
|
|
7
8
|
| "processing shortcircuited"
|
|
8
9
|
| "state was changed underwater back to dirty, leaving the dirty flag alone"
|
|
9
10
|
| "inner call failed with transient failure"
|
|
10
|
-
export type Debounced<Value> = Value & { lastUpdated: number; dirty: DirtyStatus; status:DebouncedStatus };
|
|
11
|
+
export type Debounced<Value> = Value & { lastUpdated: number; dirty: DirtyStatus; status: Sum<DebouncedStatus, "debug off"> };
|
|
11
12
|
export const Debounced = {
|
|
12
|
-
Default: <v>(initialValue: v): Debounced<v> => ({
|
|
13
|
+
Default: <v>(initialValue: v, debug?: boolean): Debounced<v> => ({
|
|
13
14
|
...initialValue,
|
|
14
15
|
lastUpdated: 0,
|
|
15
16
|
dirty: "not dirty",
|
|
16
|
-
status: "waiting for dirty"
|
|
17
|
+
status: debug ? Sum.Default.left("waiting for dirty") : Sum.Default.right("debug off")
|
|
17
18
|
}),
|
|
18
19
|
Updaters: {
|
|
19
20
|
Core:{
|
|
20
21
|
status: <v>(_: BasicUpdater<DebouncedStatus>): Updater<Debounced<v>> => Updater<Debounced<v>>(current => ({
|
|
21
22
|
...current,
|
|
22
|
-
status: _(current.status),
|
|
23
|
+
status: current.status.kind == "l" ? Sum.Default.left(_(current.status.value)) : Sum.Default.right("debug off"),
|
|
23
24
|
})),
|
|
24
25
|
dirty: <v>(_: BasicUpdater<DirtyStatus>): Updater<Debounced<v>> => Updater<Debounced<v>>(current => ({
|
|
25
26
|
...current,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApiResponseChecker, AsyncState, BasicUpdater, Debounced, ForeignMutationsInput, id, SimpleCallback, simpleUpdater, Synchronized, Template, unit, Unit, Updater, Value } from "../../../../../../main"
|
|
1
|
+
import { ApiResponseChecker, AsyncState, BasicUpdater, Debounced, ForeignMutationsInput, Guid, id, SimpleCallback, simpleUpdater, Synchronized, Template, unit, Unit, Updater, Value } from "../../../../../../main"
|
|
2
2
|
import { BasicFun } from "../../../../../fun/state"
|
|
3
3
|
|
|
4
4
|
export type ApiErrors = Array<string>
|
|
@@ -6,7 +6,7 @@ export type ApiErrors = Array<string>
|
|
|
6
6
|
export type EditFormContext<E,FS> = {
|
|
7
7
|
entityId:string,
|
|
8
8
|
api:{
|
|
9
|
-
get:() => Promise<E>,
|
|
9
|
+
get:(id: Guid) => Promise<E>,
|
|
10
10
|
update:BasicFun<E, Promise<ApiErrors>>
|
|
11
11
|
},
|
|
12
12
|
actualForm:Template<Value<E> & FS, FS, { onChange:SimpleCallback<BasicUpdater<E>>}>
|
|
@@ -16,7 +16,8 @@ export type PrimitiveType = (typeof PrimitiveTypes)[number]
|
|
|
16
16
|
export const GenericTypes = [
|
|
17
17
|
"SingleSelection",
|
|
18
18
|
"MultiSelection",
|
|
19
|
-
"List"
|
|
19
|
+
"List",
|
|
20
|
+
"Map"] as const
|
|
20
21
|
export type GenericType = (typeof GenericTypes)[number]
|
|
21
22
|
|
|
22
23
|
export type ApiConverter<T> = { fromAPIRawValue: BasicFun<any, T>, toAPIRawValue: BasicFun<T, any> }
|
|
@@ -30,6 +31,7 @@ export type ApiConverters = {
|
|
|
30
31
|
"SingleSelection": ApiConverter<CollectionSelection<any>>
|
|
31
32
|
"MultiSelection": ApiConverter<OrderedMap<string, any>>
|
|
32
33
|
"List": ApiConverter<List<any>>
|
|
34
|
+
"Map": ApiConverter<List<[any, any]>>
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export type PrimitiveBuiltIn = { renderers: Set<keyof BuiltIns["renderers"]>, apiConverters: ApiConverter<any>, defaultValue: any }
|
|
@@ -48,6 +50,7 @@ export type BuiltIns = {
|
|
|
48
50
|
streamSingleSelection: Set<string>;
|
|
49
51
|
streamMultiSelection: Set<string>;
|
|
50
52
|
list: Set<string>;
|
|
53
|
+
map: Set<string>;
|
|
51
54
|
};
|
|
52
55
|
};
|
|
53
56
|
|
|
@@ -65,7 +68,8 @@ export const builtInsFromFieldViews = (fieldViews: any, fieldTypeConverters: Api
|
|
|
65
68
|
"generics": Map([
|
|
66
69
|
["SingleSelection", { apiConverters: fieldTypeConverters["SingleSelection"], defaultValue: CollectionSelection().Default.right("no selection") }] as [string, GenericBuiltIn],
|
|
67
70
|
["Multiselection", { apiConverters: fieldTypeConverters["SingleSelection"], defaultValue: Map() }] as [string, GenericBuiltIn],
|
|
68
|
-
["List", { apiConverters: fieldTypeConverters["SingleSelection"], defaultValue: List() }] as [string, GenericBuiltIn]
|
|
71
|
+
["List", { apiConverters: fieldTypeConverters["SingleSelection"], defaultValue: List() }] as [string, GenericBuiltIn],
|
|
72
|
+
["Map", { apiConverters: fieldTypeConverters["SingleSelection"], defaultValue: List() }] as [string, GenericBuiltIn]
|
|
69
73
|
]),
|
|
70
74
|
"renderers": {
|
|
71
75
|
"boolean": Set(),
|
|
@@ -78,6 +82,7 @@ export const builtInsFromFieldViews = (fieldViews: any, fieldTypeConverters: Api
|
|
|
78
82
|
"number": Set(),
|
|
79
83
|
"string": Set(),
|
|
80
84
|
"list": Set(),
|
|
85
|
+
"map": Set(),
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
Object.keys(builtins.renderers).forEach((_categoryName) => {
|
|
@@ -92,31 +97,53 @@ export const builtInsFromFieldViews = (fieldViews: any, fieldTypeConverters: Api
|
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
|
|
95
|
-
export const defaultValue = (types:Map<TypeName, TypeDefinition>, builtIns:BuiltIns) => (t: TypeName): any => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
let generic = builtIns.generics.get(t)
|
|
101
|
-
if (generic != undefined) {
|
|
102
|
-
return generic.defaultValue
|
|
100
|
+
export const defaultValue = (types: Map<TypeName, TypeDefinition>, builtIns: BuiltIns) => (t: TypeName | Type): any => {
|
|
101
|
+
if (typeof t == "string") {
|
|
102
|
+
let primitive = builtIns.primitives.get(t)
|
|
103
|
+
if (primitive != undefined) {
|
|
104
|
+
return primitive.defaultValue
|
|
103
105
|
} else {
|
|
104
|
-
let
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
let generic = builtIns.generics.get(t)
|
|
107
|
+
if (generic != undefined) {
|
|
108
|
+
return generic.defaultValue
|
|
109
|
+
} else {
|
|
110
|
+
let custom = types.get(t)
|
|
111
|
+
if (custom != undefined) {
|
|
112
|
+
let res = {} as any
|
|
113
|
+
custom.fields.forEach((field, fieldName) => {
|
|
114
|
+
res[fieldName] = defaultValue(types, builtIns)(field.kind == "primitive" ? field.value : field.kind == "lookup" ? field.name : field.value)
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
return res
|
|
118
|
+
} else {
|
|
119
|
+
debugger
|
|
120
|
+
throw `cannot find type ${t} when resolving defaultValue`
|
|
109
121
|
}
|
|
110
|
-
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
if (t.kind == "application") {
|
|
126
|
+
const generic = builtIns.generics.get(t.value)
|
|
127
|
+
if (generic) {
|
|
128
|
+
const res = generic.defaultValue
|
|
111
129
|
return res
|
|
112
|
-
} else {
|
|
113
|
-
throw `cannot find type ${t} when resolving defaultValue`
|
|
114
130
|
}
|
|
115
131
|
}
|
|
132
|
+
debugger
|
|
133
|
+
throw `cannot find type ${t} when resolving defaultValue`
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
const parseTypeIShouldBePartOfFormValidation = (t:any) : TypeName | Type => {
|
|
139
|
+
if (typeof t == "string") return t
|
|
140
|
+
if ("fun" in t && "args" in t && Array.isArray(t.args)) {
|
|
141
|
+
return { kind:"application", value:t.fun, args:t.args }
|
|
116
142
|
}
|
|
143
|
+
return null!
|
|
117
144
|
}
|
|
118
145
|
|
|
119
|
-
export const fromAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, builtIns:BuiltIns, converters:ApiConverters, isKeywordsReplaced: boolean = false) => (raw:any)
|
|
146
|
+
export const fromAPIRawValue = (t: Type, types: Map<TypeName, TypeDefinition>, builtIns: BuiltIns, converters: ApiConverters, isKeywordsReplaced: boolean = false) => (raw: any): any => {
|
|
120
147
|
// alert(JSON.stringify(t))
|
|
121
148
|
if (raw == undefined) {
|
|
122
149
|
console.warn(`instantiating default value for type ${JSON.stringify(t)}: the value was undefined so something is missing from the API response`)
|
|
@@ -131,25 +158,49 @@ export const fromAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, bui
|
|
|
131
158
|
if (t.value == "SingleSelection" && t.args.length == 1) {
|
|
132
159
|
let result = converters[t.value].fromAPIRawValue(obj)
|
|
133
160
|
result = CollectionSelection().Updaters.left(
|
|
134
|
-
fromAPIRawValue({ kind:"lookup", name:t.args[0] }, types, builtIns, converters, true))(result)
|
|
161
|
+
fromAPIRawValue({ kind: "lookup", name: t.args[0] }, types, builtIns, converters, true))(result)
|
|
135
162
|
return result
|
|
136
163
|
}
|
|
137
164
|
if ((t.value == "Multiselection" || t.value == "MultiSelection") && t.args.length == 1) {
|
|
138
165
|
let result = converters["MultiSelection"].fromAPIRawValue(obj)
|
|
139
|
-
result = result.map(fromAPIRawValue({ kind:"lookup", name:t.args[0] }, types, builtIns, converters, true))
|
|
166
|
+
result = result.map(fromAPIRawValue({ kind: "lookup", name: t.args[0] }, types, builtIns, converters, true))
|
|
140
167
|
return result
|
|
141
168
|
}
|
|
142
169
|
if (t.value == "List" && t.args.length == 1) {
|
|
143
170
|
let result = converters[t.value].fromAPIRawValue(obj)
|
|
144
171
|
result = result.map(fromAPIRawValue(
|
|
145
172
|
PrimitiveTypes.some(_ => _ == t.args[0]) ?
|
|
146
|
-
{ kind:"primitive", value:t.args[0] as PrimitiveType }
|
|
147
|
-
|
|
173
|
+
{ kind: "primitive", value: t.args[0] as PrimitiveType }
|
|
174
|
+
: { kind: "lookup", name: t.args[0] }
|
|
148
175
|
, types, builtIns, converters, true))
|
|
149
176
|
return result
|
|
150
177
|
}
|
|
178
|
+
if (t.value == "Map" && t.args.length == 2) {
|
|
179
|
+
let result = converters[t.value].fromAPIRawValue(obj)
|
|
180
|
+
let t_args = t.args.map(parseTypeIShouldBePartOfFormValidation)
|
|
181
|
+
result = result.map(keyValue => ([
|
|
182
|
+
fromAPIRawValue(
|
|
183
|
+
typeof t_args[0] == "string" ?
|
|
184
|
+
PrimitiveTypes.some(_ => _ == t_args[0]) ?
|
|
185
|
+
{ kind: "primitive", value: t_args[0] as PrimitiveType }
|
|
186
|
+
: { kind: "lookup", name: t_args[0] }
|
|
187
|
+
:
|
|
188
|
+
t_args[0],
|
|
189
|
+
types, builtIns, converters, true)(keyValue[0]),
|
|
190
|
+
fromAPIRawValue(
|
|
191
|
+
typeof t_args[1] == "string" ?
|
|
192
|
+
PrimitiveTypes.some(_ => _ == t_args[1]) ?
|
|
193
|
+
{ kind: "primitive", value: t_args[1] as PrimitiveType }
|
|
194
|
+
: { kind: "lookup", name: t_args[1] }
|
|
195
|
+
:
|
|
196
|
+
t_args[1],
|
|
197
|
+
types, builtIns, converters, true)(keyValue[1]),
|
|
198
|
+
])
|
|
199
|
+
)
|
|
200
|
+
return result
|
|
201
|
+
}
|
|
151
202
|
} else { // t.kind == lookup: we are dealing with a record/object
|
|
152
|
-
let result:any = {...obj}
|
|
203
|
+
let result: any = { ...obj }
|
|
153
204
|
const tDef = types.get(t.name)!
|
|
154
205
|
tDef.fields.forEach((fieldType, fieldName) => {
|
|
155
206
|
const replacedFieldName = replaceKeyword(fieldName)
|
|
@@ -163,8 +214,8 @@ export const fromAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, bui
|
|
|
163
214
|
}
|
|
164
215
|
|
|
165
216
|
|
|
166
|
-
export const toAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, builtIns:BuiltIns, converters:ApiConverters, isKeywordsReverted: boolean = false) => (raw:any)
|
|
167
|
-
|
|
217
|
+
export const toAPIRawValue = (t: Type, types: Map<TypeName, TypeDefinition>, builtIns: BuiltIns, converters: ApiConverters, isKeywordsReverted: boolean = false) => (raw: any): any => {
|
|
218
|
+
|
|
168
219
|
const obj = !isKeywordsReverted ? replaceKeywords(raw, "to api") : raw
|
|
169
220
|
|
|
170
221
|
if (t.kind == "primitive") {
|
|
@@ -173,7 +224,7 @@ export const toAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, built
|
|
|
173
224
|
if (t.value == "SingleSelection" && t.args.length == 1) {
|
|
174
225
|
let result = converters[t.value].toAPIRawValue(obj)
|
|
175
226
|
if (result != undefined && typeof result == "object")
|
|
176
|
-
result = toAPIRawValue({ kind:"lookup", name:t.args[0] }, types, builtIns, converters, true)(result)
|
|
227
|
+
result = toAPIRawValue({ kind: "lookup", name: t.args[0] }, types, builtIns, converters, true)(result)
|
|
177
228
|
return result
|
|
178
229
|
}
|
|
179
230
|
if ((t.value == "Multiselection" || t.value == "MultiSelection") && t.args.length == 1) {
|
|
@@ -181,8 +232,8 @@ export const toAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, built
|
|
|
181
232
|
let result = converters["MultiSelection"].toAPIRawValue(obj)
|
|
182
233
|
// alert(`MultiSelect result1 = ${JSON.stringify(result)}`)
|
|
183
234
|
// alert(`${JSON.stringify(t.args[0])}`)
|
|
184
|
-
result = result.map((_:any) =>
|
|
185
|
-
typeof _ == "object" ? toAPIRawValue({ kind:"lookup", name:t.args[0] }, types, builtIns, converters, true)(_) : _)
|
|
235
|
+
result = result.map((_: any) =>
|
|
236
|
+
typeof _ == "object" ? toAPIRawValue({ kind: "lookup", name: t.args[0] }, types, builtIns, converters, true)(_) : _)
|
|
186
237
|
// alert(`MultiSelect result2 = ${JSON.stringify(result)}`)
|
|
187
238
|
return result
|
|
188
239
|
}
|
|
@@ -190,14 +241,41 @@ export const toAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, built
|
|
|
190
241
|
let result = converters[t.value].toAPIRawValue(obj)
|
|
191
242
|
result = result.map(toAPIRawValue(
|
|
192
243
|
PrimitiveTypes.some(_ => _ == t.args[0]) ?
|
|
193
|
-
{ kind:"primitive", value:t.args[0] as PrimitiveType }
|
|
194
|
-
|
|
244
|
+
{ kind: "primitive", value: t.args[0] as PrimitiveType }
|
|
245
|
+
: { kind: "lookup", name: t.args[0] },
|
|
195
246
|
// { kind:"lookup", name:t.args[0] },
|
|
196
247
|
types, builtIns, converters, true))
|
|
197
248
|
return result
|
|
198
249
|
}
|
|
250
|
+
if (t.value == "Map" && t.args.length == 2) {
|
|
251
|
+
let result = converters[t.value].toAPIRawValue(obj)
|
|
252
|
+
|
|
253
|
+
let t_args = t.args.map(parseTypeIShouldBePartOfFormValidation)
|
|
254
|
+
result = result.map((keyValue: any) => ([
|
|
255
|
+
toAPIRawValue(
|
|
256
|
+
typeof t_args[0] == "string" ?
|
|
257
|
+
PrimitiveTypes.some(_ => _ == t_args[0]) ?
|
|
258
|
+
{ kind: "primitive", value: t_args[0] as PrimitiveType }
|
|
259
|
+
: { kind: "lookup", name: t_args[0] }
|
|
260
|
+
:
|
|
261
|
+
t_args[0],
|
|
262
|
+
types, builtIns, converters, true)(keyValue[0]),
|
|
263
|
+
toAPIRawValue(
|
|
264
|
+
typeof t_args[1] == "string" ?
|
|
265
|
+
PrimitiveTypes.some(_ => _ == t_args[1]) ?
|
|
266
|
+
{ kind: "primitive", value: t_args[1] as PrimitiveType }
|
|
267
|
+
: { kind: "lookup", name: t_args[1] }
|
|
268
|
+
:
|
|
269
|
+
t_args[1],
|
|
270
|
+
types, builtIns, converters, true)(keyValue[1]),
|
|
271
|
+
])
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
return result
|
|
275
|
+
}
|
|
276
|
+
|
|
199
277
|
} else { // t.kind == lookup: we are dealing with a record/object
|
|
200
|
-
let result:any = {...obj}
|
|
278
|
+
let result: any = { ...obj }
|
|
201
279
|
const tDef = types.get(t.name)!
|
|
202
280
|
tDef.fields.forEach((fieldType, fieldName) => {
|
|
203
281
|
const revertedFieldName = revertKeyword(fieldName)
|
|
@@ -206,5 +284,5 @@ export const toAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, built
|
|
|
206
284
|
})
|
|
207
285
|
return result
|
|
208
286
|
}
|
|
209
|
-
return defaultValue(types, builtIns)(t.value)
|
|
287
|
+
return defaultValue(types, builtIns)(t.value)
|
|
210
288
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { OrderedMap } from "immutable";
|
|
1
|
+
import { Map, OrderedMap } from "immutable";
|
|
2
|
+
import { BuiltIns } from "../built-ins/state";
|
|
2
3
|
|
|
3
4
|
export type FieldName = string;
|
|
4
5
|
export type TypeName = string;
|
|
@@ -7,16 +8,19 @@ export type TypeDefinition = {
|
|
|
7
8
|
name: TypeName;
|
|
8
9
|
fields: OrderedMap<FieldName, Type>;
|
|
9
10
|
};
|
|
11
|
+
export type PrimitiveTypeName = "string" | "number" | "maybeBoolean" | "boolean" | "Date" | "CollectionReference"
|
|
10
12
|
export type Type = {
|
|
11
13
|
kind: "lookup"; name: TypeName;
|
|
12
14
|
} | {
|
|
13
|
-
kind: "primitive"; value:
|
|
15
|
+
kind: "primitive"; value: PrimitiveTypeName;
|
|
14
16
|
} | {
|
|
15
|
-
kind: "application"; value: TypeName; args: Array<TypeName>;
|
|
17
|
+
kind: "application"; value: TypeName; args: Array<TypeName>; // args: Array<TypeName | Type>;
|
|
16
18
|
};
|
|
17
19
|
export const Type = {
|
|
18
20
|
Default: {
|
|
19
|
-
lookup: (name: TypeName): Type => ({ kind: "lookup", name })
|
|
21
|
+
lookup: (name: TypeName): Type => ({ kind: "lookup", name }),
|
|
22
|
+
primitive: (name: PrimitiveTypeName): Type => ({ kind: "primitive", value: name }),
|
|
23
|
+
application: (value: TypeName, args: Array<TypeName>): Type => ({ kind: "application", value, args }),
|
|
20
24
|
},
|
|
21
25
|
Operations: {
|
|
22
26
|
Equals: (fst: Type, snd: Type): boolean =>
|
|
@@ -26,6 +30,14 @@ export const Type = {
|
|
|
26
30
|
fst.value == snd.value &&
|
|
27
31
|
fst.args.length == snd.args.length &&
|
|
28
32
|
fst.args.every((v, i) => v == snd.args[i]) :
|
|
29
|
-
false
|
|
33
|
+
false,
|
|
34
|
+
FromName: (types: Map<string, TypeDefinition>, builtIns: BuiltIns) => (typeName: string): Type | undefined => {
|
|
35
|
+
const recordTypeName = types.get(typeName)?.name
|
|
36
|
+
if (recordTypeName) return Type.Default.lookup(recordTypeName)
|
|
37
|
+
const primitiveTypeName = builtIns.primitives.get(typeName) && typeName
|
|
38
|
+
if (primitiveTypeName) return Type.Default.primitive(primitiveTypeName as any)
|
|
39
|
+
return undefined
|
|
40
|
+
}
|
|
30
41
|
}
|
|
31
42
|
}
|
|
43
|
+
|
|
@@ -6,6 +6,7 @@ export type FieldConfig = {
|
|
|
6
6
|
label: string
|
|
7
7
|
api: { stream?: string, enumOptions?: string },
|
|
8
8
|
elementRenderer?: string,
|
|
9
|
+
mapRenderer?: { keyRenderer: FieldConfig, valueRenderer: FieldConfig },
|
|
9
10
|
visible: BoolExpr<any>;
|
|
10
11
|
disabled: BoolExpr<any>;
|
|
11
12
|
};
|
|
@@ -98,8 +99,10 @@ export const FormsConfig = {
|
|
|
98
99
|
} else if (typeof configFieldType == "object") {
|
|
99
100
|
if ("fun" in configFieldType && "args" in configFieldType &&
|
|
100
101
|
typeof configFieldType["fun"] == "string" &&
|
|
101
|
-
Array.isArray(configFieldType["args"])
|
|
102
|
-
|
|
102
|
+
Array.isArray(configFieldType["args"])
|
|
103
|
+
// &&
|
|
104
|
+
// configFieldType["args"].every(_ => typeof (_) == "string")
|
|
105
|
+
) {
|
|
103
106
|
const fieldType: Type = {
|
|
104
107
|
kind: "application",
|
|
105
108
|
value: configFieldType["fun"] as any,
|
|
@@ -122,10 +125,10 @@ export const FormsConfig = {
|
|
|
122
125
|
errors.push(`field ${fieldName} of type ${typeName} is non-existent primitive type ${fieldDef.value}`);
|
|
123
126
|
if (fieldDef.kind == "lookup" && !types.has(fieldDef.name))
|
|
124
127
|
errors.push(`field ${fieldName} of type ${typeName} is non-existent type ${fieldDef.name}`);
|
|
125
|
-
if (fieldDef.kind == "application" && !builtIns.generics.has(fieldDef.value))
|
|
126
|
-
|
|
127
|
-
if (fieldDef.kind == "application" && fieldDef.args.some(argType => !builtIns.primitives.has(argType) && !types.has(argType)))
|
|
128
|
-
|
|
128
|
+
// if (fieldDef.kind == "application" && !builtIns.generics.has(fieldDef.value))
|
|
129
|
+
// errors.push(`field ${fieldName} of type ${typeName} applies non-existent generic type ${fieldDef.value}`);
|
|
130
|
+
// if (fieldDef.kind == "application" && fieldDef.args.some(argType => !builtIns.primitives.has(argType) && !types.has(argType)))
|
|
131
|
+
// errors.push(`field ${fieldName} of type ${typeName} applies non-existent type arguments ${JSON.stringify(fieldDef.args.filter(argType => !builtIns.primitives.has(argType) && !types.has(argType)))}`);
|
|
129
132
|
if (fieldDef.kind == "application" && fieldDef.value == "SingleSelection") {
|
|
130
133
|
if (fieldDef.args.length != 1)
|
|
131
134
|
errors.push(`field ${fieldName} in type ${typeName}: SingleSelection should have exactly one type argument, found ${JSON.stringify(fieldDef.args)}`);
|
|
@@ -259,7 +262,7 @@ export const FormsConfig = {
|
|
|
259
262
|
|
|
260
263
|
let forms: Map<string, FormDef> = Map();
|
|
261
264
|
Object.keys(formsConfig["forms"]).forEach((formName: any) => {
|
|
262
|
-
let formDef: FormDef = { name: formName, type: "", fields: Map(), tabs: Map(), typeDef:null! };
|
|
265
|
+
let formDef: FormDef = { name: formName, type: "", fields: Map(), tabs: Map(), typeDef: null! };
|
|
263
266
|
forms = forms.set(formName, formDef);
|
|
264
267
|
const configFormDef = formsConfig["forms"][formName];
|
|
265
268
|
if ("type" in configFormDef == false) {
|
|
@@ -288,74 +291,116 @@ export const FormsConfig = {
|
|
|
288
291
|
errors.push(`field ${fieldName} of form ${formName} has no 'renderer' attribute`);
|
|
289
292
|
}
|
|
290
293
|
const fieldTypeDef = formTypeDef?.fields.get(fieldName)
|
|
291
|
-
if (fieldTypeDef
|
|
292
|
-
|
|
293
|
-
// alert(JSON.stringify(fieldConfig["renderer"]))
|
|
294
|
-
// alert(JSON.stringify(builtIns.renderers.MaybeBooleanViews))
|
|
295
|
-
if (!builtIns.renderers.maybeBoolean.has(fieldConfig["renderer"]))
|
|
296
|
-
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
297
|
-
} else if (fieldTypeDef.value == "boolean") {
|
|
298
|
-
if (!builtIns.renderers.boolean.has(fieldConfig["renderer"]))
|
|
299
|
-
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
300
|
-
} else if (fieldTypeDef.value == "number") {
|
|
301
|
-
if (!builtIns.renderers.number.has(fieldConfig["renderer"]))
|
|
302
|
-
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
303
|
-
} else if (fieldTypeDef.value == "string") {
|
|
304
|
-
if (!builtIns.renderers.string.has(fieldConfig["renderer"]))
|
|
305
|
-
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
306
|
-
} else if (fieldTypeDef.value == "Date") {
|
|
307
|
-
if (!builtIns.renderers.date.has(fieldConfig["renderer"])) {
|
|
308
|
-
alert(JSON.stringify(builtIns.renderers))
|
|
309
|
-
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
310
|
-
}
|
|
311
|
-
} else {
|
|
312
|
-
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
313
|
-
}
|
|
314
|
-
} else if (fieldTypeDef?.kind == "application") {
|
|
315
|
-
if (fieldTypeDef?.value == "SingleSelection") {
|
|
316
|
-
if (!builtIns.renderers.enumSingleSelection.has(fieldConfig["renderer"]) &&
|
|
317
|
-
!builtIns.renderers.streamSingleSelection.has(fieldConfig["renderer"]))
|
|
318
|
-
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
319
|
-
} else if (fieldTypeDef?.value == "Multiselection") {
|
|
320
|
-
if (!builtIns.renderers.enumMultiSelection.has(fieldConfig["renderer"]) &&
|
|
321
|
-
!builtIns.renderers.streamMultiSelection.has(fieldConfig["renderer"]))
|
|
322
|
-
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
323
|
-
} else if (fieldTypeDef?.value == "List") {
|
|
324
|
-
if (!builtIns.renderers.list.has(fieldConfig["renderer"]))
|
|
325
|
-
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
326
|
-
}
|
|
327
|
-
if (fieldTypeDef.args.length < 1)
|
|
328
|
-
errors.push(`field ${fieldName} of form ${formName} should have one type argument}`);
|
|
329
|
-
else if (
|
|
330
|
-
(builtIns.renderers.list.has(fieldConfig["renderer"])) && "elementRenderer" in fieldConfig != true)
|
|
331
|
-
errors.push(`field ${fieldName} of form ${formName} is missing the 'elementRenderer' property`);
|
|
332
|
-
else if (
|
|
333
|
-
(builtIns.renderers.enumMultiSelection.has(fieldConfig["renderer"]) ||
|
|
334
|
-
builtIns.renderers.enumSingleSelection.has(fieldConfig["renderer"])) && "options" in fieldConfig != true)
|
|
335
|
-
errors.push(`field ${fieldName} of form ${formName} is missing the 'options' property`);
|
|
336
|
-
else if (
|
|
337
|
-
(builtIns.renderers.streamSingleSelection.has(fieldConfig["renderer"]) ||
|
|
338
|
-
builtIns.renderers.streamMultiSelection.has(fieldConfig["renderer"])) && "stream" in fieldConfig != true)
|
|
339
|
-
errors.push(`field ${fieldName} of form ${formName} is missing the 'stream' property`);
|
|
340
|
-
else if ((builtIns.renderers.enumMultiSelection.has(fieldConfig["renderer"]) ||
|
|
341
|
-
builtIns.renderers.enumSingleSelection.has(fieldConfig["renderer"])) && (enums.get(fieldConfig["options"])) != fieldTypeDef.args[0]) {
|
|
342
|
-
if (enums.has(fieldConfig["options"]))
|
|
343
|
-
errors.push(`field ${fieldName} of form ${formName} references an enum api with type ${enums.get(fieldConfig["options"])} when ${fieldTypeDef.args[0]} was expected`);
|
|
344
|
-
else
|
|
345
|
-
errors.push(`field ${fieldName} of form ${formName} references a non-existing enum api`);
|
|
346
|
-
} else if ((builtIns.renderers.streamMultiSelection.has(fieldConfig["renderer"]) ||
|
|
347
|
-
builtIns.renderers.streamSingleSelection.has(fieldConfig["renderer"])) && (streams.get(fieldConfig["stream"])) != fieldTypeDef.args[0]) {
|
|
348
|
-
if (streams.has(fieldConfig["stream"]))
|
|
349
|
-
errors.push(`field ${fieldName} of form ${formName} references an api with type ${streams.get(fieldConfig["stream"])} when ${fieldTypeDef.args[0]} was expected`);
|
|
350
|
-
else
|
|
351
|
-
errors.push(`field ${fieldName} of form ${formName} references a non-existing stream api`);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
294
|
+
if (!fieldTypeDef)
|
|
295
|
+
errors.push(`field ${fieldName} of form ${formName} has no corresponding type `);
|
|
354
296
|
}
|
|
355
297
|
})
|
|
356
298
|
}
|
|
357
299
|
})
|
|
358
300
|
|
|
301
|
+
const rendererMatchesType = (formName:string, fieldName:string) => (fieldTypeDef:Type, fieldConfig:any) => {
|
|
302
|
+
if (fieldTypeDef?.kind == "primitive") {
|
|
303
|
+
if (fieldTypeDef.value == "maybeBoolean") {
|
|
304
|
+
// alert(JSON.stringify(fieldConfig["renderer"]))
|
|
305
|
+
// alert(JSON.stringify(builtIns.renderers.MaybeBooleanViews))
|
|
306
|
+
if (!builtIns.renderers.maybeBoolean.has(fieldConfig["renderer"]))
|
|
307
|
+
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
308
|
+
} else if (fieldTypeDef.value == "boolean") {
|
|
309
|
+
if (!builtIns.renderers.boolean.has(fieldConfig["renderer"]))
|
|
310
|
+
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
311
|
+
} else if (fieldTypeDef.value == "number") {
|
|
312
|
+
if (!builtIns.renderers.number.has(fieldConfig["renderer"]))
|
|
313
|
+
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
314
|
+
} else if (fieldTypeDef.value == "string") {
|
|
315
|
+
if (!builtIns.renderers.string.has(fieldConfig["renderer"]))
|
|
316
|
+
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
317
|
+
} else if (fieldTypeDef.value == "Date") {
|
|
318
|
+
if (!builtIns.renderers.date.has(fieldConfig["renderer"])) {
|
|
319
|
+
alert(JSON.stringify(builtIns.renderers))
|
|
320
|
+
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
324
|
+
}
|
|
325
|
+
} else if (fieldTypeDef?.kind == "application") {
|
|
326
|
+
if (fieldTypeDef?.value == "SingleSelection") {
|
|
327
|
+
if (!builtIns.renderers.enumSingleSelection.has(fieldConfig["renderer"]) &&
|
|
328
|
+
!builtIns.renderers.streamSingleSelection.has(fieldConfig["renderer"]))
|
|
329
|
+
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
330
|
+
} else if (fieldTypeDef?.value == "Multiselection") {
|
|
331
|
+
if (!builtIns.renderers.enumMultiSelection.has(fieldConfig["renderer"]) &&
|
|
332
|
+
!builtIns.renderers.streamMultiSelection.has(fieldConfig["renderer"]))
|
|
333
|
+
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
334
|
+
} else if (fieldTypeDef?.value == "List") {
|
|
335
|
+
if (!builtIns.renderers.list.has(fieldConfig["renderer"]))
|
|
336
|
+
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
337
|
+
} else if (fieldTypeDef?.value == "Map") {
|
|
338
|
+
if (!builtIns.renderers.map.has(fieldConfig["renderer"]))
|
|
339
|
+
errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
|
|
340
|
+
if ("keyRenderer" in fieldConfig != true || "valueRenderer" in fieldConfig != true)
|
|
341
|
+
errors.push(`field ${fieldName} of form ${formName} must have both a keyRenderer and a valueRenderer`);
|
|
342
|
+
if (fieldTypeDef.args.length != 2)
|
|
343
|
+
errors.push(`field ${fieldName} of form ${formName} should have exactly two type arguments`);
|
|
344
|
+
else {
|
|
345
|
+
const typeDefToType = (typeDef:any) : Type | undefined =>
|
|
346
|
+
"fun" in typeDef == false ? undefined
|
|
347
|
+
: "args" in typeDef == false ? undefined
|
|
348
|
+
: Array.isArray(typeDef.args) == false ? undefined
|
|
349
|
+
: Type.Default.application(typeDef.fun, typeDef.args)
|
|
350
|
+
const keyType:Type | undefined = typeof fieldTypeDef.args[0] == "string" ? Type.Operations.FromName(types, builtIns)(fieldTypeDef.args[0]) : typeDefToType(fieldTypeDef.args[0] as any)
|
|
351
|
+
const valueType:Type | undefined = typeof fieldTypeDef.args[1] == "string" ? Type.Operations.FromName(types, builtIns)(fieldTypeDef.args[1]) : typeDefToType(fieldTypeDef.args[1] as any)
|
|
352
|
+
if (!keyType) {
|
|
353
|
+
errors.push(`field ${fieldName} of form ${formName} references non-existing key type ${JSON.stringify(fieldTypeDef.args[0])}`);
|
|
354
|
+
} else if (!valueType) {
|
|
355
|
+
errors.push(`field ${fieldName} of form ${formName} references non-existing value type ${JSON.stringify(fieldTypeDef.args[1])}`);
|
|
356
|
+
} else {
|
|
357
|
+
rendererMatchesType(formName, fieldName)(keyType, fieldConfig.keyRenderer)
|
|
358
|
+
rendererMatchesType(formName, fieldName)(valueType, fieldConfig.valueRenderer)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (fieldTypeDef.args.length < 1)
|
|
363
|
+
errors.push(`field ${fieldName} of form ${formName} should have one type argument}`);
|
|
364
|
+
else if (
|
|
365
|
+
(builtIns.renderers.list.has(fieldConfig["renderer"])) && "elementRenderer" in fieldConfig != true)
|
|
366
|
+
errors.push(`field ${fieldName} of form ${formName} is missing the 'elementRenderer' property`);
|
|
367
|
+
else if (
|
|
368
|
+
(builtIns.renderers.enumMultiSelection.has(fieldConfig["renderer"]) ||
|
|
369
|
+
builtIns.renderers.enumSingleSelection.has(fieldConfig["renderer"])) && "options" in fieldConfig != true)
|
|
370
|
+
errors.push(`field ${fieldName} of form ${formName} is missing the 'options' property`);
|
|
371
|
+
else if (
|
|
372
|
+
(builtIns.renderers.streamSingleSelection.has(fieldConfig["renderer"]) ||
|
|
373
|
+
builtIns.renderers.streamMultiSelection.has(fieldConfig["renderer"])) && "stream" in fieldConfig != true)
|
|
374
|
+
errors.push(`field ${fieldName} of form ${formName} is missing the 'stream' property`);
|
|
375
|
+
else if ((builtIns.renderers.enumMultiSelection.has(fieldConfig["renderer"]) ||
|
|
376
|
+
builtIns.renderers.enumSingleSelection.has(fieldConfig["renderer"])) && (enums.get(fieldConfig["options"])) != fieldTypeDef.args[0]) {
|
|
377
|
+
if (enums.has(fieldConfig["options"]))
|
|
378
|
+
errors.push(`field ${fieldName} of form ${formName} references an enum api with type ${enums.get(fieldConfig["options"])} when ${fieldTypeDef.args[0]} was expected`);
|
|
379
|
+
else
|
|
380
|
+
errors.push(`field ${fieldName} of form ${formName} references a non-existing enum api`);
|
|
381
|
+
} else if ((builtIns.renderers.streamMultiSelection.has(fieldConfig["renderer"]) ||
|
|
382
|
+
builtIns.renderers.streamSingleSelection.has(fieldConfig["renderer"])) && (streams.get(fieldConfig["stream"])) != fieldTypeDef.args[0]) {
|
|
383
|
+
if (streams.has(fieldConfig["stream"]))
|
|
384
|
+
errors.push(`field ${fieldName} of form ${formName} references an api with type ${streams.get(fieldConfig["stream"])} when ${fieldTypeDef.args[0]} was expected`);
|
|
385
|
+
else
|
|
386
|
+
errors.push(`field ${fieldName} of form ${formName} references a non-existing stream api`);
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
const formTypeDef = types.get(fieldTypeDef.name)
|
|
390
|
+
if (!formTypeDef) {
|
|
391
|
+
alert(JSON.stringify([fieldName, fieldTypeDef, fieldConfig]))
|
|
392
|
+
errors.push(`field ${fieldName} of form ${formName} references a non-existing type ${fieldTypeDef.name}`);
|
|
393
|
+
} else {
|
|
394
|
+
const form = forms.get(fieldConfig.renderer ?? "")
|
|
395
|
+
if (!form) {
|
|
396
|
+
errors.push(`field ${fieldName} of form ${formName} references non-existing form ${fieldConfig.renderer}`);
|
|
397
|
+
} else if (fieldTypeDef.name != form.typeDef.name) {
|
|
398
|
+
errors.push(`field ${fieldName} of form ${formName} expected renderer for ${fieldTypeDef.name} but instead found a renderer for ${form.typeDef.name}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
359
404
|
Object.keys(formsConfig["forms"]).forEach((formName: any) => {
|
|
360
405
|
let formDef: FormDef = forms.get(formName)!
|
|
361
406
|
const formTypeDef = types.get(formDef.type)
|
|
@@ -363,6 +408,8 @@ export const FormsConfig = {
|
|
|
363
408
|
Object.keys(configFormDef["fields"]).forEach(fieldName => {
|
|
364
409
|
const fieldConfig = configFormDef["fields"][fieldName]
|
|
365
410
|
const fieldTypeDef = formTypeDef?.fields.get(fieldName);
|
|
411
|
+
if (fieldTypeDef)
|
|
412
|
+
rendererMatchesType(formName, fieldName)(fieldTypeDef, fieldConfig)
|
|
366
413
|
if (fieldTypeDef && fieldTypeDef.kind == "application" && fieldTypeDef.value == "List" && (builtIns.renderers.list.has(fieldConfig["renderer"]))) {
|
|
367
414
|
let elementRenderer = fieldConfig["elementRenderer"]
|
|
368
415
|
let elementType = fieldTypeDef.args[0]
|
|
@@ -404,6 +451,10 @@ export const FormsConfig = {
|
|
|
404
451
|
renderer: fieldConfig.renderer,
|
|
405
452
|
label: fieldConfig.label ?? fieldName,
|
|
406
453
|
elementRenderer: fieldConfig.elementRenderer,
|
|
454
|
+
mapRenderer:
|
|
455
|
+
fieldConfig.keyRenderer && fieldConfig.valueRenderer ?
|
|
456
|
+
{ keyRenderer:fieldConfig.keyRenderer, valueRenderer:fieldConfig.valueRenderer }
|
|
457
|
+
: undefined,
|
|
407
458
|
visible: BoolExpr.Default(fieldConfig.visible),
|
|
408
459
|
disabled: fieldConfig.disabled != undefined ?
|
|
409
460
|
BoolExpr.Default(fieldConfig.disabled)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { List, Map, OrderedMap, OrderedSet, Set } from "immutable";
|
|
2
|
-
import { BoolExpr, Unit, PromiseRepo, Guid, LeafPredicatesEvaluators, Predicate, FormsConfig, BuiltIns, FormDef, Sum, BasicFun, Template, unit, EditFormState, EditFormTemplate, ApiErrors, CreateFormTemplate, EntityFormTemplate, SharedFormState, CreateFormState, Entity, EditFormContext, CreateFormContext, MappedEntityFormTemplate, Mapping, FormValidationResult, Synchronized, simpleUpdater, PrimitiveType, GenericType, ApiConverter, TypeName, ListFieldState, ListForm, TypeDefinition, ApiConverters, defaultValue, fromAPIRawValue, toAPIRawValue, EditFormForeignMutationsExpected } from "../../../../main";
|
|
2
|
+
import { BoolExpr, Unit, PromiseRepo, Guid, LeafPredicatesEvaluators, Predicate, FormsConfig, BuiltIns, FormDef, Sum, BasicFun, Template, unit, EditFormState, EditFormTemplate, ApiErrors, CreateFormTemplate, EntityFormTemplate, SharedFormState, CreateFormState, Entity, EditFormContext, CreateFormContext, MappedEntityFormTemplate, Mapping, FormValidationResult, Synchronized, simpleUpdater, PrimitiveType, GenericType, ApiConverter, TypeName, ListFieldState, ListForm, TypeDefinition, ApiConverters, defaultValue, fromAPIRawValue, toAPIRawValue, EditFormForeignMutationsExpected, MapFieldState, MapForm, Type, FieldConfig } from "../../../../main";
|
|
3
3
|
import { Value } from "../../../value/state";
|
|
4
4
|
import { CollectionReference } from "../collection/domains/reference/state";
|
|
5
5
|
import { CollectionSelection } from "../collection/domains/selection/state";
|
|
@@ -24,7 +24,7 @@ const parseOptions = (leafPredicates: any, options: any) => {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export const FieldView = //<Context, FieldViews extends DefaultFieldViews, EnumFieldConfigs extends {}, EnumSources extends {}>() => <ViewType extends keyof FieldViews, ViewName extends keyof FieldViews[ViewType]>
|
|
27
|
-
(fieldViews: any, viewType: any, viewName: any, fieldName: string, label: string, enumFieldConfigs: EnumOptionsSources, enumSources: any, leafPredicates: any): any => // FieldView<Context, FieldViews, ViewType, ViewName> =>
|
|
27
|
+
(fieldConfig:FieldConfig, fieldViews: any, viewType: any, viewName: any, fieldName: string, label: string, enumFieldConfigs: EnumOptionsSources, enumSources: any, leafPredicates: any): any => // FieldView<Context, FieldViews, ViewType, ViewName> =>
|
|
28
28
|
{
|
|
29
29
|
if (viewType == "maybeBoolean")
|
|
30
30
|
return MaybeBooleanForm<any & FormLabel, Unit>()
|
|
@@ -50,13 +50,16 @@ export const FieldView = //<Context, FieldViews extends DefaultFieldViews, EnumF
|
|
|
50
50
|
return EnumForm<any & FormLabel & BaseEnumContext<any, CollectionReference>, Unit, CollectionReference>()
|
|
51
51
|
.withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
|
|
52
52
|
.mapContext<any & EnumFormState<any & BaseEnumContext<any, CollectionReference>, CollectionReference> & Value<CollectionSelection<CollectionReference>>>(_ => ({
|
|
53
|
-
..._, label: label, getOptions: () =>
|
|
53
|
+
..._, label: label, getOptions: () => {
|
|
54
|
+
// console.log(fieldConfig, fieldViews, viewType, viewName, fieldName, label, enumFieldConfigs, enumSources, leafPredicates)
|
|
55
|
+
return ((enumFieldConfigs as any)((fieldConfig as any).options ?? fieldConfig.api.enumOptions)() as Promise<any>).then(options => parseOptions(leafPredicates, options))
|
|
56
|
+
}
|
|
54
57
|
})) as any
|
|
55
58
|
if (viewType == "enumMultiSelection")
|
|
56
59
|
return EnumMultiselectForm<any & FormLabel & BaseEnumContext<any, CollectionReference>, Unit, CollectionReference>()
|
|
57
60
|
.withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
|
|
58
61
|
.mapContext<any & EnumFormState<any & BaseEnumContext<any, CollectionReference>, CollectionReference> & Value<OrderedMap<Guid, CollectionReference>>>(_ => ({
|
|
59
|
-
..._, label: label, getOptions: () => ((enumFieldConfigs as any)((
|
|
62
|
+
..._, label: label, getOptions: () => ((enumFieldConfigs as any)((fieldConfig as any).options ?? fieldConfig.api.enumOptions)() as Promise<any>).then(options => parseOptions(leafPredicates, options))
|
|
60
63
|
})) as any
|
|
61
64
|
if (viewType == "streamSingleSelection")
|
|
62
65
|
return SearchableInfiniteStreamForm<CollectionReference, any & FormLabel, Unit>()
|
|
@@ -70,7 +73,7 @@ export const FieldView = //<Context, FieldViews extends DefaultFieldViews, EnumF
|
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
export const FieldFormState = //<Context, FieldViews extends DefaultFieldViews, InfiniteStreamSources extends {}, InfiniteStreamConfigs extends {}>() => <ViewType extends keyof FieldViews, ViewName extends keyof FieldViews[ViewType]>
|
|
73
|
-
(fieldViews: any, viewType: any, viewName: any, fieldName: string, InfiniteStreamSources: any, infiniteStreamConfigs: any): any => {
|
|
76
|
+
(fieldConfig:FieldConfig, fieldViews: any, viewType: any, viewName: any, fieldName: string, InfiniteStreamSources: any, infiniteStreamConfigs: any): any => {
|
|
74
77
|
if (viewType == "maybeBoolean" || viewType == "boolean" || viewType == "number" || viewType == "string")
|
|
75
78
|
return SharedFormState.Default();
|
|
76
79
|
if (viewType == "date")
|
|
@@ -79,12 +82,14 @@ export const FieldFormState = //<Context, FieldViews extends DefaultFieldViews,
|
|
|
79
82
|
return ({ ...EnumFormState<any, any>().Default(), ...SharedFormState.Default() });
|
|
80
83
|
if (viewType == "streamSingleSelection" || viewType == "streamMultiSelection") {
|
|
81
84
|
return ({
|
|
82
|
-
...SearchableInfiniteStreamState<any>().Default("", (InfiniteStreamSources as any)((
|
|
85
|
+
...SearchableInfiniteStreamState<any>().Default("", (InfiniteStreamSources as any)((fieldConfig as any).stream ?? fieldConfig.api.stream)
|
|
83
86
|
), ...SharedFormState.Default()
|
|
84
87
|
});
|
|
85
88
|
}
|
|
86
89
|
if (viewType == "list")
|
|
87
90
|
return ListFieldState<any, any>().Default(Map())
|
|
91
|
+
if (viewType == "map")
|
|
92
|
+
return MapFieldState<any, any, any, any>().Default(Map())
|
|
88
93
|
return `error: the view for ${viewType as string}::${viewName as string} cannot be found when creating the corresponding field form state`;
|
|
89
94
|
};
|
|
90
95
|
|
|
@@ -105,6 +110,8 @@ export const ParseForm = (
|
|
|
105
110
|
otherForms: ParsedForms,
|
|
106
111
|
fieldsViewsConfig: any,
|
|
107
112
|
formFieldElementRenderers: any,
|
|
113
|
+
formFieldKeyRenderers: any,
|
|
114
|
+
formFieldValueRenderers: any,
|
|
108
115
|
fieldsInfiniteStreamsConfig: any,
|
|
109
116
|
fieldsOptionsConfig: any,
|
|
110
117
|
InfiniteStreamSources: any,
|
|
@@ -112,7 +119,7 @@ export const ParseForm = (
|
|
|
112
119
|
leafPredicates: any,
|
|
113
120
|
visibleFieldsBoolExprs: any,
|
|
114
121
|
disabledFieldsBoolExprs: any,
|
|
115
|
-
defaultValue: BasicFun<TypeName, any>,
|
|
122
|
+
defaultValue: BasicFun<TypeName | Type, any>,
|
|
116
123
|
type: TypeDefinition
|
|
117
124
|
): ParsedForm => {
|
|
118
125
|
const fieldNameToViewCategory = (fieldName: string) => {
|
|
@@ -126,9 +133,10 @@ export const ParseForm = (
|
|
|
126
133
|
}
|
|
127
134
|
throw `cannot resolve view ${viewName} of field ${fieldName}`
|
|
128
135
|
};
|
|
129
|
-
const fieldNameToElementViewCategory = (fieldName: string) => {
|
|
136
|
+
const fieldNameToElementViewCategory = (formFieldKeyOrValueRenderers:any) => (fieldName: string) => {
|
|
130
137
|
const fieldViewCategories = Object.keys(fieldViews)
|
|
131
|
-
|
|
138
|
+
let viewName = (formFieldKeyOrValueRenderers as any)[fieldName];
|
|
139
|
+
viewName = viewName?.renderer ?? viewName
|
|
132
140
|
for (const categoryName of fieldViewCategories) {
|
|
133
141
|
const viewCategory = (fieldViews as any)[categoryName];
|
|
134
142
|
if (viewName in viewCategory) {
|
|
@@ -141,16 +149,19 @@ export const ParseForm = (
|
|
|
141
149
|
const initialFormState: any = SharedFormState.Default();
|
|
142
150
|
Object.keys(fieldsViewsConfig).forEach(fieldName => {
|
|
143
151
|
const viewName = (fieldsViewsConfig as any)[fieldName];
|
|
152
|
+
const fieldConfig = formDef.fields.get(fieldName)!
|
|
144
153
|
initialFormState[fieldName] =
|
|
145
154
|
otherForms.get(viewName)?.initialFormState ??
|
|
146
|
-
FieldFormState(fieldViews, fieldNameToViewCategory(fieldName) as any, (fieldsViewsConfig as any)[fieldName], fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig);
|
|
155
|
+
FieldFormState(fieldConfig, fieldViews, fieldNameToViewCategory(fieldName) as any, (fieldsViewsConfig as any)[fieldName], fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig);
|
|
147
156
|
if (typeof initialFormState[fieldName] == "string") {
|
|
148
157
|
throw `cannot resolve initial state ${viewName} of field ${fieldName}`
|
|
149
158
|
}
|
|
150
159
|
});
|
|
151
160
|
const formConfig: any = {};
|
|
152
|
-
Object.keys(fieldsViewsConfig)
|
|
161
|
+
const fieldNames = Object.keys(fieldsViewsConfig)
|
|
162
|
+
fieldNames.forEach(fieldName => {
|
|
153
163
|
const label = formDef.fields.get(fieldName)!.label
|
|
164
|
+
const fieldConfig = formDef.fields.get(fieldName)!
|
|
154
165
|
const viewName = (fieldsViewsConfig as any)[fieldName];
|
|
155
166
|
const otherForm = otherForms.get(viewName)
|
|
156
167
|
if (otherForm != undefined) {
|
|
@@ -171,8 +182,8 @@ export const ParseForm = (
|
|
|
171
182
|
).withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
|
|
172
183
|
.mapContext<any>(_ => ({ ..._, label: label }))
|
|
173
184
|
} else { // the list argument is a primitive
|
|
174
|
-
const elementForm = FieldView(fieldViews, fieldNameToElementViewCategory(fieldName) as any, elementRendererName, fieldName, label, EnumOptionsSources, fieldsOptionsConfig, leafPredicates)
|
|
175
|
-
const initialFormState = FieldFormState(fieldViews, fieldNameToElementViewCategory(fieldName) as any, elementRendererName, fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig);
|
|
185
|
+
const elementForm = FieldView(fieldConfig, fieldViews, fieldNameToElementViewCategory(formFieldElementRenderers)(fieldName) as any, elementRendererName, fieldName, label, EnumOptionsSources, fieldsOptionsConfig, leafPredicates)
|
|
186
|
+
const initialFormState = FieldFormState(fieldConfig, fieldViews, fieldNameToElementViewCategory(formFieldElementRenderers)(fieldName) as any, elementRendererName, fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig);
|
|
176
187
|
formConfig[fieldName] = ListForm<any, any, any & FormLabel, Unit>(
|
|
177
188
|
{ Default: () => initialFormState },
|
|
178
189
|
{ Default: () => initialElementValue },
|
|
@@ -181,7 +192,60 @@ export const ParseForm = (
|
|
|
181
192
|
.mapContext<any>(_ => ({ ..._, label: label }))
|
|
182
193
|
}
|
|
183
194
|
} else {
|
|
184
|
-
|
|
195
|
+
if (viewType == "map") {
|
|
196
|
+
const field = type.fields.get(fieldName)!
|
|
197
|
+
|
|
198
|
+
const parseType = (t:any) : TypeName | Type | undefined => {
|
|
199
|
+
if (typeof t == "string") return t
|
|
200
|
+
if ("fun" in t && "args" in t && Array.isArray(t.args)) {
|
|
201
|
+
return { kind:"application", value:t.fun, args:t.args }
|
|
202
|
+
}
|
|
203
|
+
return undefined
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const [keyType, valueType] = field.kind == "application" ? [parseType(field.args[0]), parseType(field.args[1])] : (() => {
|
|
207
|
+
throw `error processing field type ${JSON.stringify(field)} of ${fieldName}`
|
|
208
|
+
})()
|
|
209
|
+
if (!keyType || !valueType) {
|
|
210
|
+
throw `error processing field type ${JSON.stringify(field)} of ${fieldName}`
|
|
211
|
+
}
|
|
212
|
+
const initialKeyValue = defaultValue(keyType)
|
|
213
|
+
const initialValueValue = defaultValue(valueType)
|
|
214
|
+
const getFormAndInitialState = (elementRenderers:any, rendererName:any, fieldConfig:FieldConfig) => {
|
|
215
|
+
const formDef = otherForms.get(rendererName)
|
|
216
|
+
if (formDef != undefined) {
|
|
217
|
+
return [
|
|
218
|
+
formDef.form.withView(nestedContainerFormView),
|
|
219
|
+
formDef.initialFormState
|
|
220
|
+
]
|
|
221
|
+
} else {
|
|
222
|
+
const categoryName = fieldNameToElementViewCategory(elementRenderers)(fieldName) as any
|
|
223
|
+
const form = FieldView(fieldConfig, fieldViews, categoryName, rendererName, fieldName, label, EnumOptionsSources, fieldsOptionsConfig, leafPredicates)
|
|
224
|
+
const initialFormState = FieldFormState(fieldConfig, fieldViews, categoryName, rendererName, fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig);
|
|
225
|
+
return [
|
|
226
|
+
form,
|
|
227
|
+
initialFormState
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// alert(JSON.stringify([formFieldKeyValueRenderers[fieldName]]))
|
|
232
|
+
// alert(JSON.stringify([formFieldKeyValueRenderers]))
|
|
233
|
+
const keyRendererName = formFieldKeyRenderers[fieldName]
|
|
234
|
+
const valueRendererName = formFieldValueRenderers[fieldName]
|
|
235
|
+
const [keyForm,keyFormInitialState] = getFormAndInitialState(formFieldKeyRenderers, keyRendererName?.renderer ?? keyRendererName, keyRendererName as FieldConfig)
|
|
236
|
+
const [valueForm,valueFormInitialState] = getFormAndInitialState(formFieldValueRenderers, valueRendererName?.renderer ?? valueRendererName, keyRendererName as FieldConfig)
|
|
237
|
+
formConfig[fieldName] = MapForm<any, any, any, any, any & FormLabel, Unit>(
|
|
238
|
+
{ Default: () => keyFormInitialState },
|
|
239
|
+
{ Default: () => valueFormInitialState },
|
|
240
|
+
{ Default: () => initialKeyValue },
|
|
241
|
+
{ Default: () => initialValueValue },
|
|
242
|
+
keyForm,
|
|
243
|
+
valueForm
|
|
244
|
+
).withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
|
|
245
|
+
.mapContext<any>(_ => ({ ..._, label: label }))
|
|
246
|
+
} else {
|
|
247
|
+
formConfig[fieldName] = FieldView(fieldConfig, fieldViews, viewType, viewName, fieldName, label, EnumOptionsSources, fieldsOptionsConfig, leafPredicates);
|
|
248
|
+
}
|
|
185
249
|
}
|
|
186
250
|
}
|
|
187
251
|
if (typeof formConfig[fieldName] == "string") {
|
|
@@ -336,6 +400,8 @@ export const parseForms =
|
|
|
336
400
|
const formFieldStreams = formConfig.fields.filter(field => field.api.stream != undefined).map(field => field.api.stream).toObject()
|
|
337
401
|
const formFieldEnumOptions = formConfig.fields.filter(field => field.api.enumOptions != undefined).map(field => field.api.enumOptions).toObject()
|
|
338
402
|
const formFieldElementRenderers = formConfig.fields.filter(field => field.elementRenderer != undefined).map(field => field.elementRenderer).toObject()
|
|
403
|
+
const formFieldKeyRenderers = formConfig.fields.filter(field => field.mapRenderer != undefined).map(field => field.mapRenderer?.keyRenderer).toObject()
|
|
404
|
+
const formFieldValueRenderers = formConfig.fields.filter(field => field.mapRenderer != undefined).map(field => field.mapRenderer?.valueRenderer).toObject()
|
|
339
405
|
|
|
340
406
|
try {
|
|
341
407
|
const parsedForm = ParseForm(
|
|
@@ -347,6 +413,8 @@ export const parseForms =
|
|
|
347
413
|
parsedForms,
|
|
348
414
|
formFieldRenderers,
|
|
349
415
|
formFieldElementRenderers,
|
|
416
|
+
formFieldKeyRenderers,
|
|
417
|
+
formFieldValueRenderers,
|
|
350
418
|
formFieldStreams,
|
|
351
419
|
formFieldEnumOptions,
|
|
352
420
|
infiniteStreamSources,
|
|
@@ -363,7 +431,7 @@ export const parseForms =
|
|
|
363
431
|
}).mapContext<Unit>(_ => ({ ..._, disabledFields: parsedForm.disabledFields, visibleFields: parsedForm.visibleFields, layout: formConfig.tabs }))
|
|
364
432
|
parsedForms = parsedForms.set(formName, { ...parsedForm, form })
|
|
365
433
|
} catch (error: any) {
|
|
366
|
-
errors.push(error)
|
|
434
|
+
errors.push(error.message ?? error)
|
|
367
435
|
}
|
|
368
436
|
})
|
|
369
437
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { List, Map } from "immutable";
|
|
2
|
+
import { simpleUpdater, Updater, SimpleCallback, Unit } from "../../../../../../main";
|
|
3
|
+
import { BasicFun } from "../../../../../fun/state";
|
|
4
|
+
import { Template, View } from "../../../../../template/state";
|
|
5
|
+
import { Value } from "../../../../../value/state";
|
|
6
|
+
import { FormLabel } from "../../../singleton/domains/form-label/state";
|
|
7
|
+
import { OnChange, SharedFormState } from "../../../singleton/state";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export type MapFieldState<K, V, KeyFormState, ValueFormState> =
|
|
11
|
+
SharedFormState &
|
|
12
|
+
{
|
|
13
|
+
elementFormStates: Map<number, { KeyFormState: KeyFormState, ValueFormState: ValueFormState }>
|
|
14
|
+
};
|
|
15
|
+
export const MapFieldState = <K, V, KeyFormState, ValueFormState>() => ({
|
|
16
|
+
Default: (
|
|
17
|
+
elementFormStates: MapFieldState<K, V, KeyFormState, ValueFormState>["elementFormStates"],
|
|
18
|
+
): MapFieldState<K, V, KeyFormState, ValueFormState> => ({
|
|
19
|
+
...SharedFormState.Default(),
|
|
20
|
+
elementFormStates
|
|
21
|
+
}),
|
|
22
|
+
Updaters: {
|
|
23
|
+
Core: {
|
|
24
|
+
...simpleUpdater<MapFieldState<K, V, KeyFormState, ValueFormState>>()("elementFormStates"),
|
|
25
|
+
},
|
|
26
|
+
Template: {
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
export type MapFieldView<K, V, KeyFormState, ValueFormState, Context extends FormLabel, ForeignMutationsExpected> =
|
|
31
|
+
View<
|
|
32
|
+
Context & Value<List<[K, V]>> & MapFieldState<K, V, KeyFormState, ValueFormState>,
|
|
33
|
+
MapFieldState<K, V, KeyFormState, ValueFormState>,
|
|
34
|
+
ForeignMutationsExpected & {
|
|
35
|
+
onChange: OnChange<List<[K, V]>>;
|
|
36
|
+
add: SimpleCallback<Unit>;
|
|
37
|
+
remove: SimpleCallback<number>;
|
|
38
|
+
}, {
|
|
39
|
+
embeddedKeyTemplate: BasicFun<number, Template<
|
|
40
|
+
Context & Value<List<[K, V]>> & MapFieldState<K, V, KeyFormState, ValueFormState>,
|
|
41
|
+
MapFieldState<K, V, KeyFormState, ValueFormState>,
|
|
42
|
+
ForeignMutationsExpected & {
|
|
43
|
+
onChange: OnChange<List<[K, V]>>;
|
|
44
|
+
add: SimpleCallback<Unit>;
|
|
45
|
+
remove: SimpleCallback<number>;
|
|
46
|
+
}>>
|
|
47
|
+
embeddedValueTemplate: BasicFun<number, Template<
|
|
48
|
+
Context & Value<List<[K, V]>> & MapFieldState<K, V, KeyFormState, ValueFormState>,
|
|
49
|
+
MapFieldState<K, V, KeyFormState, ValueFormState>,
|
|
50
|
+
ForeignMutationsExpected & {
|
|
51
|
+
onChange: OnChange<List<[K, V]>>;
|
|
52
|
+
add: SimpleCallback<Unit>;
|
|
53
|
+
remove: SimpleCallback<number>;
|
|
54
|
+
}>>
|
|
55
|
+
}>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { List } from "immutable";
|
|
2
|
+
import { SimpleCallback, BasicFun, Unit, ValidateRunner, Updater, BasicUpdater, MapRepo, ListRepo } from "../../../../../../main";
|
|
3
|
+
import { Template } from "../../../../../template/state";
|
|
4
|
+
import { Value } from "../../../../../value/state";
|
|
5
|
+
import { FormLabel } from "../../../singleton/domains/form-label/state";
|
|
6
|
+
import { FieldValidation, FieldValidationWithPath, OnChange } from "../../../singleton/state";
|
|
7
|
+
import { MapFieldState, MapFieldView } from "./state";
|
|
8
|
+
|
|
9
|
+
export const MapForm = <K, V, KeyFormState, ValueFormState, Context extends FormLabel, ForeignMutationsExpected>(
|
|
10
|
+
KeyFormState: { Default: () => KeyFormState },
|
|
11
|
+
ValueFormState: { Default: () => ValueFormState },
|
|
12
|
+
Key: { Default: () => K },
|
|
13
|
+
Value: { Default: () => V },
|
|
14
|
+
keyTemplate: Template<
|
|
15
|
+
Context & Value<K> & KeyFormState,
|
|
16
|
+
KeyFormState,
|
|
17
|
+
ForeignMutationsExpected & {
|
|
18
|
+
onChange: OnChange<K>;
|
|
19
|
+
}>,
|
|
20
|
+
valueTemplate: Template<
|
|
21
|
+
Context & Value<V> & ValueFormState,
|
|
22
|
+
ValueFormState,
|
|
23
|
+
ForeignMutationsExpected & {
|
|
24
|
+
onChange: OnChange<V>;
|
|
25
|
+
}>,
|
|
26
|
+
validation?: BasicFun<List<[K, V]>, Promise<FieldValidation>>,
|
|
27
|
+
) => {
|
|
28
|
+
const embeddedKeyTemplate = (elementIndex: number) =>
|
|
29
|
+
keyTemplate
|
|
30
|
+
.mapForeignMutations((_: ForeignMutationsExpected & {
|
|
31
|
+
onChange: OnChange<List<[K, V]>>;
|
|
32
|
+
add: SimpleCallback<Unit>;
|
|
33
|
+
remove: SimpleCallback<number>;
|
|
34
|
+
}): ForeignMutationsExpected & {
|
|
35
|
+
onChange: OnChange<K>;
|
|
36
|
+
} =>
|
|
37
|
+
({
|
|
38
|
+
..._,
|
|
39
|
+
onChange: (elementUpdater, path) => {
|
|
40
|
+
_.onChange(Updater((elements: List<[K, V]>) => elements.update(elementIndex, (_: [K, V] | undefined) => _ == undefined ? _ : [elementUpdater(_[0]), _[1]])), path)
|
|
41
|
+
},
|
|
42
|
+
add: (newElement: [K, V]) => { },
|
|
43
|
+
remove: (elementIndex: number) => { }
|
|
44
|
+
})
|
|
45
|
+
)
|
|
46
|
+
.mapContext((_: Context & Value<List<[K, V]>> & MapFieldState<K, V, KeyFormState, ValueFormState>): (Context & Value<K> & KeyFormState) | undefined => {
|
|
47
|
+
const element = _.value.get(elementIndex)
|
|
48
|
+
if (element == undefined) return undefined
|
|
49
|
+
const elementFormState = _.elementFormStates.get(elementIndex) || { KeyFormState: KeyFormState.Default(), ValueFormState: ValueFormState.Default() }
|
|
50
|
+
const elementContext: Context & Value<K> & KeyFormState = ({ ..._, ...elementFormState.KeyFormState, value: element[0] })
|
|
51
|
+
return elementContext
|
|
52
|
+
})
|
|
53
|
+
.mapState((_: BasicUpdater<KeyFormState>): Updater<MapFieldState<K, V, KeyFormState, ValueFormState>> =>
|
|
54
|
+
MapFieldState<K, V, KeyFormState, ValueFormState>().Updaters.Core.elementFormStates(
|
|
55
|
+
MapRepo.Updaters.upsert(elementIndex, () => ({ KeyFormState: KeyFormState.Default(), ValueFormState: ValueFormState.Default() }), current => ({ ...current, KeyFormState:_(current.KeyFormState) }))
|
|
56
|
+
))
|
|
57
|
+
const embeddedValueTemplate = (elementIndex: number) =>
|
|
58
|
+
valueTemplate
|
|
59
|
+
.mapForeignMutations((_: ForeignMutationsExpected & {
|
|
60
|
+
onChange: OnChange<List<[K, V]>>;
|
|
61
|
+
add: SimpleCallback<Unit>;
|
|
62
|
+
remove: SimpleCallback<number>;
|
|
63
|
+
}): ForeignMutationsExpected & {
|
|
64
|
+
onChange: OnChange<V>;
|
|
65
|
+
} =>
|
|
66
|
+
({
|
|
67
|
+
..._,
|
|
68
|
+
onChange: (elementUpdater, path) => {
|
|
69
|
+
_.onChange(Updater((elements: List<[K, V]>) => elements.update(elementIndex, (_: [K, V] | undefined) => _ == undefined ? _ : [_[0], elementUpdater(_[1])])), path)
|
|
70
|
+
},
|
|
71
|
+
add: (newElement: [K, V]) => { },
|
|
72
|
+
remove: (elementIndex: number) => { }
|
|
73
|
+
})
|
|
74
|
+
)
|
|
75
|
+
.mapContext((_: Context & Value<List<[K, V]>> & MapFieldState<K, V, KeyFormState, ValueFormState>): (Context & Value<V> & ValueFormState) | undefined => {
|
|
76
|
+
const element = _.value.get(elementIndex)
|
|
77
|
+
if (element == undefined) return undefined
|
|
78
|
+
const elementFormState = _.elementFormStates.get(elementIndex) || { KeyFormState: KeyFormState.Default(), ValueFormState: ValueFormState.Default() }
|
|
79
|
+
const elementContext: Context & Value<V> & ValueFormState = ({ ..._, ...elementFormState.ValueFormState, value: element[1] })
|
|
80
|
+
return elementContext
|
|
81
|
+
})
|
|
82
|
+
.mapState((_: BasicUpdater<ValueFormState>): Updater<MapFieldState<K, V, KeyFormState, ValueFormState>> =>
|
|
83
|
+
MapFieldState<K, V, KeyFormState, ValueFormState>().Updaters.Core.elementFormStates(
|
|
84
|
+
MapRepo.Updaters.upsert(elementIndex, () => ({ KeyFormState: KeyFormState.Default(), ValueFormState: ValueFormState.Default() }), current => ({ ...current, ValueFormState: _(current.ValueFormState) }))
|
|
85
|
+
))
|
|
86
|
+
return Template.Default<Context & Value<List<[K, V]>> & { disabled: boolean }, MapFieldState<K, V, KeyFormState, ValueFormState>, ForeignMutationsExpected & { onChange: OnChange<List<[K, V]>>; },
|
|
87
|
+
MapFieldView<K, V, KeyFormState, ValueFormState, Context, ForeignMutationsExpected>>(props => <>
|
|
88
|
+
<props.view {...props}
|
|
89
|
+
context={{
|
|
90
|
+
...props.context,
|
|
91
|
+
}}
|
|
92
|
+
foreignMutations={{
|
|
93
|
+
...props.foreignMutations,
|
|
94
|
+
add: (_) => {
|
|
95
|
+
props.foreignMutations.onChange(
|
|
96
|
+
ListRepo.Updaters.push([Key.Default(), Value.Default()]), List()
|
|
97
|
+
)
|
|
98
|
+
},
|
|
99
|
+
remove: (_) => {
|
|
100
|
+
props.foreignMutations.onChange(
|
|
101
|
+
ListRepo.Updaters.remove(_), List()
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
}}
|
|
105
|
+
embeddedKeyTemplate={embeddedKeyTemplate}
|
|
106
|
+
embeddedValueTemplate={embeddedValueTemplate}
|
|
107
|
+
/>
|
|
108
|
+
</>
|
|
109
|
+
).any([
|
|
110
|
+
ValidateRunner<Context & { disabled: boolean }, MapFieldState<K, V, KeyFormState, ValueFormState>, ForeignMutationsExpected, List<[K, V]>>(
|
|
111
|
+
validation ? _ => validation(_).then(FieldValidationWithPath.Default.fromFieldValidation) : undefined
|
|
112
|
+
),
|
|
113
|
+
]);
|
|
114
|
+
};
|