@onehat/ui 0.4.101 → 0.4.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/package.json +1 -1
  2. package/src/Components/Accordion/Accordion.js +65 -6
  3. package/src/Components/Container/Container.js +10 -4
  4. package/src/Components/Form/Field/Combo/Combo.js +10 -4
  5. package/src/Components/Form/Field/Tag/Tag.js +6 -0
  6. package/src/Components/Form/Form.js +8 -3
  7. package/src/Components/Grid/Grid.js +232 -154
  8. package/src/Components/Grid/GridRow.js +7 -3
  9. package/src/Components/Hoc/withPresetButtons.js +18 -6
  10. package/src/Components/Icons/ArrowsLeftRight.js +10 -0
  11. package/src/Components/Icons/Bar.js +10 -0
  12. package/src/Components/Icons/Box.js +11 -0
  13. package/src/Components/Icons/BoxOpen.js +11 -0
  14. package/src/Components/Icons/Bucket.js +10 -0
  15. package/src/Components/Icons/Bump.js +21 -0
  16. package/src/Components/Icons/Calculator.js +12 -0
  17. package/src/Components/Icons/Dots.js +20 -0
  18. package/src/Components/Icons/Fleets.js +26 -0
  19. package/src/Components/Icons/Lock.js +11 -0
  20. package/src/Components/Icons/Microchip.js +12 -0
  21. package/src/Components/Icons/Num1.js +10 -0
  22. package/src/Components/Icons/Num2.js +10 -0
  23. package/src/Components/Icons/Num3.js +10 -0
  24. package/src/Components/Icons/Num4.js +10 -0
  25. package/src/Components/Icons/OilCan.js +11 -0
  26. package/src/Components/Icons/Operations.js +10 -0
  27. package/src/Components/Icons/OverduePms.js +10 -0
  28. package/src/Components/Icons/SackDollar.js +11 -0
  29. package/src/Components/Icons/ShortBar.js +15 -0
  30. package/src/Components/Icons/Tower.js +10 -0
  31. package/src/Components/Icons/UpcomingPms.js +10 -0
  32. package/src/Components/Layout/ScreenHeader.js +35 -3
  33. package/src/Components/Layout/SetupButton.js +31 -0
  34. package/src/Components/Layout/UserIndicator.js +35 -0
  35. package/src/Components/Pms/Editor/BumpPmsEditor.js +9 -0
  36. package/src/Components/Pms/Editor/MetersEditor.js +173 -0
  37. package/src/Components/Pms/Editor/PmEventsEditor.js +291 -0
  38. package/src/Components/Pms/Grid/UpcomingPmsGrid.js +569 -0
  39. package/src/Components/Pms/Layout/TreeSpecific/MakeTreeSelection.js +11 -0
  40. package/src/Components/Pms/Layout/TreeSpecific/TreeSpecific.js +30 -0
  41. package/src/Components/Pms/Modals/BulkAssignTechnician.js +104 -0
  42. package/src/Components/Pms/Screens/PmsManager.js +136 -0
  43. package/src/Components/Pms/Window/BumpPmsEditorWindow.js +25 -0
  44. package/src/Components/Screens/Manager.js +3 -0
  45. package/src/Components/Screens/ReportsManager.js +51 -26
  46. package/src/Components/Tree/Tree.js +15 -6
  47. package/src/Components/Viewer/PmCalcDebugViewer.js +164 -146
  48. package/src/Components/Viewer/TextWithLinks.js +9 -1
  49. package/src/Components/Viewer/Viewer.js +38 -30
  50. package/src/Functions/flatten.js +39 -0
  51. package/src/Functions/verifyCanCrudPmEvents.js +33 -0
