@onehat/ui 0.4.108 → 0.4.109
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/package.json +1 -1
- package/src/Components/Form/Field/Tag/Tag.js +1 -1
- package/src/Components/Form/Form.js +31 -12
- package/src/Components/Hoc/withPdfButtons.js +2 -2
- package/src/Components/Hoc/withPresetButtons.js +6 -2
- package/src/Components/Viewer/Viewer.js +20 -12
- package/src/Components/Window/UploadsDownloadsWindow.js +30 -12
- package/src/Components/Form/Field/FormikForm.js +0 -1019
package/package.json
CHANGED
|
@@ -321,7 +321,11 @@ function Form(props) {
|
|
|
321
321
|
type = 'Text';
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
|
-
const
|
|
324
|
+
const
|
|
325
|
+
Element = getComponentFromType(type),
|
|
326
|
+
shouldHideFieldUi = type === 'Hidden',
|
|
327
|
+
isCombo = type?.match && type.match(/Combo/);
|
|
328
|
+
|
|
325
329
|
if (config.hasOwnProperty('autoLoad')) {
|
|
326
330
|
editorTypeProps.autoLoad = config.autoLoad;
|
|
327
331
|
} else {
|
|
@@ -335,9 +339,8 @@ function Form(props) {
|
|
|
335
339
|
editorTypeProps.showXButton = true;
|
|
336
340
|
}
|
|
337
341
|
}
|
|
338
|
-
const Element = getComponentFromType(type);
|
|
339
342
|
|
|
340
|
-
if (isEditorViewOnly || !isEditable) {
|
|
343
|
+
if ((isEditorViewOnly || !isEditable) && !shouldHideFieldUi) {
|
|
341
344
|
let value = null;
|
|
342
345
|
if (renderer) {
|
|
343
346
|
value = renderer(record);
|
|
@@ -483,6 +486,9 @@ function Form(props) {
|
|
|
483
486
|
{...dynamicProps}
|
|
484
487
|
className={elementClassName}
|
|
485
488
|
/>;
|
|
489
|
+
if (shouldHideFieldUi) {
|
|
490
|
+
return element;
|
|
491
|
+
}
|
|
486
492
|
|
|
487
493
|
const dirtyIcon = isDirty && !disableDirtyIcon ?
|
|
488
494
|
<Icon
|
|
@@ -532,6 +538,7 @@ function Form(props) {
|
|
|
532
538
|
isEditable = true,
|
|
533
539
|
isEditingEnabledInPlainEditor,
|
|
534
540
|
label,
|
|
541
|
+
disableLabel = false,
|
|
535
542
|
labelWidth,
|
|
536
543
|
items,
|
|
537
544
|
onChange: onEditorChange,
|
|
@@ -588,7 +595,11 @@ function Form(props) {
|
|
|
588
595
|
type = 'Text';
|
|
589
596
|
}
|
|
590
597
|
}
|
|
591
|
-
const
|
|
598
|
+
const
|
|
599
|
+
Element = getComponentFromType(type),
|
|
600
|
+
shouldHideFieldUi = type === 'Hidden',
|
|
601
|
+
isCombo = type?.match && type.match(/Combo/);
|
|
602
|
+
|
|
592
603
|
if (item.hasOwnProperty('autoLoad')) {
|
|
593
604
|
editorTypeProps.autoLoad = item.autoLoad;
|
|
594
605
|
} else {
|
|
@@ -602,7 +613,6 @@ function Form(props) {
|
|
|
602
613
|
editorTypeProps.showXButton = true;
|
|
603
614
|
}
|
|
604
615
|
}
|
|
605
|
-
const Element = getComponentFromType(type);
|
|
606
616
|
|
|
607
617
|
if (inArray(type, ['Column', 'Row', 'FieldSet'])) {
|
|
608
618
|
if (_.isEmpty(items)) {
|
|
@@ -676,7 +686,7 @@ function Form(props) {
|
|
|
676
686
|
label = propertyDef.title;
|
|
677
687
|
}
|
|
678
688
|
|
|
679
|
-
if (isEditorViewOnly || !isEditable) {
|
|
689
|
+
if ((isEditorViewOnly || !isEditable) && !shouldHideFieldUi) {
|
|
680
690
|
let value = null;
|
|
681
691
|
if (isSingle) {
|
|
682
692
|
value = record?.properties?.[name]?.displayValue || null;
|
|
@@ -713,7 +723,10 @@ function Form(props) {
|
|
|
713
723
|
{...viewerTypeProps}
|
|
714
724
|
className={elementClassName}
|
|
715
725
|
/>;
|
|
716
|
-
if (
|
|
726
|
+
if (shouldHideFieldUi) {
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
if (!disableLabels && !disableLabel && label) {
|
|
717
730
|
const style = {};
|
|
718
731
|
if (defaults?.labelWidth) {
|
|
719
732
|
style.width = defaults.labelWidth;
|
|
@@ -846,6 +859,9 @@ function Form(props) {
|
|
|
846
859
|
{...dynamicProps}
|
|
847
860
|
className={elementClassName}
|
|
848
861
|
/>;
|
|
862
|
+
if (shouldHideFieldUi) {
|
|
863
|
+
return element;
|
|
864
|
+
}
|
|
849
865
|
let message = null;
|
|
850
866
|
if (error) {
|
|
851
867
|
message = error.message;
|
|
@@ -905,7 +921,7 @@ function Form(props) {
|
|
|
905
921
|
}
|
|
906
922
|
}
|
|
907
923
|
const labelToUse = dynamicProps.label || label;
|
|
908
|
-
if (!disableLabels && labelToUse && editorType !== EDITOR_TYPE__INLINE) {
|
|
924
|
+
if (!disableLabels && !disableLabel && labelToUse && editorType !== EDITOR_TYPE__INLINE) {
|
|
909
925
|
const style = {};
|
|
910
926
|
if (defaults?.labelWidth) {
|
|
911
927
|
style.width = defaults.labelWidth;
|
|
@@ -933,7 +949,7 @@ function Form(props) {
|
|
|
933
949
|
{element}
|
|
934
950
|
</VStack>;
|
|
935
951
|
}
|
|
936
|
-
} else if (disableLabels && requiredIndicator) {
|
|
952
|
+
} else if ((disableLabels || disableLabel) && requiredIndicator) {
|
|
937
953
|
element = <HStack className="Form-HStack10 w-full">
|
|
938
954
|
{requiredIndicator}
|
|
939
955
|
{element}
|
|
@@ -1274,7 +1290,10 @@ function Form(props) {
|
|
|
1274
1290
|
showCancelBtn = false,
|
|
1275
1291
|
showSaveBtn = false,
|
|
1276
1292
|
showSubmitBtn = false,
|
|
1277
|
-
isAddMode = getEditorMode() === EDITOR_MODE__ADD
|
|
1293
|
+
isAddMode = getEditorMode() === EDITOR_MODE__ADD,
|
|
1294
|
+
isEditableMode =
|
|
1295
|
+
getEditorMode() === EDITOR_MODE__ADD ||
|
|
1296
|
+
getEditorMode() === EDITOR_MODE__EDIT;
|
|
1278
1297
|
if (containerWidth) { // we need to render this component twice in order to get the container width. Skip this on first render
|
|
1279
1298
|
|
|
1280
1299
|
// create editor
|
|
@@ -1363,7 +1382,7 @@ function Form(props) {
|
|
|
1363
1382
|
if (onDelete && getEditorMode() === EDITOR_MODE__EDIT && isSingle) {
|
|
1364
1383
|
showDeleteBtn = true;
|
|
1365
1384
|
}
|
|
1366
|
-
if (!isEditorViewOnly && !hideResetButton) {
|
|
1385
|
+
if (!isEditorViewOnly && isEditableMode && !hideResetButton) {
|
|
1367
1386
|
showResetBtn = true;
|
|
1368
1387
|
}
|
|
1369
1388
|
// determine whether we should show the close or cancel button
|
|
@@ -1394,7 +1413,7 @@ function Form(props) {
|
|
|
1394
1413
|
}
|
|
1395
1414
|
}
|
|
1396
1415
|
}
|
|
1397
|
-
if (!isEditorViewOnly && onSave) {
|
|
1416
|
+
if (!isEditorViewOnly && isEditableMode && onSave) {
|
|
1398
1417
|
showSaveBtn = true;
|
|
1399
1418
|
}
|
|
1400
1419
|
if (!!onSubmit) {
|
|
@@ -62,9 +62,9 @@ export default function withPdfButtons(WrappedComponent) {
|
|
|
62
62
|
buildModalItems = () => {
|
|
63
63
|
// Build a cloned PDF item tree so we never mutate source items by reference.
|
|
64
64
|
const
|
|
65
|
-
|
|
65
|
+
itemsToUse = pdfItems || _.filter(items, (item) => item?.type !== 'Hidden'),
|
|
66
66
|
ancillaryItemsToUse = pdfAncillaryItems || ancillaryItems,
|
|
67
|
-
modalItems = _.compact(_.map(
|
|
67
|
+
modalItems = _.compact(_.map(itemsToUse, (item, ix) => buildNextLayer(item, ix, columnDefaults)));
|
|
68
68
|
|
|
69
69
|
if (!_.isEmpty(ancillaryItemsToUse)) {
|
|
70
70
|
const
|
|
@@ -436,6 +436,10 @@ export default function withPresetButtons(WrappedComponent) {
|
|
|
436
436
|
showInfo('Copied to clipboard!');
|
|
437
437
|
}
|
|
438
438
|
},
|
|
439
|
+
getColumnsConfigForDownload = () => {
|
|
440
|
+
const activeColumnsConfig = localColumnsConfig.length ? localColumnsConfig : props.columnsConfig;
|
|
441
|
+
return _.filter(activeColumnsConfig, (config) => !config.isHidden);
|
|
442
|
+
},
|
|
439
443
|
onUploadDownload = () => {
|
|
440
444
|
const onUploadDecorator = async () => {
|
|
441
445
|
if (onUpload) {
|
|
@@ -454,7 +458,7 @@ export default function withPresetButtons(WrappedComponent) {
|
|
|
454
458
|
reference="uploadsDownloads"
|
|
455
459
|
onClose={hideModal}
|
|
456
460
|
Repository={Repository}
|
|
457
|
-
columnsConfig={
|
|
461
|
+
columnsConfig={getColumnsConfigForDownload()}
|
|
458
462
|
uploadHeaders={uploadHeaders}
|
|
459
463
|
uploadParams={uploadParams}
|
|
460
464
|
onUpload={onUploadDecorator}
|
|
@@ -471,7 +475,7 @@ export default function withPresetButtons(WrappedComponent) {
|
|
|
471
475
|
onClose={hideModal}
|
|
472
476
|
isDownloadOnly={true}
|
|
473
477
|
Repository={Repository}
|
|
474
|
-
columnsConfig={
|
|
478
|
+
columnsConfig={getColumnsConfigForDownload()}
|
|
475
479
|
downloadHeaders={downloadHeaders}
|
|
476
480
|
downloadParams={downloadParams}
|
|
477
481
|
/>,
|
|
@@ -107,6 +107,14 @@ function Viewer(props) {
|
|
|
107
107
|
}),
|
|
108
108
|
isSideEditor = editorType === EDITOR_TYPE__SIDE,
|
|
109
109
|
isSmartEditor = editorType === EDITOR_TYPE__SMART,
|
|
110
|
+
parentEditorModeRaw = (getEditorMode && getEditorMode()) || editorMode || null,
|
|
111
|
+
parentEditorMode = parentEditorModeRaw === EDITOR_MODE__ADD
|
|
112
|
+
? EDITOR_MODE__EDIT
|
|
113
|
+
: parentEditorModeRaw,
|
|
114
|
+
normalizedParentEditorMode =
|
|
115
|
+
parentEditorMode === EDITOR_MODE__EDIT || parentEditorMode === EDITOR_MODE__VIEW
|
|
116
|
+
? parentEditorMode
|
|
117
|
+
: null,
|
|
110
118
|
styles = UiGlobals.styles,
|
|
111
119
|
flex = props.flex || 1,
|
|
112
120
|
buildFromItems = () => {
|
|
@@ -125,6 +133,7 @@ function Viewer(props) {
|
|
|
125
133
|
title,
|
|
126
134
|
name,
|
|
127
135
|
label,
|
|
136
|
+
disableLabel = false,
|
|
128
137
|
items,
|
|
129
138
|
useSelectorId = false,
|
|
130
139
|
isHidden = false,
|
|
@@ -160,7 +169,11 @@ function Viewer(props) {
|
|
|
160
169
|
type = 'Text';
|
|
161
170
|
}
|
|
162
171
|
}
|
|
163
|
-
const
|
|
172
|
+
const
|
|
173
|
+
Element = getComponentFromType(type),
|
|
174
|
+
shouldHideFieldUi = type === 'Hidden',
|
|
175
|
+
isCombo = type?.match && type.match(/Combo/);
|
|
176
|
+
|
|
164
177
|
if (item.hasOwnProperty('autoLoad')) {
|
|
165
178
|
viewerTypeProps.autoLoad = item.autoLoad;
|
|
166
179
|
} else {
|
|
@@ -175,7 +188,6 @@ function Viewer(props) {
|
|
|
175
188
|
if (type?.match(/(Grid|GridEditor)$/)) {
|
|
176
189
|
viewerTypeProps.canEditorViewOnly = true;
|
|
177
190
|
}
|
|
178
|
-
const Element = getComponentFromType(type);
|
|
179
191
|
|
|
180
192
|
if (inArray(type, ['Column', 'Row', 'FieldSet'])) {
|
|
181
193
|
if (_.isEmpty(items)) {
|
|
@@ -264,6 +276,10 @@ function Viewer(props) {
|
|
|
264
276
|
>{children}</Element>;
|
|
265
277
|
}
|
|
266
278
|
|
|
279
|
+
if (shouldHideFieldUi) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
267
283
|
if (!label && Repository && propertyDef?.title) {
|
|
268
284
|
label = propertyDef.title;
|
|
269
285
|
}
|
|
@@ -334,7 +350,7 @@ function Viewer(props) {
|
|
|
334
350
|
</HStack>;
|
|
335
351
|
}
|
|
336
352
|
|
|
337
|
-
if (!disableLabels && label) {
|
|
353
|
+
if (!disableLabels && !disableLabel && label) {
|
|
338
354
|
const style = {};
|
|
339
355
|
if (defaults?.labelWidth) {
|
|
340
356
|
style.width = defaults.labelWidth;
|
|
@@ -359,14 +375,6 @@ function Viewer(props) {
|
|
|
359
375
|
buildAncillary = () => {
|
|
360
376
|
const
|
|
361
377
|
validAncillaryItems = _.filter(ancillaryItems, (item) => !!item), // filter out any null/undefined items
|
|
362
|
-
parentEditorModeRaw = (getEditorMode && getEditorMode()) || editorMode || null,
|
|
363
|
-
parentEditorMode = parentEditorModeRaw === EDITOR_MODE__ADD
|
|
364
|
-
? EDITOR_MODE__EDIT
|
|
365
|
-
: parentEditorModeRaw,
|
|
366
|
-
normalizedParentEditorMode =
|
|
367
|
-
parentEditorMode === EDITOR_MODE__EDIT || parentEditorMode === EDITOR_MODE__VIEW
|
|
368
|
-
? parentEditorMode
|
|
369
|
-
: null,
|
|
370
378
|
components = [];
|
|
371
379
|
setAncillaryButtons([]);
|
|
372
380
|
if (validAncillaryItems.length) {
|
|
@@ -584,7 +592,7 @@ function Viewer(props) {
|
|
|
584
592
|
|
|
585
593
|
<Toolbar className="justify-end">
|
|
586
594
|
<HStack className="flex-1 items-center">
|
|
587
|
-
<Text className="text-[20px] ml-1 text-grey-500">{isEditorModeControlledByParent ? 'View Mode (Inherited)' : 'View Mode'}</Text>
|
|
595
|
+
<Text className="text-[20px] ml-1 text-grey-500">{isEditorModeControlledByParent && normalizedParentEditorMode === EDITOR_MODE__VIEW ? 'View Mode (Inherited)' : 'View Mode'}</Text>
|
|
588
596
|
</HStack>
|
|
589
597
|
{onEditMode && (!canUser || canUser(EDIT)) &&
|
|
590
598
|
<Button
|
|
@@ -18,6 +18,11 @@ import Upload from '../Icons/Upload';
|
|
|
18
18
|
import Cookies from 'js-cookie';
|
|
19
19
|
import _ from 'lodash';
|
|
20
20
|
|
|
21
|
+
const
|
|
22
|
+
MODES__GRID = 'grid',
|
|
23
|
+
MODES__BATCH = 'batch',
|
|
24
|
+
MODES__TEMPLATE = 'template';
|
|
25
|
+
|
|
21
26
|
function UploadsDownloadsWindow(props) {
|
|
22
27
|
const {
|
|
23
28
|
Repository,
|
|
@@ -37,8 +42,8 @@ function UploadsDownloadsWindow(props) {
|
|
|
37
42
|
showInfo,
|
|
38
43
|
} = props,
|
|
39
44
|
[importFile, setImportFile] = useState(null),
|
|
40
|
-
[width, height] = useAdjustedWindowSize(400,
|
|
41
|
-
onDownload = (
|
|
45
|
+
[width, height] = useAdjustedWindowSize(400, 550),
|
|
46
|
+
onDownload = (mode) => {
|
|
42
47
|
|
|
43
48
|
const win = window.open('');
|
|
44
49
|
win.document.write('<html><head><title>Downloading</title></head><body><img style="position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);" src="' +
|
|
@@ -63,7 +68,7 @@ function UploadsDownloadsWindow(props) {
|
|
|
63
68
|
columns,
|
|
64
69
|
order,
|
|
65
70
|
model,
|
|
66
|
-
|
|
71
|
+
mode,
|
|
67
72
|
...Repository._params,
|
|
68
73
|
...downloadParams,
|
|
69
74
|
}),
|
|
@@ -79,9 +84,6 @@ function UploadsDownloadsWindow(props) {
|
|
|
79
84
|
}
|
|
80
85
|
}, 1000);
|
|
81
86
|
},
|
|
82
|
-
onDownloadTemplate = () => {
|
|
83
|
-
onDownload(true);
|
|
84
|
-
},
|
|
85
87
|
onUploadLocal = async () => {
|
|
86
88
|
const
|
|
87
89
|
url = Repository.api.baseURL + Repository.name + '/uploadBatch',
|
|
@@ -130,24 +132,40 @@ function UploadsDownloadsWindow(props) {
|
|
|
130
132
|
const items = [
|
|
131
133
|
{
|
|
132
134
|
type: 'DisplayField',
|
|
133
|
-
text:
|
|
135
|
+
text: "Get the current grid's contents as an Excel file.",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: 'Button',
|
|
139
|
+
text: 'Get as Excel',
|
|
140
|
+
isEditable: false,
|
|
141
|
+
icon: Excel,
|
|
142
|
+
_icon: {
|
|
143
|
+
size: 'md',
|
|
144
|
+
},
|
|
145
|
+
onPress: () => onDownload(MODES__GRID),
|
|
146
|
+
className: 'mb-5',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
type: 'DisplayField',
|
|
150
|
+
text: "Batch download the current grid's records,\n"
|
|
151
|
+
+ "so they can be edited and re-uploaded.",
|
|
134
152
|
},
|
|
135
153
|
{
|
|
136
154
|
type: 'Button',
|
|
137
|
-
text: 'Download',
|
|
155
|
+
text: 'Batch Download',
|
|
138
156
|
isEditable: false,
|
|
139
157
|
icon: Excel,
|
|
140
158
|
_icon: {
|
|
141
159
|
size: 'md',
|
|
142
160
|
},
|
|
143
|
-
onPress: () => onDownload(),
|
|
161
|
+
onPress: () => onDownload(MODES__BATCH),
|
|
144
162
|
className: 'mb-5',
|
|
145
163
|
},
|
|
146
164
|
];
|
|
147
165
|
if (!isDownloadOnly) {
|
|
148
166
|
items.push({
|
|
149
167
|
type: 'DisplayField',
|
|
150
|
-
text: '
|
|
168
|
+
text: 'Batch upload an Excel file to the current grid.',
|
|
151
169
|
});
|
|
152
170
|
items.push({
|
|
153
171
|
type: 'File',
|
|
@@ -161,7 +179,7 @@ function UploadsDownloadsWindow(props) {
|
|
|
161
179
|
items: [
|
|
162
180
|
{
|
|
163
181
|
type: 'Button',
|
|
164
|
-
text: 'Upload',
|
|
182
|
+
text: 'Batch Upload',
|
|
165
183
|
isEditable: false,
|
|
166
184
|
icon: Upload,
|
|
167
185
|
_icon: {
|
|
@@ -175,7 +193,7 @@ function UploadsDownloadsWindow(props) {
|
|
|
175
193
|
text: 'Get Template',
|
|
176
194
|
icon: Download,
|
|
177
195
|
isEditable: false,
|
|
178
|
-
onPress:
|
|
196
|
+
onPress: () => onDownload(MODES__TEMPLATE),
|
|
179
197
|
},
|
|
180
198
|
|
|
181
199
|
],
|
|
@@ -1,1019 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState, useRef, isValidElement, } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
Box,
|
|
4
|
-
HStack,
|
|
5
|
-
Icon,
|
|
6
|
-
ScrollView,
|
|
7
|
-
Text,
|
|
8
|
-
TextNative,
|
|
9
|
-
VStack,
|
|
10
|
-
VStackNative,
|
|
11
|
-
} from '@project-components/Gluestack';
|
|
12
|
-
import clsx from 'clsx';
|
|
13
|
-
import { View, } from 'react-native';
|
|
14
|
-
import {
|
|
15
|
-
EDITOR_TYPE__INLINE,
|
|
16
|
-
EDITOR_TYPE__WINDOWED,
|
|
17
|
-
EDITOR_TYPE__SIDE,
|
|
18
|
-
EDITOR_TYPE__SMART,
|
|
19
|
-
EDITOR_TYPE__PLAIN,
|
|
20
|
-
EDITOR_MODE__VIEW,
|
|
21
|
-
EDITOR_MODE__ADD,
|
|
22
|
-
EDITOR_MODE__EDIT,
|
|
23
|
-
} from '../../../Constants/Editor.js';
|
|
24
|
-
import { Form, Formik, Field } from "formik"; // https://formik.org/docs/overview
|
|
25
|
-
import { useForm, Controller } from 'react-hook-form'; // https://react-hook-form.com/api/
|
|
26
|
-
import * as yup from 'yup'; // https://github.com/jquense/yup#string
|
|
27
|
-
import { yupResolver } from '@hookform/resolvers/yup';
|
|
28
|
-
import useForceUpdate from '../../../Hooks/useForceUpdate.js';
|
|
29
|
-
import UiGlobals from '../../../UiGlobals.js';
|
|
30
|
-
import withAlert from '../../../Hoc/withAlert.js';
|
|
31
|
-
import withComponent from '../../../Hoc/withComponent.js';
|
|
32
|
-
import withEditor from '../../../Hoc/withEditor.js';
|
|
33
|
-
import withPdfButton from '../../../Hoc/withPdfButton.js';
|
|
34
|
-
import inArray from '../../../Functions/inArray.js';
|
|
35
|
-
import getComponentFromType from '../../../Functions/getComponentFromType.js';
|
|
36
|
-
import buildAdditionalButtons from '../../../Functions/buildAdditionalButtons.js';
|
|
37
|
-
import Button from '../../../Buttons/Button.js';
|
|
38
|
-
import IconButton from '../../../Buttons/IconButton.js';
|
|
39
|
-
import AngleLeft from '../../../Icons/AngleLeft.js';
|
|
40
|
-
import Eye from '../../../Icons/Eye.js';
|
|
41
|
-
import Rotate from '../../../Icons/Rotate.js';
|
|
42
|
-
import Pencil from '../../../Icons/Pencil.js';
|
|
43
|
-
import Footer from '../../../Layout/Footer.js';
|
|
44
|
-
import Label from '../../../Form/Label.js';
|
|
45
|
-
import _ from 'lodash';
|
|
46
|
-
|
|
47
|
-
// TODO: memoize field Components
|
|
48
|
-
|
|
49
|
-
// Modes:
|
|
50
|
-
// EDITOR_TYPE__INLINE
|
|
51
|
-
// Form is a single scrollable row, based on columnsConfig and Repository
|
|
52
|
-
//
|
|
53
|
-
// EDITOR_TYPE__WINDOWED
|
|
54
|
-
// EDITOR_TYPE__SIDE
|
|
55
|
-
// Form is a popup or side window, used for editing items in a grid. Integrated with Repository
|
|
56
|
-
//
|
|
57
|
-
// EDITOR_TYPE__SMART
|
|
58
|
-
// Form is a standalone editor
|
|
59
|
-
//
|
|
60
|
-
// EDITOR_TYPE__PLAIN
|
|
61
|
-
// Form is embedded on screen in some other way. Mainly use startingValues, items, validator
|
|
62
|
-
|
|
63
|
-
function FormikForm(props) {
|
|
64
|
-
const
|
|
65
|
-
{
|
|
66
|
-
editorType = EDITOR_TYPE__WINDOWED, // EDITOR_TYPE__INLINE | EDITOR_TYPE__WINDOWED | EDITOR_TYPE__SIDE | EDITOR_TYPE__SMART | EDITOR_TYPE__PLAIN
|
|
67
|
-
startingValues = {},
|
|
68
|
-
items = [], // Columns, FieldSets, Fields, etc to define the form
|
|
69
|
-
ancillaryItems = [], // additional items which are not controllable form elements, but should appear in the form
|
|
70
|
-
columnDefaults = {}, // defaults for each Column defined in items (above)
|
|
71
|
-
columnsConfig, // Which columns are shown in Grid, so the inline editor can match. Used only for EDITOR_TYPE__INLINE
|
|
72
|
-
validator, // custom validator, mainly for EDITOR_TYPE__PLAIN
|
|
73
|
-
footerProps = {},
|
|
74
|
-
buttonGroupProps = {}, // buttons in footer
|
|
75
|
-
checkIsEditingDisabled = true,
|
|
76
|
-
disableLabels = false,
|
|
77
|
-
disableDirtyIcon = false,
|
|
78
|
-
onBack,
|
|
79
|
-
onReset,
|
|
80
|
-
onInit,
|
|
81
|
-
onViewMode,
|
|
82
|
-
submitBtnLabel,
|
|
83
|
-
onSubmit,
|
|
84
|
-
formSetup, // this fn will be executed after the form setup is complete
|
|
85
|
-
additionalEditButtons,
|
|
86
|
-
useAdditionalEditButtons = true,
|
|
87
|
-
additionalFooterButtons,
|
|
88
|
-
|
|
89
|
-
// sizing of outer container
|
|
90
|
-
h,
|
|
91
|
-
maxHeight,
|
|
92
|
-
minHeight = 0,
|
|
93
|
-
w,
|
|
94
|
-
maxWidth,
|
|
95
|
-
flex,
|
|
96
|
-
onLayout, // onLayout handler for main view
|
|
97
|
-
|
|
98
|
-
// withComponent
|
|
99
|
-
self,
|
|
100
|
-
|
|
101
|
-
// withData
|
|
102
|
-
Repository,
|
|
103
|
-
|
|
104
|
-
// withEditor
|
|
105
|
-
isEditorViewOnly = false,
|
|
106
|
-
isSaving = false,
|
|
107
|
-
editorMode,
|
|
108
|
-
onCancel,
|
|
109
|
-
onSave,
|
|
110
|
-
onClose,
|
|
111
|
-
onDelete,
|
|
112
|
-
editorStateRef,
|
|
113
|
-
disableView,
|
|
114
|
-
|
|
115
|
-
// parent container
|
|
116
|
-
selectorId,
|
|
117
|
-
selectorSelected,
|
|
118
|
-
|
|
119
|
-
// withAlert
|
|
120
|
-
alert,
|
|
121
|
-
} = props,
|
|
122
|
-
formRef = useRef(),
|
|
123
|
-
styles = UiGlobals.styles,
|
|
124
|
-
record = props.record?.length === 1 ? props.record[0] : props.record;
|
|
125
|
-
let skipAll = false;
|
|
126
|
-
if (record?.isDestroyed) {
|
|
127
|
-
skipAll = true; // if record is destroyed, skip render, but allow hooks to still be called
|
|
128
|
-
if (self?.parent?.parent?.setIsEditorShown) {
|
|
129
|
-
self.parent.parent.setIsEditorShown(false); // close the editor
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const
|
|
133
|
-
isMultiple = _.isArray(record),
|
|
134
|
-
isSingle = !isMultiple, // for convenience
|
|
135
|
-
isPhantom = !skipAll && !!record?.isPhantom, //
|
|
136
|
-
forceUpdate = useForceUpdate(),
|
|
137
|
-
[previousRecord, setPreviousRecord] = useState(record),
|
|
138
|
-
[containerWidth, setContainerWidth] = useState(),
|
|
139
|
-
initialValues = _.merge(startingValues, (record && !record.isDestroyed ? record.submitValues : {})),
|
|
140
|
-
defaultValues = isMultiple ? getNullFieldValues(initialValues, Repository) : initialValues, // when multiple entities, set all default values to null
|
|
141
|
-
validatorToUse = validator || (isMultiple ? disableRequiredYupFields(Repository?.schema?.model?.validator) : Repository?.schema?.model?.validator) || yup.object(),
|
|
142
|
-
{
|
|
143
|
-
control,
|
|
144
|
-
formState,
|
|
145
|
-
handleSubmit,
|
|
146
|
-
// register,
|
|
147
|
-
// unregister,
|
|
148
|
-
reset,
|
|
149
|
-
// watch,
|
|
150
|
-
// resetField,
|
|
151
|
-
// setError,
|
|
152
|
-
// clearErrors,
|
|
153
|
-
setValue: formSetValue,
|
|
154
|
-
// setFocus,
|
|
155
|
-
getValues: formGetValues,
|
|
156
|
-
// getFieldState,
|
|
157
|
-
trigger,
|
|
158
|
-
} = useForm({
|
|
159
|
-
mode: 'onChange', // onChange | onBlur | onSubmit | onTouched | all
|
|
160
|
-
// reValidateMode: 'onChange', // onChange | onBlur | onSubmit
|
|
161
|
-
defaultValues,
|
|
162
|
-
// values: defaultValues,
|
|
163
|
-
// resetOptions: {
|
|
164
|
-
// keepDirtyValues: false, // user-interacted input will be retained
|
|
165
|
-
// keepErrors: false, // input errors will be retained with value update
|
|
166
|
-
// },
|
|
167
|
-
// criteriaMode: 'firstError', // firstError | all
|
|
168
|
-
// shouldFocusError: false,
|
|
169
|
-
// delayError: 0,
|
|
170
|
-
// shouldUnregister: false,
|
|
171
|
-
// shouldUseNativeValidation: false,
|
|
172
|
-
resolver: yupResolver(validatorToUse),
|
|
173
|
-
context: { isPhantom },
|
|
174
|
-
}),
|
|
175
|
-
buildFromColumnsConfig = () => {
|
|
176
|
-
// For InlineEditor
|
|
177
|
-
// Build the fields that match the current columnsConfig in the grid
|
|
178
|
-
const
|
|
179
|
-
model = Repository.getSchema().model,
|
|
180
|
-
elements = [],
|
|
181
|
-
columnProps = {
|
|
182
|
-
justifyContent: 'center',
|
|
183
|
-
alignItems: 'center',
|
|
184
|
-
borderRightWidth: 1,
|
|
185
|
-
borderRightColor: 'grey-200',
|
|
186
|
-
px: 1,
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
if (editorType === EDITOR_TYPE__INLINE) {
|
|
190
|
-
columnProps.minWidth = styles.INLINE_EDITOR_MIN_WIDTH;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
_.each(columnsConfig, (config, ix) => {
|
|
194
|
-
let {
|
|
195
|
-
fieldName,
|
|
196
|
-
isEditable,
|
|
197
|
-
editor,
|
|
198
|
-
renderer,
|
|
199
|
-
w,
|
|
200
|
-
flex,
|
|
201
|
-
useSelectorId = false,
|
|
202
|
-
} = config;
|
|
203
|
-
|
|
204
|
-
if (!isEditable) {
|
|
205
|
-
let renderedValue = renderer ? renderer(record) : record[fieldName];
|
|
206
|
-
if (_.isBoolean(renderedValue)) {
|
|
207
|
-
renderedValue = renderedValue.toString();
|
|
208
|
-
}
|
|
209
|
-
renderedValue += "\n(not editable)";
|
|
210
|
-
elements.push(<Box key={ix} {...columnProps} className={` flex-${flex} w-${w} `}>
|
|
211
|
-
<TextNative numberOfLines={1} ellipsizeMode="head">{renderedValue}</TextNative>
|
|
212
|
-
</Box>);
|
|
213
|
-
} else {
|
|
214
|
-
elements.push(<Controller
|
|
215
|
-
key={'controller-' + ix}
|
|
216
|
-
name={fieldName}
|
|
217
|
-
// rules={rules}
|
|
218
|
-
control={control}
|
|
219
|
-
render={(args) => {
|
|
220
|
-
const {
|
|
221
|
-
field,
|
|
222
|
-
fieldState,
|
|
223
|
-
// formState,
|
|
224
|
-
} = args,
|
|
225
|
-
{
|
|
226
|
-
onChange,
|
|
227
|
-
onBlur,
|
|
228
|
-
name,
|
|
229
|
-
value,
|
|
230
|
-
// ref,
|
|
231
|
-
} = field,
|
|
232
|
-
{
|
|
233
|
-
isTouched,
|
|
234
|
-
isDirty,
|
|
235
|
-
error,
|
|
236
|
-
} = fieldState;
|
|
237
|
-
let _editor = {};
|
|
238
|
-
if (!editor) {
|
|
239
|
-
const propertyDef = fieldName && Repository?.getSchema().getPropertyDefinition(fieldName);
|
|
240
|
-
editor = propertyDef && propertyDef[fieldName].editorType;
|
|
241
|
-
if (_.isPlainObject(editor)) {
|
|
242
|
-
const {
|
|
243
|
-
type,
|
|
244
|
-
onChange: onEditorChange,
|
|
245
|
-
...p
|
|
246
|
-
} = editor;
|
|
247
|
-
_editor = p;
|
|
248
|
-
editor = type;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
const Element = getComponentFromType(editor);
|
|
252
|
-
|
|
253
|
-
if (useSelectorId) {
|
|
254
|
-
_editor.selectorId = selectorId;
|
|
255
|
-
_editor.selectorSelected = _editor;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
let element = <Element
|
|
259
|
-
name={name}
|
|
260
|
-
value={value}
|
|
261
|
-
setValue={(newValue) => {
|
|
262
|
-
onChange(newValue);
|
|
263
|
-
if (onEditorChange) {
|
|
264
|
-
onEditorChange(newValue, formSetValue, formGetValues, formState);
|
|
265
|
-
}
|
|
266
|
-
}}
|
|
267
|
-
onBlur={onBlur}
|
|
268
|
-
flex={1}
|
|
269
|
-
{..._editor}
|
|
270
|
-
parent={self}
|
|
271
|
-
reference={fieldName}
|
|
272
|
-
// {...defaults}
|
|
273
|
-
// {...propsToPass}
|
|
274
|
-
/>;
|
|
275
|
-
|
|
276
|
-
// element = <Tooltip key={ix} label={header} placement="bottom">
|
|
277
|
-
// {element}
|
|
278
|
-
// </Tooltip>;
|
|
279
|
-
// if (error) {
|
|
280
|
-
// element = <VStack className="pt-1 flex-1">
|
|
281
|
-
// {element}
|
|
282
|
-
// <Text color="#f00">{error.message}</Text>
|
|
283
|
-
// </VStack>;
|
|
284
|
-
// }
|
|
285
|
-
|
|
286
|
-
const dirtyIcon = isDirty && !disableDirtyIcon ? <Icon
|
|
287
|
-
as={Pencil}
|
|
288
|
-
size="2xs"
|
|
289
|
-
className="text-grey-300 absolute top-[2px] left-[2px]"
|
|
290
|
-
/> : null;
|
|
291
|
-
return <HStack
|
|
292
|
-
key={ix}
|
|
293
|
-
{...columnProps}
|
|
294
|
-
className={` flex-${flex} w-${w} ${error ? "bg-[#fdd]" : "bg-white"} `}
|
|
295
|
-
>{dirtyIcon}{element}</HStack>;
|
|
296
|
-
}}
|
|
297
|
-
/>);
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
return <HStack>{elements}</HStack>;
|
|
301
|
-
},
|
|
302
|
-
buildFromItems = () => {
|
|
303
|
-
return _.map(items, (item, ix) => buildFromItem(item, ix, columnDefaults));
|
|
304
|
-
},
|
|
305
|
-
buildFromItem = (item, ix, defaults) => {
|
|
306
|
-
if (!item) {
|
|
307
|
-
return null;
|
|
308
|
-
}
|
|
309
|
-
if (React.isValidElement(item)) {
|
|
310
|
-
return item;
|
|
311
|
-
}
|
|
312
|
-
let {
|
|
313
|
-
type,
|
|
314
|
-
title,
|
|
315
|
-
name,
|
|
316
|
-
isEditable = true,
|
|
317
|
-
label,
|
|
318
|
-
items,
|
|
319
|
-
onChange: onEditorChange,
|
|
320
|
-
useSelectorId = false,
|
|
321
|
-
isHidden = false,
|
|
322
|
-
getDynamicProps,
|
|
323
|
-
getIsRequired,
|
|
324
|
-
...propsToPass
|
|
325
|
-
} = item,
|
|
326
|
-
editorTypeProps = {};
|
|
327
|
-
|
|
328
|
-
if (isHidden) {
|
|
329
|
-
return null;
|
|
330
|
-
}
|
|
331
|
-
if (type === 'DisplayField') {
|
|
332
|
-
isEditable = false;
|
|
333
|
-
}
|
|
334
|
-
const propertyDef = name && Repository?.getSchema().getPropertyDefinition(name);
|
|
335
|
-
if (!useAdditionalEditButtons) {
|
|
336
|
-
item = _.omit(item, 'additionalEditButtons');
|
|
337
|
-
}
|
|
338
|
-
if (propertyDef?.isEditingDisabled && checkIsEditingDisabled) {
|
|
339
|
-
isEditable = false;
|
|
340
|
-
}
|
|
341
|
-
if (!type) {
|
|
342
|
-
if (isEditable) {
|
|
343
|
-
const
|
|
344
|
-
{
|
|
345
|
-
type: t,
|
|
346
|
-
...p
|
|
347
|
-
} = propertyDef?.editorType;
|
|
348
|
-
type = t;
|
|
349
|
-
editorTypeProps = p;
|
|
350
|
-
} else if (propertyDef?.viewerType) {
|
|
351
|
-
const
|
|
352
|
-
{
|
|
353
|
-
type: t,
|
|
354
|
-
...p
|
|
355
|
-
} = propertyDef?.viewerType;
|
|
356
|
-
type = t;
|
|
357
|
-
} else {
|
|
358
|
-
type = 'Text';
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
const isCombo = type?.match && type.match(/Combo/);
|
|
362
|
-
if (item.hasOwnProperty('autoLoad')) {
|
|
363
|
-
editorTypeProps.autoLoad = item.autoLoad;
|
|
364
|
-
} else {
|
|
365
|
-
if (isCombo && Repository?.isRemote && !Repository?.isLoaded) {
|
|
366
|
-
editorTypeProps.autoLoad = true;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
if (isCombo) {
|
|
370
|
-
// editorTypeProps.showEyeButton = true;
|
|
371
|
-
if (_.isNil(propsToPass.showXButton)) {
|
|
372
|
-
editorTypeProps.showXButton = true;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
const Element = getComponentFromType(type);
|
|
376
|
-
let children;
|
|
377
|
-
|
|
378
|
-
if (inArray(type, ['Column', 'Row', 'FieldSet'])) {
|
|
379
|
-
if (_.isEmpty(items)) {
|
|
380
|
-
return null;
|
|
381
|
-
}
|
|
382
|
-
if (type === 'Column') {
|
|
383
|
-
if (containerWidth < styles.FORM_ONE_COLUMN_THRESHOLD) {
|
|
384
|
-
// everything is in one column
|
|
385
|
-
if (propsToPass.hasOwnProperty('flex')) {
|
|
386
|
-
delete propsToPass.flex;
|
|
387
|
-
}
|
|
388
|
-
if (propsToPass.hasOwnProperty('width')) {
|
|
389
|
-
delete propsToPass.width;
|
|
390
|
-
}
|
|
391
|
-
if (propsToPass.hasOwnProperty('w')) {
|
|
392
|
-
delete propsToPass.w;
|
|
393
|
-
}
|
|
394
|
-
propsToPass.w = '100%';
|
|
395
|
-
propsToPass.mb = 1;
|
|
396
|
-
}
|
|
397
|
-
propsToPass.pl = 3;
|
|
398
|
-
}
|
|
399
|
-
if (type === 'Row') {
|
|
400
|
-
propsToPass.w = '100%';
|
|
401
|
-
}
|
|
402
|
-
const itemDefaults = item.defaults;
|
|
403
|
-
children = _.map(items, (item, ix) => {
|
|
404
|
-
return buildFromItem(item, ix, itemDefaults);
|
|
405
|
-
});
|
|
406
|
-
return <Element key={ix} title={title} {...itemDefaults} {...propsToPass} {...editorTypeProps}>{children}</Element>;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (!label && Repository && propertyDef?.title) {
|
|
410
|
-
label = propertyDef.title;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
if (isEditorViewOnly || !isEditable) {
|
|
414
|
-
let value = null;
|
|
415
|
-
if (record?.properties && record.properties[name]) {
|
|
416
|
-
value = record.properties[name].displayValue;
|
|
417
|
-
}
|
|
418
|
-
if (_.isNil(value) && record && record[name]) {
|
|
419
|
-
value = record[name];
|
|
420
|
-
}
|
|
421
|
-
if (_.isNil(value) && startingValues && startingValues[name]) {
|
|
422
|
-
value = startingValues[name];
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
let element = <Element
|
|
426
|
-
value={value}
|
|
427
|
-
parent={self}
|
|
428
|
-
reference={name}
|
|
429
|
-
{...propsToPass}
|
|
430
|
-
/>;
|
|
431
|
-
if (!disableLabels && label) {
|
|
432
|
-
const labelProps = {};
|
|
433
|
-
if (defaults?.labelWidth) {
|
|
434
|
-
labelProps.w = defaults.labelWidth;
|
|
435
|
-
}
|
|
436
|
-
if (containerWidth > styles.FORM_STACK_ROW_THRESHOLD) {
|
|
437
|
-
element = <><Label {...labelProps}>{label}</Label>{element}</>;
|
|
438
|
-
} else {
|
|
439
|
-
element = <VStack><Label {...labelProps}>{label}</Label>{element}</VStack>;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
return <HStack key={ix} className="px-2 pb-1">{element}</HStack>;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
// // These rules are for fields *outside* the model
|
|
448
|
-
// // but which want validation on the form anyway.
|
|
449
|
-
// // The useForm() resolver disables this
|
|
450
|
-
// const
|
|
451
|
-
// rules = {},
|
|
452
|
-
// rulesToCheck = [
|
|
453
|
-
// 'required',
|
|
454
|
-
// 'min',
|
|
455
|
-
// 'max',
|
|
456
|
-
// 'minLength',
|
|
457
|
-
// 'maxLength',
|
|
458
|
-
// 'pattern',
|
|
459
|
-
// 'validate',
|
|
460
|
-
// ];
|
|
461
|
-
// _.each(rulesToCheck, (rule) => {
|
|
462
|
-
// if (item.hasOwnProperty(rule)) {
|
|
463
|
-
// rules[rule] = item[rule];
|
|
464
|
-
// }
|
|
465
|
-
// });
|
|
466
|
-
|
|
467
|
-
return <Controller
|
|
468
|
-
key={'controller-' + ix}
|
|
469
|
-
name={name}
|
|
470
|
-
// rules={rules}
|
|
471
|
-
control={control}
|
|
472
|
-
render={(args) => {
|
|
473
|
-
const {
|
|
474
|
-
field,
|
|
475
|
-
fieldState,
|
|
476
|
-
// formState,
|
|
477
|
-
} = args,
|
|
478
|
-
{
|
|
479
|
-
onChange,
|
|
480
|
-
onBlur,
|
|
481
|
-
name,
|
|
482
|
-
value,
|
|
483
|
-
// ref,
|
|
484
|
-
} = field,
|
|
485
|
-
{
|
|
486
|
-
isTouched,
|
|
487
|
-
isDirty,
|
|
488
|
-
error,
|
|
489
|
-
} = fieldState;
|
|
490
|
-
if (isValidElement(Element)) {
|
|
491
|
-
throw new Error('Should not yet be valid React element. Did you use <Element> instead of () => <Element> when defining it?')
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
if (useSelectorId) { // This causes the whole form to use selectorId
|
|
495
|
-
editorTypeProps.selectorId = selectorId;
|
|
496
|
-
}
|
|
497
|
-
if (propsToPass.selectorId || editorTypeProps.selectorId) { // editorTypeProps.selectorId causes just this one field to use selectorId
|
|
498
|
-
if (_.isNil(propsToPass.selectorSelected)) {
|
|
499
|
-
editorTypeProps.selectorSelected = record;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
let dynamicProps = {};
|
|
503
|
-
if (getDynamicProps) {
|
|
504
|
-
dynamicProps = getDynamicProps({ fieldState, formSetValue, formGetValues, formState });
|
|
505
|
-
}
|
|
506
|
-
let element = <Element
|
|
507
|
-
name={name}
|
|
508
|
-
value={value}
|
|
509
|
-
onChangeValue={(newValue) => {
|
|
510
|
-
if (newValue === undefined) {
|
|
511
|
-
newValue = null; // React Hook Form doesn't respond well when setting value to undefined
|
|
512
|
-
}
|
|
513
|
-
onChange(newValue);
|
|
514
|
-
if (onEditorChange) {
|
|
515
|
-
onEditorChange(newValue, formSetValue, formGetValues, formState, trigger);
|
|
516
|
-
}
|
|
517
|
-
}}
|
|
518
|
-
onBlur={onBlur}
|
|
519
|
-
flex={1}
|
|
520
|
-
parent={self}
|
|
521
|
-
reference={name}
|
|
522
|
-
{...defaults}
|
|
523
|
-
{...propsToPass}
|
|
524
|
-
{...editorTypeProps}
|
|
525
|
-
{...dynamicProps}
|
|
526
|
-
/>;
|
|
527
|
-
if (editorType !== EDITOR_TYPE__INLINE) {
|
|
528
|
-
let message = null;
|
|
529
|
-
if (error) {
|
|
530
|
-
message = error.message;
|
|
531
|
-
if (label && error.ref?.name) {
|
|
532
|
-
message = message.replace(error.ref.name, label);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
if (message) {
|
|
536
|
-
message = <Text className="text-[#f00]">{message}</Text>;
|
|
537
|
-
}
|
|
538
|
-
element = <VStack className="pt-1 flex-1">
|
|
539
|
-
{element}
|
|
540
|
-
{message}
|
|
541
|
-
</VStack>;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
if (item.additionalEditButtons) {
|
|
545
|
-
const buttons = buildAdditionalButtons(item.additionalEditButtons, self, { fieldState, formSetValue, formGetValues, formState });
|
|
546
|
-
if (containerWidth > styles.FORM_STACK_ROW_THRESHOLD) {
|
|
547
|
-
element = <HStack className="flex-1 flex-wrap">
|
|
548
|
-
{element}
|
|
549
|
-
{buttons}
|
|
550
|
-
</HStack>;
|
|
551
|
-
} else {
|
|
552
|
-
element = <VStack className="flex-1 w-full">
|
|
553
|
-
{element}
|
|
554
|
-
<HStack className="flex-1 w-full mt-2 flex-wrap">
|
|
555
|
-
{buttons}
|
|
556
|
-
</HStack>
|
|
557
|
-
</VStack>;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
let isRequired = false,
|
|
562
|
-
requiredIndicator = null;
|
|
563
|
-
if (!isMultiple) { // Don't require fields if editing multiple records
|
|
564
|
-
if (getIsRequired) {
|
|
565
|
-
isRequired = getIsRequired(formGetValues, formState);
|
|
566
|
-
} else if (validatorToUse?.fields && validatorToUse.fields[name]?.exclusiveTests?.required) {
|
|
567
|
-
// submitted validator
|
|
568
|
-
isRequired = true;
|
|
569
|
-
} else if ((propertyDef?.validator?.spec && !propertyDef.validator.spec.optional) ||
|
|
570
|
-
(propertyDef?.requiredIfPhantom && isPhantom) ||
|
|
571
|
-
(propertyDef?.requiredIfNotPhantom && !isPhantom)) {
|
|
572
|
-
// property definition
|
|
573
|
-
isRequired = true;
|
|
574
|
-
}
|
|
575
|
-
if (isRequired) {
|
|
576
|
-
requiredIndicator = <Text className="text-[#f00] text-[30px] pr-1">*</Text>;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
if (!disableLabels && label && editorType !== EDITOR_TYPE__INLINE) {
|
|
580
|
-
const labelProps = {};
|
|
581
|
-
if (defaults?.labelWidth) {
|
|
582
|
-
labelProps.w = defaults.labelWidth;
|
|
583
|
-
}
|
|
584
|
-
if (containerWidth > styles.FORM_STACK_ROW_THRESHOLD) {
|
|
585
|
-
element = <HStack className="w-full py-1">
|
|
586
|
-
<Label {...labelProps}>{requiredIndicator}{label}</Label>
|
|
587
|
-
{element}
|
|
588
|
-
</HStack>;
|
|
589
|
-
} else {
|
|
590
|
-
element = <VStack className="w-full py-1 mt-3">
|
|
591
|
-
<Label {...labelProps}>{requiredIndicator}{label}</Label>
|
|
592
|
-
{element}
|
|
593
|
-
</VStack>;
|
|
594
|
-
}
|
|
595
|
-
} else if (disableLabels && requiredIndicator) {
|
|
596
|
-
element = <HStack className="w-full py-1">
|
|
597
|
-
{requiredIndicator}
|
|
598
|
-
{element}
|
|
599
|
-
</HStack>;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const dirtyIcon = isDirty && !disableDirtyIcon ? <Icon
|
|
603
|
-
as={Pencil}
|
|
604
|
-
size="2xs"
|
|
605
|
-
className="text-grey-300 absolute top-[2px] left-[2px]" /> : null;
|
|
606
|
-
return (
|
|
607
|
-
<HStack
|
|
608
|
-
key={ix}
|
|
609
|
-
className={` ${error ? "bg-[#fdd]" : "bg-[null]"} px-2 pb-1 `}>{dirtyIcon}{element}</HStack>
|
|
610
|
-
);
|
|
611
|
-
}}
|
|
612
|
-
/>;
|
|
613
|
-
},
|
|
614
|
-
buildAncillary = () => {
|
|
615
|
-
const components = [];
|
|
616
|
-
if (ancillaryItems.length) {
|
|
617
|
-
_.each(ancillaryItems, (item, ix) => {
|
|
618
|
-
let {
|
|
619
|
-
type,
|
|
620
|
-
title = null,
|
|
621
|
-
description = null,
|
|
622
|
-
selectorId,
|
|
623
|
-
...propsToPass
|
|
624
|
-
} = item;
|
|
625
|
-
if (isMultiple && type !== 'Attachments') {
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
if (!propsToPass.h) {
|
|
629
|
-
propsToPass.h = 400;
|
|
630
|
-
}
|
|
631
|
-
const
|
|
632
|
-
Element = getComponentFromType(type),
|
|
633
|
-
element = <Element
|
|
634
|
-
selectorId={selectorId}
|
|
635
|
-
selectorSelected={selectorSelected || record}
|
|
636
|
-
flex={1}
|
|
637
|
-
uniqueRepository={true}
|
|
638
|
-
parent={self}
|
|
639
|
-
{...propsToPass}
|
|
640
|
-
/>;
|
|
641
|
-
if (title) {
|
|
642
|
-
if (record?.displayValue) {
|
|
643
|
-
title += ' for ' + record.displayValue;
|
|
644
|
-
}
|
|
645
|
-
title = <Text
|
|
646
|
-
className={` ${styles.FORM_ANCILLARY_TITLE_CLASSNAME} font-bold `}
|
|
647
|
-
>{title}</Text>;
|
|
648
|
-
}
|
|
649
|
-
if (description) {
|
|
650
|
-
description = <Text
|
|
651
|
-
className={` ${styles.FORM_ANCILLARY_DESCRIPTION_CLASSNAME} italic-italic `}
|
|
652
|
-
>{description}</Text>;
|
|
653
|
-
}
|
|
654
|
-
components.push(<VStack key={'ancillary-' + ix} className="mx-1 my-3">{title}{description}{element}</VStack>);
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
return components;
|
|
658
|
-
},
|
|
659
|
-
onSubmitError = (errors, e) => {
|
|
660
|
-
if (editorType === EDITOR_TYPE__INLINE) {
|
|
661
|
-
alert(errors.message);
|
|
662
|
-
}
|
|
663
|
-
},
|
|
664
|
-
doReset = (values) => {
|
|
665
|
-
reset(values);
|
|
666
|
-
if (onReset) {
|
|
667
|
-
onReset(values, formSetValue, formGetValues);
|
|
668
|
-
}
|
|
669
|
-
},
|
|
670
|
-
onSaveDecorated = async (data, e) => {
|
|
671
|
-
// reset the form after a save
|
|
672
|
-
const result = await onSave(data, e);
|
|
673
|
-
if (result) {
|
|
674
|
-
const values = record.submitValues;
|
|
675
|
-
doReset(values);
|
|
676
|
-
}
|
|
677
|
-
},
|
|
678
|
-
onSubmitDecorated = async (data, e) => {
|
|
679
|
-
const result = await onSubmit(data, e);
|
|
680
|
-
if (result) {
|
|
681
|
-
const values = record.submitValues;
|
|
682
|
-
doReset(values);
|
|
683
|
-
}
|
|
684
|
-
},
|
|
685
|
-
onLayoutDecorated = (e) => {
|
|
686
|
-
if (onLayout) {
|
|
687
|
-
onLayout(e);
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
setContainerWidth(e.nativeEvent.layout.width);
|
|
691
|
-
};
|
|
692
|
-
|
|
693
|
-
useEffect(() => {
|
|
694
|
-
if (skipAll) {
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
if (record === previousRecord) {
|
|
698
|
-
if (onInit) {
|
|
699
|
-
onInit(initialValues, formSetValue, formGetValues);
|
|
700
|
-
}
|
|
701
|
-
} else {
|
|
702
|
-
setPreviousRecord(record);
|
|
703
|
-
doReset(defaultValues);
|
|
704
|
-
}
|
|
705
|
-
if (formSetup) {
|
|
706
|
-
formSetup(formSetValue, formGetValues, formState)
|
|
707
|
-
}
|
|
708
|
-
}, [record]);
|
|
709
|
-
|
|
710
|
-
useEffect(() => {
|
|
711
|
-
if (skipAll) {
|
|
712
|
-
return;
|
|
713
|
-
}
|
|
714
|
-
if (!Repository) {
|
|
715
|
-
return () => {
|
|
716
|
-
if (!_.isNil(editorStateRef)) {
|
|
717
|
-
editorStateRef.current = null; // clean up the editorStateRef on unmount
|
|
718
|
-
}
|
|
719
|
-
};
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
Repository.ons(['changeData', 'change'], forceUpdate);
|
|
723
|
-
|
|
724
|
-
return () => {
|
|
725
|
-
Repository.offs(['changeData', 'change'], forceUpdate);
|
|
726
|
-
if (!_.isNil(editorStateRef)) {
|
|
727
|
-
editorStateRef.current = null; // clean up the editorStateRef on unmount
|
|
728
|
-
}
|
|
729
|
-
};
|
|
730
|
-
}, [Repository]);
|
|
731
|
-
|
|
732
|
-
if (skipAll) {
|
|
733
|
-
return null;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// if (Repository && (!record || _.isEmpty(record) || record.isDestroyed)) {
|
|
737
|
-
// return null;
|
|
738
|
-
// }
|
|
739
|
-
|
|
740
|
-
if (!_.isNil(editorStateRef)) {
|
|
741
|
-
editorStateRef.current = formState; // Update state so HOC can know what's going on
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
if (self) {
|
|
745
|
-
self.ref = formRef;
|
|
746
|
-
self.formState = formState;
|
|
747
|
-
self.formSetValue = formSetValue;
|
|
748
|
-
self.formGetValues = formGetValues;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
const sizeProps = {};
|
|
752
|
-
if (!flex && !h && !w) {
|
|
753
|
-
sizeProps.flex = 1;
|
|
754
|
-
} else {
|
|
755
|
-
if (h) {
|
|
756
|
-
sizeProps.h = h;
|
|
757
|
-
}
|
|
758
|
-
if (w) {
|
|
759
|
-
sizeProps.w = w;
|
|
760
|
-
}
|
|
761
|
-
if (flex) {
|
|
762
|
-
sizeProps.flex = flex;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
if (maxWidth) {
|
|
766
|
-
sizeProps.maxWidth = maxWidth;
|
|
767
|
-
}
|
|
768
|
-
if (maxHeight) {
|
|
769
|
-
sizeProps.maxHeight = maxHeight;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
const formButtons = [];
|
|
773
|
-
let formComponents,
|
|
774
|
-
editor,
|
|
775
|
-
additionalButtons,
|
|
776
|
-
isSaveDisabled = false,
|
|
777
|
-
isSubmitDisabled = false,
|
|
778
|
-
savingProps = {},
|
|
779
|
-
|
|
780
|
-
showDeleteBtn = false,
|
|
781
|
-
showResetBtn = false,
|
|
782
|
-
showCloseBtn = false,
|
|
783
|
-
showCancelBtn = false,
|
|
784
|
-
showSaveBtn = false,
|
|
785
|
-
showSubmitBtn = false;
|
|
786
|
-
|
|
787
|
-
if (containerWidth) { // we need to render this component twice in order to get the container width. Skip this on first render
|
|
788
|
-
|
|
789
|
-
if (isSaving) {
|
|
790
|
-
savingProps.borderTopWidth = 2;
|
|
791
|
-
savingProps.borderTopColor = '#f00';
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
if (editorType === EDITOR_TYPE__INLINE) {
|
|
795
|
-
editor = buildFromColumnsConfig();
|
|
796
|
-
// } else if (editorType === EDITOR_TYPE__PLAIN) {
|
|
797
|
-
// formComponents = buildFromItems();
|
|
798
|
-
// const formAncillaryComponents = buildAncillary();
|
|
799
|
-
// editor = <>
|
|
800
|
-
// <VStack className="p-4">{formComponents}</VStack>
|
|
801
|
-
// <VStack className="pt-4">{formAncillaryComponents}</VStack>
|
|
802
|
-
// </>;
|
|
803
|
-
} else {
|
|
804
|
-
formComponents = buildFromItems();
|
|
805
|
-
const formAncillaryComponents = buildAncillary();
|
|
806
|
-
editor = <>
|
|
807
|
-
{containerWidth >= styles.FORM_ONE_COLUMN_THRESHOLD ? <HStack className="p-4 pl-0">{formComponents}</HStack> : null}
|
|
808
|
-
{containerWidth < styles.FORM_ONE_COLUMN_THRESHOLD ? <VStack className="p-4">{formComponents}</VStack> : null}
|
|
809
|
-
<VStack className="m-2 pt-4 px-2">{formAncillaryComponents}</VStack>
|
|
810
|
-
</>;
|
|
811
|
-
|
|
812
|
-
additionalButtons = buildAdditionalButtons(additionalEditButtons);
|
|
813
|
-
|
|
814
|
-
formButtons.push(<HStack key="buttonsRow" className="px-4 pt-4 items-center justify-end">
|
|
815
|
-
{isSingle && editorMode === EDITOR_MODE__EDIT && onBack &&
|
|
816
|
-
<Button
|
|
817
|
-
key="backBtn"
|
|
818
|
-
onPress={onBack}
|
|
819
|
-
leftIcon={<Icon as={AngleLeft} size="sm" className="text-white" />}
|
|
820
|
-
color="#fff"
|
|
821
|
-
>Back</Button>}
|
|
822
|
-
{isSingle && editorMode === EDITOR_MODE__EDIT && onViewMode && !disableView &&
|
|
823
|
-
<Button
|
|
824
|
-
key="viewBtn"
|
|
825
|
-
onPress={onViewMode}
|
|
826
|
-
leftIcon={<Icon as={Eye} size="sm" className="text-white" />}
|
|
827
|
-
color="#fff"
|
|
828
|
-
>To View</Button>}
|
|
829
|
-
</HStack>);
|
|
830
|
-
if (editorMode === EDITOR_MODE__EDIT && !_.isEmpty(additionalButtons)) {
|
|
831
|
-
formButtons.push(<HStack
|
|
832
|
-
key="additionalButtonsRow"
|
|
833
|
-
className="p-[4px] items-center justify-end flex-wrap"
|
|
834
|
-
>
|
|
835
|
-
{additionalButtons}
|
|
836
|
-
</HStack>)
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
if (!formState.isValid) {
|
|
841
|
-
isSaveDisabled = true;
|
|
842
|
-
isSubmitDisabled = true;
|
|
843
|
-
}
|
|
844
|
-
if (_.isEmpty(formState.dirtyFields) && !isPhantom) {
|
|
845
|
-
isSaveDisabled = true;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
if (editorType === EDITOR_TYPE__INLINE) {
|
|
849
|
-
buttonGroupProps.position = 'fixed';
|
|
850
|
-
buttonGroupProps.left = 10; // TODO: I would prefer to have this be centered, but it's a lot more complex than just making it stick to the left
|
|
851
|
-
footerProps.alignItems = 'flex-start';
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
if (onDelete && editorMode === EDITOR_MODE__EDIT && isSingle) {
|
|
855
|
-
showDeleteBtn = true;
|
|
856
|
-
}
|
|
857
|
-
if (!isEditorViewOnly) {
|
|
858
|
-
showResetBtn = true;
|
|
859
|
-
}
|
|
860
|
-
if (editorType !== EDITOR_TYPE__SIDE) { // side editor won't show either close or cancel buttons!
|
|
861
|
-
// determine whether we should show the close or cancel button
|
|
862
|
-
if (isEditorViewOnly) {
|
|
863
|
-
showCloseBtn = true;
|
|
864
|
-
} else {
|
|
865
|
-
const formIsDirty = formState.isDirty;
|
|
866
|
-
// console.log('formIsDirty', formIsDirty);
|
|
867
|
-
// console.log('isPhantom', isPhantom);
|
|
868
|
-
if (formIsDirty || isPhantom) {
|
|
869
|
-
if (isSingle && onCancel) {
|
|
870
|
-
showCancelBtn = true;
|
|
871
|
-
}
|
|
872
|
-
} else {
|
|
873
|
-
if (onClose) {
|
|
874
|
-
showCloseBtn = true;
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
if (!isEditorViewOnly && onSave) {
|
|
880
|
-
showSaveBtn = true;
|
|
881
|
-
}
|
|
882
|
-
if (!!onSubmit) {
|
|
883
|
-
showSubmitBtn = true;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
return <VStackNative
|
|
888
|
-
{...sizeProps}
|
|
889
|
-
onLayout={onLayoutDecorated}
|
|
890
|
-
ref={formRef}
|
|
891
|
-
>
|
|
892
|
-
{!!containerWidth && <>
|
|
893
|
-
{editorType === EDITOR_TYPE__INLINE &&
|
|
894
|
-
<ScrollView
|
|
895
|
-
horizontal={true}
|
|
896
|
-
className="flex-1 bg-white py-1 border-t-[3px] border-b-[5px] border-t-primary-100 border-b-primary-100">{editor}</ScrollView>}
|
|
897
|
-
{editorType !== EDITOR_TYPE__INLINE &&
|
|
898
|
-
<ScrollView _web={{ minHeight, }} className="w-full pb-1">
|
|
899
|
-
{formButtons}
|
|
900
|
-
{editor}
|
|
901
|
-
</ScrollView>}
|
|
902
|
-
|
|
903
|
-
<Footer className="justify-end" {...footerProps} {...savingProps}>
|
|
904
|
-
{onDelete && editorMode === EDITOR_MODE__EDIT && isSingle &&
|
|
905
|
-
|
|
906
|
-
<HStack className="flex-1 justify-start">
|
|
907
|
-
<Button
|
|
908
|
-
key="deleteBtn"
|
|
909
|
-
onPress={onDelete}
|
|
910
|
-
bg="warning"
|
|
911
|
-
_hover={{
|
|
912
|
-
bg: 'warningHover',
|
|
913
|
-
}}
|
|
914
|
-
color="#fff"
|
|
915
|
-
>Delete</Button>
|
|
916
|
-
</HStack>}
|
|
917
|
-
|
|
918
|
-
{showResetBtn &&
|
|
919
|
-
<IconButton
|
|
920
|
-
key="resetBtn"
|
|
921
|
-
onPress={() => doReset()}
|
|
922
|
-
icon={Rotate}
|
|
923
|
-
_icon={{
|
|
924
|
-
color: !formState.isDirty ? 'grey-400' : '#000',
|
|
925
|
-
}}
|
|
926
|
-
isDisabled={!formState.isDirty}
|
|
927
|
-
mr={2}
|
|
928
|
-
/>}
|
|
929
|
-
|
|
930
|
-
{showCancelBtn &&
|
|
931
|
-
<Button
|
|
932
|
-
key="cancelBtn"
|
|
933
|
-
variant="outline"
|
|
934
|
-
onPress={onCancel}
|
|
935
|
-
color="#fff"
|
|
936
|
-
>Cancel</Button>}
|
|
937
|
-
|
|
938
|
-
{showCloseBtn &&
|
|
939
|
-
<Button
|
|
940
|
-
key="closeBtn"
|
|
941
|
-
variant="outline"
|
|
942
|
-
onPress={onClose}
|
|
943
|
-
color="#fff"
|
|
944
|
-
>Close</Button>}
|
|
945
|
-
|
|
946
|
-
{showSaveBtn &&
|
|
947
|
-
<Button
|
|
948
|
-
key="saveBtn"
|
|
949
|
-
onPress={(e) => handleSubmit(onSaveDecorated, onSubmitError)(e)}
|
|
950
|
-
isDisabled={isSaveDisabled}
|
|
951
|
-
color="#fff"
|
|
952
|
-
>{editorMode === EDITOR_MODE__ADD ? 'Add' : 'Save'}</Button>}
|
|
953
|
-
|
|
954
|
-
{showSubmitBtn &&
|
|
955
|
-
<Button
|
|
956
|
-
key="submitBtn"
|
|
957
|
-
onPress={(e) => handleSubmit(onSubmitDecorated, onSubmitError)(e)}
|
|
958
|
-
isDisabled={isSubmitDisabled}
|
|
959
|
-
color="#fff"
|
|
960
|
-
>{submitBtnLabel || 'Submit'}</Button>}
|
|
961
|
-
|
|
962
|
-
{additionalFooterButtons && _.map(additionalFooterButtons, (props) => {
|
|
963
|
-
return <Button
|
|
964
|
-
{...props}
|
|
965
|
-
onPress={(e) => handleSubmit(props.onPress, onSubmitError)(e)}
|
|
966
|
-
>{props.text}</Button>;
|
|
967
|
-
})}
|
|
968
|
-
</Footer>
|
|
969
|
-
</>}
|
|
970
|
-
</VStackNative>;
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
// helper fns
|
|
974
|
-
function disableRequiredYupFields(validator) {
|
|
975
|
-
// based on https://github.com/jquense/yup/issues/1466#issuecomment-944386480
|
|
976
|
-
if (!validator) {
|
|
977
|
-
return null;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
const nextSchema = validator.clone();
|
|
981
|
-
return nextSchema.withMutation((next) => {
|
|
982
|
-
if (typeof next.fields === 'object' && next.fields != null) {
|
|
983
|
-
for (const key in next.fields) {
|
|
984
|
-
const nestedField = next.fields[key];
|
|
985
|
-
|
|
986
|
-
let nestedFieldNext = nestedField.notRequired();
|
|
987
|
-
|
|
988
|
-
if (Array.isArray(nestedField.conditions) && nestedField.conditions.length > 0) {
|
|
989
|
-
// Next is done to disable required() inside a condition
|
|
990
|
-
// https://github.com/jquense/yup/issues/1002
|
|
991
|
-
nestedFieldNext = nestedFieldNext.when('whatever', (unused, schema) => {
|
|
992
|
-
return schema.notRequired();
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
next.fields[key] = nestedFieldNext;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
function getNullFieldValues(initialValues, Repository) {
|
|
1002
|
-
const ret = {};
|
|
1003
|
-
if (Repository) {
|
|
1004
|
-
const properties = Repository.getSchema().model.properties;
|
|
1005
|
-
_.each(properties, (propertyDef) => {
|
|
1006
|
-
ret[propertyDef.name] = null;
|
|
1007
|
-
});
|
|
1008
|
-
} else {
|
|
1009
|
-
// takes a JSON object of fieldValues and sets them all to null
|
|
1010
|
-
_.each(initialValues, (value, field) => {
|
|
1011
|
-
ret[field] = null;
|
|
1012
|
-
});
|
|
1013
|
-
}
|
|
1014
|
-
return ret;
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
export const FormEditor = withComponent(withAlert(withEditor(withPdfButton(FormikForm))));
|
|
1018
|
-
|
|
1019
|
-
export default withComponent(withAlert(withPdfButton(FormikForm)));
|