ballerina-core 1.0.33 → 1.0.34

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 CHANGED
@@ -91,6 +91,10 @@ 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/base-64-file/state"
95
+ export * from "./src/forms/domains/primitives/domains/base-64-file/template"
96
+ export * from "./src/forms/domains/primitives/domains/secret/state"
97
+ export * from "./src/forms/domains/primitives/domains/secret/template"
94
98
  export * from "./src/forms/domains/primitives/domains/map/state"
95
99
  export * from "./src/forms/domains/primitives/domains/map/template"
96
100
  export * from "./src/forms/domains/singleton/domains/mapping/state"
@@ -111,6 +115,7 @@ export * from "./src/forms/domains/launcher/domains/merger/state"
111
115
  export * from "./src/forms/domains/launcher/coroutines/runner"
112
116
  export * from "./src/forms/domains/launcher/state"
113
117
  export * from "./src/forms/domains/launcher/template"
118
+ export * from "./src/forms/domains/parser/domains/injectables/state"
114
119
 
115
120
  // import { simpleUpdater, simpleUpdaterWithChildren } from "./src/fun/domains/updater/domains/simpleUpdater/state"
116
121
  // import { Updater } from "./src/fun/domains/updater/state"
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "ballerina-core",
3
3
  "author": "Dr. Giuseppe Maggiore",
4
4
  "private": false,
5
- "version": "1.0.33",
5
+ "version": "1.0.34",
6
6
  "main": "main.ts",
