@onehat/ui 0.4.107 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/ui",
3
- "version": "0.4.107",
3
+ "version": "0.4.109",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -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 !== northHeight) {
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}
@@ -320,7 +320,7 @@ function TagComponent(props) {
320
320
  />;
321
321
  }
322
322
  switch (items.length) {
323
- case 1: height = 250; break;
323
+ case 1: height = 300; break;
324
324
  case 2: height = 400; break;
325
325
  default: height = 600; break;
326
326
  }
@@ -321,7 +321,11 @@ function Form(props) {
321
321
  type = 'Text';
322
322
  }
323
323
  }
324
- const isCombo = type?.match && type.match(/Combo/);
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 isCombo = type?.match && type.match(/Combo/);
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 (!disableLabels && label) {
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) {
@@ -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
@@ -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
@@ -441,7 +442,14 @@ export default function withEditor(WrappedComponent, isTree = false) {
441
442
  cb = args;
442
443
  }
443
444
  const selection = getSelection();
444
- if (_.isEmpty(selection) || (_.isArray(selection) && (selection.length > 1 || selection[0]?.isDestroyed))) {
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
- isTree = firstSelection?.isTree,
465
- hasChildren = isTree ? firstSelection?.hasChildren : false,
472
+ isTreeNode = firstSelection?.isTree,
473
+ hasChildren = isTreeNode ? firstSelection?.hasChildren : false,
466
474
  isPhantom = firstSelection?.isPhantom;
467
475
 
468
- if (isSingle && isTree && hasChildren) {
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. ' +
@@ -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
- data.push([ filterField, propertyDef?.title ]);
401
+ if (propertyDef?.isFilteringDisabled) {
402
+ return;
403
+ }
404
+ data.push([ filterField, propertyDef?.filterTitle || propertyDef?.title ]);
402
405
  });
403
406
 
404
407
  // sort by title
@@ -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
- itemsTouse = pdfItems || items,
65
+ itemsToUse = pdfItems || _.filter(items, (item) => item?.type !== 'Hidden'),
66
66
  ancillaryItemsToUse = pdfAncillaryItems || ancillaryItems,
67
- modalItems = _.compact(_.map(itemsTouse, (item, ix) => buildNextLayer(item, ix, columnDefaults)));
67
+ modalItems = _.compact(_.map(itemsToUse, (item, ix) => buildNextLayer(item, ix, columnDefaults)));
68
68
 
69
69
  if (!_.isEmpty(ancillaryItemsToUse)) {
70
70
  const
@@ -179,7 +179,7 @@ export default function withPdfButtons(WrappedComponent) {
179
179
  type: 'Checkbox',
180
180
  name,
181
181
  title: resolvedTitle,
182
- isEditable: false, // hack to force all checkboxes to use same render branch in Form
182
+ label: resolvedTitle,
183
183
  };
184
184
  },
185
185
  buildValidator = () => {
@@ -228,6 +228,7 @@ export default function withPdfButtons(WrappedComponent) {
228
228
  parent={self}
229
229
  reference="chooseFieldsForm"
230
230
  editorType={EDITOR_TYPE__PLAIN}
231
+ checkIsEditingDisabled={false /* hack so layout looks right */}
231
232
  alert={alert}
232
233
  columnDefaults={{
233
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
  ) {
@@ -435,6 +436,10 @@ export default function withPresetButtons(WrappedComponent) {
435
436
  showInfo('Copied to clipboard!');
436
437
  }
437
438
  },
439
+ getColumnsConfigForDownload = () => {
440
+ const activeColumnsConfig = localColumnsConfig.length ? localColumnsConfig : props.columnsConfig;
441
+ return _.filter(activeColumnsConfig, (config) => !config.isHidden);
442
+ },
438
443
  onUploadDownload = () => {
439
444
  const onUploadDecorator = async () => {
440
445
  if (onUpload) {
@@ -453,7 +458,7 @@ export default function withPresetButtons(WrappedComponent) {
453
458
  reference="uploadsDownloads"
454
459
  onClose={hideModal}
455
460
  Repository={Repository}
456
- columnsConfig={props.columnsConfig}
461
+ columnsConfig={getColumnsConfigForDownload()}
457
462
  uploadHeaders={uploadHeaders}
458
463
  uploadParams={uploadParams}
459
464
  onUpload={onUploadDecorator}
@@ -470,7 +475,7 @@ export default function withPresetButtons(WrappedComponent) {
470
475
  onClose={hideModal}
471
476
  isDownloadOnly={true}
472
477
  Repository={Repository}
473
- columnsConfig={props.columnsConfig}
478
+ columnsConfig={getColumnsConfigForDownload()}
474
479
  downloadHeaders={downloadHeaders}
475
480
  downloadParams={downloadParams}
476
481
  />,
@@ -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')}
@@ -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 isCombo = type?.match && type.match(/Combo/);
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, 450),
41
- onDownload = (isTemplate = false) => {
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
- isTemplate,
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: 'Download an Excel file of the current grid contents.',
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: 'Upload an Excel file to the current grid.',
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: onDownloadTemplate,
196
+ onPress: () => onDownload(MODES__TEMPLATE),
179
197
  },
180
198
 
181
199
  ],