@onehat/ui 0.2.69 → 0.2.70

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.2.69",
3
+ "version": "0.2.70",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -0,0 +1,18 @@
1
+ import * as React from "react"
2
+ import Svg, { G, Path } from "react-native-svg"
3
+ import { Icon } from 'native-base';
4
+
5
+ function SvgComponent(props) {
6
+ return (
7
+ <Icon
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ viewBox="0 0 508.3 508.87"
10
+ {...props}
11
+ >
12
+ <Path d="M253.87 387.44c73.46 0 133-59.55 133-133s-59.55-133-133-133-133 59.55-133 133 59.55 133 133 133z" />
13
+ <Path d="M0 0H508.3V508.87H0z" fill="none" />
14
+ </Icon>
15
+ )
16
+ }
17
+
18
+ export default SvgComponent
@@ -19,6 +19,7 @@ import {
19
19
  DROP_POSITION_BEFORE,
20
20
  DROP_POSITION_AFTER,
21
21
  } from '../../Constants/Tree.js';
22
+ import sleep from '@onehat/ui/src/Functions/sleep.js';
22
23
  import * as colourMixer from '@k-renwick/colour-mixer'
23
24
  import UiGlobals from '../../UiGlobals.js';
24
25
  import useForceUpdate from '../../Hooks/useForceUpdate.js';
@@ -38,7 +39,7 @@ import TreeNode, { ReorderableTreeNode } from './TreeNode.js';
38
39
  import FormPanel from '../Panel/FormPanel.js';
39
40
  import Input from '../Form/Field/Input.js';
40
41
  import IconButton from '../Buttons/IconButton.js';
41
- import Circle from '../Icons/Circle.js';
42
+ import Dot from '../Icons/Dot.js';
42
43
  import Collapse from '../Icons/Collapse.js';
43
44
  import FolderClosed from '../Icons/FolderClosed.js';
44
45
  import FolderOpen from '../Icons/FolderOpen.js';
@@ -51,28 +52,9 @@ import Toolbar from '../Toolbar/Toolbar.js';
51
52
  import _ from 'lodash';
52
53
 
53
54
 
54
- // Tree requires the use of HOC withSelection() whenever it's used.
55
- // The default export is *with* the HOC. A separate *raw* component is
56
- // exported which can be combined with many HOCs for various functionality.
57
-
58
-
59
55
  //////////////////////
60
56
  //////////////////////
61
57
 
62
- // I'm thinking if a repository senses that it's a tree, then at initial load
63
- // it should get the root nodes +1 level of children.
64
- //
65
- // How would it then subsequently get the proper children?
66
- // i.e. When a node gets its children, how will it do this
67
- // while maintaining the nodes that already exist there?
68
- // We don't want it to *replace* all exisitng nodes!
69
- //
70
- // And if the repository does a reload, should it just get roots+1 again?
71
- // Changing filters would potentially change the tree structure.
72
- // Changing sorting would only change the ordering, not what is expanded/collapsed or visible/invisible.
73
-
74
-
75
-
76
58
  // Need to take into account whether using Repository or data.
77
59
  // If using data, everything exists at once. What format will data be in?
78
60
  // How does this interface with Repository?
@@ -82,20 +64,15 @@ import _ from 'lodash';
82
64
  //////////////////////
83
65
 
84
66
 
