@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.
- package/package.json +1 -1
- package/src/Components/Form/Field/CKEditor/CKEditor.js +1 -1
- package/src/Components/Icons/AddressBook.js +14 -0
- package/src/Components/Icons/Alt.js +17 -0
- package/src/Components/Icons/AngleLeft.js +18 -0
- package/src/Components/Icons/AngleRight.js +18 -0
- package/src/Components/Icons/AnglesLeft.js +18 -0
- package/src/Components/Icons/AnglesRight.js +18 -0
- package/src/Components/Icons/Asterisk.js +14 -0
- package/src/Components/Icons/Ban.js +18 -0
- package/src/Components/Icons/Bars.js +14 -0
- package/src/Components/Icons/BarsStaggered.js +14 -0
- package/src/Components/Icons/BigCircle.js +17 -0
- package/src/Components/Icons/Book.js +14 -0
- package/src/Components/Icons/BookOpen.js +14 -0
- package/src/Components/Icons/Bug.js +14 -0
- package/src/Components/Icons/Building.js +14 -0
- package/src/Components/Icons/Calendar.js +18 -0
- package/src/Components/Icons/Calendar2.js +18 -0
- package/src/Components/Icons/CalendarDays.js +18 -0
- package/src/Components/Icons/Camera.js +18 -0
- package/src/Components/Icons/CaretDown.js +18 -0
- package/src/Components/Icons/CaretUp.js +18 -0
- package/src/Components/Icons/CartPlus.js +14 -0
- package/src/Components/Icons/CartShopping.js +14 -0
- package/src/Components/Icons/CashRegister.js +14 -0
- package/src/Components/Icons/ChartLine.js +14 -0
- package/src/Components/Icons/Check.js +14 -0
- package/src/Components/Icons/CheckDouble.js +14 -0
- package/src/Components/Icons/ChevronDown.js +14 -0
- package/src/Components/Icons/ChevronLeft.js +14 -0
- package/src/Components/Icons/ChevronRight.js +14 -0
- package/src/Components/Icons/ChevronUp.js +14 -0
- package/src/Components/Icons/Circle.js +14 -0
- package/src/Components/Icons/CircleArrowRight.js +14 -0
- package/src/Components/Icons/CircleExclamation.js +18 -0
- package/src/Components/Icons/CircleInfo.js +14 -0
- package/src/Components/Icons/CircleQuestion.js +14 -0
- package/src/Components/Icons/CircleXmark.js +14 -0
- package/src/Components/Icons/CircleXmarkRegular.js +14 -0
- package/src/Components/Icons/Clipboard.js +18 -0
- package/src/Components/Icons/Clock.js +14 -0
- package/src/Components/Icons/ClockRegular.js +14 -0
- package/src/Components/Icons/ClockRotateLeft.js +14 -0
- package/src/Components/Icons/Clone.js +14 -0
- package/src/Components/Icons/Collapse.js +17 -0
- package/src/Components/Icons/Comment.js +14 -0
- package/src/Components/Icons/CommentRegular.js +14 -0
- package/src/Components/Icons/Comments.js +14 -0
- package/src/Components/Icons/CommentsRegular.js +14 -0
- package/src/Components/Icons/Copyright.js +14 -0
- package/src/Components/Icons/Duplicate.js +18 -0
- package/src/Components/Icons/Edit.js +18 -0
- package/src/Components/Icons/EllipsisVertical.js +18 -0
- package/src/Components/Icons/Envelope.js +14 -0
- package/src/Components/Icons/EnvelopeRegular.js +14 -0
- package/src/Components/Icons/Exclamation.js +14 -0
- package/src/Components/Icons/Expand.js +14 -0
- package/src/Components/Icons/Eye.js +18 -0
- package/src/Components/Icons/EyeSlash.js +14 -0
- package/src/Components/Icons/File.js +18 -0
- package/src/Components/Icons/FloppyDiskRegular.js +14 -0
- package/src/Components/Icons/Folder.js +14 -0
- package/src/Components/Icons/FolderClosed.js +14 -0
- package/src/Components/Icons/FolderOpen.js +14 -0
- package/src/Components/Icons/FolderTree.js +14 -0
- package/src/Components/Icons/Gear.js +18 -0
- package/src/Components/Icons/Gift.js +14 -0
- package/src/Components/Icons/Grip.js +18 -0
- package/src/Components/Icons/GripLines.js +18 -0
- package/src/Components/Icons/GripLinesVertical.js +18 -0
- package/src/Components/Icons/GripVertical.js +18 -0
- package/src/Components/Icons/Hammer.js +14 -0
- package/src/Components/Icons/Hand.js +14 -0
- package/src/Components/Icons/House.js +14 -0
- package/src/Components/Icons/Info.js +14 -0
- package/src/Components/Icons/ItunesNote.js +14 -0
- package/src/Components/Icons/Leaf.js +14 -0
- package/src/Components/Icons/List.js +14 -0
- package/src/Components/Icons/ListCheck.js +14 -0
- package/src/Components/Icons/LocationDot.js +14 -0
- package/src/Components/Icons/Loop.js +17 -0
- package/src/Components/Icons/Loop1.js +18 -0
- package/src/Components/Icons/LoopAll.js +18 -0
- package/src/Components/Icons/MagnifyingGlass.js +14 -0
- package/src/Components/Icons/Maximize.js +14 -0
- package/src/Components/Icons/Microphone.js +14 -0
- package/src/Components/Icons/Minimize.js +14 -0
- package/src/Components/Icons/Minus.js +18 -0
- package/src/Components/Icons/MobileScreenButton.js +14 -0
- package/src/Components/Icons/MoneyBill.js +14 -0
- package/src/Components/Icons/MoneyBillWave.js +14 -0
- package/src/Components/Icons/Mouth.js +24 -0
- package/src/Components/Icons/Music.js +14 -0
- package/src/Components/Icons/Na.js +17 -0
- package/src/Components/Icons/NoLoop.js +24 -0
- package/src/Components/Icons/NoReorderRows.js +25 -0
- package/src/Components/Icons/ObjectGroupRegular.js +14 -0
- package/src/Components/Icons/Pause.js +14 -0
- package/src/Components/Icons/Pencil.js +18 -0
- package/src/Components/Icons/Phone.js +14 -0
- package/src/Components/Icons/Play.js +14 -0
- package/src/Components/Icons/Plus.js +18 -0
- package/src/Components/Icons/Presentation.js +19 -0
- package/src/Components/Icons/Print.js +18 -0
- package/src/Components/Icons/Question.js +14 -0
- package/src/Components/Icons/Rate-.25x.js +20 -0
- package/src/Components/Icons/Rate-.5x.js +19 -0
- package/src/Components/Icons/Rate-.75x.js +19 -0
- package/src/Components/Icons/Rate-1.25x.js +20 -0
- package/src/Components/Icons/Rate-1.5x.js +19 -0
- package/src/Components/Icons/Rate-1.75x.js +19 -0
- package/src/Components/Icons/Rate-1x.js +19 -0
- package/src/Components/Icons/Rate-2x.js +19 -0
- package/src/Components/Icons/RateIcon-.25x.js +20 -0
- package/src/Components/Icons/RateIcon-.5x.js +19 -0
- package/src/Components/Icons/RateIcon-.75x.js +19 -0
- package/src/Components/Icons/RateIcon-1.25x.js +20 -0
- package/src/Components/Icons/RateIcon-1.5x.js +19 -0
- package/src/Components/Icons/RateIcon-1.75x.js +19 -0
- package/src/Components/Icons/RateIcon-1x.js +19 -0
- package/src/Components/Icons/RateIcon-2x.js +19 -0
- package/src/Components/Icons/RectangleXmark.js +14 -0
- package/src/Components/Icons/RectangleXmarkRegular.js +14 -0
- package/src/Components/Icons/ReorderRows.js +21 -0
- package/src/Components/Icons/RightFromBracket.js +14 -0
- package/src/Components/Icons/RightToBracket.js +14 -0
- package/src/Components/Icons/Rotate.js +18 -0
- package/src/Components/Icons/RotateLeft.js +14 -0
- package/src/Components/Icons/RotateRight.js +18 -0
- package/src/Components/Icons/ScrewdriverWrench.js +14 -0
- package/src/Components/Icons/Scroll.js +14 -0
- package/src/Components/Icons/Share.js +14 -0
- package/src/Components/Icons/Shop.js +14 -0
- package/src/Components/Icons/SortDown.js +14 -0
- package/src/Components/Icons/SortUp.js +18 -0
- package/src/Components/Icons/Square.js +14 -0
- package/src/Components/Icons/SquareCheck.js +14 -0
- package/src/Components/Icons/SquareCheckRegular.js +14 -0
- package/src/Components/Icons/SquareMinus.js +18 -0
- package/src/Components/Icons/SquareRegular.js +14 -0
- package/src/Components/Icons/Store.js +14 -0
- package/src/Components/Icons/ThumbsDown.js +14 -0
- package/src/Components/Icons/ThumbsDownRegular.js +14 -0
- package/src/Components/Icons/ThumbsUp.js +14 -0
- package/src/Components/Icons/ThumbsUpRegular.js +14 -0
- package/src/Components/Icons/Trash.js +18 -0
- package/src/Components/Icons/TrashCan.js +18 -0
- package/src/Components/Icons/TriangleExclamation.js +18 -0
- package/src/Components/Icons/Truck.js +14 -0
- package/src/Components/Icons/TruckFast.js +14 -0
- package/src/Components/Icons/User.js +14 -0
- package/src/Components/Icons/UserGroup.js +14 -0
- package/src/Components/Icons/UserPlus.js +14 -0
- package/src/Components/Icons/UserSecret.js +14 -0
- package/src/Components/Icons/X.js +14 -0
- package/src/Components/Icons/Xmark.js +14 -0
- package/src/Components/Tree/Tree.js +463 -133
- package/src/Components/Tree/TreeNode.js +53 -54
- package/src/Constants/Styles.js +10 -0
- 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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
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
|
-
|
|
238
|
-
const 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
351
|
-
|
|
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
|
-
|
|
474
|
+
if (_.isEmpty(datum.children)) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
354
477
|
|
|
355
|
-
|
|
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
|
-
|
|
361
|
-
|
|
488
|
+
// Button handlers
|
|
489
|
+
onToggle = (datum) => {
|
|
490
|
+
datum.isExpanded = !datum.isExpanded;
|
|
491
|
+
forceUpdate();
|
|
362
492
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
509
|
+
// Hide loading indicator
|
|
510
|
+
|
|
371
511
|
},
|
|
372
|
-
|
|
373
|
-
//
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
//
|
|
377
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
|
|
674
|
-
setTreeNodeData(
|
|
944
|
+
const treeNodeData = buildTreeNodeData(rootNodes);
|
|
945
|
+
setTreeNodeData(treeNodeData);
|
|
675
946
|
}
|
|
676
947
|
|
|
677
948
|
if (!isReady) {
|
|
678
|
-
|
|
679
|
-
|
|
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
|
|
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 =
|
|
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
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1093
|
+
// Close the modal
|
|
1094
|
+
setIsSearchModalShown(false);
|
|
1095
|
+
}}
|
|
1096
|
+
/>
|
|
1097
|
+
</Column>
|
|
1098
|
+
</Modal>
|
|
1099
|
+
</>;
|
|
770
1100
|
|
|
771
1101
|
}
|
|
772
1102
|
|