@onehat/ui 0.2.58 → 0.2.60

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 (161) hide show
  1. package/package.json +1 -1
  2. package/src/Components/Form/Field/CKEditor/CKEditor.js +1 -1
  3. package/src/Components/Icons/AddressBook.js +14 -0
  4. package/src/Components/Icons/Alt.js +17 -0
  5. package/src/Components/Icons/AngleLeft.js +18 -0
  6. package/src/Components/Icons/AngleRight.js +18 -0
  7. package/src/Components/Icons/AnglesLeft.js +18 -0
  8. package/src/Components/Icons/AnglesRight.js +18 -0
  9. package/src/Components/Icons/Asterisk.js +14 -0
  10. package/src/Components/Icons/Ban.js +18 -0
  11. package/src/Components/Icons/Bars.js +14 -0
  12. package/src/Components/Icons/BarsStaggered.js +14 -0
  13. package/src/Components/Icons/BigCircle.js +17 -0
  14. package/src/Components/Icons/Book.js +14 -0
  15. package/src/Components/Icons/BookOpen.js +14 -0
  16. package/src/Components/Icons/Bug.js +14 -0
  17. package/src/Components/Icons/Building.js +14 -0
  18. package/src/Components/Icons/Calendar.js +18 -0
  19. package/src/Components/Icons/Calendar2.js +18 -0
  20. package/src/Components/Icons/CalendarDays.js +18 -0
  21. package/src/Components/Icons/Camera.js +18 -0
  22. package/src/Components/Icons/CaretDown.js +18 -0
  23. package/src/Components/Icons/CaretUp.js +18 -0
  24. package/src/Components/Icons/CartPlus.js +14 -0
  25. package/src/Components/Icons/CartShopping.js +14 -0
  26. package/src/Components/Icons/CashRegister.js +14 -0
  27. package/src/Components/Icons/ChartLine.js +14 -0
  28. package/src/Components/Icons/Check.js +14 -0
  29. package/src/Components/Icons/CheckDouble.js +14 -0
  30. package/src/Components/Icons/ChevronDown.js +14 -0
  31. package/src/Components/Icons/ChevronLeft.js +14 -0
  32. package/src/Components/Icons/ChevronRight.js +14 -0
  33. package/src/Components/Icons/ChevronUp.js +14 -0
  34. package/src/Components/Icons/Circle.js +14 -0
  35. package/src/Components/Icons/CircleArrowRight.js +14 -0
  36. package/src/Components/Icons/CircleExclamation.js +18 -0
  37. package/src/Components/Icons/CircleInfo.js +14 -0
  38. package/src/Components/Icons/CircleQuestion.js +14 -0
  39. package/src/Components/Icons/CircleXmark.js +14 -0
  40. package/src/Components/Icons/CircleXmarkRegular.js +14 -0
  41. package/src/Components/Icons/Clipboard.js +18 -0
  42. package/src/Components/Icons/Clock.js +14 -0
  43. package/src/Components/Icons/ClockRegular.js +14 -0
  44. package/src/Components/Icons/ClockRotateLeft.js +14 -0
  45. package/src/Components/Icons/Clone.js +14 -0
  46. package/src/Components/Icons/Collapse.js +17 -0
  47. package/src/Components/Icons/Comment.js +14 -0
  48. package/src/Components/Icons/CommentRegular.js +14 -0
  49. package/src/Components/Icons/Comments.js +14 -0
  50. package/src/Components/Icons/CommentsRegular.js +14 -0
  51. package/src/Components/Icons/Copyright.js +14 -0
  52. package/src/Components/Icons/Duplicate.js +18 -0
  53. package/src/Components/Icons/Edit.js +18 -0
  54. package/src/Components/Icons/EllipsisVertical.js +18 -0
  55. package/src/Components/Icons/Envelope.js +14 -0
  56. package/src/Components/Icons/EnvelopeRegular.js +14 -0
  57. package/src/Components/Icons/Exclamation.js +14 -0
  58. package/src/Components/Icons/Expand.js +14 -0
  59. package/src/Components/Icons/Eye.js +18 -0
  60. package/src/Components/Icons/EyeSlash.js +14 -0
  61. package/src/Components/Icons/File.js +18 -0
  62. package/src/Components/Icons/FloppyDiskRegular.js +14 -0
  63. package/src/Components/Icons/Folder.js +14 -0
  64. package/src/Components/Icons/FolderClosed.js +14 -0
  65. package/src/Components/Icons/FolderOpen.js +14 -0
  66. package/src/Components/Icons/FolderTree.js +14 -0
  67. package/src/Components/Icons/Gear.js +18 -0
  68. package/src/Components/Icons/Gift.js +14 -0
  69. package/src/Components/Icons/Grip.js +18 -0
  70. package/src/Components/Icons/GripLines.js +18 -0
  71. package/src/Components/Icons/GripLinesVertical.js +18 -0
  72. package/src/Components/Icons/GripVertical.js +18 -0
  73. package/src/Components/Icons/Hammer.js +14 -0
  74. package/src/Components/Icons/Hand.js +14 -0
  75. package/src/Components/Icons/House.js +14 -0
  76. package/src/Components/Icons/Info.js +14 -0
  77. package/src/Components/Icons/ItunesNote.js +14 -0
  78. package/src/Components/Icons/Leaf.js +14 -0
  79. package/src/Components/Icons/List.js +14 -0
  80. package/src/Components/Icons/ListCheck.js +14 -0
  81. package/src/Components/Icons/LocationDot.js +14 -0
  82. package/src/Components/Icons/Loop.js +17 -0
  83. package/src/Components/Icons/Loop1.js +18 -0
  84. package/src/Components/Icons/LoopAll.js +18 -0
  85. package/src/Components/Icons/MagnifyingGlass.js +14 -0
  86. package/src/Components/Icons/Maximize.js +14 -0
  87. package/src/Components/Icons/Microphone.js +14 -0
  88. package/src/Components/Icons/Minimize.js +14 -0
  89. package/src/Components/Icons/Minus.js +18 -0
  90. package/src/Components/Icons/MobileScreenButton.js +14 -0
  91. package/src/Components/Icons/MoneyBill.js +14 -0
  92. package/src/Components/Icons/MoneyBillWave.js +14 -0
  93. package/src/Components/Icons/Mouth.js +24 -0
  94. package/src/Components/Icons/Music.js +14 -0
  95. package/src/Components/Icons/Na.js +17 -0
  96. package/src/Components/Icons/NoLoop.js +24 -0
  97. package/src/Components/Icons/NoReorderRows.js +25 -0
  98. package/src/Components/Icons/ObjectGroupRegular.js +14 -0
  99. package/src/Components/Icons/Pause.js +14 -0
  100. package/src/Components/Icons/Pencil.js +18 -0
  101. package/src/Components/Icons/Phone.js +14 -0
  102. package/src/Components/Icons/Play.js +14 -0
  103. package/src/Components/Icons/Plus.js +18 -0
  104. package/src/Components/Icons/Presentation.js +19 -0
  105. package/src/Components/Icons/Print.js +18 -0
  106. package/src/Components/Icons/Question.js +14 -0
  107. package/src/Components/Icons/Rate-.25x.js +20 -0
  108. package/src/Components/Icons/Rate-.5x.js +19 -0
  109. package/src/Components/Icons/Rate-.75x.js +19 -0
  110. package/src/Components/Icons/Rate-1.25x.js +20 -0
  111. package/src/Components/Icons/Rate-1.5x.js +19 -0
  112. package/src/Components/Icons/Rate-1.75x.js +19 -0
  113. package/src/Components/Icons/Rate-1x.js +19 -0
  114. package/src/Components/Icons/Rate-2x.js +19 -0
  115. package/src/Components/Icons/RateIcon-.25x.js +20 -0
  116. package/src/Components/Icons/RateIcon-.5x.js +19 -0
  117. package/src/Components/Icons/RateIcon-.75x.js +19 -0
  118. package/src/Components/Icons/RateIcon-1.25x.js +20 -0
  119. package/src/Components/Icons/RateIcon-1.5x.js +19 -0
  120. package/src/Components/Icons/RateIcon-1.75x.js +19 -0
  121. package/src/Components/Icons/RateIcon-1x.js +19 -0
  122. package/src/Components/Icons/RateIcon-2x.js +19 -0
  123. package/src/Components/Icons/RectangleXmark.js +14 -0
  124. package/src/Components/Icons/RectangleXmarkRegular.js +14 -0
  125. package/src/Components/Icons/ReorderRows.js +21 -0
  126. package/src/Components/Icons/RightFromBracket.js +14 -0
  127. package/src/Components/Icons/RightToBracket.js +14 -0
  128. package/src/Components/Icons/Rotate.js +18 -0
  129. package/src/Components/Icons/RotateLeft.js +14 -0
  130. package/src/Components/Icons/RotateRight.js +18 -0
  131. package/src/Components/Icons/ScrewdriverWrench.js +14 -0
  132. package/src/Components/Icons/Scroll.js +14 -0
  133. package/src/Components/Icons/Share.js +14 -0
  134. package/src/Components/Icons/Shop.js +14 -0
  135. package/src/Components/Icons/SortDown.js +14 -0
  136. package/src/Components/Icons/SortUp.js +18 -0
  137. package/src/Components/Icons/Square.js +14 -0
  138. package/src/Components/Icons/SquareCheck.js +14 -0
  139. package/src/Components/Icons/SquareCheckRegular.js +14 -0
  140. package/src/Components/Icons/SquareMinus.js +18 -0
  141. package/src/Components/Icons/SquareRegular.js +14 -0
  142. package/src/Components/Icons/Store.js +14 -0
  143. package/src/Components/Icons/ThumbsDown.js +14 -0
  144. package/src/Components/Icons/ThumbsDownRegular.js +14 -0
  145. package/src/Components/Icons/ThumbsUp.js +14 -0
  146. package/src/Components/Icons/ThumbsUpRegular.js +14 -0
  147. package/src/Components/Icons/Trash.js +18 -0
  148. package/src/Components/Icons/TrashCan.js +18 -0
  149. package/src/Components/Icons/TriangleExclamation.js +18 -0
  150. package/src/Components/Icons/Truck.js +14 -0
  151. package/src/Components/Icons/TruckFast.js +14 -0
  152. package/src/Components/Icons/User.js +14 -0
  153. package/src/Components/Icons/UserGroup.js +14 -0
  154. package/src/Components/Icons/UserPlus.js +14 -0
  155. package/src/Components/Icons/UserSecret.js +14 -0
  156. package/src/Components/Icons/X.js +14 -0
  157. package/src/Components/Icons/Xmark.js +14 -0
  158. package/src/Components/Tree/Tree.js +463 -133
  159. package/src/Components/Tree/TreeNode.js +53 -54
  160. package/src/Constants/Styles.js +10 -0
  161. package/src/Constants/Tree.js +4 -0
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef, useMemo, } from 'react';
2
2
  import {
3
3
  Column,
4
4
  FlatList,
5
+ Modal,
5
6
  Pressable,
6
7
  Icon,
7
8
  Row,
@@ -31,17 +32,22 @@ import withPresetButtons from '../Hoc/withPresetButtons.js';
31
32
  import withMultiSelection from '../Hoc/withMultiSelection.js';
32
33
  import withSelection from '../Hoc/withSelection.js';
33
34
  import withWindowedEditor from '../Hoc/withWindowedEditor.js';
34
- import withInlineEditor from '../Hoc/withInlineEditor.js';
35
35
  import testProps from '../../Functions/testProps.js';
36
36
  import nbToRgb from '../../Functions/nbToRgb.js';
37
- import TreeHeaderRow from './TreeHeaderRow.js';
38
37
  import TreeNode, { ReorderableTreeNode } from './TreeNode.js';
38
+ import FormPanel from '../Panel/FormPanel.js';
39
+ import Input from '../Form/Field/Input.js';
39
40
  import IconButton from '../Buttons/IconButton.js';
41
+ import Circle from '../Icons/Circle.js';
42
+ import Collapse from '../Icons/Collapse.js';
43
+ import FolderClosed from '../Icons/FolderClosed.js';
44
+ import FolderOpen from '../Icons/FolderOpen.js';
45
+ import MagnifyingGlass from '../Icons/MagnifyingGlass.js';
46
+ import NoReorderRows from '../Icons/NoReorderRows.js';
47
+ import ReorderRows from '../Icons/ReorderRows.js';
40
48
  import PaginationToolbar from '../Toolbar/PaginationToolbar.js';
41
49
  import NoRecordsFound from './NoRecordsFound.js';
42
50
  import Toolbar from '../Toolbar/Toolbar.js';
43
- import NoReorderRows from '../Icons/NoReorderRows.js';
44
- import ReorderRows from '../Icons/ReorderRows.js';
45
51
  import _ from 'lodash';
46
52
 
47
53
 
@@ -49,17 +55,62 @@ import _ from 'lodash';
49
55
  // The default export is *with* the HOC. A separate *raw* component is
50
56
  // exported which can be combined with many HOCs for various functionality.
51
57
 
58
+
59
+ //////////////////////
60
+ //////////////////////
61
+
62
+ // I'm thinking if a repository senses that it's a tree, then at initial load
63
+ // it should get the root node +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 root+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
+ // Need to take into account whether using Repository or data.
77
+ // If using data, everything exists at once. What format will data be in?
78
+ // How does this interface with Repository?
79
+ // Maybe if Repository is not AjaxRepository, everything needs to be present at once!
80
+
81
+
82
+ // isRootVisible
83
+
84
+ //////////////////////
85
+ //////////////////////
86
+
87
+
88
+
89
+
90
+
91
+
52
92
  export function Tree(props) {
53
93
  const {
54
94
  isRootVisible = true,
55
- getChildParams = () => { // returns params needed to get child nodes from server (getEquipment, getRentalEquipment, etc). This is primarily to limit results, as different kinds of views are only interested in certain types of nodes in the returned data.
95
+ getAdditionalParams = () => { // URL params needed to get nodes from server (e.g, { venue_id: 1, getEquipment: true, getRentalEquipment: false, }), in addition to filters.
56
96
  return {};
57
97
  },
58
98
  getNodeText = (item) => { // extracts model/data and decides what the row text should be
59
99
  return item.displayValue;
60
100
  },
61
- getNodeType, // extracts model/data and decides what kind of node this should be. Helper for getNodeIcon
62
- getNodeIcon,
101
+ getNodeIcon = (item, isExpanded) => { // decides what icon to show for this node
102
+ let icon;
103
+ if (item.hasChildren) {
104
+ if (isExpanded) {
105
+ icon = FolderOpen;
106
+ } else {
107
+ icon = FolderClosed;
108
+ }
109
+ } else {
110
+ icon = Circle;
111
+ }
112
+ return icon;
113
+ },
63
114
  nodeProps = (item) => {
64
115
  return {};
65
116
  },
@@ -114,7 +165,9 @@ export function Tree(props) {
114
165
  [isReady, setIsReady] = useState(false),
115
166
  [isLoading, setIsLoading] = useState(false),
116
167
  [isReorderMode, setIsReorderMode] = useState(false),
168
+ [isSearchModalShown, setIsSearchModalShown] = useState(false),
117
169
  [treeNodeData, setTreeNodeData] = useState({}),
170
+ [searchFormData, setSearchFormData] = useState([]),
118
171
  [dragNodeSlot, setDragNodeSlot] = useState(null),
119
172
  [dragNodeIx, setDragNodeIx] = useState(),
120
173
  onNodeClick = (item, e) => {
@@ -187,7 +240,50 @@ export function Tree(props) {
187
240
  });
188
241
  }
189
242
  },
243
+ getHeaderToolbarItems = () => {
244
+ const
245
+ buttons = [
246
+
247
+ {
248
+ key: 'searchBtn',
249
+ text: 'Search tree',
250
+ handler: onSearchTree,
251
+ icon: MagnifyingGlass,
252
+ isDisabled: false,
253
+ },
254
+ {
255
+ key: 'collapseBtn',
256
+ text: 'Collapse whole tree',
257
+ handler: onCollapseAll,
258
+ icon: Collapse,
259
+ isDisabled: false,
260
+ },
261
+ ];
262
+ if (canNodesReorder) {
263
+ buttons.push({
264
+ key: 'reorderBtn',
265
+ text: 'Reorder tree',
266
+ handler: () => setIsReorderMode(!isReorderMode),
267
+ icon: isReorderMode ? NoReorderRows : ReorderRows,
268
+ isDisabled: false,
269
+ });
270
+ }
271
+ const items = _.map(buttons, getIconFromConfig);
272
+
273
+ items.unshift(<Input // Add text input to beginning of header items
274
+ key="searchTree"
275
+ flex={1}
276
+ placeholder="Search all tree nodes"
277
+ onChangeValue={onSearchTree}
278
+ autoSubmit={false}
279
+ />);
280
+
281
+ return items;
282
+ },
190
283
  getFooterToolbarItems = () => {
284
+ return _.map(additionalToolbarButtons, getIconFromConfig);
285
+ },
286
+ getIconFromConfig = (config, ix) => {
191
287
  const
192
288
  iconButtonProps = {
193
289
  _hover: {
@@ -201,57 +297,71 @@ export function Tree(props) {
201
297
  size: styles.TREE_TOOLBAR_ITEMS_ICON_SIZE,
202
298
  h: 20,
203
299
  w: 20,
204
- },
205
- items = _.map(additionalToolbarButtons, (config, ix) => {
206
- let {
207
- text,
208
- handler,
209
- icon = null,
210
- isDisabled = false,
211
- } = config;
212
- if (icon) {
213
- const thisIconProps = {
214
- color: isDisabled ? styles.TREE_TOOLBAR_ITEMS_DISABLED_COLOR : styles.TREE_TOOLBAR_ITEMS_COLOR,
215
- };
216
- icon = React.cloneElement(icon, {...iconProps, ...thisIconProps});
217
- }
218
- return <IconButton
219
- key={ix}
220
- {...iconButtonProps}
221
- onPress={handler}
222
- icon={icon}
223
- isDisabled={isDisabled}
224
- tooltip={text}
225
- />;
226
- });
227
- if (canNodesReorder) {
228
- items.unshift(<IconButton
229
- key="reorderBtn"
230
- {...iconButtonProps}
231
- onPress={() => setIsReorderMode(!isReorderMode)}
232
- icon={<Icon as={isReorderMode ? NoReorderRows : ReorderRows} color={styles.TREE_TOOLBAR_ITEMS_COLOR} />}
233
- />);
300
+ };
301
+ let {
302
+ key,
303
+ text,
304
+ handler,
305
+ icon = null,
306
+ isDisabled = false,
307
+ } = config;
308
+ if (icon) {
309
+ const thisIconProps = {
310
+ color: isDisabled ? styles.TREE_TOOLBAR_ITEMS_DISABLED_COLOR : styles.TREE_TOOLBAR_ITEMS_COLOR,
311
+ };
312
+ icon = React.cloneElement(icon, {...iconProps, ...thisIconProps});
234
313
  }
235
- return items;
314
+ return <IconButton
315
+ key={key || ix}
316
+ onPress={handler}
317
+ icon={icon}
318
+ isDisabled={isDisabled}
319
+ tooltip={text}
320
+ {...iconButtonProps}
321
+ />;
322
+ },
323
+ buildTreeNodeDatum = (treeNode) => {
324
+ // Build the data-representation of one node and its children,
325
+ // caching text & icon, keeping track of the state for whole tree
326
+ // renderTreeNode uses this to render the nodes.
327
+ const
328
+ isRoot = treeNode.isRoot,
329
+ isLeaf = !treeNode.hasChildren,
330
+ datum = {
331
+ item: treeNode,
332
+ text: getNodeText(treeNode),
333
+ iconCollapsed: isLeaf ? null : getNodeIcon(treeNode, false),
334
+ iconExpanded: isLeaf ? null : getNodeIcon(treeNode, true),
335
+ iconLeaf: isLeaf ? getNodeIcon(treeNode) : null,
336
+ isExpanded: isRoot, // all non-root treeNodes are not expanded by default
337
+ isVisible: isRoot ? isRootVisible : true,
338
+ children: buildTreeNodeData(treeNode.children), // recursively get data for children
339
+ };
340
+
341
+ return datum;
342
+ },
343
+ buildTreeNodeData = (treeNodes) => {
344
+ const data = [];
345
+ _.each(treeNodes, (item) => {
346
+ data.push(buildTreeNodeDatum(item));
347
+ });
348
+ return data;
236
349
  },
237
- renderNode = (itemData) => {
238
- const item = itemData.item;
350
+ renderTreeNode = (datum) => {
351
+ const item = datum.item;
239
352
  if (item.isDestroyed) {
240
353
  return null;
241
354
  }
355
+ if (!datum.isVisible) {
356
+ return null;
357
+ }
242
358
 
243
-
244
- // TODO: Figure out these vars
245
- // icon (optional)
246
- // onToggle (handler for if expand/collapse icon is clicked)
247
-
248
-
249
-
250
- let nodeProps = getNodeProps && !isHeaderNode ? getNodeProps(item) : {},
251
- isSelected = !isHeaderNode && isInSelection(item);
359
+ let nodeProps = getNodeProps ? getNodeProps(item) : {},
360
+ isSelected = isInSelection(item);
252
361
 
253
362
  return <Pressable
254
363
  // {...testProps(Repository ? Repository.schema.name + '-' + item.id : item.id)}
364
+ key={item.hash}
255
365
  onPress={(e) => {
256
366
  if (e.preventDefault && e.cancelable) {
257
367
  e.preventDefault();
@@ -335,55 +445,244 @@ export function Tree(props) {
335
445
  return <WhichTreeNode
336
446
  nodeProps={nodeProps}
337
447
  bg={bg}
338
- itemData={itemData}
339
-
340
- icon={icon}
448
+ datum={datum}
341
449
  onToggle={onToggle}
342
450
 
343
451
  // fields={fields}
344
- // hideNavColumn={hideNavColumn}
345
452
  {...rowReorderProps}
346
453
  />;
347
454
  }}
348
455
  </Pressable>;
349
456
  },
350
- buildTreeNode = (itemData) => {
351
- // this one is to be used recursively on children
457
+ renderTreeNodes = (data) => {
458
+ const nodes = [];
459
+ _.each(data, (datum) => {
460
+ nodes.push(renderTreeNode(datum));
461
+ });
462
+ return nodes;
463
+ },
464
+ renderAllTreeNodes = () => {
465
+ const nodes = [];
466
+ _.each(treeNodeData, (datum) => {
467
+ const node = renderTreeNode(datum);
468
+ if (_.isEmpty(node)) {
469
+ return;
470
+ }
471
+
472
+ nodes.push(node);
352
473
 
353
- // isVisible // skip if not visible, just return keyed array
474
+ if (_.isEmpty(datum.children)) {
475
+ return;
476
+ }
354
477
 
355
- // renderNode(itemData);
478
+ const children = renderTreeNodes(datum.children);
479
+ if (_.isEmpty(children)) {
480
+ return;
481
+ }
356
482
 
483
+ nodes.concat(children);
484
+ });
485
+ return nodes;
357
486
  },
358
- buildTreeNodes = () => {
359
487
 
360
- // const entities = Repository ? (Repository.isRemote ? Repository.entities : Repository.getEntitiesOnPage()) : data;
361
- // let rowData = _.clone(entities); // don't use the original array, make a new one so alterations to it are temporary
488
+ // Button handlers
489
+ onToggle = (datum) => {
490
+ datum.isExpanded = !datum.isExpanded;
491
+ forceUpdate();
362
492
 
363
- const
364
- rootEntity = Repository.getRootEntity(),
365
- rootNode = buildTreeNode(rootEntity);
493
+ if (datum.item?.repository.isRemote && datum.item.hasChildren && !datum.item.isChildrenLoaded) {
494
+ loadChildren(datum, 1);
495
+ }
496
+ },
497
+ loadChildren = async (datum, depth) => {
498
+ // Helper for onToggle
366
499
 
500
+ // TODO: Flesh this out
501
+ // Show loading indicator (red bar at top? Spinner underneath current node?)
367
502
 
503
+
504
+ // Calls getAdditionalParams(), then submits to server
505
+ // Server returns this for each node:
506
+ // Build up treeNodeData for just these new nodes
368
507
 
369
508
 
370
- return rootNode;
509
+ // Hide loading indicator
510
+
371
511
  },
372
- getChildren = (node_id, depth) => {
373
- // Calls getChildParams(), then submits to server
374
- // Server returns this for each node:
375
- // hasChildren (so view can show/hide caret)
376
- // model (e.g. "Fleet", "Equipment)
377
- // data (json encoded representation of entity)
512
+ onCollapseAll = (setNewTreeNodeData = true) => {
513
+ // Go through whole tree and collapse all nodes
514
+ const newTreeNodeData = _.clone(treeNodeData);
515
+
516
+ // Recursive method to collapse all children
517
+ function collapseChildren(children) {
518
+ _.each(children, (child) => {
519
+ child.isExpanded = true;
520
+ if (!_.isEmpty(child.children)) {
521
+ collapseChildren(child.children);
522
+ }
523
+ });
524
+ }
525
+
526
+ collapseChildren(newTreeNodeData);
527
+
528
+ if (setNewTreeNodeData) {
529
+ setTreeNodeData(newTreeNodeData);
530
+ }
531
+ return newTreeNodeData;
532
+ },
533
+ onSearchTree = async (value) => {
534
+
535
+ let found = [];
536
+ if (Repository?.isRemote) {
537
+ // Search tree on server
538
+ found = await Repository.searchTree(value);
539
+ } else {
540
+ // Search local tree data
541
+ found = findTreeNodesByText(value);
542
+ }
543
+
544
+
545
+ const isMultipleHits = found.length > 1;
546
+ let path = '';
547
+ let searchFormData = [];
378
548
 
549
+ if (Repository?.isRemote) {
550
+ if (isMultipleHits) {
551
+ // 'found' is the results from the server. Use these to show the modal and choose which node you want to select
552
+
553
+
554
+
555
+
556
+ } else {
557
+ // Search local tree data
558
+ found = findTreeNodesByText(value);
559
+ }
560
+
561
+ // TODO: create searchFormData based on 'found' array
562
+
563
+
564
+
565
+
566
+
567
+ setSearchFormData(searchFormData);
568
+ setIsSearchModalShown(true);
569
+
570
+ } else {
571
+ // Expand that one path immediately
572
+ expandPath(path);
573
+ }
379
574
  },
575
+ findTreeNodesByText = (text) => {
576
+ // Helper for onSearchTree
577
+ // Searches whole treeNodeData for any matching items
578
+ // Returns multiple nodes
380
579
 
381
- // Button handlers
382
- expandPath = (path) => {} // - drills down the tree based on path (usually given by server). Path would be a list of sequential IDs (3/35/263/1024)
383
- collapseOne = (node_id) => {},
384
- collapseAll = () => {},
385
- expandOne = (node_id) => {},
386
- expandAll = () => {},
580
+ const regex = new RegExp(text, 'i'); // instead of matching based on full text match, search for a partial match
581
+
582
+ function searchChildren(children, found = []) {
583
+ _.each(children, (child) => {
584
+ if (child.text.match(regex)) {
585
+ found.push(child);
586
+ }
587
+ if (child.children) {
588
+ searchChildren(child.children, found);
589
+ }
590
+ });
591
+ return found;
592
+ }
593
+ return searchChildren(treeNodeData);
594
+ },
595
+ getTreeNodeByNodeId = (node_id) => {
596
+ if (Repository) {
597
+ return Repository.getById(node_id);
598
+ }
599
+ return data[node_id]; // TODO: This is probably not right!
600
+ },
601
+ getPathByTreeNode = (treeNode) => {
602
+
603
+ ///////// THIS DOESN'T WORK YET /////////
604
+
605
+ function searchChildren(children, currentPath = []) {
606
+ let found = [];
607
+ _.each(children, (child) => {
608
+ const
609
+ item = child.item,
610
+ id = idField ? item[idField] : item.id;
611
+ if (child.text.match(regex)) {
612
+ found.push(child);
613
+ return false;
614
+ }
615
+ if (child.children) {
616
+ const childrenFound = searchChildren(child.children, [...currentPath, id]);
617
+ if (!_.isEmpty(childrenFound)) {
618
+ return false;
619
+ }
620
+ }
621
+ });
622
+ return found;
623
+ }
624
+ const nodes = searchChildren(treeNodeData);
625
+ return nodes.join('/');
626
+
627
+ },
628
+ expandPath = (path) => {
629
+ // Helper for onSearchTree
630
+
631
+ // Drills down the tree based on path (usually given by server).
632
+ // Path would be a list of sequential IDs (3/35/263/1024)
633
+ // Initially, it closes thw whole tree.
634
+
635
+ let newTreeNodeData = collapseAll(false); // false = don't set new treeNodeData
636
+
637
+ // As it navigates down, it will expand the appropriate branches,
638
+ // and then finally highlight & select the node in question
639
+ let pathParts,
640
+ id,
641
+ currentLevelData = newTreeNodeData,
642
+ currentDatum,
643
+ currentNode;
644
+
645
+ while(path.length) {
646
+ pathParts = path.split('/');
647
+ id = parseInt(pathParts[0], 10); // grab the first part of the path
648
+
649
+ // find match in current level
650
+ currentDatum = _.find(currentLevelData, (treeNodeDatum) => {
651
+ return treeNodeDatum.item.id === id;
652
+ });
653
+
654
+ currentNode = currentDatum.item;
655
+
656
+ // THE MAGIC!
657
+ currentDatum.isExpanded = true;
658
+
659
+ path = pathParts.slice(1).join('/'); // put the rest of it back together
660
+ currentLevelData = currentDatum.children;
661
+ }
662
+
663
+ setSelection([currentNode]);
664
+ scrollToNode(currentNode);
665
+ highlightNode(currentNode);
666
+
667
+ setTreeNodeData(newTreeNodeData);
668
+ },
669
+ scrollToNode = (node) => {
670
+ // Helper for expandPath
671
+ // Scroll the tree so the given node is in view
672
+
673
+ // TODO: This will probably need different methods in web and mobile
674
+
675
+
676
+ },
677
+ highlightNode = (node) => {
678
+ // Helper for expandPath
679
+ // Show a brief highlight animation to draw attention to the node
680
+
681
+ // TODO: This will probably need different methods in web and mobile
682
+ // react-highlight for web?
683
+
684
+
685
+ },
387
686
 
388
687
  // Drag/Drop
389
688
  getReorderProxy = (node) => {
@@ -632,52 +931,27 @@ export function Tree(props) {
632
931
 
633
932
  useEffect(() => {
634
933
 
934
+ async function buildAndSetTreeNodeData() {
635
935
 
636
- function buildTreeNodeData() {
637
- // Take the Repository and build up the tree node data from its entities
638
- const
639
- entities = Repository.entities,
640
- treeNodes = {};
641
-
642
- // TODO: Get root node?
643
- // I'm thinking if a repository senses that it's a tree, then at initial load
644
- // it should get the root node +1 level of children.
645
- //
646
- // How would it then subsequently get the proper children?
647
- // i.e. When a node gets its children, how will it do this
648
- // while maintaining the nodes that already exist there?
649
- // We don't want it to *replace* all exisitng nodes!
650
- //
651
- // And if the repository does a reload, should it just get root+1 again?
652
- // Changing filters would potentially change the tree structure.
653
- // Changing sorting would only change the ordering, not what is expanded/collapsed or visible/invisible.
654
-
655
- // include the following on each node
656
- // - item
657
- // - isExpanded,
658
- // - isVisible,
659
- // - hasChildren,
660
- // - children
661
- // - depth,
662
- // - text,
663
-
664
- // Need to take into account whether using Repository or data.
665
- // If using data, everything exists at once. What format will data be in?
666
- // How does this interface with Repository?
667
- // Maybe if Repository is not AjaxRepository, everything needs to be present at once!
668
-
669
-
670
- return treeNodes;
671
- }
936
+ let rootNodes;
937
+ if (Repository) {
938
+ rootNodes = await Repository.getRootNodes(true, 1, getAdditionalParams);
939
+ } else {
940
+ // TODO: Make this work for data array
941
+
942
+ }
672
943
 
673
- function buildAndSetTreeNodeData() {
674
- setTreeNodeData(buildTreeNodeData());
944
+ const treeNodeData = buildTreeNodeData(rootNodes);
945
+ setTreeNodeData(treeNodeData);
675
946
  }
676
947
 
677
948
  if (!isReady) {
678
- buildAndSetTreeNodeData();
679
- setIsReady(true);
949
+ (async () => {
950
+ await buildAndSetTreeNodeData();
951
+ setIsReady(true);
952
+ })();
680
953
  }
954
+
681
955
  if (!Repository) {
682
956
  return () => {};
683
957
  }
@@ -729,14 +1003,16 @@ export function Tree(props) {
729
1003
 
730
1004
  }, [selectorId, selectorSelected]);
731
1005
 
732
- const footerToolbarItemComponents = useMemo(() => getFooterToolbarItems(), [additionalToolbarButtons, isReorderMode]);
1006
+ const
1007
+ headerToolbarItemComponents = useMemo(() => getHeaderToolbarItems(), []),
1008
+ footerToolbarItemComponents = useMemo(() => getFooterToolbarItems(), [additionalToolbarButtons, isReorderMode]);
733
1009
 
734
1010
  if (!isReady) {
735
1011
  return null;
736
1012
  }
737
1013
 
738
1014
  // Actual TreeNodes
739
- const treeNodes = buildTreeNodes();
1015
+ const treeNodes = renderAllTreeNodes();
740
1016
 
741
1017
  // headers & footers
742
1018
  let treeFooterComponent = null;
@@ -748,25 +1024,79 @@ export function Tree(props) {
748
1024
  }
749
1025
  }
750
1026
 
751
- return <Column
752
- {...testProps('Tree')}
753
- flex={1}
754
- w="100%"
755
- >
756
- {topToolbar}
1027
+ return <>
1028
+ <Column
1029
+ {...testProps('Tree')}
1030
+ flex={1}
1031
+ w="100%"
1032
+ >
1033
+ {topToolbar}
1034
+ {headerToolbarItemComponents}
1035
+
1036
+ <Column w="100%" flex={1} borderTopWidth={isLoading ? 2 : 1} borderTopColor={isLoading ? '#f00' : 'trueGray.300'} onClick={() => {
1037
+ if (!isReorderMode) {
1038
+ deselectAll();
1039
+ }
1040
+ }}>
1041
+ {!treeNodes.length ? <NoRecordsFound text={noneFoundText} onRefresh={onRefresh} /> :
1042
+ treeNodes}
1043
+ </Column>
757
1044
 
758
- <Column w="100%" flex={1} borderTopWidth={isLoading ? 2 : 1} borderTopColor={isLoading ? '#f00' : 'trueGray.300'} onClick={() => {
759
- if (!isReorderMode) {
760
- deselectAll();
761
- }
762
- }}>
763
- {!treeNodes.length ? <NoRecordsFound text={noneFoundText} onRefresh={onRefresh} /> :
764
- treeNodes}
1045
+ {treeFooterComponent}
765
1046
  </Column>
766
1047
 
767
- {treeFooterComponent}
1048
+ <Modal
1049
+ isOpen={isSearchModalShown}
1050
+ onClose={() => setIsSearchModalShown(false)}
1051
+ >
1052
+ <Column bg="#fff" w={500}>
1053
+ <FormPanel
1054
+ title="Choose Tree Node"
1055
+ instructions="Multiple tree nodes matched your search. Please select which one to show."
1056
+ flex={1}
1057
+ items={[
1058
+ {
1059
+ type: 'Column',
1060
+ flex: 1,
1061
+ items: [
1062
+ {
1063
+ key: 'node_id',
1064
+ name: 'node_id',
1065
+ type: 'Combo',
1066
+ label: 'Tree Node',
1067
+ data: searchFormData,
1068
+ }
1069
+ ],
1070
+ },
1071
+ ]}
1072
+ onCancel={(e) => {
1073
+ // Just close the modal
1074
+ setIsSearchModalShown(false);
1075
+ }}
1076
+ onSave={(data, e) => {
1077
+
1078
+ const node_id = data.node_id; // NOT SURE THIS IS CORRECT!
1079
+
1080
+ if (isMultipleHits) {
1081
+ // Tell the server which one you want and get it, loading all children necessary to get there
1082
+
1083
+
1084
+
1085
+ } else {
1086
+ // Show the path based on local data
1087
+ const
1088
+ treeNode = getTreeNodeByNodeId(node_id),
1089
+ path = getPathByTreeNode(treeNode);
1090
+ expandPath(path);
1091
+ }
768
1092
 
769
- </Column>;
1093
+ // Close the modal
1094
+ setIsSearchModalShown(false);
1095
+ }}
1096
+ />
1097
+ </Column>
1098
+ </Modal>
1099
+ </>;
770
1100
 
771
1101
  }
772
1102