@axdspub/axiom-ui-forms 0.1.0

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.
Files changed (68) hide show
  1. package/.dockerignore +4 -0
  2. package/.env +1 -0
  3. package/.eslintrc.json +37 -0
  4. package/.gitlab-ci.yml +72 -0
  5. package/.storybook/main.ts +43 -0
  6. package/.storybook/preview.ts +16 -0
  7. package/.vscode/extensions.json +5 -0
  8. package/.vscode/settings.json +10 -0
  9. package/Dockerfile +34 -0
  10. package/README.md +19 -0
  11. package/craco.config.js +42 -0
  12. package/docker/nginx/conf.d/default.conf +46 -0
  13. package/docker-compose.yml +13 -0
  14. package/package.json +103 -0
  15. package/public/exampleForm.json +77 -0
  16. package/public/favicon.ico +0 -0
  17. package/public/index.html +43 -0
  18. package/public/logo192.png +0 -0
  19. package/public/logo512.png +0 -0
  20. package/public/manifest.json +25 -0
  21. package/public/robots.txt +3 -0
  22. package/rollup.config.mjs +108 -0
  23. package/src/App.tsx +25 -0
  24. package/src/Form/Components/FieldCreator.tsx +206 -0
  25. package/src/Form/Components/FieldLabel.tsx +14 -0
  26. package/src/Form/Components/Inputs/Boolean.tsx +13 -0
  27. package/src/Form/Components/Inputs/JSONString.tsx +40 -0
  28. package/src/Form/Components/Inputs/LongString.tsx +22 -0
  29. package/src/Form/Components/Inputs/Number.tsx +22 -0
  30. package/src/Form/Components/Inputs/Object.tsx +56 -0
  31. package/src/Form/Components/Inputs/RadioGroup.tsx +24 -0
  32. package/src/Form/Components/Inputs/SingleSelect.tsx +24 -0
  33. package/src/Form/Components/Inputs/String.tsx +18 -0
  34. package/src/Form/Components/Inputs/index.tsx +6 -0
  35. package/src/Form/Components/Inputs/inputMap.ts +23 -0
  36. package/src/Form/Components/index.tsx +2 -0
  37. package/src/Form/FormCreator.tsx +62 -0
  38. package/src/Form/FormCreatorTypes.ts +187 -0
  39. package/src/Form/FormMappingTypes.ts +17 -0
  40. package/src/Form/Manage/CopyableJSONOutput.tsx +75 -0
  41. package/src/Form/Manage/FormConfigInput.tsx +61 -0
  42. package/src/Form/Manage/FormMappedOutput.tsx +131 -0
  43. package/src/Form/Manage/FormMappingInput.tsx +60 -0
  44. package/src/Form/Manage/Manage.tsx +132 -0
  45. package/src/Form/Manage/RawFormOutput.tsx +20 -0
  46. package/src/Form/MapTester.tsx +107 -0
  47. package/src/Form/SchemaToForm.tsx +348 -0
  48. package/src/Form/formDefinition.json +8 -0
  49. package/src/Form/helpers.ts +85 -0
  50. package/src/Form/index.ts +1 -0
  51. package/src/Form/testData/assetData.json +65 -0
  52. package/src/Form/testData/exampleParticle.json +112 -0
  53. package/src/Form/testData/fields.json +151 -0
  54. package/src/Form/testData/nestedForm.json +156 -0
  55. package/src/Form/testData/testSchema.json +89 -0
  56. package/src/SetTester.tsx +61 -0
  57. package/src/helpers.ts +36 -0
  58. package/src/index.css +39 -0
  59. package/src/index.tsx +19 -0
  60. package/src/library.ts +3 -0
  61. package/src/reportWebVitals.ts +15 -0
  62. package/src/state/formAtom.ts +21 -0
  63. package/src/state/formMappingAtom.ts +21 -0
  64. package/src/state/formValuesAtom.ts +22 -0
  65. package/src/types/generate-schema.d.ts +8 -0
  66. package/tailwind.config.js +11 -0
  67. package/tsconfig.json +32 -0
  68. package/tsconfig.paths.json +19 -0
