@onehat/ui 0.4.106 → 0.4.108
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/Container/Container.js +23 -1
- package/src/Components/Editor/Editor.js +2 -1
- package/src/Components/Form/Field/Combo/Combo.js +20 -0
- package/src/Components/Form/Form.js +28 -3
- package/src/Components/Grid/Grid.js +1 -1
- package/src/Components/Grid/GridRow.js +17 -12
- package/src/Components/Hoc/withEditor.js +18 -9
- package/src/Components/Hoc/withFilters.js +6 -3
- package/src/Components/Hoc/withPdfButtons.js +62 -48
- package/src/Components/Hoc/withPresetButtons.js +2 -1
- package/src/Components/Layout/ScreenHeader.js +2 -0
- package/src/Components/Layout/SetupButton.js +29 -3
- package/src/Components/Viewer/Viewer.js +7 -4
package/package.json
CHANGED
|
@@ -424,6 +424,8 @@ function Container(props) {
|
|
|
424
424
|
|
|
425
425
|
componentProps._panel.isCollapsible = false;
|
|
426
426
|
componentProps._panel.isDisabled = isDisabled || isComponentsDisabled;
|
|
427
|
+
componentProps.isCollapsible = false;
|
|
428
|
+
componentProps.isDisabled = isDisabled || isComponentsDisabled;
|
|
427
429
|
componentProps.onLayout = debouncedOnLayout;
|
|
428
430
|
centerComponent = cloneElement(center, componentProps);
|
|
429
431
|
if (north) {
|
|
@@ -432,9 +434,11 @@ function Container(props) {
|
|
|
432
434
|
|
|
433
435
|
componentProps._panel.isDisabled = isDisabled || isComponentsDisabled;
|
|
434
436
|
componentProps._panel.className = 'h-full w-full ' + (north.props.className || '');
|
|
437
|
+
componentProps.isDisabled = !!north.props?.isDisabled || isDisabled || isComponentsDisabled;
|
|
438
|
+
componentProps.className = 'h-full w-full ' + (north.props.className || '');
|
|
435
439
|
wrapperProps.onLayout = (e) => {
|
|
436
440
|
const height = parseFloat(e.nativeEvent.layout.height);
|
|
437
|
-
if (height && height !==
|
|
441
|
+
if (height && height !== getNorthHeight()) {
|
|
438
442
|
setNorthHeight(height);
|
|
439
443
|
}
|
|
440
444
|
};
|
|
@@ -453,6 +457,9 @@ function Container(props) {
|
|
|
453
457
|
componentProps._panel.collapseDirection = VERTICAL;
|
|
454
458
|
componentProps._panel.isCollapsed = getNorthIsCollapsed();
|
|
455
459
|
componentProps._panel.setIsCollapsed = setNorthIsCollapsed;
|
|
460
|
+
componentProps.collapseDirection = VERTICAL;
|
|
461
|
+
componentProps.isCollapsed = getNorthIsCollapsed();
|
|
462
|
+
componentProps.setIsCollapsed = setNorthIsCollapsed;
|
|
456
463
|
if (isWeb && northIsResizable) {
|
|
457
464
|
northSplitter = <Splitter
|
|
458
465
|
mode={VERTICAL}
|
|
@@ -470,6 +477,8 @@ function Container(props) {
|
|
|
470
477
|
|
|
471
478
|
componentProps._panel.isDisabled = isDisabled || isComponentsDisabled;
|
|
472
479
|
componentProps._panel.className = 'h-full w-full ' + (south.props.className || '');
|
|
480
|
+
componentProps.isDisabled = !!south.props?.isDisabled || isDisabled || isComponentsDisabled;
|
|
481
|
+
componentProps.className = 'h-full w-full ' + (south.props.className || '');
|
|
473
482
|
wrapperProps.onLayout = (e) => {
|
|
474
483
|
const height = parseFloat(e.nativeEvent.layout.height);
|
|
475
484
|
if (height && height !== getSouthHeight()) {
|
|
@@ -491,6 +500,9 @@ function Container(props) {
|
|
|
491
500
|
componentProps._panel.collapseDirection = VERTICAL;
|
|
492
501
|
componentProps._panel.isCollapsed = getSouthIsCollapsed();
|
|
493
502
|
componentProps._panel.setIsCollapsed = setSouthIsCollapsed;
|
|
503
|
+
componentProps.collapseDirection = VERTICAL;
|
|
504
|
+
componentProps.isCollapsed = getSouthIsCollapsed();
|
|
505
|
+
componentProps.setIsCollapsed = setSouthIsCollapsed;
|
|
494
506
|
if (isWeb && southIsResizable) {
|
|
495
507
|
southSplitter = <Splitter
|
|
496
508
|
mode={VERTICAL}
|
|
@@ -508,6 +520,8 @@ function Container(props) {
|
|
|
508
520
|
|
|
509
521
|
componentProps._panel.isDisabled = isDisabled || isComponentsDisabled;
|
|
510
522
|
componentProps._panel.className = 'h-full w-full ' + (east.props.className || '');
|
|
523
|
+
componentProps.isDisabled = !!east.props?.isDisabled || isDisabled || isComponentsDisabled;
|
|
524
|
+
componentProps.className = 'h-full w-full ' + (east.props.className || '');
|
|
511
525
|
wrapperProps.onLayout = (e) => {
|
|
512
526
|
const width = parseFloat(e.nativeEvent.layout.width);
|
|
513
527
|
if (width && width !== getEastWidth()) {
|
|
@@ -529,6 +543,9 @@ function Container(props) {
|
|
|
529
543
|
componentProps._panel.collapseDirection = HORIZONTAL;
|
|
530
544
|
componentProps._panel.isCollapsed = getEastIsCollapsed();
|
|
531
545
|
componentProps._panel.setIsCollapsed = setEastIsCollapsed;
|
|
546
|
+
componentProps.collapseDirection = HORIZONTAL;
|
|
547
|
+
componentProps.isCollapsed = getEastIsCollapsed();
|
|
548
|
+
componentProps.setIsCollapsed = setEastIsCollapsed;
|
|
532
549
|
if (isWeb && eastIsResizable) {
|
|
533
550
|
eastSplitter = <Splitter
|
|
534
551
|
mode={HORIZONTAL}
|
|
@@ -546,6 +563,8 @@ function Container(props) {
|
|
|
546
563
|
|
|
547
564
|
componentProps._panel.isDisabled = isDisabled || isComponentsDisabled;
|
|
548
565
|
componentProps._panel.className = 'h-full w-full ' + (west.props.className || '');
|
|
566
|
+
componentProps.isDisabled = !!west.props?.isDisabled || isDisabled || isComponentsDisabled;
|
|
567
|
+
componentProps.className = 'h-full w-full ' + (west.props.className || '');
|
|
549
568
|
wrapperProps.onLayout = (e) => {
|
|
550
569
|
const width = parseFloat(e.nativeEvent.layout.width);
|
|
551
570
|
if (width && width !== getWestWidth()) {
|
|
@@ -567,6 +586,9 @@ function Container(props) {
|
|
|
567
586
|
componentProps._panel.collapseDirection = HORIZONTAL;
|
|
568
587
|
componentProps._panel.isCollapsed = getWestIsCollapsed();
|
|
569
588
|
componentProps._panel.setIsCollapsed = setWestIsCollapsed;
|
|
589
|
+
componentProps.collapseDirection = HORIZONTAL;
|
|
590
|
+
componentProps.isCollapsed = getWestIsCollapsed();
|
|
591
|
+
componentProps.setIsCollapsed = setWestIsCollapsed;
|
|
570
592
|
if (isWeb && westIsResizable) {
|
|
571
593
|
westSplitter = <Splitter
|
|
572
594
|
mode={HORIZONTAL}
|
|
@@ -46,10 +46,11 @@ function Editor(props) {
|
|
|
46
46
|
if (canRecordBeEdited && !canRecordBeEdited(selection)) {
|
|
47
47
|
canEdit = false;
|
|
48
48
|
}
|
|
49
|
+
const record = selection[0];
|
|
50
|
+
self.record = record; // make it so we can target the record from within a Viewer or Form
|
|
49
51
|
|
|
50
52
|
// Repository?.isRemotePhantomMode && selection.length === 1 &&
|
|
51
53
|
if (getEditorMode() === EDITOR_MODE__VIEW || isEditorViewOnly || !canEdit) {
|
|
52
|
-
const record = selection[0];
|
|
53
54
|
if (record.isDestroyed) {
|
|
54
55
|
return null;
|
|
55
56
|
}
|
|
@@ -1223,6 +1223,25 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
1223
1223
|
}
|
|
1224
1224
|
|
|
1225
1225
|
if (isViewerShown && Editor) {
|
|
1226
|
+
let modalBackdrop = <ModalBackdrop className="Combo-viewer-ModalBackdrop" />;
|
|
1227
|
+
if (CURRENT_MODE === UI_MODE_NATIVE) {
|
|
1228
|
+
// Gluestack's ModalBackdrop was not working on Native,
|
|
1229
|
+
// so workaround is to do it manually for now
|
|
1230
|
+
modalBackdrop = <Pressable
|
|
1231
|
+
onPress={onViewerClose}
|
|
1232
|
+
className={clsx(
|
|
1233
|
+
'Combo-viewer-ModalBackdrop-replacment',
|
|
1234
|
+
'h-full',
|
|
1235
|
+
'w-full',
|
|
1236
|
+
'absolute',
|
|
1237
|
+
'top-0',
|
|
1238
|
+
'left-0',
|
|
1239
|
+
'bg-[#000]',
|
|
1240
|
+
'opacity-50',
|
|
1241
|
+
)}
|
|
1242
|
+
/>;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1226
1245
|
const propsForViewer = _.pick(props, [
|
|
1227
1246
|
'disableCopy',
|
|
1228
1247
|
'disableDuplicate',
|
|
@@ -1246,6 +1265,7 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
1246
1265
|
isOpen={true}
|
|
1247
1266
|
onClose={onViewerClose}
|
|
1248
1267
|
>
|
|
1268
|
+
{modalBackdrop}
|
|
1249
1269
|
<Editor
|
|
1250
1270
|
editorType={EDITOR_TYPE__WINDOWED}
|
|
1251
1271
|
isEditorViewOnly={true}
|
|
@@ -240,6 +240,7 @@ function Form(props) {
|
|
|
240
240
|
resolver: yupResolver(validatorToUse),
|
|
241
241
|
context: { isPhantom },
|
|
242
242
|
}),
|
|
243
|
+
currentEditorMode = getEditorMode(),
|
|
243
244
|
buildFromColumnsConfig = () => {
|
|
244
245
|
// Only used in InlineEditor
|
|
245
246
|
// Build the fields that match the current columnsConfig in the grid
|
|
@@ -1045,7 +1046,7 @@ function Form(props) {
|
|
|
1045
1046
|
)}
|
|
1046
1047
|
>{title}</Text>;
|
|
1047
1048
|
if (icon) {
|
|
1048
|
-
titleElement = <HStack className="items-center"><Icon as={icon} className="w-[32px] h-[32px] mr-2" />{titleElement}</HStack>
|
|
1049
|
+
titleElement = <HStack className="items-center mb-1"><Icon as={icon} className="w-[32px] h-[32px] mr-2" />{titleElement}</HStack>
|
|
1049
1050
|
}
|
|
1050
1051
|
}
|
|
1051
1052
|
if (description) {
|
|
@@ -1149,6 +1150,29 @@ function Form(props) {
|
|
|
1149
1150
|
}
|
|
1150
1151
|
}, [record]);
|
|
1151
1152
|
|
|
1153
|
+
useEffect(() => {
|
|
1154
|
+
if (skipAll) {
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
if (currentEditorMode !== EDITOR_MODE__ADD) {
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
if (!containerWidth) {
|
|
1161
|
+
// Wait until fields are mounted; before this, isValid can be false with empty errors.
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// In some flows the editor mode flips to ADD after the record effect runs.
|
|
1166
|
+
// Validate again so the Add button is enabled immediately when form is valid.
|
|
1167
|
+
const timeoutId = setTimeout(() => {
|
|
1168
|
+
trigger();
|
|
1169
|
+
}, 0);
|
|
1170
|
+
|
|
1171
|
+
return () => {
|
|
1172
|
+
clearTimeout(timeoutId);
|
|
1173
|
+
};
|
|
1174
|
+
}, [record, currentEditorMode, containerWidth, trigger]);
|
|
1175
|
+
|
|
1152
1176
|
useEffect(() => {
|
|
1153
1177
|
if (skipAll) {
|
|
1154
1178
|
return;
|
|
@@ -1249,7 +1273,8 @@ function Form(props) {
|
|
|
1249
1273
|
showCloseBtn = false,
|
|
1250
1274
|
showCancelBtn = false,
|
|
1251
1275
|
showSaveBtn = false,
|
|
1252
|
-
showSubmitBtn = false
|
|
1276
|
+
showSubmitBtn = false,
|
|
1277
|
+
isAddMode = getEditorMode() === EDITOR_MODE__ADD;
|
|
1253
1278
|
if (containerWidth) { // we need to render this component twice in order to get the container width. Skip this on first render
|
|
1254
1279
|
|
|
1255
1280
|
// create editor
|
|
@@ -1332,7 +1357,7 @@ function Form(props) {
|
|
|
1332
1357
|
isSaveDisabled = true;
|
|
1333
1358
|
isSubmitDisabled = true;
|
|
1334
1359
|
}
|
|
1335
|
-
if (_.isEmpty(formState.dirtyFields) && !isPhantom) {
|
|
1360
|
+
if (_.isEmpty(formState.dirtyFields) && !isPhantom && !isAddMode) {
|
|
1336
1361
|
isSaveDisabled = true;
|
|
1337
1362
|
}
|
|
1338
1363
|
if (onDelete && getEditorMode() === EDITOR_MODE__EDIT && isSingle) {
|
|
@@ -430,7 +430,7 @@ function GridComponent(props) {
|
|
|
430
430
|
return processedConfig;
|
|
431
431
|
});
|
|
432
432
|
const items = _.map(processedButtons, (config, ix) => getIconButtonFromConfig(config, ix, self));
|
|
433
|
-
if (canRowsReorder && CURRENT_MODE === UI_MODE_WEB) { // DND is currently web-only TODO: implement for RN
|
|
433
|
+
if (canRowsReorder && CURRENT_MODE === UI_MODE_WEB && onEdit && (!canUser || canUser(EDIT)) && !isEditorViewOnly) { // DND is currently web-only TODO: implement for RN
|
|
434
434
|
items.unshift(<IconButton
|
|
435
435
|
{...testProps('reorderBtn')}
|
|
436
436
|
key="reorderBtn"
|
|
@@ -375,9 +375,11 @@ const GridRow = forwardRef((props, ref) => {
|
|
|
375
375
|
// TODO: incorporate better scrollbar formatting with
|
|
376
376
|
// tailwind plugin 'tailwind-scrollbar' (already installed, just not yet used here)
|
|
377
377
|
|
|
378
|
+
const isEmptyCellValue = _.isNil(value) || value === '';
|
|
379
|
+
|
|
378
380
|
let textClassName = clsx(
|
|
379
381
|
'GridRow-TextNative',
|
|
380
|
-
'self-center',
|
|
382
|
+
isEmptyCellValue ? 'self-stretch' : 'self-center', // if the cell value is empty, stretch the cell to be full height
|
|
381
383
|
areCellsScrollable ? 'overflow-auto' : 'overflow-hidden',
|
|
382
384
|
'[&::-webkit-scrollbar]:h-2',
|
|
383
385
|
'[&::-webkit-scrollbar-thumb]:bg-gray-300',
|
|
@@ -386,6 +388,14 @@ const GridRow = forwardRef((props, ref) => {
|
|
|
386
388
|
styles.GRID_CELL_CLASSNAME,
|
|
387
389
|
styles.GRID_ROW_MAX_HEIGHT_EXTRA,
|
|
388
390
|
);
|
|
391
|
+
const textStyle = {
|
|
392
|
+
// userSelect: 'none',
|
|
393
|
+
...colStyle,
|
|
394
|
+
...(isEmptyCellValue ? { // if the cell value is empty, stretch the cell to be full height
|
|
395
|
+
height: '100%',
|
|
396
|
+
display: 'block',
|
|
397
|
+
} : {}),
|
|
398
|
+
};
|
|
389
399
|
if (rowProps?._cell?.className) {
|
|
390
400
|
textClassName += ' ' + rowProps._cell.className;
|
|
391
401
|
}
|
|
@@ -395,16 +405,13 @@ const GridRow = forwardRef((props, ref) => {
|
|
|
395
405
|
return <TextNative
|
|
396
406
|
{...testProps('cell-' + config.fieldName)}
|
|
397
407
|
key={key}
|
|
398
|
-
style={
|
|
399
|
-
// userSelect: 'none',
|
|
400
|
-
...colStyle,
|
|
401
|
-
}}
|
|
408
|
+
style={textStyle}
|
|
402
409
|
numberOfLines={1}
|
|
403
410
|
ellipsizeMode="head"
|
|
404
411
|
className={textClassName}
|
|
405
412
|
{...elementProps}
|
|
406
413
|
{...propsToPass}
|
|
407
|
-
>{value}</TextNative>;
|
|
414
|
+
>{isEmptyCellValue ? ' ' : value}</TextNative>;
|
|
408
415
|
});
|
|
409
416
|
} else {
|
|
410
417
|
// TODO: if 'columnsConfig' is an object, parse its contents
|
|
@@ -450,6 +457,8 @@ const GridRow = forwardRef((props, ref) => {
|
|
|
450
457
|
)}
|
|
451
458
|
/>}
|
|
452
459
|
</>;
|
|
460
|
+
const hasCustomBgClass = rowProps?.className && /\bbg-/.test(rowProps.className);
|
|
461
|
+
|
|
453
462
|
if (dropTargetRef) {
|
|
454
463
|
rowContents = <HStack
|
|
455
464
|
ref={dropTargetRef}
|
|
@@ -459,9 +468,7 @@ const GridRow = forwardRef((props, ref) => {
|
|
|
459
468
|
'flex-1',
|
|
460
469
|
'grow-1',
|
|
461
470
|
)}
|
|
462
|
-
style={{
|
|
463
|
-
backgroundColor: bg,
|
|
464
|
-
}}
|
|
471
|
+
style={hasCustomBgClass ? undefined : { backgroundColor: bg }}
|
|
465
472
|
>{rowContents}</HStack>;
|
|
466
473
|
}
|
|
467
474
|
|
|
@@ -485,9 +492,7 @@ const GridRow = forwardRef((props, ref) => {
|
|
|
485
492
|
{...rowProps}
|
|
486
493
|
key={hash}
|
|
487
494
|
className={rowClassName}
|
|
488
|
-
style={{
|
|
489
|
-
backgroundColor: bg,
|
|
490
|
-
}}
|
|
495
|
+
style={hasCustomBgClass ? undefined : { backgroundColor: bg }}
|
|
491
496
|
>{rowContents}</HStackNative>;
|
|
492
497
|
if (rowProps.tooltip) {
|
|
493
498
|
row = <Tooltip
|
|
@@ -36,6 +36,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
36
36
|
disableAdd = false,
|
|
37
37
|
disableEdit = false,
|
|
38
38
|
disableDelete = false,
|
|
39
|
+
enableMultiDelete = false, // deleting multiple records at once is opt-in only
|
|
39
40
|
disableDuplicate = false,
|
|
40
41
|
disableView = false,
|
|
41
42
|
useRemoteDuplicate = false, // call specific copyToNew function on server, rather than simple duplicate on client
|
|
@@ -149,7 +150,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
149
150
|
selection = getSelection();
|
|
150
151
|
if (!_.isEmpty(formState?.dirtyFields) && newSelection !== selection && getEditorMode() === EDITOR_MODE__EDIT) {
|
|
151
152
|
confirm('This record has unsaved changes. Are you sure you want to cancel editing? Changes will be lost.', doIt);
|
|
152
|
-
} else if (selection && selection[0] && !selection[0].isDestroyed &&
|
|
153
|
+
} else if (selection && selection[0] && !selection[0].isDestroyed && selection[0].isPhantom) {
|
|
153
154
|
confirm('This new record is unsaved. Are you sure you want to cancel editing? Changes will be lost.', async () => {
|
|
154
155
|
await selection[0].delete();
|
|
155
156
|
doIt();
|
|
@@ -216,7 +217,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
216
217
|
if (!record || record.isDestroyed) {
|
|
217
218
|
return false;
|
|
218
219
|
}
|
|
219
|
-
return !!
|
|
220
|
+
return !!record.isPhantom;
|
|
220
221
|
},
|
|
221
222
|
getIsEditorDisabledByParent = () => {
|
|
222
223
|
return getIsParentSaveLocked();
|
|
@@ -441,7 +442,14 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
441
442
|
cb = args;
|
|
442
443
|
}
|
|
443
444
|
const selection = getSelection();
|
|
444
|
-
|
|
445
|
+
const hasTreeSelection = isTree || _.some(selection, (selected) => !!selected?.isTree);
|
|
446
|
+
if (
|
|
447
|
+
_.isEmpty(selection) ||
|
|
448
|
+
(_.isArray(selection) && (
|
|
449
|
+
selection[0]?.isDestroyed ||
|
|
450
|
+
(selection.length > 1 && (!enableMultiDelete || hasTreeSelection))
|
|
451
|
+
))
|
|
452
|
+
) {
|
|
445
453
|
return;
|
|
446
454
|
}
|
|
447
455
|
if (onBeforeDelete) {
|
|
@@ -461,11 +469,11 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
461
469
|
const
|
|
462
470
|
isSingle = selection.length === 1,
|
|
463
471
|
firstSelection = selection[0],
|
|
464
|
-
|
|
465
|
-
hasChildren =
|
|
472
|
+
isTreeNode = firstSelection?.isTree,
|
|
473
|
+
hasChildren = isTreeNode ? firstSelection?.hasChildren : false,
|
|
466
474
|
isPhantom = firstSelection?.isPhantom;
|
|
467
475
|
|
|
468
|
-
if (isSingle &&
|
|
476
|
+
if (isSingle && isTreeNode && hasChildren) {
|
|
469
477
|
alert({
|
|
470
478
|
title: 'Move up children?',
|
|
471
479
|
message: 'The node you have selected for deletion has children. ' +
|
|
@@ -680,8 +688,8 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
680
688
|
// just update this one entity
|
|
681
689
|
selection[0].setValues(data);
|
|
682
690
|
|
|
683
|
-
//
|
|
684
|
-
if (selection[0].
|
|
691
|
+
// In ADD mode, if record is phantom and nothing is dirty, stage it so save() still submits and solidifies.
|
|
692
|
+
if (getEditorMode() === EDITOR_MODE__ADD && selection[0].isPhantom && !selection[0].isDirty) {
|
|
685
693
|
selection[0].markStaged();
|
|
686
694
|
useStaged = true;
|
|
687
695
|
}
|
|
@@ -834,7 +842,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
834
842
|
if (canRecordBeEdited && canRecordBeEdited(selection) === false) {
|
|
835
843
|
return EDITOR_MODE__VIEW;
|
|
836
844
|
}
|
|
837
|
-
if (selection.length === 1 && !selection[0].isDestroyed &&
|
|
845
|
+
if (selection.length === 1 && !selection[0].isDestroyed && selection[0].isPhantom && !disableAdd) {
|
|
838
846
|
return EDITOR_MODE__ADD;
|
|
839
847
|
}
|
|
840
848
|
return selection.length ? EDITOR_MODE__EDIT : EDITOR_MODE__VIEW;
|
|
@@ -1022,6 +1030,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
1022
1030
|
isEditor={true}
|
|
1023
1031
|
userCanEdit={userCanEdit}
|
|
1024
1032
|
userCanView={userCanView}
|
|
1033
|
+
enableMultiDelete={enableMultiDelete}
|
|
1025
1034
|
disableAdd={disableAdd || isEditorDisabledByParent || isCrudBlockedByInheritedView}
|
|
1026
1035
|
disableEdit={disableEdit || isEditorDisabledByParent || isCrudBlockedByInheritedView}
|
|
1027
1036
|
disableDelete={disableDelete || isEditorDisabledByParent || isCrudBlockedByInheritedView}
|
|
@@ -97,7 +97,7 @@ export default function withFilters(WrappedComponent) {
|
|
|
97
97
|
|
|
98
98
|
let title, type;
|
|
99
99
|
if (propertyDef) {
|
|
100
|
-
title = propertyDef.title;
|
|
100
|
+
title = propertyDef.filterTitle || propertyDef.title;
|
|
101
101
|
type = propertyDef.filterType;
|
|
102
102
|
} else if (modelAncillaryFilters[field]) {
|
|
103
103
|
const ancillaryFilter = modelFilterTypes[field];
|
|
@@ -262,7 +262,7 @@ export default function withFilters(WrappedComponent) {
|
|
|
262
262
|
|
|
263
263
|
if (!title) {
|
|
264
264
|
const propertyDef = Repository.getSchema().getPropertyDefinition(field);
|
|
265
|
-
title = propertyDef?.title;
|
|
265
|
+
title = propertyDef?.filterTitle || propertyDef?.title;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
if (_.isString(filterType)) {
|
|
@@ -398,7 +398,10 @@ export default function withFilters(WrappedComponent) {
|
|
|
398
398
|
|
|
399
399
|
// basic property filter
|
|
400
400
|
const propertyDef = Repository.getSchema().getPropertyDefinition(filterField);
|
|
401
|
-
|
|
401
|
+
if (propertyDef?.isFilteringDisabled) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
data.push([ filterField, propertyDef?.filterTitle || propertyDef?.title ]);
|
|
402
405
|
});
|
|
403
406
|
|
|
404
407
|
// sort by title
|
|
@@ -33,7 +33,9 @@ export default function withPdfButtons(WrappedComponent) {
|
|
|
33
33
|
additionalEditButtons = [],
|
|
34
34
|
additionalViewButtons = [],
|
|
35
35
|
items = [],
|
|
36
|
+
pdfItems,
|
|
36
37
|
ancillaryItems = [],
|
|
38
|
+
pdfAncillaryItems,
|
|
37
39
|
columnDefaults = {},
|
|
38
40
|
|
|
39
41
|
// withComponent
|
|
@@ -58,32 +60,15 @@ export default function withPdfButtons(WrappedComponent) {
|
|
|
58
60
|
styles = UiGlobals.styles,
|
|
59
61
|
propertyNames = [],
|
|
60
62
|
buildModalItems = () => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
let {
|
|
70
|
-
additionalEditButtons,
|
|
71
|
-
items,
|
|
72
|
-
} = item;
|
|
73
|
-
if (!_.isEmpty(items)) {
|
|
74
|
-
_.each(items, (item) => {
|
|
75
|
-
walkTreeToDeleteAdditionalEditButtons(item);
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
if (additionalEditButtons) {
|
|
79
|
-
delete item.additionalEditButtons;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
_.each(modalItems, walkTreeToDeleteAdditionalEditButtons);
|
|
63
|
+
// Build a cloned PDF item tree so we never mutate source items by reference.
|
|
64
|
+
const
|
|
65
|
+
itemsTouse = pdfItems || items,
|
|
66
|
+
ancillaryItemsToUse = pdfAncillaryItems || ancillaryItems,
|
|
67
|
+
modalItems = _.compact(_.map(itemsTouse, (item, ix) => buildNextLayer(item, ix, columnDefaults)));
|
|
83
68
|
|
|
84
|
-
if (!_.isEmpty(
|
|
69
|
+
if (!_.isEmpty(ancillaryItemsToUse)) {
|
|
85
70
|
const
|
|
86
|
-
ancillaryItemsClone = _.cloneDeepWith(
|
|
71
|
+
ancillaryItemsClone = _.cloneDeepWith(ancillaryItemsToUse, (value) => {
|
|
87
72
|
// Exclude the 'parent' property from being cloned, as it would introduce an infinitely recursive loop
|
|
88
73
|
if (value && value.parent) {
|
|
89
74
|
const { parent, ...rest } = value;
|
|
@@ -127,47 +112,75 @@ export default function withPdfButtons(WrappedComponent) {
|
|
|
127
112
|
return modalItems;
|
|
128
113
|
},
|
|
129
114
|
buildNextLayer = (item, ix, defaults) => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
115
|
+
if (!item) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const {
|
|
120
|
+
type,
|
|
121
|
+
name,
|
|
122
|
+
title,
|
|
123
|
+
items: childItems,
|
|
124
|
+
isHiddenInViewMode,
|
|
125
|
+
} = item;
|
|
126
|
+
|
|
135
127
|
if (inArray(type, ['Column', 'FieldSet'])) {
|
|
136
|
-
|
|
137
|
-
|
|
128
|
+
const nextDefaults = {
|
|
129
|
+
...(defaults || {}),
|
|
130
|
+
...(item.defaults || {}),
|
|
131
|
+
labelWidth: '90%',
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const nextItem = {
|
|
135
|
+
type,
|
|
136
|
+
defaults: nextDefaults,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
if (title) {
|
|
140
|
+
nextItem.title = title;
|
|
141
|
+
}
|
|
142
|
+
if (item.reference) {
|
|
143
|
+
nextItem.reference = item.reference;
|
|
144
|
+
}
|
|
145
|
+
if (item.flex) {
|
|
146
|
+
nextItem.flex = item.flex;
|
|
138
147
|
}
|
|
139
148
|
if (type === 'FieldSet') {
|
|
140
|
-
|
|
141
|
-
|
|
149
|
+
nextItem.showToggleAllCheckbox = true;
|
|
150
|
+
nextItem.isCollapsible = false;
|
|
142
151
|
}
|
|
143
|
-
|
|
144
|
-
if (!_.isEmpty(
|
|
145
|
-
|
|
146
|
-
item.items = _.map(items, (item, ix) => {
|
|
147
|
-
if (!item){
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
return buildNextLayer(item, ix, defaults);
|
|
151
|
-
});
|
|
152
|
+
|
|
153
|
+
if (!_.isEmpty(childItems)) {
|
|
154
|
+
nextItem.items = _.compact(_.map(childItems, (childItem, childIx) => buildNextLayer(childItem, childIx, nextDefaults)));
|
|
152
155
|
}
|
|
153
|
-
|
|
156
|
+
|
|
157
|
+
return nextItem;
|
|
154
158
|
}
|
|
155
159
|
|
|
156
|
-
if (
|
|
160
|
+
if (isHiddenInViewMode || type === 'Button') {
|
|
157
161
|
return null;
|
|
158
162
|
}
|
|
159
163
|
|
|
160
|
-
|
|
164
|
+
let resolvedTitle = title;
|
|
165
|
+
if (!resolvedTitle) {
|
|
161
166
|
const propertyDef = name && Repository?.getSchema().getPropertyDefinition(name);
|
|
162
167
|
if (propertyDef?.title) {
|
|
163
|
-
|
|
168
|
+
resolvedTitle = propertyDef.title;
|
|
164
169
|
}
|
|
165
170
|
}
|
|
166
171
|
if (name) {
|
|
167
172
|
propertyNames.push(name); // for validator
|
|
168
173
|
}
|
|
169
|
-
|
|
170
|
-
|
|
174
|
+
if (!name) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
type: 'Checkbox',
|
|
180
|
+
name,
|
|
181
|
+
title: resolvedTitle,
|
|
182
|
+
label: resolvedTitle,
|
|
183
|
+
};
|
|
171
184
|
},
|
|
172
185
|
buildValidator = () => {
|
|
173
186
|
const propertyValidatorDefs = {};
|
|
@@ -215,6 +228,7 @@ export default function withPdfButtons(WrappedComponent) {
|
|
|
215
228
|
parent={self}
|
|
216
229
|
reference="chooseFieldsForm"
|
|
217
230
|
editorType={EDITOR_TYPE__PLAIN}
|
|
231
|
+
checkIsEditingDisabled={false /* hack so layout looks right */}
|
|
218
232
|
alert={alert}
|
|
219
233
|
columnDefaults={{
|
|
220
234
|
labelWidth: '100px',
|
|
@@ -75,6 +75,7 @@ export default function withPresetButtons(WrappedComponent) {
|
|
|
75
75
|
disableAdd = !isEditor,
|
|
76
76
|
disableEdit = !isEditor,
|
|
77
77
|
disableDelete = !isEditor,
|
|
78
|
+
enableMultiDelete = false,
|
|
78
79
|
disableView = isTree,
|
|
79
80
|
disableCopy = isTree,
|
|
80
81
|
disableDuplicate = !isEditor,
|
|
@@ -300,7 +301,7 @@ export default function withPresetButtons(WrappedComponent) {
|
|
|
300
301
|
icon = Trash;
|
|
301
302
|
if (isNoSelectorSelected() ||
|
|
302
303
|
isEmptySelection() ||
|
|
303
|
-
isMultiSelection() ||
|
|
304
|
+
(isMultiSelection() && (!enableMultiDelete || isTree)) ||
|
|
304
305
|
isProtectedValue() ||
|
|
305
306
|
(canRecordBeDeleted && !canRecordBeDeleted(selection))
|
|
306
307
|
) {
|
|
@@ -22,6 +22,7 @@ function ScreenHeader(props) {
|
|
|
22
22
|
const {
|
|
23
23
|
title,
|
|
24
24
|
icon,
|
|
25
|
+
additionalButtons,
|
|
25
26
|
info,
|
|
26
27
|
_info = {},
|
|
27
28
|
useModeIcons = false,
|
|
@@ -88,6 +89,7 @@ function ScreenHeader(props) {
|
|
|
88
89
|
tooltip="To side editor"
|
|
89
90
|
/>
|
|
90
91
|
</>}
|
|
92
|
+
{additionalButtons}
|
|
91
93
|
{info &&
|
|
92
94
|
<IconButton
|
|
93
95
|
{...testProps('infoBtn')}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
selectIsSetupMode,
|
|
4
4
|
toggleSetupMode,
|
|
5
5
|
} from '@src/Models/Slices/AppSlice';
|
|
6
|
+
import clsx from 'clsx';
|
|
6
7
|
import Button from '../Buttons/Button';
|
|
7
8
|
import IconButton from '../Buttons/IconButton';
|
|
8
9
|
import Gear from '../Icons/Gear';
|
|
@@ -13,19 +14,44 @@ export default function SetupButton(props) {
|
|
|
13
14
|
} = props,
|
|
14
15
|
dispatch = useDispatch(),
|
|
15
16
|
isSetupMode = useSelector(selectIsSetupMode),
|
|
16
|
-
onPress = () => dispatch(toggleSetupMode())
|
|
17
|
+
onPress = () => dispatch(toggleSetupMode()),
|
|
18
|
+
buttonClassName = clsx(
|
|
19
|
+
'SetupButton',
|
|
20
|
+
isSetupMode
|
|
21
|
+
? 'bg-red-500 data-[hover=true]:bg-red-600 data-[active=true]:bg-red-700'
|
|
22
|
+
: 'bg-grey-100 data-[hover=true]:bg-grey-900/20 data-[active=true]:bg-grey-900/50',
|
|
23
|
+
),
|
|
24
|
+
textClassName = clsx(
|
|
25
|
+
isSetupMode
|
|
26
|
+
? 'text-white data-[hover=true]:text-white data-[active=true]:text-white'
|
|
27
|
+
: 'text-black data-[hover=true]:text-black data-[active=true]:text-black',
|
|
28
|
+
),
|
|
29
|
+
iconClassName = clsx(
|
|
30
|
+
isSetupMode ? 'fill-white' : 'fill-black',
|
|
31
|
+
isSetupMode ? 'text-white' : 'text-black',
|
|
32
|
+
);
|
|
33
|
+
|
|
17
34
|
return isMinimized ?
|
|
18
35
|
<IconButton
|
|
19
36
|
icon={Gear}
|
|
37
|
+
_icon={{
|
|
38
|
+
className: iconClassName,
|
|
39
|
+
}}
|
|
20
40
|
onPress={onPress}
|
|
21
41
|
tooltip="Toggle Setup Mode"
|
|
22
|
-
className=
|
|
42
|
+
className={buttonClassName}
|
|
23
43
|
/> :
|
|
24
44
|
<Button
|
|
25
45
|
text={isSetupMode ? 'Exit Setup' : 'Setup'}
|
|
26
46
|
icon={Gear}
|
|
47
|
+
_text={{
|
|
48
|
+
className: textClassName,
|
|
49
|
+
}}
|
|
50
|
+
_icon={{
|
|
51
|
+
className: iconClassName,
|
|
52
|
+
}}
|
|
27
53
|
onPress={onPress}
|
|
28
54
|
tooltip="Toggle Setup Mode"
|
|
29
|
-
className=
|
|
55
|
+
className={buttonClassName}
|
|
30
56
|
/>;
|
|
31
57
|
};
|
|
@@ -51,6 +51,7 @@ function Viewer(props) {
|
|
|
51
51
|
const {
|
|
52
52
|
viewerCanDelete = false,
|
|
53
53
|
items = [], // Columns, FieldSets, Fields, etc to define the form
|
|
54
|
+
isItemsCustomLayout = false,
|
|
54
55
|
ancillaryItems = [], // additional items which are not controllable form elements, but should appear in the form
|
|
55
56
|
showAncillaryButtons = false,
|
|
56
57
|
columnDefaults = {}, // defaults for each Column defined in items (above)
|
|
@@ -183,7 +184,7 @@ function Viewer(props) {
|
|
|
183
184
|
let children;
|
|
184
185
|
const style = {};
|
|
185
186
|
if (type === 'Column') {
|
|
186
|
-
const isEverythingInOneColumn = containerWidth < styles.FORM_ONE_COLUMN_THRESHOLD;
|
|
187
|
+
const isEverythingInOneColumn = isItemsCustomLayout || containerWidth < styles.FORM_ONE_COLUMN_THRESHOLD;
|
|
187
188
|
if (itemPropsToPass.hasOwnProperty('flex')) {
|
|
188
189
|
if (!isEverythingInOneColumn) {
|
|
189
190
|
style.flex = itemPropsToPass.flex;
|
|
@@ -489,7 +490,9 @@ function Viewer(props) {
|
|
|
489
490
|
const
|
|
490
491
|
showDeleteBtn = onDelete && viewerCanDelete,
|
|
491
492
|
showCloseBtn = !isSideEditor && !isSmartEditor && onClose,
|
|
492
|
-
showFooter = (showDeleteBtn || showCloseBtn)
|
|
493
|
+
showFooter = (showDeleteBtn || showCloseBtn),
|
|
494
|
+
hasTopLevelColumns = _.some(items, (item) => item?.type === 'Column'),
|
|
495
|
+
shouldUseHorizontalViewerLayout = !isItemsCustomLayout && hasTopLevelColumns && containerWidth >= styles.FORM_ONE_COLUMN_THRESHOLD;
|
|
493
496
|
let additionalButtons = null,
|
|
494
497
|
viewerComponents = null,
|
|
495
498
|
ancillaryComponents = null,
|
|
@@ -611,8 +614,8 @@ function Viewer(props) {
|
|
|
611
614
|
{buildAdditionalButtons(_.omitBy(getAncillaryButtons(), (btnConfig) => btnConfig.reference === 'scrollToTop'))}
|
|
612
615
|
</Toolbar>}
|
|
613
616
|
|
|
614
|
-
{
|
|
615
|
-
{
|
|
617
|
+
{shouldUseHorizontalViewerLayout ? <HStack className="Viewer-formComponents-HStack p-4 gap-4 justify-center">{viewerComponents}</HStack> : null}
|
|
618
|
+
{!shouldUseHorizontalViewerLayout ? <VStack className="Viewer-formComponents-VStack p-4">{viewerComponents}</VStack> : null}
|
|
616
619
|
<VStack className="Viewer-AncillaryComponents m-2 pt-4 px-2">{ancillaryComponents}</VStack>
|
|
617
620
|
</ScrollView>
|
|
618
621
|
|