@onehat/ui 0.2.74 → 0.2.76

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.
@@ -52,18 +52,6 @@ import Toolbar from '../Toolbar/Toolbar.js';
52
52
  import _ from 'lodash';
53
53
 
54
54
 
55
- //////////////////////
56
- //////////////////////
57
-
58
- // Need to take into account whether using Repository or data.
59
- // If using data, everything exists at once. What format will data be in?
60
- // How does this interface with Repository?
61
- // Maybe if Repository is not AjaxRepository, everything needs to be present at once!
62
-
63
- //////////////////////
64
- //////////////////////
65
-
66
-
67
55
  function TreeComponent(props) {
68
56
  const {
69
57
  areRootsVisible = true,
@@ -103,7 +91,7 @@ function TreeComponent(props) {
103
91
  additionalToolbarButtons = [],
104
92
  reload = null, // Whenever this value changes after initial render, the tree will reload from scratch
105
93
  parentIdIx,
106
-
94
+
107
95
  // withEditor
108
96
  onAdd,
109
97
  onEdit,
@@ -112,6 +100,7 @@ function TreeComponent(props) {
112
100
  onDuplicate,
113
101
  onReset,
114
102
  onContextMenu,
103
+ setWithEditListeners,
115
104
 
116
105
  // withData
117
106
  Repository,
@@ -141,16 +130,27 @@ function TreeComponent(props) {
141
130
  styles = UiGlobals.styles,
142
131
  forceUpdate = useForceUpdate(),
143
132
  treeRef = useRef(),
133
+ treeNodeData = useRef(),
144
134
  [isReady, setIsReady] = useState(false),
145
135
  [isLoading, setIsLoading] = useState(false),
146
136
  [isReorderMode, setIsReorderMode] = useState(false),
147
137
  [isSearchModalShown, setIsSearchModalShown] = useState(false),
148
- [treeNodeData, setTreeNodeData] = useState({}),
149
138
  [searchResults, setSearchResults] = useState([]),
150
139
  [searchFormData, setSearchFormData] = useState([]),
151
140
  [dragNodeSlot, setDragNodeSlot] = useState(null),
152
141
  [dragNodeIx, setDragNodeIx] = useState(),
153
142
  [treeSearchValue, setTreeSearchValue] = useState(''),
143
+
144
+ // state getters & setters
145
+ getTreeNodeData = () => {
146
+ return treeNodeData.current;
147
+ },
148
+ setTreeNodeData = (tnd) => {
149
+ treeNodeData.current = tnd;
150
+ forceUpdate();
151
+ },
152
+
153
+ // event handers
154
154
  onNodeClick = (item, e) => {
155
155
  if (!setSelection) {
156
156
  return;
@@ -213,65 +213,137 @@ function TreeComponent(props) {
213
213
  }
214
214
  }
215
215
  },
216
- onRefresh = () => {
217
- if (!Repository) {
218
- return;
216
+ onBeforeAdd = async () => {
217
+ // Load children before adding the new node
218
+ const
219
+ parent = selection[0],
220
+ parentDatum = getNodeData(parent.id);
221
+
222
+ if (parent.hasChildren && !parent.areChildrenLoaded) {
223
+ await loadChildren(parentDatum);
219
224
  }
220
- const promise = Repository.reload();
221
- if (promise) { // Some repository types don't use promises
222
- promise.then(() => {
223
- setIsLoading(false);
224
- forceUpdate();
225
- });
225
+ },
226
+ onAfterAdd = async (entity) => {
227
+ // Expand the parent before showing the new node
228
+ const
229
+ parent = entity.parent,
230
+ parentDatum = getNodeData(parent.id);
231
+
232
+ if (!parentDatum.isExpanded) {
233
+ parentDatum.isExpanded = true;
226
234
  }
235
+
236
+ // Add the entity to the tree
237
+ const entityDatum = buildTreeNodeDatum(entity);
238
+ parentDatum.children.unshift(entityDatum);
239
+ forceUpdate();
227
240
  },
228
- getHeaderToolbarItems = () => {
241
+ onBeforeEditSave = (entities) => {
242
+ onBeforeSave(entities);
243
+ },
244
+ onAfterEdit = async (entities) => {
245
+ // Refresh the node's display
229
246
  const
230
- buttons = [
231
- {
232
- key: 'searchBtn',
233
- text: 'Search tree',
234
- handler: onSearchTree,
235
- icon: MagnifyingGlass,
236
- isDisabled: !treeSearchValue.length,
237
- },
238
- {
239
- key: 'collapseBtn',
240
- text: 'Collapse whole tree',
241
- handler: onCollapseAll,
242
- icon: Collapse,
243
- isDisabled: false,
244
- },
245
- ];
246
- if (canNodesReorder) {
247
- buttons.push({
248
- key: 'reorderBtn',
249
- text: 'Enter reorder mode',
250
- handler: () => setIsReorderMode(!isReorderMode),
251
- icon: isReorderMode ? NoReorderRows : ReorderRows,
252
- isDisabled: false,
253
- });
247
+ node = entities[0],
248
+ existingDatum = getNodeData(node.id), // TODO: Make this work for >1 entity
249
+ newDatum = buildTreeNodeDatum(node);
250
+
251
+ // copy the updated data to existingDatum
252
+ _.merge(existingDatum, newDatum);
253
+ existingDatum.isLoading = false;
254
+ forceUpdate();
255
+ },
256
+ onBeforeDeleteSave = (entities) => {
257
+ onBeforeSave(entities);
258
+ },
259
+ onBeforeSave = (entities) => {
260
+ const
261
+ node = entities[0],
262
+ datum = getNodeData(node.id); // TODO: Make this work for >1 entity
263
+
264
+ datum.isLoading = true;
265
+ forceUpdate();
266
+ },
267
+ onAfterDelete = async (entities) => {
268
+ // TODO: Refresh the parent node
269
+
270
+ debugger;
271
+ },
272
+ onToggle = (datum) => {
273
+ if (datum.isLoading) {
274
+ return;
254
275
  }
255
- const items = _.map(buttons, getIconButtonFromConfig);
256
276
 
257
- items.unshift(<Input // Add text input to beginning of header items
258
- key="searchNodes"
259
- flex={1}
260
- placeholder="Find tree node"
261
- onChangeText={(val) => setTreeSearchValue(val)}
262
- onKeyPress={(e, value) => {
263
- if (e.key === 'Enter') {
264
- onSearchTree(value);
265
- }
266
- }}
267
- value={treeSearchValue}
268
- autoSubmit={false}
269
- />);
277
+ datum.isExpanded = !datum.isExpanded;
270
278
 
271
- return items;
279
+ if (datum.isExpanded && datum.item.repository?.isRemote && datum.item.hasChildren && !datum.item.areChildrenLoaded) {
280
+ loadChildren(datum, 1);
281
+ return;
282
+ }
283
+
284
+ if (!datum.isExpanded && datumContainsSelection(datum)) {
285
+ deselectAll();
286
+ }
287
+
288
+ forceUpdate();
272
289
  },
273
- getFooterToolbarItems = () => {
274
- return _.map(additionalToolbarButtons, getIconButtonFromConfig);
290
+ onCollapseAll = (setNewTreeNodeData = true) => {
291
+ // Go through whole tree and collapse all nodes
292
+ const newTreeNodeData = _.clone(getTreeNodeData());
293
+ collapseNodes(newTreeNodeData);
294
+
295
+ if (setNewTreeNodeData) {
296
+ setTreeNodeData(newTreeNodeData);
297
+ }
298
+ return newTreeNodeData;
299
+ },
300
+ onSearchTree = async (value) => {
301
+ let found = [];
302
+ if (Repository?.isRemote) {
303
+ // Search tree on server
304
+ found = await Repository.searchNodes(value);
305
+ } else {
306
+ // Search local tree data
307
+ found = findTreeNodesByText(value);
308
+ }
309
+
310
+ const isMultipleHits = found.length > 1;
311
+ if (!isMultipleHits) {
312
+ expandPath(found[0].path);
313
+ return;
314
+ }
315
+
316
+ const searchFormData = [];
317
+ _.each(found, (item) => {
318
+ searchFormData.push([item.id, getNodeText(item)]);
319
+ });
320
+ setSearchFormData(searchFormData);
321
+ setSearchResults(found);
322
+ setIsSearchModalShown(true);
323
+ },
324
+
325
+ // utilities
326
+ getNodeData = (itemId) => {
327
+ function findNodeById(node, id) {
328
+ if (node.item.id === id) {
329
+ return node;
330
+ }
331
+ if (!_.isEmpty(node.children)) {
332
+ return _.find(node.children, (node2) => {
333
+ return findNodeById(node2, id);
334
+ })
335
+ }
336
+ return false;
337
+ }
338
+ let found = null;
339
+ _.each(getTreeNodeData(), (node) => {
340
+ const foundNode = findNodeById(node, itemId);
341
+ if (foundNode) {
342
+ found = foundNode;
343
+ return false;
344
+ }
345
+ });
346
+ return found;
275
347
  },
276
348
  buildTreeNodeDatum = (treeNode) => {
277
349
  // Build the data-representation of one node and its children,
@@ -286,7 +358,7 @@ function TreeComponent(props) {
286
358
  iconCollapsed: getNodeIcon(treeNode, false),
287
359
  iconExpanded: getNodeIcon(treeNode, true),
288
360
  iconLeaf: getNodeIcon(treeNode),
289
- isExpanded: isRoot, // all non-root treeNodes are not expanded by default
361
+ isExpanded: isRoot, // all non-root treeNodes are collapsed by default
290
362
  isVisible: isRoot ? areRootsVisible : true,
291
363
  isLoading: false,
292
364
  children,
@@ -301,131 +373,56 @@ function TreeComponent(props) {
301
373
  });
302
374
  return data;
303
375
  },
304
- renderTreeNode = (datum) => {
305
- const
306
- item = datum.item,
307
- depth = item.depth;
308
- if (item.isDestroyed) {
309
- return null;
310
- }
311
- if (!datum.isVisible) {
312
- return null;
376
+ buildAndSetTreeNodeData = async () => {
377
+ let nodes = [];
378
+ if (Repository) {
379
+ if (!Repository.areRootNodesLoaded) {
380
+ nodes = await Repository.loadRootNodes(1);
381
+ } else {
382
+ nodes = Repository.getRootNodes();
383
+ }
384
+ } else {
385
+ nodes = assembleDataTreeNodes();
313
386
  }
314
387
 
315
- let nodeProps = getNodeProps ? getNodeProps(item) : {},
316
- isSelected = isInSelection(item);
388
+ setTreeNodeData(buildTreeNodeData(nodes));
389
+ },
390
+ datumContainsSelection = (datum) => {
391
+ if (_.isEmpty(selection)) {
392
+ return false;
393
+ }
394
+ const
395
+ selectionIds = _.map(selection, (item) => item.id),
396
+ datumIds = getDatumChildIds(datum),
397
+ intersection = selectionIds.filter(x => datumIds.includes(x));
317
398
 
318
- return <Pressable
319
- // {...testProps(Repository ? Repository.schema.name + '-' + item.id : item.id)}
320
- key={item.hash}
321
- onPress={(e) => {
322
- if (e.preventDefault && e.cancelable) {
323
- e.preventDefault();
324
- }
325
- if (isReorderMode) {
326
- return
327
- }
328
- switch (e.detail) {
329
- case 1: // single click
330
- onNodeClick(item, e); // sets selection
331
- break;
332
- case 2: // double click
333
- if (!isSelected) { // If a row was already selected when double-clicked, the first click will deselect it,
334
- onNodeClick(item, e); // so reselect it
335
- }
336
- if (onEdit) {
337
- onEdit();
338
- }
339
- break;
340
- case 3: // triple click
341
- break;
342
- default:
343
- }
344
- }}
345
- onLongPress={(e) => {
346
- if (e.preventDefault && e.cancelable) {
347
- e.preventDefault();
348
- }
349
- if (isReorderMode) {
350
- return;
351
- }
399
+ return !_.isEmpty(intersection);
400
+ },
401
+ findTreeNodesByText = (text) => {
402
+ // Helper for onSearchTree
403
+ // Searches whole treeNodeData for any matching items
404
+ // Returns multiple nodes
352
405
 
353
- if (!setSelection) {
354
- return;
355
- }
356
-
357
- // context menu
358
- const selection = [item];
359
- setSelection(selection);
360
- if (onContextMenu) {
361
- onContextMenu(item, e, selection, setSelection);
362
- }
363
- }}
364
- flexDirection="row"
365
- ml={((areRootsVisible ? depth : depth -1) * 20) + 'px'}
366
- >
367
- {({
368
- isHovered,
369
- isFocused,
370
- isPressed,
371
- }) => {
372
- let bg = nodeProps.bg || styles.TREE_NODE_BG,
373
- mixWith;
374
- if (isSelected) {
375
- if (showHovers && isHovered) {
376
- mixWith = styles.TREE_NODE_SELECTED_HOVER_BG;
377
- } else {
378
- mixWith = styles.TREE_NODE_SELECTED_BG;
379
- }
380
- } else if (showHovers && isHovered) {
381
- mixWith = styles.TREE_NODE_HOVER_BG;
382
- }
383
- if (mixWith) {
384
- const
385
- mixWithObj = nbToRgb(mixWith),
386
- ratio = mixWithObj.alpha ? 1 - mixWithObj.alpha : 0.5;
387
- bg = colourMixer.blend(bg, ratio, mixWithObj.color);
388
- }
389
- let WhichTreeNode = TreeNode,
390
- rowReorderProps = {};
391
- if (canNodesReorder && isReorderMode) {
392
- WhichTreeNode = ReorderableTreeNode;
393
- rowReorderProps = {
394
- mode: VERTICAL,
395
- onDragStart: onNodeReorderDragStart,
396
- onDrag: onNodeReorderDrag,
397
- onDragStop: onNodeReorderDragStop,
398
- proxyParent: treeRef.current?.getScrollableNode().children[0],
399
- proxyPositionRelativeToParent: true,
400
- getParentNode: (node) => node.parentElement.parentElement.parentElement,
401
- getProxy: getReorderProxy,
402
- };
403
- }
404
-
405
- return <WhichTreeNode
406
- nodeProps={nodeProps}
407
- bg={bg}
408
- datum={datum}
409
- onToggle={onToggle}
406
+ const regex = new RegExp(text, 'i'); // instead of matching based on full text match, search for a partial match
410
407
 
411
- // fields={fields}
412
- {...rowReorderProps}
413
- />;
414
- }}
415
- </Pressable>;
408
+ function searchChildren(children, found = []) {
409
+ _.each(children, (child) => {
410
+ if (child.text.match(regex)) {
411
+ found.push(child);
412
+ }
413
+ if (child.children) {
414
+ searchChildren(child.children, found);
415
+ }
416
+ });
417
+ return found;
418
+ }
419
+ return searchChildren(treeNodeData);
416
420
  },
417
- renderTreeNodes = (data) => {
418
- let nodes = [];
419
- _.each(data, (datum) => {
420
- const node = renderTreeNode(datum);
421
- nodes.push(node);
422
-
423
- if (datum.children.length && datum.isExpanded) {
424
- const childTreeNodes = renderTreeNodes(datum.children); // recursion
425
- nodes = nodes.concat(childTreeNodes);
426
- }
427
- });
428
- return nodes;
421
+ getTreeNodeByNodeId = (node_id) => {
422
+ if (Repository) {
423
+ return Repository.getById(node_id);
424
+ }
425
+ return data[node_id]; // TODO: This is probably not right!
429
426
  },
430
427
  getDatumChildIds = (datum) => {
431
428
  let ids = [];
@@ -439,30 +436,6 @@ function TreeComponent(props) {
439
436
  });
440
437
  return ids;
441
438
  },
442
- datumContainsSelection = (datum) => {
443
- if (_.isEmpty(selection)) {
444
- return false;
445
- }
446
- const
447
- selectionIds = _.map(selection, (item) => item.id),
448
- datumIds = getDatumChildIds(datum),
449
- intersection = selectionIds.filter(x => datumIds.includes(x));
450
-
451
- return !_.isEmpty(intersection);
452
- },
453
- buildAndSetTreeNodeData = async () => {
454
- let rootNodes;
455
- if (Repository) {
456
- if (!Repository.areRootNodesLoaded) {
457
- rootNodes = await Repository.getRootNodes(1);
458
- }
459
- } else {
460
- rootNodes = assembleDataTreeNodes();
461
- }
462
-
463
- const treeNodeData = buildTreeNodeData(rootNodes);
464
- setTreeNodeData(treeNodeData);
465
- },
466
439
  assembleDataTreeNodes = () => {
467
440
  // Populates the TreeNodes with .parent and .children references
468
441
  // NOTE: This is only for 'data', not for Repositories!
@@ -502,47 +475,27 @@ function TreeComponent(props) {
502
475
  treeNode.depth = i;
503
476
  treeNode.hash = treeNode[idIx];
504
477
 
505
- if (treeNode.isRoot) {
506
- treeNodes.push(treeNode);
507
- }
508
- });
509
-
510
- return treeNodes;
511
- },
512
- reloadTree = () => {
513
- Repository.areRootNodesLoaded = false;
514
- return buildAndSetTreeNodeData();
515
- };
516
-
517
- // Button handlers
518
- onToggle = (datum) => {
519
- if (datum.isLoading) {
520
- return;
521
- }
522
-
523
- datum.isExpanded = !datum.isExpanded;
524
-
525
- if (datum.isExpanded && datum.item.repository?.isRemote && datum.item.hasChildren && !datum.item.areChildrenLoaded) {
526
- loadChildren(datum, 1);
527
- return;
528
- }
478
+ if (treeNode.isRoot) {
479
+ treeNodes.push(treeNode);
480
+ }
481
+ });
529
482
 
530
- if (!datum.isExpanded && datumContainsSelection(datum)) {
531
- deselectAll();
532
- }
533
-
534
- forceUpdate();
483
+ return treeNodes;
484
+ },
485
+ reloadTree = () => {
486
+ Repository.areRootNodesLoaded = false;
487
+ return buildAndSetTreeNodeData();
535
488
  },
536
- loadChildren = async (datum, depth) => {
489
+ loadChildren = async (datum, depth = 1) => {
537
490
  // Show loading indicator (spinner underneath current node?)
538
491
  datum.isLoading = true;
539
492
  forceUpdate();
540
493
 
541
494
  try {
542
495
 
543
- const children = await datum.item.loadChildren(1);
544
- const tnd = buildTreeNodeData(children);
545
- datum.children = tnd;
496
+ const children = await datum.item.loadChildren(depth);
497
+ datum.children = buildTreeNodeData(children);
498
+ datum.isExpanded = true;
546
499
 
547
500
  } catch (err) {
548
501
  // TODO: how do I handle errors?
@@ -556,16 +509,6 @@ function TreeComponent(props) {
556
509
  datum.isLoading = false;
557
510
  forceUpdate();
558
511
  },
559
- onCollapseAll = (setNewTreeNodeData = true) => {
560
- // Go through whole tree and collapse all nodes
561
- const newTreeNodeData = _.clone(treeNodeData);
562
- collapseNodes(newTreeNodeData);
563
-
564
- if (setNewTreeNodeData) {
565
- setTreeNodeData(newTreeNodeData);
566
- }
567
- return newTreeNodeData;
568
- },
569
512
  collapseNodes = (nodes) => {
570
513
  _.each(nodes, (node) => {
571
514
  node.isExpanded = false;
@@ -574,61 +517,9 @@ function TreeComponent(props) {
574
517
  }
575
518
  });
576
519
  },
577
- onSearchTree = async (value) => {
578
- let found = [];
579
- if (Repository?.isRemote) {
580
- // Search tree on server
581
- found = await Repository.searchNodes(value);
582
- } else {
583
- // Search local tree data
584
- found = findTreeNodesByText(value);
585
- }
586
-
587
- const isMultipleHits = found.length > 1;
588
- if (!isMultipleHits) {
589
- expandPath(found[0].path);
590
- return;
591
- }
592
-
593
- const searchFormData = [];
594
- _.each(found, (item) => {
595
- searchFormData.push([item.id, getNodeText(item)]);
596
- });
597
- setSearchFormData(searchFormData);
598
- setSearchResults(found);
599
- setIsSearchModalShown(true);
600
- },
601
- findTreeNodesByText = (text) => {
602
- // Helper for onSearchTree
603
- // Searches whole treeNodeData for any matching items
604
- // Returns multiple nodes
605
-
606
- const regex = new RegExp(text, 'i'); // instead of matching based on full text match, search for a partial match
607
-
608
- function searchChildren(children, found = []) {
609
- _.each(children, (child) => {
610
- if (child.text.match(regex)) {
611
- found.push(child);
612
- }
613
- if (child.children) {
614
- searchChildren(child.children, found);
615
- }
616
- });
617
- return found;
618
- }
619
- return searchChildren(treeNodeData);
620
- },
621
- getTreeNodeByNodeId = (node_id) => {
622
- if (Repository) {
623
- return Repository.getById(node_id);
624
- }
625
- return data[node_id]; // TODO: This is probably not right!
626
- },
627
520
  expandPath = async (path) => {
628
- // Helper for onSearchTree
629
-
630
521
  // First, close thw whole tree.
631
- let newTreeNodeData = _.clone(treeNodeData);
522
+ let newTreeNodeData = _.clone(getTreeNodeData());
632
523
  collapseNodes(newTreeNodeData);
633
524
 
634
525
  // As it navigates down, it will expand the appropriate branches,
@@ -692,7 +583,185 @@ function TreeComponent(props) {
692
583
 
693
584
  },
694
585
 
695
- // Drag/Drop
586
+ // render
587
+ getHeaderToolbarItems = () => {
588
+ const
589
+ buttons = [
590
+ {
591
+ key: 'searchBtn',
592
+ text: 'Search tree',
593
+ handler: onSearchTree,
594
+ icon: MagnifyingGlass,
595
+ isDisabled: !treeSearchValue.length,
596
+ },
597
+ {
598
+ key: 'collapseBtn',
599
+ text: 'Collapse whole tree',
600
+ handler: onCollapseAll,
601
+ icon: Collapse,
602
+ isDisabled: false,
603
+ },
604
+ ];
605
+ if (canNodesReorder) {
606
+ buttons.push({
607
+ key: 'reorderBtn',
608
+ text: 'Enter reorder mode',
609
+ handler: () => setIsReorderMode(!isReorderMode),
610
+ icon: isReorderMode ? NoReorderRows : ReorderRows,
611
+ isDisabled: false,
612
+ });
613
+ }
614
+ const items = _.map(buttons, getIconButtonFromConfig);
615
+
616
+ items.unshift(<Input // Add text input to beginning of header items
617
+ key="searchNodes"
618
+ flex={1}
619
+ placeholder="Find tree node"
620
+ onChangeText={(val) => setTreeSearchValue(val)}
621
+ onKeyPress={(e, value) => {
622
+ if (e.key === 'Enter') {
623
+ onSearchTree(value);
624
+ }
625
+ }}
626
+ value={treeSearchValue}
627
+ autoSubmit={false}
628
+ />);
629
+
630
+ return items;
631
+ },
632
+ getFooterToolbarItems = () => {
633
+ return _.map(additionalToolbarButtons, getIconButtonFromConfig);
634
+ },
635
+ renderTreeNode = (datum) => {
636
+ if (!datum.isVisible) {
637
+ return null;
638
+ }
639
+ const item = datum.item;
640
+ if (item.isDestroyed) {
641
+ return null;
642
+ }
643
+ const depth = item.depth;
644
+
645
+ let nodeProps = getNodeProps ? getNodeProps(item) : {},
646
+ isSelected = isInSelection(item);
647
+
648
+ return <Pressable
649
+ // {...testProps(Repository ? Repository.schema.name + '-' + item.id : item.id)}
650
+ key={item.hash}
651
+ onPress={(e) => {
652
+ if (e.preventDefault && e.cancelable) {
653
+ e.preventDefault();
654
+ }
655
+ if (isReorderMode) {
656
+ return
657
+ }
658
+ switch (e.detail) {
659
+ case 1: // single click
660
+ onNodeClick(item, e); // sets selection
661
+ break;
662
+ case 2: // double click
663
+ if (!isSelected) { // If a row was already selected when double-clicked, the first click will deselect it,
664
+ onNodeClick(item, e); // so reselect it
665
+ }
666
+ if (onEdit) {
667
+ onEdit();
668
+ }
669
+ break;
670
+ case 3: // triple click
671
+ break;
672
+ default:
673
+ }
674
+ }}
675
+ onLongPress={(e) => {
676
+ if (e.preventDefault && e.cancelable) {
677
+ e.preventDefault();
678
+ }
679
+ if (isReorderMode) {
680
+ return;
681
+ }
682
+
683
+ if (!setSelection) {
684
+ return;
685
+ }
686
+
687
+ // context menu
688
+ const selection = [item];
689
+ setSelection(selection);
690
+ if (onContextMenu) {
691
+ onContextMenu(item, e, selection, setSelection);
692
+ }
693
+ }}
694
+ flexDirection="row"
695
+ ml={((areRootsVisible ? depth : depth -1) * 20) + 'px'}
696
+ >
697
+ {({
698
+ isHovered,
699
+ isFocused,
700
+ isPressed,
701
+ }) => {
702
+ let bg = nodeProps.bg || styles.TREE_NODE_BG,
703
+ mixWith;
704
+ if (isSelected) {
705
+ if (showHovers && isHovered) {
706
+ mixWith = styles.TREE_NODE_SELECTED_HOVER_BG;
707
+ } else {
708
+ mixWith = styles.TREE_NODE_SELECTED_BG;
709
+ }
710
+ } else if (showHovers && isHovered) {
711
+ mixWith = styles.TREE_NODE_HOVER_BG;
712
+ }
713
+ if (mixWith) {
714
+ const
715
+ mixWithObj = nbToRgb(mixWith),
716
+ ratio = mixWithObj.alpha ? 1 - mixWithObj.alpha : 0.5;
717
+ bg = colourMixer.blend(bg, ratio, mixWithObj.color);
718
+ }
719
+ let WhichTreeNode = TreeNode,
720
+ rowReorderProps = {};
721
+ if (canNodesReorder && isReorderMode) {
722
+ WhichTreeNode = ReorderableTreeNode;
723
+ rowReorderProps = {
724
+ mode: VERTICAL,
725
+ onDragStart: onNodeReorderDragStart,
726
+ onDrag: onNodeReorderDrag,
727
+ onDragStop: onNodeReorderDragStop,
728
+ proxyParent: treeRef.current?.getScrollableNode().children[0],
729
+ proxyPositionRelativeToParent: true,
730
+ getParentNode: (node) => node.parentElement.parentElement.parentElement,
731
+ getProxy: getReorderProxy,
732
+ };
733
+ }
734
+
735
+ return <WhichTreeNode
736
+ nodeProps={nodeProps}
737
+ bg={bg}
738
+ datum={datum}
739
+ onToggle={onToggle}
740
+
741
+ // fields={fields}
742
+ {...rowReorderProps}
743
+ />;
744
+ }}
745
+ </Pressable>;
746
+ },
747
+ renderTreeNodes = (data) => {
748
+ let nodes = [];
749
+ _.each(data, (datum) => {
750
+ const node = renderTreeNode(datum);
751
+ if (!node) {
752
+ return;
753
+ }
754
+ nodes.push(node);
755
+
756
+ if (datum.children.length && datum.isExpanded) {
757
+ const childTreeNodes = renderTreeNodes(datum.children); // recursion
758
+ nodes = nodes.concat(childTreeNodes);
759
+ }
760
+ });
761
+ return nodes;
762
+ },
763
+
764
+ // drag/drop
696
765
  getReorderProxy = (node) => {
697
766
  const
698
767
  row = node.parentElement.parentElement,
@@ -967,16 +1036,12 @@ function TreeComponent(props) {
967
1036
 
968
1037
  Repository.on('beforeLoad', setTrue);
969
1038
  Repository.on('load', setFalse);
970
- Repository.ons(['changePage', 'changePageSize',], deselectAll);
971
- Repository.ons(['changeData', 'change'], buildAndSetTreeNodeData);
972
1039
  Repository.on('changeFilters', reloadTree);
973
1040
  Repository.on('changeSorters', reloadTree);
974
1041
 
975
1042
  return () => {
976
1043
  Repository.off('beforeLoad', setTrue);
977
1044
  Repository.off('load', setFalse);
978
- Repository.offs(['changePage', 'changePageSize',], deselectAll);
979
- Repository.offs(['changeData', 'change'], buildAndSetTreeNodeData);
980
1045
  Repository.off('changeFilters', reloadTree);
981
1046
  Repository.off('changeSorters', reloadTree);
982
1047
  };
@@ -993,17 +1058,26 @@ function TreeComponent(props) {
993
1058
  }
994
1059
  Repository.filter(selectorId, id, false); // so it doesn't clear existing filters
995
1060
  }
996
-
997
1061
  }, [selectorId, selectorSelected]);
998
1062
 
1063
+ setWithEditListeners({ // Update withEdit's listeners on every render
1064
+ onBeforeAdd,
1065
+ onAfterAdd,
1066
+ onBeforeEditSave,
1067
+ onAfterEdit,
1068
+ onBeforeDeleteSave,
1069
+ onAfterDelete,
1070
+ });
1071
+
999
1072
  const
1000
- headerToolbarItemComponents = useMemo(() => getHeaderToolbarItems(), [treeSearchValue, treeNodeData]),
1001
- footerToolbarItemComponents = useMemo(() => getFooterToolbarItems(), [additionalToolbarButtons, isReorderMode, treeNodeData]);
1073
+ headerToolbarItemComponents = useMemo(() => getHeaderToolbarItems(), [treeSearchValue, getTreeNodeData()]),
1074
+ footerToolbarItemComponents = useMemo(() => getFooterToolbarItems(), [additionalToolbarButtons, isReorderMode, getTreeNodeData()]);
1002
1075
 
1003
1076
  if (!isReady) {
1004
1077
  return null;
1005
1078
  }
1006
- const treeNodes = renderTreeNodes(treeNodeData);
1079
+
1080
+ const treeNodes = renderTreeNodes(getTreeNodeData());
1007
1081
 
1008
1082
  // headers & footers
1009
1083
  let treeFooterComponent = null;
@@ -1029,7 +1103,7 @@ function TreeComponent(props) {
1029
1103
  deselectAll();
1030
1104
  }
1031
1105
  }}>
1032
- {!treeNodes?.length ? <NoRecordsFound text={noneFoundText} onRefresh={onRefresh} /> :
1106
+ {!treeNodes?.length ? <NoRecordsFound text={noneFoundText} onRefresh={reloadTree} /> :
1033
1107
  treeNodes}
1034
1108
  </Column>
1035
1109