85
-
86
-
87
-
88
-
89
67
  export function Tree(props) {
90
68
  const {
91
69
  areRootsVisible = true,
92
- getAdditionalParams = () => { // URL params needed to get nodes from server (e.g, { venue_id: 1, getEquipment: true, getRentalEquipment: false, }), in addition to filters.
93
- return {};
94
- },
70
+ extraParams = {}, // Additional params to send with each request ( e.g. { order: 'Categories.name ASC' })
95
71
  getNodeText = (item) => { // extracts model/data and decides what the row text should be
96
72
  return item.displayValue;
97
73
  },
98
74
  getNodeIcon = (item, isExpanded) => { // decides what icon to show for this node
75
+ // TODO: Allow for dynamic props on the icon (e.g. special color for some icons)
99
76
  let icon;
100
77
  if (item.hasChildren) {
101
78
  if (isExpanded) {
@@ -104,7 +81,7 @@ export function Tree(props) {
104
81
  icon = FolderClosed;
105
82
  }
106
83
  } else {
107
- icon = Circle;
84
+ icon = Dot;
108
85
  }
109
86
  return icon;
110
87
  },
@@ -169,6 +146,10 @@ export function Tree(props) {
169
146
  [dragNodeIx, setDragNodeIx] = useState(),
170
147
  [treeSearchValue, setTreeSearchValue] = useState(''),
171
148
  onNodeClick = (item, e) => {
149
+ if (!setSelection) {
150
+ return;
151
+ }
152
+
172
153
  const
173
154
  {
174
155
  shiftKey,
@@ -268,10 +249,15 @@ export function Tree(props) {
268
249
  const items = _.map(buttons, getIconFromConfig);
269
250
 
270
251
  items.unshift(<Input // Add text input to beginning of header items
271
- key="searchTree"
252
+ key="searchNodes"
272
253
  flex={1}
273
- placeholder="Search all tree nodes"
254
+ placeholder="Find tree node"
274
255
  onChangeText={(val) => setTreeSearchValue(val)}
256
+ onKeyPress={(e, value) => {
257
+ if (e.key === 'Enter') {
258
+ onSearchTree(value);
259
+ }
260
+ }}
275
261
  value={treeSearchValue}
276
262
  autoSubmit={false}
277
263
  />);
@@ -320,6 +306,7 @@ export function Tree(props) {
320
306
  // renderTreeNode uses this to render the nodes.
321
307
  const
322
308
  isRoot = treeNode.isRoot,
309
+ children = buildTreeNodeData(treeNode.children), // recursively get data for children
323
310
  datum = {
324
311
  item: treeNode,
325
312
  text: getNodeText(treeNode),
@@ -328,7 +315,8 @@ export function Tree(props) {
328
315
  iconLeaf: getNodeIcon(treeNode),
329
316
  isExpanded: isRoot, // all non-root treeNodes are not expanded by default
330
317
  isVisible: isRoot ? areRootsVisible : true,
331
- children: buildTreeNodeData(treeNode.children), // recursively get data for children
318
+ isLoading: false,
319
+ children,
332
320
  };
333
321
 
334
322
  return datum;
@@ -386,7 +374,11 @@ export function Tree(props) {
386
374
  e.preventDefault();
387
375
  }
388
376
  if (isReorderMode) {
389
- return
377
+ return;
378
+ }
379
+
380
+ if (!setSelection) {
381
+ return;
390
382
  }
391
383
 
392
384
  // context menu
@@ -450,78 +442,87 @@ export function Tree(props) {
450
442
  </Pressable>;
451
443
  },
452
444
  renderTreeNodes = (data) => {
453
- const nodes = [];
454
- _.each(data, (datum) => {
455
- nodes.push(renderTreeNode(datum));
456
- });
457
- return nodes;
458
- },
459
- renderAllTreeNodes = () => {
460
445
  let nodes = [];
461
- _.each(treeNodeData, (datum) => {
446
+ _.each(data, (datum) => {
462
447
  const node = renderTreeNode(datum);
463
- if (_.isEmpty(node)) {
464
- return;
465
- }
466
-
467
448
  nodes.push(node);
468
449
 
469
- if (!datum.isExpanded) {
470
- return;
450
+ if (datum.children.length && datum.isExpanded) {
451
+ const childTreeNodes = renderTreeNodes(datum.children); // recursion
452
+ nodes = nodes.concat(childTreeNodes);
471
453
  }
472
-
473
- if (_.isEmpty(datum.children)) {
474
- return;
475
- }
476
-
477
- const children = renderTreeNodes(datum.children);
478
- if (_.isEmpty(children)) {
479
- return;
480
- }
481
-
482
- nodes = nodes.concat(children);
483
454
  });
484
455
  return nodes;
485
456
  },
457
+ getDatumChildIds = (datum) => {
458
+ let ids = [];
459
+ _.each(datum.children, (childDatum) => {
460
+ ids.push(childDatum.item.id);
461
+ if (childDatum.children.length) {
462
+ const childIds = getDatumChildIds(childDatum);
463
+ ids = ids.concat(childIds);
464
+ const t = true;
465
+ }
466
+ });
467
+ return ids;
468
+ },
469
+ datumContainsSelection = (datum) => {
470
+ if (_.isEmpty(selection)) {
471
+ return false;
472
+ }
473
+ const
474
+ selectionIds = _.map(selection, (item) => item.id),
475
+ datumIds = getDatumChildIds(datum),
476
+ intersection = selectionIds.filter(x => datumIds.includes(x));
477
+
478
+ return !_.isEmpty(intersection);
479
+ },
486
480
 
487
481
  // Button handlers
488
482
  onToggle = (datum) => {
483
+ if (datum.isLoading) {
484
+ return;
485
+ }
486
+
489
487
  datum.isExpanded = !datum.isExpanded;
490
- forceUpdate();
491
488
 
492
- if (datum.isExpanded && datum.item?.repository.isRemote && datum.item.hasChildren && !datum.item.isChildrenLoaded) {
489
+ if (datum.isExpanded && datum.item.repository?.isRemote && datum.item.hasChildren && !datum.item.areChildrenLoaded) {
493
490
  loadChildren(datum, 1);
491
+ return;
492
+ }
493
+
494
+ if (!datum.isExpanded && datumContainsSelection(datum)) {
495
+ deselectAll();
494
496
  }
497
+
498
+ forceUpdate();
495
499
  },
496
500
  loadChildren = async (datum, depth) => {
497
- // Helper for onToggle
498
-
499
- // TODO: Flesh this out
500
- // Show loading indicator (red bar at top? Spinner underneath current node?)
501
-
501
+ // Show loading indicator (spinner underneath current node?)
502
+ datum.isLoading = true;
503
+ forceUpdate();
502
504
 
503
- // Calls getAdditionalParams(), then submits to server
504
- // Server returns this for each node:
505
- // Build up treeNodeData for just these new nodes
506
-
505
+ try {
506
+
507
+ const children = await datum.item.loadChildren(1);
508
+ const tnd = buildTreeNodeData(children);
509
+ datum.children = tnd;
510
+
511
+ } catch (err) {
512
+ // TODO: how do I handle errors?
513
+ // Color parent node red
514
+ // Modal alert box?
515
+ // Inline error msg? I'm concerned about modals not stacking correctly, but if we put it inline, it'll work.
516
+ datum.isExpanded = false;
517
+ }
507
518
 
508
519
  // Hide loading indicator
509
-
520
+ datum.isLoading = false;
521
+ forceUpdate();
510
522
  },
511
523
  onCollapseAll = (setNewTreeNodeData = true) => {
512
524
  // Go through whole tree and collapse all nodes
513
525
  const newTreeNodeData = _.clone(treeNodeData);
514
-
515
- // Recursive method to collapse all children
516
- function collapseNodes(nodes) {
517
- _.each(nodes, (node) => {
518
- node.isExpanded = false;
519
- if (!_.isEmpty(node.children)) {
520
- collapseNodes(node.children);
521
- }
522
- });
523
- }
524
-
525
526
  collapseNodes(newTreeNodeData);
526
527
 
527
528
  if (setNewTreeNodeData) {
@@ -529,47 +530,37 @@ export function Tree(props) {
529
530
  }
530
531
  return newTreeNodeData;
531
532
  },
533
+ collapseNodes = (nodes) => {
534
+ _.each(nodes, (node) => {
535
+ node.isExpanded = false;
536
+ if (!_.isEmpty(node.children)) {
537
+ collapseNodes(node.children);
538
+ }
539
+ });
540
+ },
532
541
  onSearchTree = async (value) => {
533
542
 
534
543
  let found = [];
535
544
  if (Repository?.isRemote) {
536
545
  // Search tree on server
537
- found = await Repository.searchTree(value);
546
+ found = await Repository.searchNodes(value);
538
547
  } else {
539
548
  // Search local tree data
540
549
  found = findTreeNodesByText(value);
541
550
  }
542
551
 
543
-
544
552
  const isMultipleHits = found.length > 1;
545
553
  let path = '';
546
554
  let searchFormData = [];
547
555
 
548
- if (Repository?.isRemote) {
549
- if (isMultipleHits) {
550
- // 'found' is the results from the server. Use these to show the modal and choose which node you want to select
551
-
552
-
553
-
554
-
555
- } else {
556
- // Search local tree data
557
- found = findTreeNodesByText(value);
558
- }
559
-
560
- // TODO: create searchFormData based on 'found' array
561
-
562
-
563
-
564
-
565
-
566
- setSearchFormData(searchFormData);
567
- setIsSearchModalShown(true);
568
-
569
- } else {
570
- // Expand that one path immediately
556
+ if (!isMultipleHits) {
557
+ path = found[0].path;
571
558
  expandPath(path);
559
+ return;
572
560
  }
561
+
562
+ setSearchFormData(searchFormData);
563
+ setIsSearchModalShown(true);
573
564
  },
574
565
  findTreeNodesByText = (text) => {
575
566
  // Helper for onSearchTree
@@ -624,14 +615,12 @@ export function Tree(props) {
624
615
  return nodes.join('/');
625
616
 
626
617
  },
627
- expandPath = (path) => {
618
+ expandPath = async (path) => {
628
619
  // Helper for onSearchTree
629
620
 
630
- // Drills down the tree based on path (usually given by server).
631
- // Path would be a list of sequential IDs (3/35/263/1024)
632
- // Initially, it closes thw whole tree.
633
-
634
- let newTreeNodeData = collapseAll(false); // false = don't set new treeNodeData
621
+ // First, close thw whole tree.
622
+ let newTreeNodeData = _.clone(treeNodeData);
623
+ collapseNodes(newTreeNodeData);
635
624
 
636
625
  // As it navigates down, it will expand the appropriate branches,
637
626
  // and then finally highlight & select the node in question
@@ -649,6 +638,13 @@ export function Tree(props) {
649
638
  currentDatum = _.find(currentLevelData, (treeNodeDatum) => {
650
639
  return treeNodeDatum.item.id === id;
651
640
  });
641
+
642
+ if (!currentDatum) {
643
+ // datum is not currently loaded, so load it
644
+
645
+ // LEFT OFF HERE
646
+ debugger;
647
+ }
652
648
 
653
649
  currentNode = currentDatum.item;
654
650
 
@@ -935,7 +931,7 @@ export function Tree(props) {
935
931
  let rootNodes;
936
932
  if (Repository) {
937
933
  if (!Repository.areRootNodesLoaded) {
938
- rootNodes = await Repository.getRootNodes(true, 1, getAdditionalParams);
934
+ rootNodes = await Repository.getRootNodes(1);
939
935
  }
940
936
  } else {
941
937
  // TODO: Make this work for data array
@@ -946,7 +942,15 @@ export function Tree(props) {
946
942
  setTreeNodeData(treeNodeData);
947
943
  }
948
944
 
945
+ function reloadTreeData() {
946
+ Repository.areRootNodesLoaded = false;
947
+ return buildAndSetTreeNodeData();
948
+ }
949
+
949
950
  if (!isReady) {
951
+ if (Repository) {
952
+ Repository.setBaseParams(extraParams);
953
+ }
950
954
  (async () => {
951
955
  await buildAndSetTreeNodeData();
952
956
  setIsReady(true);
@@ -956,29 +960,18 @@ export function Tree(props) {
956
960
  if (!Repository) {
957
961
  return () => {};
958
962
  }
959
-
963
+
960
964
  // set up @onehat/data repository
961
965
  const
962
966
  setTrue = () => setIsLoading(true),
963
- setFalse = () => setIsLoading(false),
964
- onChangeFilters = () => {
965
- if (!Repository.isAutoLoad) {
966
- Repository.reload();
967
- }
968
- },
969
- onChangeSorters = () => {
970
- if (!Repository.isAutoLoad) {
971
- Repository.reload();
972
- }
973
- };
974
-
967
+ setFalse = () => setIsLoading(false);
968
+
975
969
  Repository.on('beforeLoad', setTrue);
976
970
  Repository.on('load', setFalse);
977
971
  Repository.ons(['changePage', 'changePageSize',], deselectAll);
978
972
  Repository.ons(['changeData', 'change'], buildAndSetTreeNodeData);
979
- Repository.on('changeFilters', onChangeFilters);
980
- Repository.on('changeSorters', onChangeSorters);
981
-
973
+ Repository.on('changeFilters', reloadTreeData);
974
+ Repository.on('changeSorters', reloadTreeData);
982
975
 
983
976
  return () => {
984
977
  Repository.off('beforeLoad', setTrue);
@@ -1011,7 +1004,7 @@ export function Tree(props) {
1011
1004
  if (!isReady) {
1012
1005
  return null;
1013
1006
  }
1014
- const treeNodes = renderAllTreeNodes();
1007
+ const treeNodes = renderTreeNodes(treeNodeData);
1015
1008
 
1016
1009
  // headers & footers
1017
1010
  let treeFooterComponent = null;
@@ -3,6 +3,7 @@ import {
3
3
  Box,
4
4
  Icon,
5
5
  Row,
6
+ Spinner,
6
7
  Text,
7
8
  } from 'native-base';
8
9
  import {
@@ -17,7 +18,7 @@ import _ from 'lodash';
17
18
 
18
19
  export default function TreeNode(props) {
19
20
  const {
20
- nodeProps,
21
+ nodeProps = {},
21
22
  bg,
22
23
  datum,
23
24
  onToggle,
@@ -27,6 +28,7 @@ export default function TreeNode(props) {
27
28
  item = datum.item,
28
29
  isPhantom = item.isPhantom,
29
30
  isExpanded = datum.isExpanded,
31
+ isLoading = datum.isLoading,
30
32
  hasChildren = item.hasChildren,
31
33
  depth = item.depth,
32
34
  text = datum.text,
@@ -45,11 +47,12 @@ export default function TreeNode(props) {
45
47
  {...nodeProps}
46
48
  bg={bg}
47
49
  key={hash}
48
- pl={(depth * 10) + 'px'}
49
50
  >
50
51
  {isPhantom && <Box position="absolute" bg="#f00" h={2} w={2} t={0} l={0} />}
51
52
 
52
- {hasChildren ? <IconButton icon={icon} onPress={() => onToggle(datum)} /> : <Icon as={icon} />}
53
+ {isLoading ?
54
+ <Spinner px={2} /> :
55
+ (hasChildren ? <IconButton icon={icon} onPress={() => onToggle(datum)} /> : <Icon as={icon} px={2} />)}
53
56
 
54
57
  <Text
55
58
  overflow="hidden"
@@ -77,6 +80,7 @@ export default function TreeNode(props) {
77
80
  text,
78
81
  icon,
79
82
  onToggle,
83
+ isLoading,
80
84
  ]);
81
85
  }
82
86