@@ -147,6 +147,7 @@ function GridComponent(props) {
147
147
  showHeaders = true,
148
148
  showHovers = true,
149
149
  showSelectHandle = true,
150
+ isRowTextSelectable, // if false, user can't select text in rows (e.g. to copy/paste)
150
151
  isRowSelectable = true,
151
152
  isRowHoverable = true,
152
153
  isDisabled = false,
@@ -295,6 +296,11 @@ function GridComponent(props) {
295
296
  footerToolbarRef = useRef(null),
296
297
  rowRefs = useRef([]),
297
298
  previousEntitiesLength = useRef(0),
299
+ paginationSelectionGuardRef = useRef({
300
+ source: null,
301
+ pending: new Set(),
302
+ expiresAt: 0,
303
+ }),
298
304
  hasRemeasuredAfterRowsAppeared = useRef(false),
299
305
  [isInited, setIsInited] = useState(false),
300
306
  [isReady, setIsReady] = useState(false),
@@ -304,20 +310,6 @@ function GridComponent(props) {
304
310
  showRowHandle = showSelectHandle || areRowsDragSource || (canRowsReorder && isReorderMode),
305
311
  rowLongPressDelay = rowLongPressDelayMs ?? ((areRowsDragSource || canRowsReorder) ? 800 : undefined),
306
312
  [lastMeasuredContainerHeight, setLastMeasuredContainerHeight] = useState(0),
307
- getMeasurementPhase = () => {
308
- return measurementPhaseRaw.current;
309
- },
310
- setMeasurementPhase = (phase) => {
311
- measurementPhaseRaw.current = phase;
312
- forceUpdate();
313
- },
314
- getMeasuredRowHeight = () => {
315
- return measuredRowHeightRaw.current;
316
- },
317
- setMeasuredRowHeight = (height) => {
318
- measuredRowHeightRaw.current = height;
319
- forceUpdate();
320
- },
321
313
  getIsExpanded = (index) => {
322
314
  return !!expandedRowsRef.current[index];
323
315
  },
@@ -426,13 +418,15 @@ function GridComponent(props) {
426
418
  }
427
419
  },
428
420
  getFooterToolbarItems = () => {
429
- // Process additionalToolbarButtons to evaluate getIsButtonDisabled functions
421
+ // Process additionalToolbarButtons to evaluate functions
430
422
  const processedButtons = _.map(additionalToolbarButtons, (config) => {
431
423
  const processedConfig = { ...config };
432
- // If the button has an getIsButtonDisabled function, evaluate it with current selection
433
424
  if (_.isFunction(config.getIsButtonDisabled)) {
434
425
  processedConfig.isDisabled = config.getIsButtonDisabled(selection);
435
426
  }
427
+ if (_.isFunction(config.getText)) {
428
+ processedConfig.text = config.getText(selection);
429
+ }
436
430
  return processedConfig;
437
431
  });
438
432
  const items = _.map(processedButtons, (config, ix) => getIconButtonFromConfig(config, ix, self));
@@ -789,6 +783,7 @@ function GridComponent(props) {
789
783
  areCellsScrollable={areCellsScrollable}
790
784
  showHovers={showHovers}
791
785
  showRowHandle={showRowHandle}
786
+ isRowTextSelectable={isRowTextSelectable}
792
787
  rowCanSelect={rowCanSelect}
793
788
  rowCanDrag={rowCanDrag}
794
789
  index={index}
@@ -1016,6 +1011,151 @@ function GridComponent(props) {
1016
1011
  marker.remove();
1017
1012
  cachedDragElements.current = null;
1018
1013
  },
1014
+ applySelectorSelected = () => {
1015
+ if (disableSelectorSelected || !selectorId) {
1016
+ return
1017
+ }
1018
+
1019
+ if (previousSelectorId.current && selectorId !== previousSelectorId.current) {
1020
+ Repository.pauseEvents();
1021
+ Repository.clearFilters(previousSelectorId.current);
1022
+ Repository.resumeEvents();
1023
+ }
1024
+ previousSelectorId.current = selectorId;
1025
+
1026
+ let value = null;
1027
+ if (selectorSelected) {
1028
+ value = selectorSelected[selectorSelectedField];
1029
+ }
1030
+ if (noSelectorMeansNoResults && _.isEmpty(selectorSelected)) {
1031
+ value = 'NO_MATCHES';
1032
+ }
1033
+
1034
+ Repository.filter(selectorId, value, false); // false so it doesn't clear existing filters
1035
+ },
1036
+ onGridKeyDown = (e) => {
1037
+ if (isInlineEditorShown) {
1038
+ return;
1039
+ }
1040
+ if (disableWithSelection) {
1041
+ return;
1042
+ }
1043
+ const {
1044
+ shiftKey = false,
1045
+ } = e;
1046
+ if (selectionMode === SELECTION_MODE_MULTI && shiftKey) {
1047
+ switch(e.key) {
1048
+ case 'ArrowDown':
1049
+ e.preventDefault();
1050
+ addNextToSelection();
1051
+ break;
1052
+ case 'ArrowUp':
1053
+ e.preventDefault();
1054
+ addPrevToSelection();
1055
+ break;
1056
+ }
1057
+ } else {
1058
+ // selectionMode is SELECTION_MODE_SINGLE
1059
+ switch(e.key) {
1060
+ case 'Enter':
1061
+ // NOTE: This is never being reached.
1062
+ // The event is getting captured somwhere else,
1063
+ // but I can't find where.
1064
+ // e.preventDefault();
1065
+
1066
+ // launch inline or windowed editor
1067
+ // const p = props;
1068
+ // debugger;
1069
+ break;
1070
+ case 'ArrowDown':
1071
+ e.preventDefault();
1072
+ selectNext();
1073
+ break;
1074
+ case 'ArrowUp':
1075
+ e.preventDefault();
1076
+ selectPrev();
1077
+ break;
1078
+ }
1079
+ }
1080
+ },
1081
+ showColumnsSelector = () => {
1082
+ const
1083
+ modalItems = _.map(localColumnsConfig, (config, ix) => {
1084
+ return {
1085
+ name: config.id,
1086
+ label: config.header,
1087
+ type: config.isHidable ? 'Checkbox' : 'Text',
1088
+ isEditable: config.isHidable ?? false,
1089
+ };
1090
+ }),
1091
+ startingValues = (() => {
1092
+ const startingValues = {};
1093
+ _.each(localColumnsConfig, (config) => {
1094
+ const value = !config.isHidden; // checkbox implies to show it, so flip the polarity
1095
+ startingValues[config.id] = config.isHidable ? value : 'Always shown';
1096
+ });
1097
+ return startingValues;
1098
+ })();
1099
+
1100
+ showModal({
1101
+ title: 'Column Selector',
1102
+ includeReset: true,
1103
+ includeCancel: true,
1104
+ h: 800,
1105
+ w: styles.FORM_STACK_ROW_THRESHOLD + 10,
1106
+ body: <Form
1107
+ editorType={EDITOR_TYPE__PLAIN}
1108
+ columnDefaults={{
1109
+ labelWidth: '250px',
1110
+ }}
1111
+ items={[
1112
+ {
1113
+ name: 'instructions',
1114
+ type: 'DisplayField',
1115
+ text: 'Please select which columns to show in the grid.',
1116
+ className: 'mb-3',
1117
+ },
1118
+ {
1119
+ type: 'FieldSet',
1120
+ title: 'Columns',
1121
+ reference: 'columns',
1122
+ showToggleAllCheckbox: true,
1123
+ items: [
1124
+ ...modalItems,
1125
+ ],
1126
+ }
1127
+ ]}
1128
+ startingValues={startingValues}
1129
+ onSave={(values)=> {
1130
+ hideModal();
1131
+
1132
+ const newColumnsConfig = _.cloneDeep(localColumnsConfig);
1133
+ _.each(newColumnsConfig, (config, ix) => {
1134
+ if (config.isHidable) {
1135
+ newColumnsConfig[ix].isHidden = !values[config.id]; // checkbox implies to show it, so flip the polarity
1136
+ }
1137
+ });
1138
+ setLocalColumnsConfig(newColumnsConfig);
1139
+ }}
1140
+ />,
1141
+ });
1142
+ },
1143
+
1144
+ // These methods relate to auto-pageSize measurement and adjustment:
1145
+ getMeasurementPhase = () => {
1146
+ return measurementPhaseRaw.current;
1147
+ },
1148
+ setMeasurementPhase = (phase) => {
1149
+ measurementPhaseRaw.current = phase;
1150
+ forceUpdate();
1151
+ },
1152
+ getMeasuredRowHeight = () => {
1153
+ return measuredRowHeightRaw.current;
1154
+ },
1155
+ setMeasuredRowHeight = (height) => {
1156
+ measuredRowHeightRaw.current = height;
1157
+ forceUpdate();
1158
+ },
1019
1159
  calculatePageSize = (containerHeight, useActualMeasurements = false) => {
1020
1160
  if (DEBUG) {
1021
1161
  console.log(`${getMeasurementPhase()}, calculatePageSize A containerHeight=${containerHeight}, useActualMeasurements=${useActualMeasurements}, measuredRowHeight=${getMeasuredRowHeight()}`);
@@ -1200,6 +1340,7 @@ function GridComponent(props) {
1200
1340
  if (DEBUG) {
1201
1341
  console.log(`${getMeasurementPhase()}, applyMeasuredRowHeight B Repository.setPageSize(${newPageSize})`);
1202
1342
  }
1343
+ startAutoPageSizeSelectionGuard();
1203
1344
  Repository.setPageSize(newPageSize);
1204
1345
  }
1205
1346
  }
@@ -1259,6 +1400,7 @@ function GridComponent(props) {
1259
1400
  if (DEBUG) {
1260
1401
  console.log(`${getMeasurementPhase()}, adjustPageSizeToHeight D Repository.setPageSize(${pageSize})`);
1261
1402
  }
1403
+ startAutoPageSizeSelectionGuard();
1262
1404
  Repository.setPageSize(pageSize);
1263
1405
  }
1264
1406
  }
@@ -1277,136 +1419,51 @@ function GridComponent(props) {
1277
1419
  }
1278
1420
  },
1279
1421
  debouncedAdjustPageSizeToHeight = useCallback(_.debounce(adjustPageSizeToHeight, 200), []),
1280
- applySelectorSelected = () => {
1281
- if (disableSelectorSelected || !selectorId) {
1282
- return
1283
- }
1284
-
1285
- if (previousSelectorId.current && selectorId !== previousSelectorId.current) {
1286
- Repository.pauseEvents();
1287
- Repository.clearFilters(previousSelectorId.current);
1288
- Repository.resumeEvents();
1289
- }
1290
- previousSelectorId.current = selectorId;
1291
1422
 
1292
- let value = null;
1293
- if (selectorSelected) {
1294
- value = selectorSelected[selectorSelectedField];
1295
- }
1296
- if (noSelectorMeansNoResults && _.isEmpty(selectorSelected)) {
1297
- value = 'NO_MATCHES';
1298
- }
1299
-
1300
- Repository.filter(selectorId, value, false); // false so it doesn't clear existing filters
1423
+ // These methods guard for selection/pagination interaction
1424
+ // (If user makes a selection during the auto-pageSize adjustment,
1425
+ // we don't want to clear the selection if the current selection
1426
+ // would still be valid with the new page size):
1427
+ clearPaginationSelectionGuard = () => {
1428
+ paginationSelectionGuardRef.current = {
1429
+ source: null,
1430
+ pending: new Set(),
1431
+ expiresAt: 0,
1432
+ };
1301
1433
  },
1302
- onGridKeyDown = (e) => {
1303
- if (isInlineEditorShown) {
1434
+ startAutoPageSizeSelectionGuard = () => {
1435
+ if (disableWithSelection || !deselectAll) {
1304
1436
  return;
1305
1437
  }
1306
- if (disableWithSelection) {
1307
- return;
1438
+ paginationSelectionGuardRef.current = {
1439
+ source: 'autoPageSize',
1440
+ pending: new Set(['changePage', 'changePageSize',]),
1441
+ expiresAt: Date.now() + 5000,
1442
+ };
1443
+ },
1444
+ consumePaginationSelectionGuard = (eventType) => {
1445
+ const guard = paginationSelectionGuardRef.current;
1446
+ if (guard.source !== 'autoPageSize') {
1447
+ return false;
1308
1448
  }
1309
- const {
1310
- shiftKey = false,
1311
- } = e;
1312
- if (selectionMode === SELECTION_MODE_MULTI && shiftKey) {
1313
- switch(e.key) {
1314
- case 'ArrowDown':
1315
- e.preventDefault();
1316
- addNextToSelection();
1317
- break;
1318
- case 'ArrowUp':
1319
- e.preventDefault();
1320
- addPrevToSelection();
1321
- break;
1322
- }
1323
- } else {
1324
- // selectionMode is SELECTION_MODE_SINGLE
1325
- switch(e.key) {
1326
- case 'Enter':
1327
- // NOTE: This is never being reached.
1328
- // The event is getting captured somwhere else,
1329
- // but I can't find where.
1330
- // e.preventDefault();
1331
-
1332
- // launch inline or windowed editor
1333
- // const p = props;
1334
- // debugger;
1335
- break;
1336
- case 'ArrowDown':
1337
- e.preventDefault();
1338
- selectNext();
1339
- break;
1340
- case 'ArrowUp':
1341
- e.preventDefault();
1342
- selectPrev();
1343
- break;
1344
- }
1449
+ if (Date.now() > guard.expiresAt) {
1450
+ clearPaginationSelectionGuard();
1451
+ return false;
1452
+ }
1453
+ if (!guard.pending.has(eventType)) {
1454
+ return false;
1345
1455
  }
1346
- },
1347
- showColumnsSelector = () => {
1348
- const
1349
- modalItems = _.map(localColumnsConfig, (config, ix) => {
1350
- return {
1351
- name: config.id,
1352
- label: config.header,
1353
- type: config.isHidable ? 'Checkbox' : 'Text',
1354
- isEditable: config.isHidable ?? false,
1355
- };
1356
- }),
1357
- startingValues = (() => {
1358
- const startingValues = {};
1359
- _.each(localColumnsConfig, (config) => {
1360
- const value = !config.isHidden; // checkbox implies to show it, so flip the polarity
1361
- startingValues[config.id] = config.isHidable ? value : 'Always shown';
1362
- });
1363
- return startingValues;
1364
- })();
1365
-
1366
- showModal({
1367
- title: 'Column Selector',
1368
- includeReset: true,
1369
- includeCancel: true,
1370
- h: 800,
1371
- w: styles.FORM_STACK_ROW_THRESHOLD + 10,
1372
- body: <Form
1373
- editorType={EDITOR_TYPE__PLAIN}
1374
- columnDefaults={{
1375
- labelWidth: '250px',
1376
- }}
1377
- items={[
1378
- {
1379
- name: 'instructions',
1380
- type: 'DisplayField',
1381
- text: 'Please select which columns to show in the grid.',
1382
- className: 'mb-3',
1383
- },
1384
- {
1385
- type: 'FieldSet',
1386
- title: 'Columns',
1387
- reference: 'columns',
1388
- showToggleAllCheckbox: true,
1389
- items: [
1390
- ...modalItems,
1391
- ],
1392
- }
1393
- ]}
1394
- startingValues={startingValues}
1395
- onSave={(values)=> {
1396
- hideModal();
1397
1456
 
1398
- const newColumnsConfig = _.cloneDeep(localColumnsConfig);
1399
- _.each(newColumnsConfig, (config, ix) => {
1400
- if (config.isHidable) {
1401
- newColumnsConfig[ix].isHidden = !values[config.id]; // checkbox implies to show it, so flip the polarity
1402
- }
1403
- });
1404
- setLocalColumnsConfig(newColumnsConfig);
1405
- }}
1406
- />,
1407
- });
1457
+ guard.pending.delete(eventType);
1458
+
1459
+ if (!guard.pending.size) {
1460
+ clearPaginationSelectionGuard();
1461
+ }
1462
+
1463
+ return true;
1408
1464
  };
1409
1465
 
1466
+
1410
1467
  if (forceLoadOnRender && disableLoadOnRender) {
1411
1468
  throw new Error('incompatible config! forceLoadOnRender and disableLoadOnRender cannot both be true');
1412
1469
  }
@@ -1529,7 +1586,27 @@ function GridComponent(props) {
1529
1586
  // set up @onehat/data repository
1530
1587
  const
1531
1588
  setTrue = () => setIsLoading(true),
1532
- setFalse = () => setIsLoading(false),
1589
+ setFalse = () => {
1590
+ setIsLoading(false);
1591
+ clearPaginationSelectionGuard();
1592
+ },
1593
+ onPaginationEvent = (eventType) => {
1594
+ if (consumePaginationSelectionGuard(eventType)) {
1595
+ return;
1596
+ }
1597
+ if (!disableWithSelection && deselectAll) {
1598
+ deselectAll();
1599
+ }
1600
+ if (eventType === 'changePage' && showRowExpander) {
1601
+ expandedRowsRef.current = {}; // clear expanded rows
1602
+ }
1603
+ },
1604
+ onChangePageSize = () => {
1605
+ if (disableWithSelection || !deselectAll) {
1606
+ return;
1607
+ }
1608
+ onPaginationEvent('changePageSize');
1609
+ },
1533
1610
  onChangeFilters = () => {
1534
1611
  if (DEBUG) {
1535
1612
  console.log('onChangeFilters, reload and re-measure');
@@ -1544,20 +1621,16 @@ function GridComponent(props) {
1544
1621
  }
1545
1622
  },
1546
1623
  onChangePage = () => {
1547
- if (showRowExpander) {
1548
- expandedRowsRef.current = {}; // clear expanded rows
1549
- }
1624
+ onPaginationEvent('changePage');
1550
1625
  };
1551
1626
 
1552
1627
  Repository.on('beforeLoad', setTrue);
1553
1628
  Repository.on('load', setFalse);
1554
- if (!disableWithSelection) {
1555
- Repository.ons(['changePage', 'changePageSize',], deselectAll);
1556
- }
1629
+ Repository.on('changePage', onChangePage);
1630
+ Repository.on('changePageSize', onChangePageSize);
1557
1631
  Repository.ons(['changeData', 'change'], forceUpdate);
1558
1632
  Repository.on('changeFilters', onChangeFilters);
1559
1633
  Repository.on('changeSorters', onChangeSorters);
1560
- Repository.on('changePage', onChangePage);
1561
1634
 
1562
1635
  applySelectorSelected();
1563
1636
 
@@ -1576,13 +1649,11 @@ function GridComponent(props) {
1576
1649
  return () => {
1577
1650
  Repository.off('beforeLoad', setTrue);
1578
1651
  Repository.off('load', setFalse);
1579
- if (!disableWithSelection) {
1580
- Repository.offs(['changePage', 'changePageSize',], deselectAll);
1581
- }
1652
+ Repository.off('changePage', onChangePage);
1653
+ Repository.off('changePageSize', onChangePageSize);
1582
1654
  Repository.offs(['changeData', 'change'], forceUpdate);
1583
1655
  Repository.off('changeFilters', onChangeFilters);
1584
1656
  Repository.off('changeSorters', onChangeSorters);
1585
- Repository.off('changePage', onChangePage);
1586
1657
  };
1587
1658
  }, [isInited]);
1588
1659
 
@@ -1598,8 +1669,8 @@ function GridComponent(props) {
1598
1669
 
1599
1670
  }, [selectorId, selectorSelected]);
1600
1671
 
1601
- // Effect to trigger row height measurement after render
1602
1672
  useEffect(() => {
1673
+ // trigger row height measurement after render
1603
1674
  if (getMeasurementPhase() === PHASES__MEASURING) {
1604
1675
  // Small delay to ensure elements are fully rendered
1605
1676
  const timer = setTimeout(async () => {
@@ -1635,13 +1706,13 @@ function GridComponent(props) {
1635
1706
  }
1636
1707
  }, [autoAdjustPageSizeToHeight]);
1637
1708
 
1638
- // Reset measurement when rows were first empty then became populated
1639
1709
  useEffect(() => {
1710
+ // Reset measurement when rows were first empty then became populated
1640
1711
  const
1641
1712
  currentLength = entities?.length || 0,
1642
1713
  wasEmpty = previousEntitiesLength.current === 0,
1643
1714
  isNowPopulated = currentLength > 0,
1644
- hasPhantomRecord = entities?.some(entity => entity?.isPhantom);
1715
+ hasPhantomRecord = entities?.some(entity => !entity.isDestroyed && entity?.isPhantom);
1645
1716
 
1646
1717
  // NOTE: The Repository was reloading when a phantom record was added,
1647
1718
  // and this broke the Editor because selection was being reset to zero.
@@ -1672,6 +1743,15 @@ function GridComponent(props) {
1672
1743
  previousEntitiesLength.current = currentLength;
1673
1744
  }, [entities?.length, autoAdjustPageSizeToHeight]);
1674
1745
 
1746
+
1747
+ // Memoize footer toolbar items to avoid unnecessary re-renders, but only if they don't have dynamic properties
1748
+ const
1749
+ hasDynamicFooterToolbarItems = useMemo(() => _.some(additionalToolbarButtons, (config) => {
1750
+ return _.isFunction(config.getIsButtonDisabled) || _.isFunction(config.getText);
1751
+ }), [additionalToolbarButtons]),
1752
+ memoizedFooterToolbarItemComponents = useMemo(() => getFooterToolbarItems(), [Repository?.hash, additionalToolbarButtons, isReorderMode]),
1753
+ footerToolbarItemComponents = hasDynamicFooterToolbarItems ? getFooterToolbarItems() : memoizedFooterToolbarItemComponents;
1754
+
1675
1755
  if (canUser && !canUser('view')) {
1676
1756
  return <Unauthorized />;
1677
1757
  }
@@ -1683,8 +1763,6 @@ function GridComponent(props) {
1683
1763
 
1684
1764
  isAddingRaw.current = isAdding;
1685
1765
 
1686
- const footerToolbarItemComponents = useMemo(() => getFooterToolbarItems(), [Repository?.hash, additionalToolbarButtons, isReorderMode]);
1687
-
1688
1766
  if (!isInited) {
1689
1767
  // first time through, render a placeholder so we can get container dimensions
1690
1768
  return <VStackNative
@@ -38,6 +38,7 @@ const GridRow = forwardRef((props, ref) => {
38
38
  rowProps,
39
39
  hideNavColumn,
40
40
  showRowHandle,
41
+ isRowTextSelectable,
41
42
  areCellsScrollable,
42
43
  rowCanSelect,
43
44
  rowCanDrag,
@@ -126,6 +127,8 @@ const GridRow = forwardRef((props, ref) => {
126
127
  const
127
128
  visibleColumns = _.filter(columnsConfig, (config) => !config.isHidden),
128
129
  isOnlyOneVisibleColumn = visibleColumns.length === 1,
130
+ canSelectTextOnRow = isRowTextSelectable === false ? false : isDragFromHandleOnly,
131
+ shouldUseTextCursor = showRowHandle && canSelectTextOnRow,
129
132
  rowShouldHaveDragRef = !isDragFromHandleOnly && (isDragSource || isDraggable) && !!dragSourceRef;
130
133
  const setRowRef = (node) => {
131
134
  if (typeof ref === 'function') {
@@ -152,7 +155,7 @@ const GridRow = forwardRef((props, ref) => {
152
155
  const
153
156
  propsToPass = columnProps[key] || {},
154
157
  colStyle = {},
155
- whichCursor = showRowHandle && isDragFromHandleOnly ? 'cursor-text' : 'cursor-pointer'; // when using rowSelectHandle, indicate that the row text is selectable, otherwise indicate that the row itself is selectable
158
+ whichCursor = shouldUseTextCursor ? 'cursor-text' : 'cursor-pointer';
156
159
  let colClassName = clsx(
157
160
  'GridRow-column',
158
161
  'p-1',
@@ -160,7 +163,7 @@ const GridRow = forwardRef((props, ref) => {
160
163
  'border-r-black-100',
161
164
  'block',
162
165
  areCellsScrollable ? 'overflow-auto' : 'overflow-hidden',
163
- isDragFromHandleOnly ? null : 'select-none',
166
+ canSelectTextOnRow ? null : 'select-none',
164
167
  whichCursor,
165
168
  styles.GRID_ROW_MAX_HEIGHT_EXTRA,
166
169
  );
@@ -465,7 +468,7 @@ const GridRow = forwardRef((props, ref) => {
465
468
  let rowClassName = clsx(
466
469
  'GridRow-HStackNative',
467
470
  'items-center',
468
- isDragFromHandleOnly ? null : 'select-none',
471
+ canSelectTextOnRow ? null : 'select-none',
469
472
  );
470
473
  if (isOnlyOneVisibleColumn) {
471
474
  rowClassName += ' w-full';
@@ -514,6 +517,7 @@ const GridRow = forwardRef((props, ref) => {
514
517
  dragPreviewRef,
515
518
  dropTargetRef,
516
519
  showRowHandle,
520
+ isRowTextSelectable,
517
521
  rowCanSelect,
518
522
  rowCanDrag,
519
523
  isDragFromHandleOnly,
@@ -39,7 +39,7 @@ const presetButtons = [
39
39
  DOWNLOAD,
40
40
  ];
41
41
 
42
- export default function withPresetButtons(WrappedComponent, isGrid = false) {
42
+ export default function withPresetButtons(WrappedComponent) {
43
43
  return forwardRef((props, ref) => {
44
44
 
45
45
  if (props.disablePresetButtons || props.alreadyHasWithPresetButtons) {
@@ -64,7 +64,7 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
64
64
  {
65
65
  // extract and pass down
66
66
  isEditor = false,
67
- isTree = false,
67
+ isTree = false, // from withWindowedEditor or withSideEditor in Tree
68
68
  canDeleteRootNode = false,
69
69
  isSideEditor = false,
70
70
  canEditorViewOnly = false,
@@ -74,10 +74,10 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
74
74
  disableAdd = !isEditor,
75
75
  disableEdit = !isEditor,
76
76
  disableDelete = !isEditor,
77
- disableView = !isGrid,
78
- disableCopy = !isGrid,
77
+ disableView = isTree,
78
+ disableCopy = isTree,
79
79
  disableDuplicate = !isEditor,
80
- disablePrint = !isGrid,
80
+ disablePrint = isTree,
81
81
  protectedValues, // records with these values cannot be edited or deleted
82
82
  addDisplayMsg,
83
83
  editDisplayMsg,
@@ -434,6 +434,18 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
434
434
  }
435
435
  },
436
436
  onUploadDownload = () => {
437
+ const onUploadDecorator = async () => {
438
+ if (onUpload) {
439
+ await onUpload();
440
+ }
441
+ if (Repository && !Repository.isDestroyed) {
442
+ if (Repository.loadRootNodes) {
443
+ await Repository.loadRootNodes(1);
444
+ } else {
445
+ await Repository.reload();
446
+ }
447
+ }
448
+ };
437
449
  showModal({
438
450
  body: <UploadsDownloadsWindow
439
451
  reference="uploadsDownloads"
@@ -442,7 +454,7 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
442
454
  columnsConfig={props.columnsConfig}
443
455
  uploadHeaders={uploadHeaders}
444
456
  uploadParams={uploadParams}
445
- onUpload={onUpload}
457
+ onUpload={onUploadDecorator}
446
458
  downloadHeaders={downloadHeaders}
447
459
  downloadParams={downloadParams}
448
460
  />,
@@ -0,0 +1,10 @@
1
+ import { createIcon } from "../Gluestack/icon";
2
+ import Svg, { Path } from 'react-native-svg';
3
+
4
+ const SvgComponent = createIcon({
5
+ Root: Svg,
6
+ viewBox: '0 0 512 512',
7
+ path: <Path d="M406.6 374.6l96-96c12.5-12.5 12.5-32.8 0-45.3l-96-96c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l41.4 41.4H109.2l41.4-41.4c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-96 96c-12.5 12.5-12.5 32.8 0 45.3l96 96c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 288h293.5l-41.4 41.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0z" />,
8
+ });
9
+
10
+ export default SvgComponent
@@ -0,0 +1,10 @@
1
+ import { createIcon } from "../Gluestack/icon";
2
+ import Svg, { Path } from 'react-native-svg';
3
+
4
+ const SvgComponent = createIcon({
5
+ Root: Svg,
6
+ viewBox: '0 0 228.6 32.7',
7
+ path: <Path d="M212.2 32.7c9 0 16.3-7.3 16.3-16.3S221.2.1 212.2.1H16.3C7.3 0 0 7.3 0 16.3s7.3 16.3 16.3 16.3h195.9z" />,
8
+ });
9
+
10
+ export default SvgComponent
@@ -0,0 +1,11 @@
1
+ // Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc.
2
+ import { createIcon } from "../Gluestack/icon";
3
+ import Svg, { Path } from 'react-native-svg';
4
+
5
+ const SvgComponent = createIcon({
6
+ Root: Svg,
7
+ viewBox: '0 0 448 512',
8
+ path: <Path d="M50.7 58.5L0 160h208V32H93.7c-18.2 0-34.8 10.3-43 26.5zM240 160h208L397.3 58.5c-8.2-16.2-24.8-26.5-43-26.5H240v128zm208 32H0v224c0 35.3 28.7 64 64 64h320c35.3 0 64-28.7 64-64V192z" />,
9
+ });
10
+
11
+ export default SvgComponent
@@ -0,0 +1,11 @@
1
+ // Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc.
2
+ import { createIcon } from "../Gluestack/icon";
3
+ import Svg, { Path } from 'react-native-svg';
4
+
5
+ const SvgComponent = createIcon({
6
+ Root: Svg,
7
+ viewBox: '0 0 640 512',
8
+ path: <Path d="M58.9 42.1c3-6.1 9.6-9.6 16.3-8.7L320 64l244.8-30.6c6.7-.8 13.3 2.7 16.3 8.7l41.7 83.4c9 17.9-.6 39.6-19.8 45.1l-163.4 46.7c-13.9 4-28.8-1.9-36.2-14.3L320 64l-83.4 139c-7.4 12.4-22.3 18.3-36.2 14.3L37.1 170.6c-19.3-5.5-28.8-27.2-19.8-45.1l41.6-83.4zM321.1 128l54.9 91.4c14.9 24.8 44.6 36.6 72.5 28.6L576 211.6v167c0 22-15 41.2-36.4 46.6l-204.1 51c-10.2 2.6-20.9 2.6-31 0l-204.1-51C79 419.7 64 400.5 64 378.5v-167L191.6 248c27.8 8 57.6-3.8 72.5-28.6l54.8-91.4h2.2z" />,
9
+ });
10
+
11
+ export default SvgComponent
@@ -0,0 +1,10 @@
1
+ import { createIcon } from "../Gluestack/icon";
2
+ import Svg, { Path } from 'react-native-svg';
3
+
4
+ const SvgComponent = createIcon({
5
+ Root: Svg,
6
+ viewBox: '0 0 448 512',
7
+ path: <Path d="M96 152v8H48v-8C48 68.1 116.1 0 200 0h48c83.9 0 152 68.1 152 152v8h-48v-8c0-57.4-46.6-104-104-104h-48C142.6 48 96 94.6 96 152zM0 224c0-17.7 14.3-32 32-32h384c17.7 0 32 14.3 32 32s-14.3 32-32 32h-5.1l-22.4 213c-2.6 24.4-23.2 43-47.7 43H107.2c-24.6 0-45.2-18.5-47.7-43L37.1 256H32c-17.7 0-32-14.3-32-32z" />,
8
+ });
9
+
10
+ export default SvgComponent