@@ -0,0 +1,107 @@
1
+ import { type IFormField, type IForm } from '@/Form/FormCreatorTypes'
2
+ import { type IFormMapping } from '@/Form/FormMappingTypes'
3
+ import { copyAndAddPathToFields, getPathFromField } from '@/Form/helpers'
4
+ import { CopyButton } from '@/Form/Manage/CopyableJSONOutput'
5
+ import FormMappingInput from '@/Form/Manage/FormMappingInput'
6
+ import testForm from '@/Form/testData/nestedForm.json'
7
+ import { Checkbox, Input, TextArea } from '@axdspub/axiom-ui-utilities'
8
+ import React, { useEffect, useState, type ReactElement } from 'react'
9
+
10
+ const FieldMap = ({ field, mappingState }: { field: IFormField, mappingState: [IFormMapping, (m: IFormMapping) => void] }): ReactElement => {
11
+ const isContainer = field.type === 'object' || field.type === 'section'
12
+ const [include, setInclude] = useState<boolean>(true)
13
+ const [path, setPath] = useState<string | undefined>(undefined)
14
+ return (
15
+ <div className={`${isContainer ? 'px-4 py-6 border-2 border-slate-400 border-dotted' : 'px-4 py-1'} m-2 bg-slate-400 [&>.bg-slate-400]:bg-slate-200 [&>.bg-slate-200]:bg-slate-100 bg-opacity-50`}>
16
+ <p>{field.label}</p>
17
+ <p className='text-xs'>{`${getPathFromField(field)}`}</p>
18
+ <div className='flex flex-row p-2'>
19
+ <Checkbox id={`${field.id}-include`} testId={`${field.id}-include`} value={include} onChange={(e) => {
20
+ setInclude(e)
21
+ }} />
22
+ <Input size='xs' wrapperClassName='flex-grow' id={`${field.id}-target`} testId={`${field.id}-target`} value={path} onChange={(e) => {
23
+ setPath(e === '' ? undefined : e)
24
+ }} />
25
+ </div>
26
+ {
27
+ field.type === 'object' && field.fields?.map(f => {
28
+ return (
29
+ <FieldMap key={f.id} field={f} mappingState={mappingState} />
30
+ )
31
+ })
32
+ }
33
+ </div>
34
+ )
35
+ }
36
+
37
+ const MapTester = (): ReactElement => {
38
+ const [inputObject, setInputObject] = useState<IForm>(copyAndAddPathToFields(testForm as IForm))
39
+ const [mapping, setMapping] = useState<IFormMapping>({
40
+ fields: {},
41
+ $targetSchema: ''
42
+ })
43
+ const [error, setError] = useState<string | undefined>(undefined)
44
+ const [str, setStr] = useState<string | undefined>(JSON.stringify(inputObject))
45
+ useEffect(() => {
46
+ try {
47
+ const ob = JSON.parse(str === '' || str === undefined ? '{}' : str)
48
+ setInputObject({
49
+ fields: [],
50
+ label: '',
51
+ id: '',
52
+ ...ob
53
+ })
54
+ } catch {
55
+ setError('Invalid JSON')
56
+ }
57
+ }, [str])
58
+ return (
59
+ <div className='p-20 h-full'>
60
+ <h1 className='font-bold'>Map Tester</h1>
61
+ <div className='grid grid-cols-3 gap-8 flex-grow h-full'>
62
+ <div className='h-full'>
63
+ { error !== undefined
64
+ ? <p className='text-red-500 py-4'>
65
+ {error}
66
+ </p>
67
+ : ''
68
+ }
69
+
70
+ <TextArea
71
+ label='Input object'
72
+ id='object'
73
+ testId='object'
74
+ wrapperClassName='h-full relative'
75
+ className='h-full'
76
+ value={JSON.stringify(inputObject, null, 2)}
77
+ onChange={(e) => {
78
+ setStr(e)
79
+ }}
80
+ after={<CopyButton string={JSON.stringify(inputObject, null, 2)} className='pointer-events-auto absolute right-8 top-12' />}
81
+ />
82
+ </div>
83
+ <div>
84
+ <div className='hidden'>
85
+ <FormMappingInput form={inputObject} mappingState={[mapping, setMapping]} />
86
+ </div>
87
+ <div className='flex flex-col gap-2'>
88
+ {
89
+ inputObject.fields.map(f => {
90
+ return (
91
+ <FieldMap key={f.id} field={f} mappingState={[mapping, setMapping]} />
92
+ )
93
+ })
94
+ }
95
+ </div>
96
+ </div>
97
+ <div>
98
+ <p>Mapped output will go here..</p>
99
+ </div>
100
+
101
+ </div>
102
+
103
+ </div>
104
+ )
105
+ }
106
+
107
+ export default MapTester
@@ -0,0 +1,348 @@
1
+ import { CopyButton } from '@/Form/Manage/CopyableJSONOutput'
2
+ import { Tabs, TextArea } from '@axdspub/axiom-ui-utilities'
3
+ import { type JSONSchema7Type, type JSONSchema7, type JSONSchema7Definition } from 'json-schema'
4
+ import React, { useEffect, useState, type ReactElement } from 'react'
5
+ import metaSchemaDraftV7 from 'ajv/lib/refs/json-schema-draft-07.json'
6
+ import metaSchemaDraftV6 from 'ajv/lib/refs/json-schema-draft-06.json'
7
+ import metaSchemaV5 from 'ajv/lib/refs/json-schema-2020-12/schema.json'
8
+ import metaSchemaV4 from 'ajv/lib/refs/json-schema-2019-09/schema.json'
9
+ import testSchema from '@/Form/testData/testSchema.json'
10
+ import Ajv, { type ValidateFunction } from 'ajv'
11
+ import GenerateSchema from 'generate-schema'
12
+ import { type IFormFieldType, type IForm, type IFormField, type IFormValues } from '@/Form/FormCreatorTypes'
13
+ import FormCreator from '@/Form/FormCreator'
14
+ import { ExclamationTriangleIcon } from '@radix-ui/react-icons'
15
+
16
+ const getValidator = (schema: number): ValidateFunction => {
17
+ const ajv = new Ajv({ strict: false })
18
+ switch (schema) {
19
+ case 4:
20
+ return ajv.compile(metaSchemaV4)
21
+ case 5:
22
+ return ajv.compile(metaSchemaV5)
23
+ case 6:
24
+ return ajv.compile(metaSchemaDraftV6)
25
+ case 7:
26
+ return ajv.compile(metaSchemaDraftV7)
27
+ default:
28
+ return ajv.compile(metaSchemaV5)
29
+ }
30
+ }
31
+
32
+ export const objectToSchema = (ob: unknown): JSONSchema7 => {
33
+ return GenerateSchema.json('Schema', ob) as JSONSchema7
34
+ }
35
+
36
+ const validateSchema = (schemaOb: unknown, version: number = 6): string | undefined => {
37
+ const ajv = new Ajv({ strict: false })
38
+ const validator = getValidator(version)
39
+ const valid = validator(schemaOb)
40
+ if (!valid) {
41
+ return ajv.errorsText(validator.errors)
42
+ }
43
+ return undefined
44
+ }
45
+
46
+ const validateAgainstSchema = (schema: JSONSchema7, formValues: IFormValues): string[] | undefined => {
47
+ const ajv = new Ajv({ strict: false, allErrors: true })
48
+ const validator = ajv.compile(schema)
49
+ const valid = validator(formValues)
50
+ if (validator.errors !== null && validator.errors !== undefined && !valid) {
51
+ return validator.errors.map(e => {
52
+ return `${e.instancePath} ${e.message}`
53
+ })
54
+ }
55
+ return undefined
56
+ }
57
+
58
+ const makeId = (options: Array<string | number | undefined | null>): string => {
59
+ const validOptions = options.filter((o) => o !== undefined && o !== null)
60
+ if (validOptions.length === 0) {
61
+ return crypto !== undefined ? crypto.randomUUID() : Math.random().toString(36).substring(2)
62
+ }
63
+ return String(validOptions[0])
64
+ }
65
+
66
+ const makeLabel = (options: Array<string | number | undefined | null>): string | undefined => {
67
+ const validOptions = options.filter((o) => o !== undefined && o !== null)
68
+ if (validOptions.length === 0) {
69
+ return undefined
70
+ }
71
+ return String(validOptions[0])
72
+ .replace(/_/g, ' ')
73
+ .split(' ')
74
+ .map((s, i) => {
75
+ return i === 0 ? `${s.charAt(0).toUpperCase()}${s.slice(1)}` : s
76
+ }).join(' ')
77
+ }
78
+
79
+ const getFieldType = (schema: JSONSchema7): IFormFieldType => {
80
+ const schemaType = schema.type
81
+ if (schemaType === 'string' || schemaType === 'number' || schemaType === 'integer') {
82
+ if (schema.enum !== undefined || schema.oneOf !== undefined) {
83
+ return 'select'
84
+ } else if (schema.anyOf !== undefined) {
85
+ return 'checkbox'
86
+ } else if (schemaType === 'string' && (schema.maxLength !== undefined && schema.maxLength <= 100)) {
87
+ return 'text'
88
+ } else if (schemaType === 'number' || schemaType === 'integer') {
89
+ return 'number'
90
+ }
91
+ return 'long_text'
92
+ } else if (schemaType === 'boolean') {
93
+ return 'boolean'
94
+ } else if (schemaType === 'object') {
95
+ return 'object'
96
+ }
97
+
98
+ return 'text'
99
+ }
100
+
101
+ export const getValueFromSchema = (schema: JSONSchema7Type | JSONSchema7Definition | undefined): string | number | boolean | undefined => {
102
+ if (schema === undefined || schema === null) {
103
+ return undefined
104
+ }
105
+ if (typeof schema === 'string' || typeof schema === 'number' || typeof schema === 'boolean') {
106
+ return schema
107
+ }
108
+ if (Array.isArray(schema)) {
109
+ if (schema.length > 0) {
110
+ return getValueFromSchema(schema[0])
111
+ }
112
+ return undefined
113
+ }
114
+ if (schema.const !== undefined) {
115
+ return String(schema.const)
116
+ }
117
+ return undefined
118
+ }
119
+
120
+ export const getLabelFromSchema = (schema: JSONSchema7Type | JSONSchema7Definition | undefined): string | undefined => {
121
+ if (schema === undefined || schema === null) {
122
+ return undefined
123
+ }
124
+ if (typeof schema === 'boolean') {
125
+ return schema ? 'true' : 'false'
126
+ }
127
+ if (typeof schema === 'string' || typeof schema === 'number' || typeof schema === 'boolean') {
128
+ return String(schema)
129
+ }
130
+ if (Array.isArray(schema)) {
131
+ if (schema.length > 0) {
132
+ return getLabelFromSchema(schema[0])
133
+ }
134
+ return undefined
135
+ }
136
+ if (schema.title !== undefined && schema.title !== null) {
137
+ return getLabelFromSchema(schema.title)
138
+ }
139
+ return String(getValueFromSchema(schema))
140
+ }
141
+
142
+ const schemaToFormField = (schema: JSONSchema7, property: string, multiple?: boolean): IFormField => {
143
+ if (schema.type === 'array') {
144
+ return schemaToFormField(schema.items as JSONSchema7, property, true)
145
+ }
146
+ const type = getFieldType(schema)
147
+ const id = makeId([
148
+ schema.$id,
149
+ property,
150
+ schema.title?.toLowerCase().replace(' ', '-')
151
+ ])
152
+ const label = makeLabel([
153
+ schema.title,
154
+ property
155
+ ])
156
+ const ob: Pick<IFormField, 'id' | 'label' | 'multiple'> = {
157
+ id,
158
+ label,
159
+ multiple
160
+ }
161
+ if (type === 'text' || type === 'number' || type === 'long_text' || type === 'boolean') {
162
+ return {
163
+ ...ob,
164
+ type
165
+ }
166
+ }
167
+
168
+ if (type === 'select' || type === 'checkbox') {
169
+ const schemaOptions = schema.enum ?? schema.oneOf ?? schema.anyOf ?? []
170
+ const options = schemaOptions.map(e => {
171
+ const value = getValueFromSchema(e)
172
+ const label = getLabelFromSchema(e)
173
+ return value !== undefined
174
+ ? {
175
+ value: String(value),
176
+ label: label ?? String(value)
177
+ }
178
+ : null
179
+ }).filter(d => d !== null)
180
+ return {
181
+ ...ob,
182
+ type,
183
+ options
184
+ }
185
+ }
186
+ if (type === 'object') {
187
+ const properties = schema.properties ?? {}
188
+ const fields: IFormField[] = []
189
+ for (const key in properties) {
190
+ if (properties[key] !== undefined && typeof properties[key] !== 'boolean') {
191
+ fields.push(schemaToFormField(properties[key], key))
192
+ }
193
+ }
194
+ return {
195
+ ...ob,
196
+ type,
197
+ fields,
198
+ multiple
199
+ }
200
+ }
201
+
202
+ return {
203
+ id,
204
+ type: 'text',
205
+ multiple
206
+ }
207
+ }
208
+
209
+ export const schemaToFormObject = (schema: JSONSchema7): IForm => {
210
+ const formFields: IFormField[] = []
211
+ for (const key in schema.properties) {
212
+ if (schema.properties[key] !== undefined && typeof schema.properties[key] !== 'boolean') {
213
+ formFields.push(schemaToFormField(schema.properties[key], key))
214
+ }
215
+ }
216
+ return {
217
+ id: makeId([schema.$id, schema.title?.toLowerCase().replace(' ', '-')]),
218
+ label: schema.title ?? 'Untitled',
219
+ fields: formFields
220
+ }
221
+ }
222
+
223
+ const SchemaToForm = (): ReactElement => {
224
+ const [schema, setSchema] = useState<JSONSchema7 | undefined>(undefined)
225
+ const [form, setForm] = useState<IForm | undefined>(undefined)
226
+ const [formValues, setFormValues] = useState<IFormValues>({})
227
+ const [error, setError] = useState<string | undefined>(validateSchema(schema))
228
+ const [str, setStr] = useState<string | undefined>(JSON.stringify(testSchema, null, 2))
229
+ const [formOutputErrors, setFormOutputErrors] = useState<string[] | undefined>(undefined)
230
+ useEffect(() => {
231
+ if (str !== '' && str !== undefined) {
232
+ try {
233
+ const ob = JSON.parse(str)
234
+ const validationResponse = validateSchema(ob)
235
+ setError(validationResponse)
236
+ if (validationResponse === undefined) {
237
+ setSchema(ob as JSONSchema7)
238
+ setForm(schemaToFormObject(ob as JSONSchema7))
239
+ }
240
+ } catch {
241
+ setError('Invalid JSON')
242
+ }
243
+ } else {
244
+ setSchema(undefined)
245
+ setForm(undefined)
246
+ }
247
+ }, [str])
248
+ useEffect(() => {
249
+ setFormOutputErrors(validateAgainstSchema(schema ?? {}, formValues))
250
+ }, [formValues])
251
+ return (
252
+ <div className='flex flex-col h-full gap-4 p-20'>
253
+ <h1 className='text-2xl'>Schema to Form</h1>
254
+ <p>
255
+ This page will allow you to convert a JSON schema to a form UI schema
256
+ </p>
257
+ <div className='grid grid-cols-2 gap-8 flex-grow'>
258
+ <div className='h-full bg-slate-100 p-8'>
259
+ {
260
+ form === undefined
261
+ ? <p>Waiting on valid schema</p>
262
+ : <Tabs
263
+ tabs={[
264
+ {
265
+ id: 'form',
266
+ label: 'Form',
267
+ content: <FormCreator form={form} formValueState={ [formValues, setFormValues]} />
268
+ },
269
+ {
270
+ id: 'output',
271
+ label: <>Form output {formOutputErrors !== undefined ? <ExclamationTriangleIcon className='inline ml-2' /> : ''}</>,
272
+ content: <div>{
273
+ schema !== undefined
274
+ ? <>
275
+ <p>{formOutputErrors !== undefined
276
+ ? <>Errors: <ul className='text-rose-800 text-xs list-disc p-4'>{
277
+ formOutputErrors.map((e) => {
278
+ return <li key={e}>{e}</li>
279
+ })
280
+ }</ul></>
281
+ : 'Form output is valid'}</p>
282
+ <div className='p-10 relative bg-yellow-200'>
283
+ <CopyButton string={JSON.stringify(form ?? '', null, 2)} className='absolute right-10 top-10 pointer-events-auto' />
284
+ <pre>{JSON.stringify(formValues ?? '', null, 2)}</pre>
285
+ </div>
286
+ </>
287
+ : 'No schema'
288
+ }
289
+ </div>
290
+ }
291
+ ]}
292
+ />
293
+ }
294
+ </div>
295
+ <div className='h-full bg-slate-100 p-8 overflow-auto'>
296
+ <div className='flex flex-col gap-2'>
297
+ <p>Schema</p>
298
+
299
+ <p className={`${error !== undefined ? 'text-rose-800' : 'text-green-800'}`}>
300
+ {error ?? 'No errors'}
301
+ </p>
302
+
303
+ <div className='relative'>
304
+ <CopyButton string={JSON.stringify(schema, null, 2)} className='absolute right-10 top-10 pointer-events-auto' />
305
+ <TextArea
306
+ id='schemaInput'
307
+ testId='schemaInput'
308
+ value={JSON.stringify(schema, null, 2)}
309
+ className={`h-full mt-0 w-full flex-grow min-h-[600px] shadow-inner-x ${error !== undefined ? 'bg-rose-100' : 'bg-green-100'}`}
310
+ onChange={(e) => {
311
+ setStr(e)
312
+ }}
313
+ />
314
+ </div>
315
+ </div>
316
+ <div className='flex flex-col gap-2'>
317
+
318
+ <p>UI Config</p>
319
+ <div className='relative'>
320
+ {
321
+ form !== undefined
322
+ ? <>
323
+ <CopyButton string={JSON.stringify(form ?? '', null, 2)} className='absolute right-10 top-10 pointer-events-auto' />
324
+ <TextArea
325
+ id='formInput'
326
+ testId='formInput'
327
+ value={JSON.stringify(form, null, 2)}
328
+ className='h-full mt-0 w-full flex-grow min-h-[600px] shadow-inner-x bg-blue-900 text-white'
329
+ onChange={(e) => {
330
+ setForm(e !== undefined ? JSON.parse(e) : undefined)
331
+ }}
332
+ />
333
+
334
+ </>
335
+ : 'Waiting on valid schema'
336
+ }
337
+
338
+ </div>
339
+
340
+ </div>
341
+
342
+ </div>
343
+ </div>
344
+
345
+ </div>
346
+ )
347
+ }
348
+ export default SchemaToForm
@@ -0,0 +1,8 @@
1
+ {
2
+ "label": "My form",
3
+ "fields": [
4
+ {
5
+ "type": ""
6
+ }
7
+ ]
8
+ }
@@ -0,0 +1,85 @@
1
+ import { type IFormFieldSection, type IObjectField, type IForm, type IFormField, type IValueType, type IFormValues } from '@/Form/FormCreatorTypes'
2
+
3
+ export const getChildFields = (field: IFormField): IFormField[] => {
4
+ return field.type === 'object' || field.type === 'section' ? field.fields ?? [] : []
5
+ }
6
+
7
+ export const addFieldPath = (field: IFormField, parentPath?: string[]): IFormField => {
8
+ if (field.type === 'object' && field.skip_path === true) {
9
+ field.path = parentPath !== undefined ? parentPath.slice() : []
10
+ field.level = parentPath !== undefined ? parentPath.length : 0
11
+ } else {
12
+ const newSegment = field.id // `${field.id}${field.multiple === true ? '[]' : ''}`
13
+ field.path = parentPath !== undefined ? parentPath.concat(newSegment) : [newSegment]
14
+ field.level = parentPath !== undefined ? parentPath.length + 1 : 1
15
+ }
16
+ if ((field.type === 'object' || field.type === 'section') && field.fields !== undefined) {
17
+ field.fields = field.fields.map(childField => {
18
+ return addFieldPath(childField, field.path)
19
+ })
20
+ }
21
+ return field
22
+ }
23
+
24
+ export const getUniqueFormFields = (form: IForm): IFormField[] => {
25
+ const fieldMap = Object.fromEntries(form.fields.map(f => [f.id, f]))
26
+ return Object.values(fieldMap)
27
+ }
28
+
29
+ export const getFields = (fields: IFormField[]): IFormField[] => {
30
+ const all = fields.map(field => {
31
+ let fields = [field]
32
+ const children = getChildFields(field)
33
+ children.forEach(c => {
34
+ fields = fields.concat(getFields([c]))
35
+ })
36
+ return fields
37
+ }).flat(Infinity) as IFormField[]
38
+ return all
39
+ }
40
+
41
+ export function copyAndAddPathToFields<T extends IForm | IFormFieldSection | IObjectField> (formOrContainer: T): T {
42
+ const form = JSON.parse(JSON.stringify(formOrContainer)) as T
43
+ // const fields = getFields(form.fields)
44
+ form.fields = form.fields.map(field => {
45
+ return addFieldPath(field)
46
+ })
47
+ return form
48
+ }
49
+
50
+ export function getFieldValue (field: IFormField, formValues: IFormValues): IValueType | IValueType[] | undefined {
51
+ return formValues[getPathFromField(field)]
52
+ }
53
+
54
+ export function getPathFromField (field: IFormField): string {
55
+ // console.log(`${field.path !== undefined ? field.path.join('.') : 'nopath'} = ${field.id}`)
56
+ return field.path !== undefined ? field.path.join('.') : field.id
57
+ }
58
+
59
+ // THIS DOESN'T HANDLE NESTED YET
60
+ export const checkCondition = (field: IFormField, formValues: IFormValues): boolean => {
61
+ if (field.conditions !== undefined) {
62
+ const dependsOn = Array.isArray(field.conditions.dependsOn) ? field.conditions.dependsOn : [field.conditions.dependsOn]
63
+
64
+ const val = field.conditions.value
65
+ return dependsOn.every(d => val !== undefined
66
+ ? formValues[d] === val
67
+ : formValues !== null && formValues[d] !== undefined && formValues[d] !== false
68
+ )
69
+ }
70
+ return true
71
+ }
72
+
73
+ export function cleanUnusedDependenciesFromFormValues (form: IForm, formValues: IFormValues): IFormValues {
74
+ Object.keys(formValues).forEach(key => {
75
+ const field = form.fields.find(f => f.id === key)
76
+ if (field !== undefined && !checkCondition(field, formValues)) {
77
+ formValues[key] = undefined
78
+ }
79
+ })
80
+
81
+ const fields = getFields(form.fields)
82
+ const fieldIds = fields.map(f => f.id)
83
+ const newFormValues = Object.fromEntries(Object.entries(formValues).filter(([key]) => fieldIds.includes(key)))
84
+ return newFormValues
85
+ }
@@ -0,0 +1 @@
1
+ export * from '@/Form/helpers'
@@ -0,0 +1,65 @@
1
+ {
2
+ "source_descriptors": {
3
+ "human":{
4
+ "label":"Human",
5
+ "user":"Trevor Golden",
6
+ "update":"2024-04-20T12:31.000Z",
7
+ "create":"2022-05-23T09:32.000Z",
8
+ "priority": 1
9
+ },
10
+ "netcdf":{
11
+ "label":"NetCDF",
12
+ "update": "2023-11-02T12:31.000Z",
13
+ "create": "2022-05-23T09:32.000Z",
14
+ "priority": 2
15
+ },
16
+ "csv":{
17
+ "label":"Messy CSV",
18
+ "update": "2023-13-12T12:31.000Z",
19
+ "create": "2022-06-23T09:32.000Z",
20
+ "priority": 3
21
+ }
22
+ },
23
+ "fields": [
24
+ {
25
+ "id": "label",
26
+ "values": {
27
+ "netcdf": "Label from the NetCDF file",
28
+ "csv": "Label from the CSV file",
29
+ "human": "Label from the human"
30
+ }
31
+ },
32
+ {
33
+ "id": "description",
34
+ "values": {
35
+ "netcdf": "Description from the NetCDF file",
36
+ "csv": "Description from the CSV file",
37
+ "human": null
38
+ }
39
+ },
40
+ {
41
+ "id": "yesno",
42
+ "values": {
43
+ "netcdf": null,
44
+ "csv": true,
45
+ "human": false
46
+ }
47
+ },
48
+ {
49
+ "id": "onecolor",
50
+ "values": {
51
+ "netcdf": "red",
52
+ "csv": "green",
53
+ "human": "blue"
54
+ }
55
+ },
56
+ {
57
+ "id": "manychoices",
58
+ "values": {
59
+ "netcdf": "red",
60
+ "csv": "green",
61
+ "human": "blue"
62
+ }
63
+ }
64
+ ]
65
+ }