7
7
  "dependencies": {
8
8
  "immutable": "^5.0.0-beta.5",
@@ -19,7 +19,7 @@ export const createFormRunner = <E, FS>() => {
19
19
  Co.Seq([
20
20
  Co.GetState().then(current =>
21
21
  Debounce<Synchronized<Unit, Synchronized<E, ApiErrors>>, CreateFormContext<E, FS>>(
22
- Synchronize<E, ApiErrors>(e => current.api.create(e), _ => "transient failure", 5, 50)
22
+ Synchronize<E, ApiErrors>(e => current.api.create([e, current.formState]), _ => "transient failure", 5, 50)
23
23
  .embed(
24
24
  _ => AsyncState.Operations.hasValue(_.sync) ? _.sync.value : undefined,
25
25
  _ => Synchronized.Updaters.sync<Unit, Synchronized<E, ApiErrors>>(AsyncState.Operations.map(_))
@@ -5,7 +5,7 @@ export type CreateFormContext<E,FS> = {
5
5
  entityId:string,
6
6
  api:{
7
7
  default:() => Promise<E>,
8
- create:BasicFun<E, Promise<ApiErrors>>
8
+ create:BasicFun<[E, FS], Promise<ApiErrors>>
9
9
  },
10
10
  actualForm:Template<Value<E> & FS, FS, { onChange:SimpleCallback<BasicUpdater<E>> }>
11
11
  }
@@ -40,7 +40,7 @@ export const editFormRunner = <E, FS>() => {
40
40
  Synchronize<Unit, ApiErrors, EditFormWritableState<E, FS>>(
41
41
  (_) =>
42
42
  current.entity.sync.kind == "loaded"
43
- ? current.api.update(current.entityId, current.entity.sync.value)
43
+ ? current.api.update(current.entityId, current.entity.sync.value, current.formState)
44
44
  : Promise.resolve([]),
45
45
  (_) => "transient failure",
46
46
  5,
@@ -7,7 +7,7 @@ export type EditFormContext<E,FS> = {
7
7
  entityId:string,
8
8
  api:{
9
9
  get:(id: Guid) => Promise<E>,
10
- update:(id: Guid, entity:E) => Promise<ApiErrors>
10
+ update:(id: Guid, entity:E, formstate: FS) => Promise<ApiErrors>
11
11
  },
12
12
  actualForm:Template<Value<E> & FS, FS, { onChange:SimpleCallback<BasicUpdater<E>>}>
13
13
  }
@@ -1,21 +1,23 @@
1
- import { AsyncState, builtInsFromFieldViews, FormsConfig, Sum, Synchronize, Unit } from "../../../../../main"
1
+ import { AsyncState, builtInsFromFieldViews, FormsConfig, injectablesFromFieldViews, Sum, Synchronize, Unit } from "../../../../../main"
2
2
  import { CoTypedFactory } from "../../../../coroutines/builder"
3
3
  import { FormParsingResult, FormsParserContext, FormsParserState, parseForms, replaceKeywords } from "../state"
4
4
 
5
- export const LoadValidateAndParseFormsConfig = () => {
6
- const Co = CoTypedFactory<FormsParserContext, FormsParserState>()
5
+ export const LoadValidateAndParseFormsConfig = <T extends {[key in keyof T] : {type: any, state: any}}>() => {
6
+ const Co = CoTypedFactory<FormsParserContext<T>, FormsParserState>()
7
7
 
8
8
  return Co.Template<Unit>(
9
9
  Co.GetState().then(current =>
10
10
  Synchronize<Unit, FormParsingResult>(async() => {
11
11
  const rawFormsConfig = await current.getFormsConfig();
12
12
  const formsConfig = replaceKeywords(rawFormsConfig, "from api")
13
- const builtIns = builtInsFromFieldViews(current.fieldViews, current.fieldTypeConverters)
14
- const validationResult = FormsConfig.Default.validateAndParseAPIResponse(builtIns)(formsConfig)
13
+ const builtIns = builtInsFromFieldViews(current.fieldViews)
14
+ const injectedPrimitives = current.injectedPrimitives ? injectablesFromFieldViews(current.fieldViews, current.injectedPrimitives) : undefined
15
+ const validationResult = FormsConfig.Default.validateAndParseAPIResponse(builtIns, current.fieldTypeConverters, injectedPrimitives)(formsConfig)
15
16
  if (validationResult.kind == "r")
16
17
  return Sum.Default.right(validationResult.value)
17
18
  return parseForms(
18
19
  builtIns,
20
+ injectedPrimitives,
19
21
  current.fieldTypeConverters,
20
22
  current.containerFormView,
21
23
  current.nestedContainerFormView,
@@ -23,7 +25,7 @@ export const LoadValidateAndParseFormsConfig = () => {
23
25
  current.infiniteStreamSources,
24
26
  current.enumOptionsSources,
25
27
  current.entityApis,
26
- current.leafPredicates)(validationResult.value)
28
+ current.leafPredicates,)(validationResult.value)
27
29
  }, _ => "transient failure", 5, 50)
28
30
  .embed(
29
31
  _ => _.formsConfig,
@@ -2,7 +2,7 @@ import { Map, List, Set, OrderedMap } from "immutable"
2
2
  import { CollectionReference } from "../../../collection/domains/reference/state";
3
3
  import { CollectionSelection } from "../../../collection/domains/selection/state";
4
4
  import { BasicFun } from "../../../../../fun/state";
5
- import { replaceKeyword, replaceKeywords, revertKeyword, Type, TypeDefinition, TypeName } from "../../../../../../main";
5
+ import { InjectedPrimitives, replaceKeyword, replaceKeywords, revertKeyword, Type, TypeDefinition, TypeName } from "../../../../../../main";
6
6
 
7
7
  export const PrimitiveTypes =
8
8
  ["string",
@@ -10,7 +10,10 @@ export const PrimitiveTypes =
10
10
  "boolean",
11
11
  "maybeBoolean",
12
12
  "Date",
13
- "CollectionReference"] as const
13
+ "CollectionReference",
14
+ "base64File",
15
+ "secret",
16
+ ] as const
14
17
  export type PrimitiveType = (typeof PrimitiveTypes)[number]
15
18
 
16
19
  export const GenericTypes = [
@@ -20,22 +23,25 @@ export const GenericTypes = [
20
23
  "Map"] as const
21
24
  export type GenericType = (typeof GenericTypes)[number]
22
25
 
23
- export type ApiConverter<T> = { fromAPIRawValue: BasicFun<any, T>, toAPIRawValue: BasicFun<T, any> }
24
- export type ApiConverters = {
26
+ export type ApiConverter<T> = { fromAPIRawValue: BasicFun<any, T>, toAPIRawValue: BasicFun<[T, boolean], any> }
27
+ export type ApiConverters<U> = {[key in keyof U]: ApiConverter<U[key]> } & BuiltInApiConverters
28
+ export type BuiltInApiConverters = {
25
29
  "string": ApiConverter<string>
26
30
  "number": ApiConverter<number>
27
31
  "boolean": ApiConverter<boolean>
28
32
  "maybeBoolean": ApiConverter<boolean | undefined>
33
+ "base64File": ApiConverter<string>
34
+ "secret": ApiConverter<string>,
29
35
  "Date": ApiConverter<Date>
30
36
  "CollectionReference": ApiConverter<CollectionReference>
31
37
  "SingleSelection": ApiConverter<CollectionSelection<any>>
32
38
  "MultiSelection": ApiConverter<OrderedMap<string, any>>
33
- "List": ApiConverter<List<any>>
39
+ "List": ApiConverter<List<any>>,
34
40
  "Map": ApiConverter<List<[any, any]>>
35
41
  }
36
42
 
37
- export type PrimitiveBuiltIn = { renderers: Set<keyof BuiltIns["renderers"]>, apiConverters: ApiConverter<any>, defaultValue: any }
38
- export type GenericBuiltIn = { defaultValue: any, apiConverters: ApiConverter<any> }
43
+ export type PrimitiveBuiltIn = { renderers: Set<keyof BuiltIns["renderers"]>, defaultValue: any }
44
+ export type GenericBuiltIn = { defaultValue: any }
39
45
  export type BuiltIns = {
40
46
  primitives: Map<string, PrimitiveBuiltIn>;
41
47
  generics: Map<string, GenericBuiltIn>;
@@ -44,6 +50,8 @@ export type BuiltIns = {
44
50
  maybeBoolean: Set<string>;
45
51
  number: Set<string>;
46
52
  string: Set<string>;
53
+ base64File: Set<string>;
54
+ secret: Set<string>;
47
55
  date: Set<string>;
48
56
  enumSingleSelection: Set<string>;
49
57
  enumMultiSelection: Set<string>;
@@ -54,22 +62,24 @@ export type BuiltIns = {
54
62
  };
55
63
  };
56
64
 
57
- export const builtInsFromFieldViews = (fieldViews: any, fieldTypeConverters: ApiConverters): BuiltIns => {
65
+ export const builtInsFromFieldViews = (fieldViews: any): BuiltIns => {
58
66
  let builtins: BuiltIns = {
59
67
  "primitives": Map<string, PrimitiveBuiltIn>([
60
- ["string", { renderers: Set(["string"]), apiConverters: fieldTypeConverters["string"], defaultValue: "" }] as [string, PrimitiveBuiltIn],
61
- ["number", { renderers: Set(["number"]), apiConverters: fieldTypeConverters["number"], defaultValue: 0 }] as [string, PrimitiveBuiltIn],
62
- ["boolean", { renderers: Set(["boolean"]), apiConverters: fieldTypeConverters["boolean"], defaultValue: false }],
63
- ["maybeBoolean", { renderers: Set(["maybeBoolean"]), apiConverters: fieldTypeConverters["maybeBoolean"], defaultValue: undefined }] as [string, PrimitiveBuiltIn],
64
- ["date", { renderers: Set(["date"]), apiConverters: fieldTypeConverters["Date"], defaultValue: new Date(Date.now()) }] as [string, PrimitiveBuiltIn],
65
- ["Date", { renderers: Set(["date"]), apiConverters: fieldTypeConverters["Date"], defaultValue: new Date(Date.now()) }] as [string, PrimitiveBuiltIn],
66
- ["CollectionReference", { renderers: Set(["enumSingleSelection", "enumMultiSelection", "streamSingleSelection", "streamMultiSelection"]), apiConverters: fieldTypeConverters["CollectionReference"], defaultValue: CollectionReference.Default("", "") }] as [string, PrimitiveBuiltIn]
68
+ ["string", { renderers: Set(["string"]), defaultValue: "" }] as [string, PrimitiveBuiltIn],
69
+ ["number", { renderers: Set(["number"]), defaultValue: 0 }] as [string, PrimitiveBuiltIn],
70
+ ["boolean", { renderers: Set(["boolean"]), defaultValue: false }],
71
+ ["maybeBoolean", { renderers: Set(["maybeBoolean"]), defaultValue: undefined }] as [string, PrimitiveBuiltIn],
72
+ ["date", { renderers: Set(["date"]), defaultValue: new Date(Date.now()) }] as [string, PrimitiveBuiltIn],
73
+ ["Date", { renderers: Set(["date"]), defaultValue: new Date(Date.now()) }] as [string, PrimitiveBuiltIn],
74
+ ["CollectionReference", { renderers: Set(["enumSingleSelection", "enumMultiSelection", "streamSingleSelection", "streamMultiSelection"]), defaultValue: CollectionReference.Default("", "") }] as [string, PrimitiveBuiltIn],
75
+ ["base64File", { renderers: Set(["base64File"]), defaultValue: "" }] as [string, PrimitiveBuiltIn],
76
+ ["secret", { renderers: Set(["secret"]), defaultValue: "" }] as [string, PrimitiveBuiltIn],
67
77
  ]),
68
78
  "generics": Map([
69
- ["SingleSelection", { apiConverters: fieldTypeConverters["SingleSelection"], defaultValue: CollectionSelection().Default.right("no selection") }] as [string, GenericBuiltIn],
70
- ["Multiselection", { apiConverters: fieldTypeConverters["SingleSelection"], defaultValue: Map() }] as [string, GenericBuiltIn],
71
- ["List", { apiConverters: fieldTypeConverters["SingleSelection"], defaultValue: List() }] as [string, GenericBuiltIn],
72
- ["Map", { apiConverters: fieldTypeConverters["SingleSelection"], defaultValue: List() }] as [string, GenericBuiltIn]
79
+ ["SingleSelection", { defaultValue: CollectionSelection().Default.right("no selection") }] as [string, GenericBuiltIn],
80
+ ["Multiselection", { defaultValue: Map() }] as [string, GenericBuiltIn],
81
+ ["List", { defaultValue: List() }] as [string, GenericBuiltIn],
82
+ ["Map", { defaultValue: List() }] as [string, GenericBuiltIn]
73
83
  ]),
74
84
  "renderers": {
75
85
  "boolean": Set(),
@@ -82,6 +92,8 @@ export const builtInsFromFieldViews = (fieldViews: any, fieldTypeConverters: Api
82
92
  "number": Set(),
83
93
  "string": Set(),
84
94
  "list": Set(),
95
+ "base64File": Set(),
96
+ "secret": Set(),
85
97
  "map": Set(),
86
98
  }
87
99
  }
@@ -96,12 +108,17 @@ export const builtInsFromFieldViews = (fieldViews: any, fieldTypeConverters: Api
96
108
  return builtins
97
109
  }
98
110
 
99
-
100
- export const defaultValue = (types: Map<TypeName, TypeDefinition>, builtIns: BuiltIns) => (t: TypeName | Type): any => {
111
+ export const defaultValue = <T>(types: Map<TypeName, TypeDefinition>, builtIns: BuiltIns, injectedPrimitives?: InjectedPrimitives<T>) => (t: TypeName | Type): any => {
101
112
  if (typeof t == "string") {
102
113
  let primitive = builtIns.primitives.get(t)
114
+ let injectedPrimitive = injectedPrimitives?.injectedPrimitives.get(t as keyof T)
115
+ if(primitive && injectedPrimitive) {
116
+ throw `both primitive and injected primitive are defined for ${t}`
117
+ }
103
118
  if (primitive != undefined) {
104
119
  return primitive.defaultValue
120
+ } else if (injectedPrimitive != undefined) {
121
+ return injectedPrimitive.defaultValue
105
122
  } else {
106
123
  let generic = builtIns.generics.get(t)
107
124
  if (generic != undefined) {
@@ -143,11 +160,11 @@ const parseTypeIShouldBePartOfFormValidation = (t:any) : TypeName | Type => {
143
160
  return null!
144
161
  }
145
162
 
146
- export const fromAPIRawValue = (t: Type, types: Map<TypeName, TypeDefinition>, builtIns: BuiltIns, converters: ApiConverters, isKeywordsReplaced: boolean = false) => (raw: any): any => {
163
+ export const fromAPIRawValue = <T>(t: Type, types: Map<TypeName, TypeDefinition>, builtIns: BuiltIns, converters: BuiltInApiConverters, isKeywordsReplaced: boolean = false, injectedPrimitives?: InjectedPrimitives<T>) => (raw: any): any => {
147
164
  // alert(JSON.stringify(t))
148
165
  if (raw == undefined) {
149
166
  console.warn(`instantiating default value for type ${JSON.stringify(t)}: the value was undefined so something is missing from the API response`)
150
- return defaultValue(types, builtIns)(t.kind == "primitive" ? t.value : t.kind == "lookup" ? t.name : t.value)
167
+ return defaultValue(types, builtIns, injectedPrimitives)(t.kind == "primitive" ? t.value : t.kind == "lookup" ? t.name : t.value)
151
168
  }
152
169
 
153
170
  const obj = !isKeywordsReplaced ? replaceKeywords(raw, "from api") : raw
@@ -158,43 +175,46 @@ export const fromAPIRawValue = (t: Type, types: Map<TypeName, TypeDefinition>, b
158
175
  if (t.value == "SingleSelection" && t.args.length == 1) {
159
176
  let result = converters[t.value].fromAPIRawValue(obj)
160
177
  result = CollectionSelection().Updaters.left(
161
- fromAPIRawValue({ kind: "lookup", name: t.args[0] }, types, builtIns, converters, true))(result)
178
+ fromAPIRawValue({ kind: "lookup", name: t.args[0] }, types, builtIns, converters, true, injectedPrimitives))(result)
162
179
  return result
163
180
  }
164
181
  if ((t.value == "Multiselection" || t.value == "MultiSelection") && t.args.length == 1) {
165
182
  let result = converters["MultiSelection"].fromAPIRawValue(obj)
166
- result = result.map(fromAPIRawValue({ kind: "lookup", name: t.args[0] }, types, builtIns, converters, true))
183
+ result = result.map(fromAPIRawValue({ kind: "lookup", name: t.args[0] }, types, builtIns, converters, true, injectedPrimitives))
167
184
  return result
168
185
  }
169
186
  if (t.value == "List" && t.args.length == 1) {
170
187
  let result = converters[t.value].fromAPIRawValue(obj)
188
+ const isPrimitive = PrimitiveTypes.some(_ => _ == t.args[0]) || injectedPrimitives?.injectedPrimitives.has(t.args[0] as keyof T)
171
189
  result = result.map(fromAPIRawValue(
172
- PrimitiveTypes.some(_ => _ == t.args[0]) ?
190
+ isPrimitive ?
173
191
  { kind: "primitive", value: t.args[0] as PrimitiveType }
174
192
  : { kind: "lookup", name: t.args[0] }
175
- , types, builtIns, converters, true))
193
+ , types, builtIns, converters, true, injectedPrimitives))
176
194
  return result
177
195
  }
178
196
  if (t.value == "Map" && t.args.length == 2) {
179
197
  let result = converters[t.value].fromAPIRawValue(obj)
180
198
  let t_args = t.args.map(parseTypeIShouldBePartOfFormValidation)
199
+ const isKeyPrimitive = PrimitiveTypes.some(_ => _ == t.args[0]) || injectedPrimitives?.injectedPrimitives.has(t.args[0] as keyof T)
200
+ const isValuePrimitive = PrimitiveTypes.some(_ => _ == t.args[1]) || injectedPrimitives?.injectedPrimitives.has(t.args[1] as keyof T)
181
201
  result = result.map(keyValue => ([
182
202
  fromAPIRawValue(
183
203
  typeof t_args[0] == "string" ?
184
- PrimitiveTypes.some(_ => _ == t_args[0]) ?
204
+ isKeyPrimitive ?
185
205
  { kind: "primitive", value: t_args[0] as PrimitiveType }
186
206
  : { kind: "lookup", name: t_args[0] }
187
207
  :
188
208
  t_args[0],
189
- types, builtIns, converters, true)(keyValue[0]),
209
+ types, builtIns, converters, true, injectedPrimitives)(keyValue[0]),
190
210
  fromAPIRawValue(
191
211
  typeof t_args[1] == "string" ?
192
- PrimitiveTypes.some(_ => _ == t_args[1]) ?
212
+ isValuePrimitive ?
193
213
  { kind: "primitive", value: t_args[1] as PrimitiveType }
194
214
  : { kind: "lookup", name: t_args[1] }
195
215
  :
196
216
  t_args[1],
197
- types, builtIns, converters, true)(keyValue[1]),
217
+ types, builtIns, converters, true, injectedPrimitives)(keyValue[1]),
198
218
  ])
199
219
  )
200
220
  return result
@@ -205,7 +225,7 @@ export const fromAPIRawValue = (t: Type, types: Map<TypeName, TypeDefinition>, b
205
225
  tDef.fields.forEach((fieldType, fieldName) => {
206
226
  const replacedFieldName = replaceKeyword(fieldName)
207
227
  const fieldValue = obj[replacedFieldName]
208
- result[replacedFieldName] = fromAPIRawValue(fieldType, types, builtIns, converters, true)(fieldValue)
228
+ result[replacedFieldName] = fromAPIRawValue(fieldType, types, builtIns, converters, true, injectedPrimitives)(fieldValue)
209
229
  })
210
230
  return result
211
231
  }
@@ -214,64 +234,67 @@ export const fromAPIRawValue = (t: Type, types: Map<TypeName, TypeDefinition>, b
214
234
  }
215
235
 
216
236
 
217
- export const toAPIRawValue = (t: Type, types: Map<TypeName, TypeDefinition>, builtIns: BuiltIns, converters: ApiConverters, isKeywordsReverted: boolean = false) => (raw: any): any => {
218
-
237
+ export const toAPIRawValue = <T>(t: Type, types: Map<TypeName, TypeDefinition>, builtIns: BuiltIns, converters: BuiltInApiConverters, isKeywordsReverted: boolean = false, injectedPrimitives?: InjectedPrimitives<T>) => (raw: any, formState: any) : any => {
219
238
  const obj = !isKeywordsReverted ? replaceKeywords(raw, "to api") : raw
220
239
 
221
240
  if (t.kind == "primitive") {
222
- return converters[t.value].toAPIRawValue(obj as never)
241
+ return converters[t.value].toAPIRawValue([obj, formState.modifiedByUser] as never)
223
242
  } else if (t.kind == "application") { // application here means "generic type application"
224
243
  if (t.value == "SingleSelection" && t.args.length == 1) {
225
- let result = converters[t.value].toAPIRawValue(obj)
226
- if (result != undefined && typeof result == "object")
227
- result = toAPIRawValue({ kind: "lookup", name: t.args[0] }, types, builtIns, converters, true)(result)
244
+ let result = converters[t.value].toAPIRawValue([obj, formState.modifiedByUser])
245
+ if (result != undefined && typeof result == "object"){
246
+ result = toAPIRawValue({ kind:"lookup", name:t.args[0] }, types, builtIns, converters, true, injectedPrimitives)(result, formState)
247
+ }
228
248
  return result
229
249
  }
230
250
  if ((t.value == "Multiselection" || t.value == "MultiSelection") && t.args.length == 1) {
231
251
  // alert(`MultiSelect ${JSON.stringify(t)} ${JSON.stringify(obj)}`)
232
- let result = converters["MultiSelection"].toAPIRawValue(obj)
252
+ let result = converters["MultiSelection"].toAPIRawValue([obj, formState.modifiedByUser])
233
253
  // alert(`MultiSelect result1 = ${JSON.stringify(result)}`)
234
254
  // alert(`${JSON.stringify(t.args[0])}`)
235
- result = result.map((_: any) =>
236
- typeof _ == "object" ? toAPIRawValue({ kind: "lookup", name: t.args[0] }, types, builtIns, converters, true)(_) : _)
255
+ result = result.map((_:any) =>
256
+ typeof _ == "object" ? toAPIRawValue({ kind:"lookup", name: t.args[0] }, types, builtIns, converters, true, injectedPrimitives)(_, formState) : _)
237
257
  // alert(`MultiSelect result2 = ${JSON.stringify(result)}`)
238
258
  return result
239
259
  }
240
260
  if (t.value == "List" && t.args.length == 1) {
241
- let result = converters[t.value].toAPIRawValue(obj)
242
- result = result.map(toAPIRawValue(
243
- PrimitiveTypes.some(_ => _ == t.args[0]) ?
244
- { kind: "primitive", value: t.args[0] as PrimitiveType }
245
- : { kind: "lookup", name: t.args[0] },
246
- // { kind:"lookup", name:t.args[0] },
247
- types, builtIns, converters, true))
248
- return result
261
+ const converterResult = converters[t.value].toAPIRawValue([obj, formState.modifiedByUser])
262
+ const isPrimitive = PrimitiveTypes.some(_ => _ == t.args[0]) || injectedPrimitives?.injectedPrimitives.has(t.args[0] as keyof T)
263
+ return converterResult.map((item: any, index: number) =>
264
+ toAPIRawValue(
265
+ isPrimitive ?
266
+ { kind:"primitive", value:t.args[0] as PrimitiveType }
267
+ : { kind:"lookup", name:t.args[0] },
268
+ types, builtIns, converters, true, injectedPrimitives)(item,
269
+ formState.elementFormStates.get(index)
270
+ ))
249
271
  }
250
272
  if (t.value == "Map" && t.args.length == 2) {
251
- let result = converters[t.value].toAPIRawValue(obj)
252
-
273
+ const converterResult = converters[t.value].toAPIRawValue([obj, formState.modifiedByUser])
274
+ const isKeyPrimitive = PrimitiveTypes.some(_ => _ == t.args[0]) || injectedPrimitives?.injectedPrimitives.has(t.args[0] as keyof T)
275
+ const isValuePrimitive = PrimitiveTypes.some(_ => _ == t.args[1]) || injectedPrimitives?.injectedPrimitives.has(t.args[1] as keyof T)
253
276
  let t_args = t.args.map(parseTypeIShouldBePartOfFormValidation)
254
- result = result.map((keyValue: any) => ([
277
+ return converterResult.map((keyValue: any, index: number) => ([
255
278
  toAPIRawValue(
256
279
  typeof t_args[0] == "string" ?
257
- PrimitiveTypes.some(_ => _ == t_args[0]) ?
280
+ isKeyPrimitive ?
258
281
  { kind: "primitive", value: t_args[0] as PrimitiveType }
259
282
  : { kind: "lookup", name: t_args[0] }
260
283
  :
261
284
  t_args[0],
262
- types, builtIns, converters, true)(keyValue[0]),
285
+ types, builtIns, converters, true, injectedPrimitives)(keyValue[0], formState.elementFormStates.get(index).KeyFormState
286
+ ),
263
287
  toAPIRawValue(
264
288
  typeof t_args[1] == "string" ?
265
- PrimitiveTypes.some(_ => _ == t_args[1]) ?
289
+ isValuePrimitive ?
266
290
  { kind: "primitive", value: t_args[1] as PrimitiveType }
267
291
  : { kind: "lookup", name: t_args[1] }
268
292
  :
269
293
  t_args[1],
270
- types, builtIns, converters, true)(keyValue[1]),
294
+ types, builtIns, converters, true, injectedPrimitives)(keyValue[1], formState.elementFormStates.get(index).ValueFormState
295
+ ),
271
296
  ])
272
297
  )
273
-
274
- return result
275
298
  }
276
299
 
277
300
  } else { // t.kind == lookup: we are dealing with a record/object
@@ -280,7 +303,7 @@ export const toAPIRawValue = (t: Type, types: Map<TypeName, TypeDefinition>, bui
280
303
  tDef.fields.forEach((fieldType, fieldName) => {
281
304
  const revertedFieldName = revertKeyword(fieldName)
282
305
  const fieldValue = obj[revertedFieldName]
283
- result[revertedFieldName] = toAPIRawValue(fieldType, types, builtIns, converters, true)(fieldValue)
306
+ result[revertedFieldName] = toAPIRawValue(fieldType, types, builtIns, converters, true, injectedPrimitives)(fieldValue, formState[fieldName])
284
307
  })
285
308
  return result
286
309
  }
@@ -0,0 +1,42 @@
1
+ import { Map, Set } from 'immutable';
2
+ import { Unit } from '../../../../../../main';
3
+
4
+ export type InjectablePrimitive<T, U = Unit> = {
5
+ defaultValue: T;
6
+ fieldView: any;
7
+ defaultState?: U;
8
+ }
9
+
10
+ export type InjectedPrimitive<T> = {
11
+ renderers: Set<keyof T>;
12
+ defaultValue: any;
13
+ fieldView: any;
14
+ defaultState: any;
15
+ }
16
+
17
+ export type Injectables<T extends {[key in keyof T] : {type: any, state: any}}> = Map<keyof T, InjectablePrimitive<T[keyof T]["type"], T[keyof T]["state"]>>;
18
+
19
+ export type InjectedPrimitives<T> =
20
+ {
21
+ injectedPrimitives: Map<keyof T, InjectedPrimitive<T>>;
22
+ renderers: {
23
+ [key in keyof T]: Set<string>;
24
+ }
25
+ }
26
+
27
+ export const injectablesFromFieldViews = <T extends {[key in keyof T] : {type: any, state: any}}>(fieldViews: any, injectables: Injectables<T>): InjectedPrimitives<T> => {
28
+ let result = {injectedPrimitives: Map<string, InjectedPrimitive<T>>(), renderers: {}} as InjectedPrimitives<T>;
29
+ result.injectedPrimitives = injectables.map(((injectable, key) =>
30
+ ({ renderers: Set([key]), defaultValue: injectable.defaultValue, fieldView: injectable.fieldView, defaultState: injectable.defaultState }) as InjectedPrimitive<T>
31
+ ))
32
+ result.renderers = { ...injectables.map((_, key) => ({[key]: Set<string>()})).valueSeq().toArray().reduce((acc, x) => ({...acc, ...x}), {}) } as InjectedPrimitives<T>['renderers']
33
+ Object.keys(result.renderers).forEach((_categoryName) => {
34
+ const categoryName = _categoryName
35
+ if (categoryName in fieldViews) {
36
+ Object.keys(fieldViews[categoryName]).forEach(viewName => {
37
+ result.renderers[categoryName as keyof T] = result.renderers[categoryName as keyof T].add(viewName)
38
+ })
39
+ }
40
+ })
41
+ return result
42
+ }
@@ -1,5 +1,6 @@
1
1
  import { Map, OrderedMap } from "immutable";
2
2
  import { BuiltIns } from "../built-ins/state";
3
+ import { InjectedPrimitives } from "../injectables/state";
3
4
 
4
5
  export type FieldName = string;
5
6
  export type TypeName = string;
@@ -8,7 +9,7 @@ export type TypeDefinition = {
8
9
  name: TypeName;
9
10
  fields: OrderedMap<FieldName, Type>;
10
11
  };
11
- export type PrimitiveTypeName = "string" | "number" | "maybeBoolean" | "boolean" | "Date" | "CollectionReference"
12
+ export type PrimitiveTypeName = "string" | "number" | "maybeBoolean" | "boolean" | "Date" | "CollectionReference" | "base64File" | "secret";
12
13
  export type Type = {
13
14
  kind: "lookup"; name: TypeName;
14
15
  } | {
@@ -31,10 +32,10 @@ export const Type = {
31
32
  fst.args.length == snd.args.length &&
32
33
  fst.args.every((v, i) => v == snd.args[i]) :
33
34
  false,
34
- FromName: (types: Map<string, TypeDefinition>, builtIns: BuiltIns) => (typeName: string): Type | undefined => {
35
+ FromName: <T>(types: Map<string, TypeDefinition>, builtIns: BuiltIns, injectedPrimitives?: InjectedPrimitives<T>) => (typeName: string): Type | undefined => {
35
36
  const recordTypeName = types.get(typeName)?.name
36
37
  if (recordTypeName) return Type.Default.lookup(recordTypeName)
37
- const primitiveTypeName = builtIns.primitives.get(typeName) && typeName
38
+ const primitiveTypeName = (builtIns.primitives.get(typeName) && typeName) ?? (injectedPrimitives?.injectedPrimitives.get(typeName as keyof T) && typeName)
38
39
  if (primitiveTypeName) return Type.Default.primitive(primitiveTypeName as any)
39
40
  return undefined
40
41
  }
@@ -1,11 +1,12 @@
1
1
  import { Set, Map, OrderedMap } from "immutable";
2
- import { BoolExpr, BuiltIns, FieldName, FormsConfigMerger, MappingPaths, Sum, Type, TypeDefinition, TypeName } from "../../../../../../main";
2
+ import { ApiConverters, BoolExpr, BuiltIns, FieldName, FormsConfigMerger, InjectedPrimitives, MappingPaths, revertKeyword, Sum, Type, TypeDefinition, TypeName } from "../../../../../../main";
3
3
 
4
4
  export type FieldConfig = {
5
5
  renderer: string;
6
6
  label: string
7
7
  api: { stream?: string, enumOptions?: string },
8
8
  elementRenderer?: string,
9
+ elementLabel?: string,
9
10
  mapRenderer?: { keyRenderer: FieldConfig, valueRenderer: FieldConfig },
10
11
  visible: BoolExpr<any>;
11
12
  disabled: BoolExpr<any>;
@@ -16,6 +17,7 @@ export type FormDef = {
16
17
  typeDef: TypeDefinition;
17
18
  fields: Map<FieldName, FieldConfig>;
18
19
  tabs: FormLayout;
20
+ header?: string;
19
21
  };
20
22
  export type FormLayout = OrderedMap<string, TabLayout>
21
23
  export type GroupLayout = Array<FieldName>;
@@ -66,7 +68,7 @@ export type FormValidationError = string;
66
68
  export type FormValidationResult = Sum<FormsConfig, Array<FormValidationError>>
67
69
  export const FormsConfig = {
68
70
  Default: {
69
- validateAndParseAPIResponse: (builtIns: BuiltIns) => (fc: any): FormValidationResult => {
71
+ validateAndParseAPIResponse: <T>(builtIns: BuiltIns, apiConverters: ApiConverters<T>, injectedPrimitives?: InjectedPrimitives<T>) => (fc: any): FormValidationResult => {
70
72
  let errors: Array<FormValidationError> = [];
71
73
  const formsConfig = Array.isArray(fc) ? FormsConfigMerger.Default.merge(fc) : fc;
72
74
  let types: Map<TypeName, TypeDefinition> = Map();
@@ -74,6 +76,12 @@ export const FormsConfig = {
74
76
  errors.push("the formsConfig does not contain a 'types' field");
75
77
  return Sum.Default.right(errors);
76
78
  }
79
+ if(injectedPrimitives){
80
+ injectedPrimitives?.injectedPrimitives.keySeq().toArray().some((injectedPrimitiveName) => {
81
+ if(!Object.keys(apiConverters).includes(injectedPrimitiveName as string)){
82
+ errors.push(`the formsConfig does not contain an Api Converter for injected primitive: ${injectedPrimitiveName as string}`);
83
+ }})
84
+ }
77
85
  Object.keys(formsConfig["types"]).forEach((typeName: any) => {
78
86
  let typeDef: TypeDefinition = { name: typeName, extends: [], fields: OrderedMap() };
79
87
  types = types.set(typeName, typeDef);
@@ -91,9 +99,12 @@ export const FormsConfig = {
91
99
  Object.keys(configTypeDef["fields"]).forEach((fieldName: any) => {
92
100
  let configFieldType = configTypeDef["fields"][fieldName];
93
101
  if (typeof configFieldType == "string") {
94
- if (builtIns.primitives.has(configFieldType))
102
+ if (injectedPrimitives?.injectedPrimitives.has(configFieldType as keyof T) &&
103
+ (builtIns.primitives.has(configFieldType) || builtIns.generics.has(configFieldType))) {
104
+ errors.push(`field ${fieldName} in type ${typeName}: injectedPrimitive cannot have same name as builtIn primitive`);
105
+ }
106
+ if (builtIns.primitives.has(configFieldType) || injectedPrimitives?.injectedPrimitives.has(configFieldType as keyof T))
95
107
  typeDef.fields = typeDef.fields.set(fieldName, { kind: "primitive", value: configFieldType as any });
96
-
97
108
  else
98
109
  typeDef.fields = typeDef.fields.set(fieldName, { kind: "lookup", name: configFieldType as any });
99
110
  } else if (typeof configFieldType == "object") {
@@ -117,11 +128,11 @@ export const FormsConfig = {
117
128
  });
118
129
  types.forEach((typeDef, typeName) => {
119
130
  typeDef.extends.forEach(extendedTypeName => {
120
- if (!builtIns.primitives.has(extendedTypeName) && !types.has(extendedTypeName))
131
+ if ((!builtIns.primitives.has(extendedTypeName) && !injectedPrimitives?.injectedPrimitives.has(extendedTypeName as keyof T)) && !types.has(extendedTypeName))
121
132
  errors.push(`type ${typeName} extends non-existent type ${extendedTypeName}`);
122
133
  });
123
134
  typeDef.fields.forEach((fieldDef, fieldName) => {
124
- if (fieldDef.kind == "primitive" && !builtIns.primitives.has(fieldDef.value))
135
+ if (fieldDef.kind == "primitive" && (!builtIns.primitives.has(fieldDef.value) && !injectedPrimitives?.injectedPrimitives.has(fieldDef.value as keyof T) ))
125
136
  errors.push(`field ${fieldName} of type ${typeName} is non-existent primitive type ${fieldDef.value}`);
126
137
  if (fieldDef.kind == "lookup" && !types.has(fieldDef.name))
127
138
  errors.push(`field ${fieldName} of type ${typeName} is non-existent type ${fieldDef.name}`);
@@ -300,7 +311,11 @@ export const FormsConfig = {
300
311
 
301
312
  const rendererMatchesType = (formName:string, fieldName:string) => (fieldTypeDef:Type, fieldConfig:any) => {
302
313
  if (fieldTypeDef?.kind == "primitive") {
303
- if (fieldTypeDef.value == "maybeBoolean") {
314
+ if(injectedPrimitives?.injectedPrimitives.has(fieldTypeDef.value as keyof T)){
315
+ if (!injectedPrimitives.renderers[fieldTypeDef.value as keyof T].has(fieldConfig["renderer"]))
316
+ errors.push(`field ${fieldName} of form ${formName} references non-existing injected primitive 'renderer' ${fieldConfig["renderer"]}`);
317
+ }
318
+ else if (fieldTypeDef.value == "maybeBoolean") {
304
319
  // alert(JSON.stringify(fieldConfig["renderer"]))
305
320
  // alert(JSON.stringify(builtIns.renderers.MaybeBooleanViews))
306
321
  if (!builtIns.renderers.maybeBoolean.has(fieldConfig["renderer"]))
@@ -322,6 +337,10 @@ export const FormsConfig = {
322
337
  } else {
323
338
  errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
324
339
  }
340
+ if(injectedPrimitives?.injectedPrimitives.has(fieldTypeDef.value as keyof T)){
341
+ if (!injectedPrimitives.renderers[fieldTypeDef.value as keyof T].has(fieldConfig["renderer"]))
342
+ errors.push(`field ${fieldName} of form ${formName} references non-existing injected primitive 'renderer' ${fieldConfig["renderer"]}`);
343
+ }
325
344
  } else if (fieldTypeDef?.kind == "application") {
326
345
  if (fieldTypeDef?.value == "SingleSelection") {
327
346
  if (!builtIns.renderers.enumSingleSelection.has(fieldConfig["renderer"]) &&
@@ -332,8 +351,9 @@ export const FormsConfig = {
332
351
  !builtIns.renderers.streamMultiSelection.has(fieldConfig["renderer"]))
333
352
  errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
334
353
  } 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"]}`);
354
+ if (!builtIns.renderers.list.has(fieldConfig["renderer"]) ){
355
+ errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`)
356
+ }
337
357
  } else if (fieldTypeDef?.value == "Map") {
338
358
  if (!builtIns.renderers.map.has(fieldConfig["renderer"]))
339
359
  errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
@@ -347,8 +367,8 @@ export const FormsConfig = {
347
367
  : "args" in typeDef == false ? undefined
348
368
  : Array.isArray(typeDef.args) == false ? undefined
349
369
  : 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)
370
+ const keyType:Type | undefined = typeof fieldTypeDef.args[0] == "string" ? Type.Operations.FromName(types, builtIns, injectedPrimitives)(fieldTypeDef.args[0]) : typeDefToType(fieldTypeDef.args[0] as any)
371
+ const valueType:Type | undefined = typeof fieldTypeDef.args[1] == "string" ? Type.Operations.FromName(types, builtIns, injectedPrimitives)(fieldTypeDef.args[1]) : typeDefToType(fieldTypeDef.args[1] as any)
352
372
  if (!keyType) {
353
373
  errors.push(`field ${fieldName} of form ${formName} references non-existing key type ${JSON.stringify(fieldTypeDef.args[0])}`);
354
374
  } else if (!valueType) {
@@ -405,6 +425,9 @@ export const FormsConfig = {
405
425
  let formDef: FormDef = forms.get(formName)!
406
426
  const formTypeDef = types.get(formDef.type)
407
427
  const configFormDef = formsConfig["forms"][formName];
428
+ if (formsConfig["forms"][formName].header){
429
+ formDef.header = formsConfig["forms"][formName].header
430
+ }
408
431
  Object.keys(configFormDef["fields"]).forEach(fieldName => {
409
432
  const fieldConfig = configFormDef["fields"][fieldName]
410
433
  const fieldTypeDef = formTypeDef?.fields.get(fieldName);
@@ -415,9 +438,12 @@ export const FormsConfig = {
415
438
  let elementType = fieldTypeDef.args[0]
416
439
  const rendererHasType = (elementRenderer: string, elementType: string): Array<string> => {
417
440
  const primitiveRendererNames = builtIns.primitives.get(elementType)
418
- if (primitiveRendererNames != undefined) {
441
+ const injectedPrimitiveRendererNames = injectedPrimitives?.injectedPrimitives.get(elementType as keyof T)
442
+ if (primitiveRendererNames != undefined || injectedPrimitiveRendererNames != undefined) {
419
443
  const primitiveRenderers =
420
- Set(primitiveRendererNames.renderers.flatMap(_ => builtIns.renderers[_]).toArray())
444
+ Set(primitiveRendererNames ? primitiveRendererNames.renderers.flatMap(_ => builtIns.renderers[_]).toArray() : []).concat(
445
+ injectedPrimitives ? Set(injectedPrimitiveRendererNames?.renderers.flatMap(_ => injectedPrimitives.renderers[_])).toArray() : []
446
+ )
421
447
  if (!primitiveRenderers.has(elementRenderer)) {
422
448
  return [`${elementType} cannot be rendered by primitive renderer ${elementRenderer}`]
423
449
  }
@@ -439,18 +465,19 @@ export const FormsConfig = {
439
465
  }
440
466
  if (fieldTypeDef?.kind == "lookup") {
441
467
  if (!forms.has(fieldConfig.renderer))
442
- errors.push(`field ${fieldName} of form ${formName} references non-existing form ${fieldConfig["renderer"]}`);
468
+ errors.push(`field ${revertKeyword(fieldName)} of form ${formName} references non-existing form ${fieldConfig["renderer"]}`);
443
469
  else {
444
470
  const otherForm = forms.get(fieldConfig.renderer)!
445
471
  if (otherForm.type != fieldTypeDef.name)
446
- errors.push(`field ${fieldName} of form ${formName} references form ${fieldConfig["renderer"]}, which has type ${otherForm.type} whereas ${fieldTypeDef.name} was expected`);
472
+ errors.push(`field ${revertKeyword(fieldName)} of form ${formName} references form ${fieldConfig["renderer"]}, which has type ${otherForm.type} whereas ${fieldTypeDef.name} was expected`);
447
473
  }
448
474
  }
449
475
  formDef.fields = formDef.fields.set(
450
476
  fieldName, {
451
477
  renderer: fieldConfig.renderer,
452
- label: fieldConfig.label ?? fieldName,
478
+ label: fieldConfig.label ?? revertKeyword(fieldName),
453
479
  elementRenderer: fieldConfig.elementRenderer,
480
+ elementLabel: fieldConfig.elementLabel,
454
481
  mapRenderer:
455
482
  fieldConfig.keyRenderer && fieldConfig.valueRenderer ?
456
483
  { keyRenderer:fieldConfig.keyRenderer, valueRenderer:fieldConfig.valueRenderer }
@@ -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, MapFieldState, MapForm, Type, FieldConfig } 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, BuiltInApiConverters, defaultValue, fromAPIRawValue, toAPIRawValue, EditFormForeignMutationsExpected, MapFieldState, MapForm, Type, FieldConfig, Base64FileForm, SecretForm, InjectedPrimitives, Injectables, ApiConverters } 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
- (fieldConfig:FieldConfig, fieldViews: any, viewType: any, viewName: any, fieldName: string, label: string, enumFieldConfigs: EnumOptionsSources, enumSources: any, leafPredicates: any): any => // FieldView<Context, FieldViews, ViewType, ViewName> =>
27
+ <T,>(fieldConfig:FieldConfig, fieldViews: any, viewType: any, viewName: any, fieldName: string, label: string, enumFieldConfigs: EnumOptionsSources, enumSources: any, leafPredicates: any, injectedPrimitives?: InjectedPrimitives<T>): any =>
28
28
  {
29
29
  if (viewType == "maybeBoolean")
30
30
  return MaybeBooleanForm<any & FormLabel, Unit>()
@@ -69,13 +69,35 @@ export const FieldView = //<Context, FieldViews extends DefaultFieldViews, EnumF
69
69
  return InfiniteMultiselectDropdownForm<CollectionReference, any & FormLabel, Unit>()
70
70
  .withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
71
71
  .mapContext<any & FormLabel & SharedFormState & SearchableInfiniteStreamState<CollectionReference> & Value<OrderedMap<Guid, CollectionReference>>>(_ => ({ ..._, label: label })) as any
72
+ if (viewType == "base64File")
73
+ return Base64FileForm<any & FormLabel, Unit>()
74
+ .withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
75
+ .mapContext<any & FormLabel & SharedFormState & Value<string>>(_ => ({ ..._, label: label })) as any
76
+ if (viewType == "secret")
77
+ return SecretForm<any & FormLabel, Unit>()
78
+ .withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
79
+ .mapContext<any & FormLabel & SharedFormState & Value<string>>(_ => ({ ..._, label: label })) as any
80
+ // check injectedViews
81
+ if (injectedPrimitives?.injectedPrimitives.has(viewType)) {
82
+ const injectedPrimitive = injectedPrimitives.injectedPrimitives.get(viewType)
83
+ return injectedPrimitive?.fieldView(fieldViews, viewType, viewName, label) as any
84
+ }
72
85
  return `error: the view for ${viewType as string}::${viewName as string} cannot be found`;
73
86
  }
74
87
 
75
88
  export const FieldFormState = //<Context, FieldViews extends DefaultFieldViews, InfiniteStreamSources extends {}, InfiniteStreamConfigs extends {}>() => <ViewType extends keyof FieldViews, ViewName extends keyof FieldViews[ViewType]>
76
- (fieldConfig:FieldConfig, fieldViews: any, viewType: any, viewName: any, fieldName: string, InfiniteStreamSources: any, infiniteStreamConfigs: any): any => {
77
- if (viewType == "maybeBoolean" || viewType == "boolean" || viewType == "number" || viewType == "string")
89
+ <T,>(fieldConfig:FieldConfig, fieldViews: any, viewType: any, viewName: any, fieldName: string, InfiniteStreamSources: any, infiniteStreamConfigs: any, injectedPrimitives?: InjectedPrimitives<T>): any => {
90
+ if (viewType == "maybeBoolean" || viewType == "boolean" || viewType == "number" || viewType == "string" || viewType == "base64File" || viewType == "secret")
78
91
  return SharedFormState.Default();
92
+ if( injectedPrimitives?.injectedPrimitives.has(viewType)){
93
+ const injectedPrimitiveDefaultState = injectedPrimitives.injectedPrimitives.get(viewType)?.defaultState;
94
+ return injectedPrimitiveDefaultState != undefined ?
95
+ ({
96
+ ...injectedPrimitiveDefaultState,
97
+ ...SharedFormState.Default()
98
+ }) : SharedFormState.Default();
99
+ return SharedFormState.Default()
100
+ }
79
101
  if (viewType == "date")
80
102
  return DateFormState.Default("");
81
103
  if (viewType == "enumSingleSelection" || viewType == "enumMultiSelection")
@@ -101,7 +123,7 @@ export type ParsedForm = {
101
123
  visibleFields: any,
102
124
  disabledFields: any,
103
125
  }
104
- export const ParseForm = (
126
+ export const ParseForm = <T,>(
105
127
  formName: string,
106
128
  formDef: FormDef,
107
129
  containerFormView: any,
@@ -120,7 +142,8 @@ export const ParseForm = (
120
142
  visibleFieldsBoolExprs: any,
121
143
  disabledFieldsBoolExprs: any,
122
144
  defaultValue: BasicFun<TypeName | Type, any>,
123
- type: TypeDefinition
145
+ type: TypeDefinition,
146
+ injectedPrimitives?: InjectedPrimitives<T>
124
147
  ): ParsedForm => {
125
148
  const fieldNameToViewCategory = (fieldName: string) => {
126
149
  const fieldViewCategories = Object.keys(fieldViews)
@@ -152,7 +175,7 @@ export const ParseForm = (
152
175
  const fieldConfig = formDef.fields.get(fieldName)!
153
176
  initialFormState[fieldName] =
154
177
  otherForms.get(viewName)?.initialFormState ??
155
- FieldFormState(fieldConfig, fieldViews, fieldNameToViewCategory(fieldName) as any, (fieldsViewsConfig as any)[fieldName], fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig);
178
+ FieldFormState(fieldConfig, fieldViews, fieldNameToViewCategory(fieldName) as any, (fieldsViewsConfig as any)[fieldName], fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig, injectedPrimitives);
156
179
  if (typeof initialFormState[fieldName] == "string") {
157
180
  throw `cannot resolve initial state ${viewName} of field ${fieldName}`
158
181
  }
@@ -171,6 +194,7 @@ export const ParseForm = (
171
194
  if (viewType == "list") {
172
195
  const elementRendererName = formFieldElementRenderers[fieldName]
173
196
  const field = type.fields.get(fieldName)!
197
+ const elementLabel = formDef.fields.get(fieldName)!.elementLabel
174
198
  const initialElementValue = defaultValue(field.kind == "primitive" ? field.value : field.kind == "lookup" ? field.name : field.args[0])
175
199
  const elementForm = otherForms.get(elementRendererName)
176
200
  if (elementForm != undefined) { // the list argument is a nested form
@@ -178,18 +202,18 @@ export const ParseForm = (
178
202
  formConfig[fieldName] = ListForm<any, any, any & FormLabel, Unit>(
179
203
  { Default: () => ({ ...initialFormState }) },
180
204
  { Default: () => ({ ...initialElementValue }) },
181
- elementForm.form.withView(nestedContainerFormView),
205
+ elementForm.form.withView(nestedContainerFormView).mapContext<any>(_ => ({ ..._, label: elementLabel }))
182
206
  ).withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
183
- .mapContext<any>(_ => ({ ..._, label: label }))
207
+ .mapContext<any>(_ => ({ ..._, label }))
184
208
  } else { // the list argument is a primitive
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);
209
+ const elementForm = FieldView(fieldConfig, fieldViews, fieldNameToElementViewCategory(formFieldElementRenderers)(fieldName) as any, elementRendererName, fieldName, elementLabel ?? label, EnumOptionsSources, fieldsOptionsConfig, leafPredicates, injectedPrimitives)
210
+ const initialFormState = FieldFormState(fieldConfig, fieldViews, fieldNameToElementViewCategory(formFieldElementRenderers)(fieldName) as any, elementRendererName, fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig, injectedPrimitives);
187
211
  formConfig[fieldName] = ListForm<any, any, any & FormLabel, Unit>(
188
212
  { Default: () => initialFormState },
189
213
  { Default: () => initialElementValue },
190
214
  elementForm,
191
215
  ).withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
192
- .mapContext<any>(_ => ({ ..._, label: label }))
216
+ .mapContext<any>(_ => ({ ..._, label }))
193
217
  }
194
218
  } else {
195
219
  if (viewType == "map") {
@@ -213,15 +237,16 @@ export const ParseForm = (
213
237
  const initialValueValue = defaultValue(valueType)
214
238
  const getFormAndInitialState = (elementRenderers:any, rendererName:any, fieldConfig:FieldConfig) => {
215
239
  const formDef = otherForms.get(rendererName)
240
+ const elementLabel = elementRenderers[fieldName].label ?? label
216
241
  if (formDef != undefined) {
217
242
  return [
218
- formDef.form.withView(nestedContainerFormView),
243
+ formDef.form.withView(nestedContainerFormView).mapContext<any>(_ => ({ ..._, label: elementLabel })),
219
244
  formDef.initialFormState
220
245
  ]
221
246
  } else {
222
247
  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);
248
+ const form = FieldView(fieldConfig, fieldViews, categoryName, rendererName, fieldName, elementLabel, EnumOptionsSources, fieldsOptionsConfig, leafPredicates, injectedPrimitives)
249
+ const initialFormState = FieldFormState(fieldConfig, fieldViews, categoryName, rendererName, fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig, injectedPrimitives);
225
250
  return [
226
251
  form,
227
252
  initialFormState
@@ -244,7 +269,7 @@ export const ParseForm = (
244
269
  ).withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
245
270
  .mapContext<any>(_ => ({ ..._, label: label }))
246
271
  } else {
247
- formConfig[fieldName] = FieldView(fieldConfig, fieldViews, viewType, viewName, fieldName, label, EnumOptionsSources, fieldsOptionsConfig, leafPredicates);
272
+ formConfig[fieldName] = FieldView(fieldConfig, fieldViews, viewType, viewName, fieldName, label, EnumOptionsSources, fieldsOptionsConfig, leafPredicates, injectedPrimitives);
248
273
  }
249
274
  }
250
275
  }
@@ -338,9 +363,10 @@ export type EnumName = string
338
363
 
339
364
  export type EnumOptionsSources = BasicFun<EnumName, BasicFun<Unit, Promise<Array<[CollectionReference, BoolExpr<Unit>]>>>>
340
365
  export const parseForms =
341
- <LeafPredicates,>(
366
+ <LeafPredicates, T,>(
342
367
  builtIns: BuiltIns,
343
- apiConverters: ApiConverters,
368
+ injectedPrimitives: InjectedPrimitives<T> | undefined,
369
+ apiConverters: BuiltInApiConverters,
344
370
  containerFormView: any,
345
371
  nestedContainerFormView: any,
346
372
  fieldViews: any,
@@ -422,8 +448,9 @@ export const parseForms =
422
448
  leafPredicates,
423
449
  formFieldVisibilities,
424
450
  formFieldDisabled,
425
- defaultValue(formsConfig.types, builtIns),
451
+ defaultValue(formsConfig.types, builtIns, injectedPrimitives),
426
452
  formConfig.typeDef,
453
+ injectedPrimitives
427
454
  )
428
455
  const formBuilder = Form<any, any, any, any>().Default<any>()
429
456
  const form = formBuilder.template({
@@ -447,11 +474,11 @@ export const parseForms =
447
474
  get: (id: string) => entityApis.get(launcher.api)(id).then((raw: any) => {
448
475
  // alert(JSON.stringify(raw))
449
476
  // alert(JSON.stringify(parsedForm.formDef.type))
450
- const parsed = fromAPIRawValue({ kind: "lookup", name: parsedForm.formDef.type }, formsConfig.types, builtIns, apiConverters)(raw)
477
+ const parsed = fromAPIRawValue({ kind: "lookup", name: parsedForm.formDef.type }, formsConfig.types, builtIns, apiConverters, false, injectedPrimitives)(raw)
451
478
  return parsed
452
479
  }),
453
- update: (id: Guid, value: any) =>
454
- entityApis.update(launcher.api)(id, toAPIRawValue({ kind: "lookup", name: parsedForm.formDef.type }, formsConfig.types, builtIns, apiConverters)(value))
480
+ update: (id: Guid, value: any, formState: any) =>
481
+ entityApis.update(launcher.api)(id, toAPIRawValue({ kind: "lookup", name: parsedForm.formDef.type }, formsConfig.types, builtIns, apiConverters, false, injectedPrimitives)(value, formState))
455
482
  }
456
483
  parsedLaunchers.edit = parsedLaunchers.edit.set(
457
484
  launcherName,
@@ -474,16 +501,16 @@ export const parseForms =
474
501
  const form = parsedForm.form
475
502
  const initialState = parsedForm.initialFormState
476
503
  const api = {
477
- create: (value: any) => {
504
+ create: ([value, formState]: [any, any]) => {
478
505
  // alert(`type = ${JSON.stringify(parsedForm.formDef.type)}`)
479
506
  // alert(`value = ${JSON.stringify(value)}`)
480
- const raw = toAPIRawValue({ kind: "lookup", name: parsedForm.formDef.type }, formsConfig.types, builtIns, apiConverters)(value)
507
+ const raw = toAPIRawValue({ kind: "lookup", name: parsedForm.formDef.type }, formsConfig.types, builtIns, apiConverters, false, injectedPrimitives)(value, formState)
481
508
  // alert(`raw = ${JSON.stringify(raw.interests)}`)
482
509
  return entityApis.create(launcher.api)(raw)
483
510
  },
484
511
  default: (_: Unit) => entityApis.default(launcher.api)(unit)
485
512
  .then((raw: any) => {
486
- const parsed = fromAPIRawValue({ kind: "lookup", name: parsedForm.formDef.type }, formsConfig.types, builtIns, apiConverters)(raw)
513
+ const parsed = fromAPIRawValue({ kind: "lookup", name: parsedForm.formDef.type }, formsConfig.types, builtIns, apiConverters, false, injectedPrimitives)(raw)
487
514
  return parsed
488
515
  })
489
516
  }
@@ -591,16 +618,17 @@ export const replaceKeywords = (obj: any, kind: "from api" | "to api"): any => {
591
618
  return obj;
592
619
  };
593
620
 
594
- export type FormsParserContext = {
621
+ export type FormsParserContext<T extends {[key in keyof T] : {type: any, state: any}}> = {
595
622
  containerFormView: any,
596
623
  nestedContainerFormView: any,
597
624
  fieldViews: any,
598
- fieldTypeConverters: ApiConverters,
625
+ fieldTypeConverters: ApiConverters<T>,
599
626
  infiniteStreamSources: InfiniteStreamSources,
600
627
  enumOptionsSources: EnumOptionsSources,
601
628
  entityApis: EntityApis,
602
629
  leafPredicates: any,
603
630
  getFormsConfig: BasicFun<void, Promise<any>>
631
+ injectedPrimitives?: Injectables<T>,
604
632
  }
605
633
  export type FormsParserState = {
606
634
  formsConfig: Synchronized<Unit, FormParsingResult>
@@ -1,4 +1,5 @@
1
+ import { Unit } from "../../../../main";
1
2
  import { LoadValidateAndParseFormsConfig } from "./coroutines/runner";
2
3
 
3
- export const FormsParserTemplate = LoadValidateAndParseFormsConfig()
4
+ export const FormsParserTemplate = <T extends {[key in keyof T] : {type: any, state: any}} = Unit>() => LoadValidateAndParseFormsConfig<T>()
4
5
 
@@ -0,0 +1,12 @@
1
+ import { SimpleCallback } from "../../../../../../main";
2
+ import { View } from "../../../../../template/state";
3
+ import { Value } from "../../../../../value/state";
4
+ import { FormLabel } from "../../../singleton/domains/form-label/state";
5
+ import { OnChange, SharedFormState } from "../../../singleton/state";
6
+
7
+ export type Base64FileView<Context extends FormLabel, ForeignMutationsExpected> =
8
+ View<
9
+ Context & Value<string> & SharedFormState & { disabled:boolean },
10
+ SharedFormState,
11
+ ForeignMutationsExpected & { onChange: OnChange<string>; setNewValue: SimpleCallback<string> }
12
+ >;
@@ -0,0 +1,24 @@
1
+ import { List } from "immutable";
2
+ import { BasicFun, replaceWith, ValidateRunner } 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, SharedFormState } from "../../../singleton/state";
7
+ import { Base64FileView } from "./state";
8
+
9
+
10
+ export const Base64FileForm = <Context extends FormLabel, ForeignMutationsExpected>(
11
+ validation?:BasicFun<string, Promise<FieldValidation>>) => {
12
+ return Template.Default<Context & Value<string> & { disabled:boolean }, SharedFormState, ForeignMutationsExpected & { onChange: OnChange<string>; }, Base64FileView<Context, ForeignMutationsExpected>>(props => <>
13
+ <props.view {...props}
14
+ foreignMutations={{
15
+ ...props.foreignMutations,
16
+ setNewValue: (_) => props.foreignMutations.onChange(replaceWith(_), List())
17
+ }} />
18
+ </>
19
+ ).any([
20
+ ValidateRunner<Context & { disabled:boolean }, SharedFormState, ForeignMutationsExpected, string>(
21
+ validation ? _ => validation(_).then(FieldValidationWithPath.Default.fromFieldValidation) : undefined
22
+ ),
23
+ ])
24
+ }
@@ -1,9 +1,9 @@
1
1
  import { List } from "immutable";
2
- import { SimpleCallback, BasicFun, Unit, ValidateRunner, Updater, BasicUpdater, MapRepo, ListRepo } from "../../../../../../main";
2
+ import { SimpleCallback, BasicFun, Unit, ValidateRunner, Updater, BasicUpdater, MapRepo, ListRepo, CoTypedFactory, Debounce, Synchronize} from "../../../../../../main";
3
3
  import { Template } from "../../../../../template/state";
4
4
  import { Value } from "../../../../../value/state";
5
5
  import { FormLabel } from "../../../singleton/domains/form-label/state";
6
- import { FieldValidation, FieldValidationWithPath, OnChange } from "../../../singleton/state";
6
+ import { FieldValidation, FieldValidationWithPath, FormValidatorSynchronized, OnChange, SharedFormState } from "../../../singleton/state";
7
7
  import { ListFieldState, ListFieldView } from "./state";
8
8
 
9
9
  export const ListForm = <Element, ElementFormState, Context extends FormLabel, ForeignMutationsExpected>(
@@ -19,20 +19,20 @@ export const ListForm = <Element, ElementFormState, Context extends FormLabel, F
19
19
  ) => {
20
20
  const embeddedElementTemplate = (elementIndex:number) =>
21
21
  elementTemplate
22
- .mapForeignMutations((_ : ForeignMutationsExpected & {
23
- onChange: OnChange<List<Element>>;
24
- add: SimpleCallback<Unit>;
25
- remove: SimpleCallback<number>;
26
- }) : ForeignMutationsExpected & {
27
- onChange: OnChange<Element>;
28
- } => ({
29
- ..._,
30
- onChange: (elementUpdater, path) => {
31
- _.onChange(Updater((elements:List<Element>) => elements.update(elementIndex, (_:Element | undefined) => _ == undefined ? _ : elementUpdater(_))), path)
32
- },
33
- add: (newElement: Element) => { },
34
- remove: (elementIndex: number) => { }
35
- }))
22
+ .mapForeignMutationsFromProps<ForeignMutationsExpected & {
23
+ onChange: OnChange<List<Element>>;
24
+ add: SimpleCallback<Unit>;
25
+ remove: SimpleCallback<number>;}>((props): ForeignMutationsExpected & {onChange: OnChange<Element>} => ({
26
+ ...props.foreignMutations,
27
+ onChange: (elementUpdater, path) => {
28
+ props.foreignMutations.onChange(Updater((elements:List<Element>) =>
29
+ elements.update(elementIndex, (_:Element | undefined) => _ == undefined ? _ : elementUpdater(_))), path)
30
+ props.setState(_ => ({..._,
31
+ modifiedByUser:true,
32
+ })) },
33
+ add: (newElement: Element) => { },
34
+ remove: (elementIndex: number) => { }
35
+ }))
36
36
  .mapContext((_: Context & Value<List<Element>> & ListFieldState<Element, ElementFormState>) : (Context & Value<Element> & ElementFormState) | undefined => {
37
37
  const element = _.value.get(elementIndex)
38
38
  if (element == undefined) return undefined
@@ -27,17 +27,17 @@ export const MapForm = <K, V, KeyFormState, ValueFormState, Context extends Form
27
27
  ) => {
28
28
  const embeddedKeyTemplate = (elementIndex: number) =>
29
29
  keyTemplate
30
- .mapForeignMutations((_: ForeignMutationsExpected & {
30
+ .mapForeignMutationsFromProps<ForeignMutationsExpected & {
31
31
  onChange: OnChange<List<[K, V]>>;
32
32
  add: SimpleCallback<Unit>;
33
33
  remove: SimpleCallback<number>;
34
- }): ForeignMutationsExpected & {
35
- onChange: OnChange<K>;
36
- } =>
34
+ }>((props): ForeignMutationsExpected & {
35
+ onChange: OnChange<K>; } =>
37
36
  ({
38
- ..._,
37
+ ...props.foreignMutations,
39
38
  onChange: (elementUpdater, path) => {
40
- _.onChange(Updater((elements: List<[K, V]>) => elements.update(elementIndex, (_: [K, V] | undefined) => _ == undefined ? _ : [elementUpdater(_[0]), _[1]])), path)
39
+ props.foreignMutations.onChange(Updater((elements: List<[K, V]>) => elements.update(elementIndex, (_: [K, V] | undefined) => _ == undefined ? _ : [elementUpdater(_[0]), _[1]])), path)
40
+ props.setState(_ => ({ ..._, modifiedByUser: true }))
41
41
  },
42
42
  add: (newElement: [K, V]) => { },
43
43
  remove: (elementIndex: number) => { }
@@ -56,17 +56,18 @@ export const MapForm = <K, V, KeyFormState, ValueFormState, Context extends Form
56
56
  ))
57
57
  const embeddedValueTemplate = (elementIndex: number) =>
58
58
  valueTemplate
59
- .mapForeignMutations((_: ForeignMutationsExpected & {
59
+ .mapForeignMutationsFromProps<ForeignMutationsExpected & {
60
60
  onChange: OnChange<List<[K, V]>>;
61
61
  add: SimpleCallback<Unit>;
62
62
  remove: SimpleCallback<number>;
63
- }): ForeignMutationsExpected & {
63
+ }>((props): ForeignMutationsExpected & {
64
64
  onChange: OnChange<V>;
65
65
  } =>
66
66
  ({
67
- ..._,
67
+ ...props.foreignMutations,
68
68
  onChange: (elementUpdater, path) => {
69
- _.onChange(Updater((elements: List<[K, V]>) => elements.update(elementIndex, (_: [K, V] | undefined) => _ == undefined ? _ : [_[0], elementUpdater(_[1])])), path)
69
+ props.foreignMutations.onChange(Updater((elements: List<[K, V]>) => elements.update(elementIndex, (_: [K, V] | undefined) => _ == undefined ? _ : [_[0], elementUpdater(_[1])])), path)
70
+ props.setState(_ => ({ ..._, modifiedByUser: true }))
70
71
  },
71
72
  add: (newElement: [K, V]) => { },
72
73
  remove: (elementIndex: number) => { }
@@ -0,0 +1,12 @@
1
+ import { SimpleCallback } from "../../../../../../main";
2
+ import { View } from "../../../../../template/state";
3
+ import { Value } from "../../../../../value/state";
4
+ import { FormLabel } from "../../../singleton/domains/form-label/state";
5
+ import { OnChange, SharedFormState } from "../../../singleton/state";
6
+
7
+ export type SecretView<Context extends FormLabel, ForeignMutationsExpected> =
8
+ View<
9
+ Context & Value<string> & SharedFormState & { disabled:boolean },
10
+ SharedFormState,
11
+ ForeignMutationsExpected & { onChange: OnChange<string>; setNewValue: SimpleCallback<string> }
12
+ >;
@@ -0,0 +1,24 @@
1
+ import { List } from "immutable";
2
+ import { BasicFun, replaceWith, ValidateRunner } 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, SharedFormState } from "../../../singleton/state";
7
+ import { SecretView } from "./state";
8
+
9
+
10
+ export const SecretForm = <Context extends FormLabel, ForeignMutationsExpected>(
11
+ validation?:BasicFun<string, Promise<FieldValidation>>) => {
12
+ return Template.Default<Context & Value<string> & { disabled:boolean }, SharedFormState, ForeignMutationsExpected & { onChange: OnChange<string>; }, SecretView<Context, ForeignMutationsExpected>>(props => <>
13
+ <props.view {...props}
14
+ foreignMutations={{
15
+ ...props.foreignMutations,
16
+ setNewValue: (_) => props.foreignMutations.onChange(replaceWith(_), List())
17
+ }} />
18
+ </>
19
+ ).any([
20
+ ValidateRunner<Context & { disabled:boolean }, SharedFormState, ForeignMutationsExpected, string>(
21
+ validation ? _ => validation(_).then(FieldValidationWithPath.Default.fromFieldValidation) : undefined
22
+ ),
23
+ ])
24
+ }
@@ -54,7 +54,7 @@ export const SharedFormState = {
54
54
  export type EntityFormState<Entity, Fields extends (keyof Entity) & (keyof FieldStates), FieldStates, Context, ForeignMutationsExpected> =
55
55
  { [f in Fields]: FieldStates[f] & SharedFormState } & SharedFormState
56
56
  export type EntityFormContext<Entity, Fields extends (keyof Entity) & (keyof FieldStates), FieldStates, Context, ForeignMutationsExpected> =
57
- Context & EntityFormState<Entity, Fields, FieldStates, Context, ForeignMutationsExpected> & { visibleFields: OrderedMap<Fields, BasicPredicate<Context>>, disabledFields: OrderedMap<Fields, BasicPredicate<Context>> } & Value<Entity>
57
+ Context & EntityFormState<Entity, Fields, FieldStates, Context, ForeignMutationsExpected> & { visibleFields: OrderedMap<Fields, BasicPredicate<Context>>, disabledFields: OrderedMap<Fields, BasicPredicate<Context>>, header?: string } & Value<Entity>
58
58
  export type OnChange<Entity> = (updater: BasicUpdater<Entity>, path: List<string>) => void
59
59
  export type EntityFormForeignMutationsExpected<Entity, Fields extends (keyof Entity) & (keyof FieldStates), FieldStates, Context, ForeignMutationsExpected> =
60
60
  ForeignMutationsExpected & { onChange: OnChange<Entity> }
@@ -1,5 +1,5 @@
1
1
  import { List, OrderedMap, OrderedSet } from "immutable"
2
- import { BasicUpdater, id, BasicPredicate, SimpleCallback, Unit, Debounced, Synchronized, unit, replaceWith, CoTypedFactory, Debounce, Synchronize, BasicFun, EntityFormState, EntityFormContext, EntityFormForeignMutationsExpected, EntityFormTemplate, EntityFormView, FieldTemplates, FieldValidationWithPath, FormValidatorSynchronized, OnChange, SharedFormState } from "../../../../main"
2
+ import { BasicUpdater, id, BasicPredicate, SimpleCallback, Unit, Debounced, Synchronized, unit, replaceWith, CoTypedFactory, Debounce, Synchronize, BasicFun, EntityFormState, EntityFormContext, EntityFormForeignMutationsExpected, EntityFormTemplate, EntityFormView, FieldTemplates, FieldValidationWithPath, FormValidatorSynchronized, OnChange, SharedFormState, DirtyStatus } from "../../../../main"
3
3
  import { Template, View } from "../../../template/state"
4
4
  import { Value } from "../../../value/state"
5
5
 
@@ -34,6 +34,13 @@ export const Form = <Entity, FieldStates, Context, ForeignMutationsExpected>() =
34
34
  modifiedByUser:true,
35
35
  validation:Debounced.Updaters.Template.value<FormValidatorSynchronized>(Synchronized.Updaters.value(replaceWith(unit)))(_[field].validation),
36
36
  }) }))
37
+ } else {
38
+ props.setState(_ => ({ ..._,
39
+ modifiedByUser: true,
40
+ [field]:({
41
+ ..._[field],
42
+ modifiedByUser:true,
43
+ }) }))
37
44
  }
38
45
  setTimeout(() =>
39
46
  props.foreignMutations.onChange((current: Entity): Entity => ({
@@ -81,22 +88,25 @@ export const Form = <Entity, FieldStates, Context, ForeignMutationsExpected>() =
81
88
  }
82
89
  })
83
90
 
91
+ // TODO: Validate runner and dirty status are also used to ensure to element is initialised, but this should be further debugged with a more correct solution
84
92
  export const ValidateRunner = <Context, FormState extends SharedFormState, ForeignMutationsExpected, Entity,>(
85
93
  validation?:BasicFun<Entity, Promise<FieldValidationWithPath>>,
86
94
  ) => {
87
95
  const Co = CoTypedFactory<Context & Value<Entity> & FormState, FormState>()
88
96
  return Co.Template<ForeignMutationsExpected & { onChange: OnChange<Entity>; }>(
89
- Co.Repeat(
97
+ validation ? Co.Repeat(
90
98
  Debounce<FormValidatorSynchronized, Value<Entity>>(
91
99
  Synchronize<Unit, FieldValidationWithPath, Value<Entity>>(
92
100
  _ => validation ? validation(_.value) : Promise.resolve([]),
93
101
  () => "transient failure", 3, 50
94
102
  ), 50
95
103
  ).embed(_ => ({..._.validation, value:_.value}), (_) => curr => ({...curr, validation:_(curr.validation)}))
96
- ),
104
+ ) :
105
+ Co.SetState((curr) => ({...curr, validation: Debounced.Updaters.Core.dirty(replaceWith<DirtyStatus>("not dirty"))}))
106
+ ,
97
107
  {
98
108
  interval:15,
99
- runFilter:props => Boolean(validation) && Debounced.Operations.shouldCoroutineRun(props.context.validation)
109
+ runFilter: props => Debounced.Operations.shouldCoroutineRun(props.context.validation)
100
110
  }
101
111
  )
102
112
  }