@onehat/ui 0.3.377 → 0.3.380

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.3.377",
3
+ "version": "0.3.380",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -50,6 +50,7 @@ import Input from '../Form/Field/Input.js';
50
50
  import Xmark from '../Icons/Xmark.js';
51
51
  import Dot from '../Icons/Dot.js';
52
52
  import Collapse from '../Icons/Collapse.js';
53
+ import Expand from '../Icons/Expand.js';
53
54
  import FolderClosed from '../Icons/FolderClosed.js';
54
55
  import FolderOpen from '../Icons/FolderOpen.js';
55
56
  import MagnifyingGlass from '../Icons/MagnifyingGlass.js';
@@ -334,36 +335,51 @@ function TreeComponent(props) {
334
335
  buildRowToDatumMap();
335
336
  }
336
337
  },
337
- onToggle = (datum) => {
338
+ onToggle = async (datum, e) => {
338
339
  if (datum.isLoading) {
339
340
  return;
340
341
  }
341
342
 
342
- datum.isExpanded = !datum.isExpanded;
343
-
344
- if (datum.isExpanded && datum.item.repository?.isRemote && datum.item.hasChildren && !datum.item.areChildrenLoaded) {
345
- loadChildren(datum, 1);
346
- return;
347
- }
348
-
349
- if (!datum.isExpanded && datumContainsSelection(datum)) {
350
- deselectAll();
343
+ const
344
+ isExpanded = !datum.isExpanded, // sets new state
345
+ isShiftKey = e.shiftKey; // hold down the shift key to load all children
346
+
347
+ datum.isExpanded = isExpanded;
348
+
349
+ if (isExpanded) {
350
+ // opening
351
+ if (datum.item.repository?.isRemote && datum.item.hasChildren) {
352
+ if (isShiftKey) {
353
+ // load ALL children
354
+ await loadChildren(datum, 'all');
355
+ return;
356
+ } else if (!datum.item.areChildrenLoaded) {
357
+ // load only one level
358
+ await loadChildren(datum, 1);
359
+ return;
360
+ }
361
+ }
362
+ } else {
363
+ // closing
364
+ if (datumContainsSelection(datum)) {
365
+ deselectAll();
366
+ }
351
367
  }
352
368
 
353
369
  forceUpdate();
354
-
355
370
  buildRowToDatumMap();
356
371
  },
357
- onCollapseAll = (setNewTreeNodeData = true) => {
358
- // Go through whole tree and collapse all nodes
372
+ onCollapseAll = () => {
359
373
  const newTreeNodeData = _.clone(getTreeNodeData());
360
374
  collapseNodes(newTreeNodeData);
361
-
362
- if (setNewTreeNodeData) {
375
+ setTreeNodeData(newTreeNodeData);
376
+ },
377
+ onExpandAll = () => {
378
+ confirm('Are you sure you want to expand the whole tree? This may take a while.', async () => {
379
+ const newTreeNodeData = _.clone(getTreeNodeData());
380
+ await expandNodes(newTreeNodeData);
363
381
  setTreeNodeData(newTreeNodeData);
364
- }
365
- buildRowToDatumMap();
366
- return newTreeNodeData;
382
+ });
367
383
  },
368
384
  onSearchTree = async (q) => {
369
385
  let found = [];
@@ -433,20 +449,20 @@ function TreeComponent(props) {
433
449
  });
434
450
  return found;
435
451
  },
436
- buildTreeNodeDatum = (treeNode) => {
452
+ buildTreeNodeDatum = (treeNode, defaultToExpanded = false) => {
437
453
  // Build the data-representation of one node and its children,
438
454
  // caching text & icon, keeping track of the state for whole tree
439
455
  // renderTreeNode uses this to render the nodes.
440
456
  const
441
457
  isRoot = treeNode.isRoot,
442
- children = buildTreeNodeData(treeNode.children), // recursively get data for children
458
+ children = buildTreeNodeData(treeNode.children, defaultToExpanded), // recursively get data for children
443
459
  datum = {
444
460
  item: treeNode,
445
461
  text: getNodeText(treeNode),
446
462
  iconCollapsed: getNodeIcon(COLLAPSED, treeNode),
447
463
  iconExpanded: getNodeIcon(EXPANDED, treeNode),
448
464
  iconLeaf: getNodeIcon(LEAF, treeNode),
449
- isExpanded: isRoot, // all non-root treeNodes are collapsed by default
465
+ isExpanded: defaultToExpanded || isRoot, // all non-root treeNodes are collapsed by default
450
466
  isVisible: isRoot ? areRootsVisible : true,
451
467
  isLoading: false,
452
468
  children,
@@ -454,10 +470,10 @@ function TreeComponent(props) {
454
470
 
455
471
  return datum;
456
472
  },
457
- buildTreeNodeData = (treeNodes) => {
473
+ buildTreeNodeData = (treeNodes, defaultToExpanded = false) => {
458
474
  const data = [];
459
475
  _.each(treeNodes, (item) => {
460
- data.push(buildTreeNodeDatum(item));
476
+ data.push(buildTreeNodeDatum(item, defaultToExpanded));
461
477
  });
462
478
  return data;
463
479
  },
@@ -655,8 +671,19 @@ function TreeComponent(props) {
655
671
 
656
672
  try {
657
673
 
658
- const children = await datum.item.loadChildren(depth);
659
- datum.children = buildTreeNodeData(children);
674
+ let defaultToExpanded = false;
675
+ if (depth === 'all') {
676
+ defaultToExpanded = true;
677
+ depth = 9999;
678
+ }
679
+
680
+ const
681
+ depthChildren = await datum.item.loadChildren(depth),
682
+ directChildren = _.filter(depthChildren, (child) => { // narrow list to only direct descendants, so buildTreeNodeData can work correctly
683
+ return child.depth === datum.item.depth + 1;
684
+ });
685
+
686
+ datum.children = buildTreeNodeData(directChildren, defaultToExpanded);
660
687
  datum.isExpanded = true;
661
688
 
662
689
  } catch (err) {
@@ -685,6 +712,25 @@ function TreeComponent(props) {
685
712
  }
686
713
  });
687
714
  },
715
+ expandNodes = async (nodes) => {
716
+
717
+ // load all children of nodes
718
+ for (const node of nodes) {
719
+ await loadChildren(node, 'all');
720
+ }
721
+
722
+ // expand them in UI
723
+ expandNodesRecursive(nodes);
724
+ buildRowToDatumMap();
725
+ },
726
+ expandNodesRecursive = (nodes) => {
727
+ _.each(nodes, (node) => {
728
+ node.isExpanded = true;
729
+ if (!_.isEmpty(node.children)) {
730
+ expandNodesRecursive(node.children);
731
+ }
732
+ });
733
+ },
688
734
  expandPath = async (cPath, highlight = true) => {
689
735
  // First, close the whole tree.
690
736
  let newTreeNodeData = _.clone(getTreeNodeData());
@@ -789,12 +835,19 @@ function TreeComponent(props) {
789
835
  isDisabled: !treeSearchValue.length,
790
836
  },
791
837
  {
792
- key: 'collapseBtn',
838
+ key: 'collapseAllBtn',
793
839
  text: 'Collapse whole tree',
794
840
  handler: onCollapseAll,
795
841
  icon: Collapse,
796
842
  isDisabled: false,
797
843
  },
844
+ {
845
+ key: 'expandAllBtn',
846
+ text: 'Expand whole tree',
847
+ handler: onExpandAll,
848
+ icon: Expand,
849
+ isDisabled: false,
850
+ },
798
851
  ];
799
852
  if (canNodesReorder) {
800
853
  buttons.push({
@@ -56,8 +56,13 @@ export default function TreeNode(props) {
56
56
 
57
57
  {isLoading ?
58
58
  <Spinner px={2} /> :
59
- (hasChildren && !isDragMode ? <IconButton icon={icon} onPress={() => onToggle(datum)} {...testProps('expandBtn')} /> : <Icon as={icon} px={2} />)}
60
-
59
+ (hasChildren && !isDragMode ?
60
+ <IconButton
61
+ icon={icon}
62
+ onPress={(e) => onToggle(datum, e)}
63
+ {...testProps('expandBtn')}
64
+ /> : <Icon as={icon} px={2} />)}
65
+
61
66
  <Text
62
67
  overflow="hidden"
63
68
  textOverflow="ellipsis"
@@ -31,7 +31,7 @@ export default function getIconButtonFromConfig(config, ix, parent) {
31
31
  key={key || ix}
32
32
  parent={parent}
33
33
  reference={key || ix}
34
- onPress={() => handler(parent)}
34
+ onPress={(e) => handler(parent, e)}
35
35
  icon={icon}
36
36
  _icon={_icon}
37
37
  isDisabled={isDisabled}
@@ -9,7 +9,7 @@ import UiGlobals from '../UiGlobals.js';
9
9
  export default function getReport(args) {
10
10
  const {
11
11
  reportId,
12
- data,
12
+ data = {},
13
13
  reportType = REPORT_TYPES__EXCEL,
14
14
  showReportHeaders = true,
15
15
  } = args;