@onehat/ui 0.2.56 → 0.2.58

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 (163) hide show
  1. package/cypress/plugins/index.js +2 -0
  2. package/cypress/support/commands.js +2 -0
  3. package/cypress/support/e2e.js +2 -0
  4. package/cypress.config.js +41 -0
  5. package/package.json +34 -25
  6. package/src/Components/Form/Form.js +1 -1
  7. package/src/Components/Grid/Grid.js +12 -5
  8. package/src/Components/Grid/GridHeaderRow.js +5 -2
  9. package/src/Components/Grid/GridRow.js +2 -0
  10. package/src/Components/Hoc/withContextMenu.js +1 -1
  11. package/src/Components/Messages/WaitMessage.js +19 -7
  12. package/src/Components/Tree/Tree.js +813 -0
  13. package/src/Components/Tree/TreeNode.js +95 -0
  14. package/src/Constants/Styles.js +4 -0
  15. package/src/Functions/BankersRound.js +1 -1
  16. package/src/Functions/getTokenHeaders.js +2 -2
  17. package/src/UiGlobals.js +6 -1
  18. package/src/Components/Icons/AddressBook.js +0 -14
  19. package/src/Components/Icons/Alt.js +0 -17
  20. package/src/Components/Icons/AngleLeft.js +0 -18
  21. package/src/Components/Icons/AngleRight.js +0 -18
  22. package/src/Components/Icons/AnglesLeft.js +0 -18
  23. package/src/Components/Icons/AnglesRight.js +0 -18
  24. package/src/Components/Icons/Asterisk.js +0 -14
  25. package/src/Components/Icons/Ban.js +0 -18
  26. package/src/Components/Icons/Bars.js +0 -14
  27. package/src/Components/Icons/BigCircle.js +0 -17
  28. package/src/Components/Icons/Book.js +0 -14
  29. package/src/Components/Icons/BookOpen.js +0 -14
  30. package/src/Components/Icons/Bug.js +0 -14
  31. package/src/Components/Icons/Building.js +0 -14
  32. package/src/Components/Icons/Calendar.js +0 -18
  33. package/src/Components/Icons/Calendar2.js +0 -18
  34. package/src/Components/Icons/CalendarDays.js +0 -18
  35. package/src/Components/Icons/Camera.js +0 -18
  36. package/src/Components/Icons/CaretDown.js +0 -18
  37. package/src/Components/Icons/CaretUp.js +0 -18
  38. package/src/Components/Icons/CartPlus.js +0 -14
  39. package/src/Components/Icons/CartShopping.js +0 -14
  40. package/src/Components/Icons/CashRegister.js +0 -14
  41. package/src/Components/Icons/ChartLine.js +0 -14
  42. package/src/Components/Icons/Check.js +0 -14
  43. package/src/Components/Icons/CheckDouble.js +0 -14
  44. package/src/Components/Icons/ChevronDown.js +0 -14
  45. package/src/Components/Icons/ChevronLeft.js +0 -14
  46. package/src/Components/Icons/ChevronRight.js +0 -14
  47. package/src/Components/Icons/ChevronUp.js +0 -14
  48. package/src/Components/Icons/CircleArrowRight.js +0 -14
  49. package/src/Components/Icons/CircleExclamation.js +0 -18
  50. package/src/Components/Icons/CircleInfo.js +0 -14
  51. package/src/Components/Icons/CircleQuestion.js +0 -14
  52. package/src/Components/Icons/CircleXmark.js +0 -14
  53. package/src/Components/Icons/CircleXmarkRegular.js +0 -14
  54. package/src/Components/Icons/Clipboard.js +0 -18
  55. package/src/Components/Icons/Clock.js +0 -14
  56. package/src/Components/Icons/ClockRegular.js +0 -14
  57. package/src/Components/Icons/ClockRotateLeft.js +0 -14
  58. package/src/Components/Icons/Clone.js +0 -14
  59. package/src/Components/Icons/Comment.js +0 -14
  60. package/src/Components/Icons/CommentRegular.js +0 -14
  61. package/src/Components/Icons/Comments.js +0 -14
  62. package/src/Components/Icons/CommentsRegular.js +0 -14
  63. package/src/Components/Icons/Copyright.js +0 -14
  64. package/src/Components/Icons/Duplicate.js +0 -18
  65. package/src/Components/Icons/Edit.js +0 -18
  66. package/src/Components/Icons/EllipsisVertical.js +0 -18
  67. package/src/Components/Icons/Envelope.js +0 -14
  68. package/src/Components/Icons/EnvelopeRegular.js +0 -14
  69. package/src/Components/Icons/Exclamation.js +0 -14
  70. package/src/Components/Icons/Expand.js +0 -14
  71. package/src/Components/Icons/Eye.js +0 -18
  72. package/src/Components/Icons/EyeSlash.js +0 -14
  73. package/src/Components/Icons/File.js +0 -18
  74. package/src/Components/Icons/FloppyDiskRegular.js +0 -14
  75. package/src/Components/Icons/Gear.js +0 -18
  76. package/src/Components/Icons/Gift.js +0 -14
  77. package/src/Components/Icons/Grip.js +0 -18
  78. package/src/Components/Icons/GripLines.js +0 -18
  79. package/src/Components/Icons/GripLinesVertical.js +0 -18
  80. package/src/Components/Icons/GripVertical.js +0 -18
  81. package/src/Components/Icons/Hammer.js +0 -14
  82. package/src/Components/Icons/Hand.js +0 -14
  83. package/src/Components/Icons/House.js +0 -14
  84. package/src/Components/Icons/Info.js +0 -14
  85. package/src/Components/Icons/ItunesNote.js +0 -14
  86. package/src/Components/Icons/List.js +0 -14
  87. package/src/Components/Icons/ListCheck.js +0 -14
  88. package/src/Components/Icons/LocationDot.js +0 -14
  89. package/src/Components/Icons/Loop.js +0 -17
  90. package/src/Components/Icons/Loop1.js +0 -18
  91. package/src/Components/Icons/LoopAll.js +0 -18
  92. package/src/Components/Icons/Maximize.js +0 -14
  93. package/src/Components/Icons/Microphone.js +0 -14
  94. package/src/Components/Icons/Minimize.js +0 -14
  95. package/src/Components/Icons/Minus.js +0 -18
  96. package/src/Components/Icons/MobileScreenButton.js +0 -14
  97. package/src/Components/Icons/MoneyBill.js +0 -14
  98. package/src/Components/Icons/MoneyBillWave.js +0 -14
  99. package/src/Components/Icons/Mouth.js +0 -24
  100. package/src/Components/Icons/Music.js +0 -14
  101. package/src/Components/Icons/Na.js +0 -17
  102. package/src/Components/Icons/NoLoop.js +0 -24
  103. package/src/Components/Icons/NoReorderRows.js +0 -25
  104. package/src/Components/Icons/ObjectGroupRegular.js +0 -14
  105. package/src/Components/Icons/Pause.js +0 -14
  106. package/src/Components/Icons/Pencil.js +0 -18
  107. package/src/Components/Icons/Phone.js +0 -14
  108. package/src/Components/Icons/Play.js +0 -14
  109. package/src/Components/Icons/Plus.js +0 -18
  110. package/src/Components/Icons/Presentation.js +0 -19
  111. package/src/Components/Icons/Print.js +0 -18
  112. package/src/Components/Icons/Question.js +0 -14
  113. package/src/Components/Icons/Rate-.25x.js +0 -20
  114. package/src/Components/Icons/Rate-.5x.js +0 -19
  115. package/src/Components/Icons/Rate-.75x.js +0 -19
  116. package/src/Components/Icons/Rate-1.25x.js +0 -20
  117. package/src/Components/Icons/Rate-1.5x.js +0 -19
  118. package/src/Components/Icons/Rate-1.75x.js +0 -19
  119. package/src/Components/Icons/Rate-1x.js +0 -19
  120. package/src/Components/Icons/Rate-2x.js +0 -19
  121. package/src/Components/Icons/RateIcon-.25x.js +0 -20
  122. package/src/Components/Icons/RateIcon-.5x.js +0 -19
  123. package/src/Components/Icons/RateIcon-.75x.js +0 -19
  124. package/src/Components/Icons/RateIcon-1.25x.js +0 -20
  125. package/src/Components/Icons/RateIcon-1.5x.js +0 -19
  126. package/src/Components/Icons/RateIcon-1.75x.js +0 -19
  127. package/src/Components/Icons/RateIcon-1x.js +0 -19
  128. package/src/Components/Icons/RateIcon-2x.js +0 -19
  129. package/src/Components/Icons/RectangleXmark.js +0 -14
  130. package/src/Components/Icons/RectangleXmarkRegular.js +0 -14
  131. package/src/Components/Icons/ReorderRows.js +0 -21
  132. package/src/Components/Icons/RightFromBracket.js +0 -14
  133. package/src/Components/Icons/RightToBracket.js +0 -14
  134. package/src/Components/Icons/Rotate.js +0 -18
  135. package/src/Components/Icons/RotateLeft.js +0 -14
  136. package/src/Components/Icons/RotateRight.js +0 -18
  137. package/src/Components/Icons/ScrewdriverWrench.js +0 -14
  138. package/src/Components/Icons/Scroll.js +0 -14
  139. package/src/Components/Icons/Share.js +0 -14
  140. package/src/Components/Icons/Shop.js +0 -14
  141. package/src/Components/Icons/SortDown.js +0 -14
  142. package/src/Components/Icons/SortUp.js +0 -18
  143. package/src/Components/Icons/Square.js +0 -14
  144. package/src/Components/Icons/SquareCheck.js +0 -14
  145. package/src/Components/Icons/SquareCheckRegular.js +0 -14
  146. package/src/Components/Icons/SquareMinus.js +0 -18
  147. package/src/Components/Icons/SquareRegular.js +0 -14
  148. package/src/Components/Icons/Store.js +0 -14
  149. package/src/Components/Icons/ThumbsDown.js +0 -14
  150. package/src/Components/Icons/ThumbsDownRegular.js +0 -14
  151. package/src/Components/Icons/ThumbsUp.js +0 -14
  152. package/src/Components/Icons/ThumbsUpRegular.js +0 -14
  153. package/src/Components/Icons/Trash.js +0 -18
  154. package/src/Components/Icons/TrashCan.js +0 -18
  155. package/src/Components/Icons/TriangleExclamation.js +0 -18
  156. package/src/Components/Icons/Truck.js +0 -14
  157. package/src/Components/Icons/TruckFast.js +0 -14
  158. package/src/Components/Icons/User.js +0 -14
  159. package/src/Components/Icons/UserGroup.js +0 -14
  160. package/src/Components/Icons/UserPlus.js +0 -14
  161. package/src/Components/Icons/UserSecret.js +0 -14
  162. package/src/Components/Icons/X.js +0 -14
  163. package/src/Components/Icons/Xmark.js +0 -14
@@ -0,0 +1,813 @@
1
+ import React, { useState, useEffect, useRef, useMemo, } from 'react';
2
+ import {
3
+ Column,
4
+ FlatList,
5
+ Pressable,
6
+ Icon,
7
+ Row,
8
+ Text,
9
+ } from 'native-base';
10
+ import {
11
+ SELECTION_MODE_SINGLE,
12
+ SELECTION_MODE_MULTI,
13
+ } from '../../Constants/Selection.js';
14
+ import {
15
+ VERTICAL,
16
+ } from '../../Constants/Directions.js';
17
+ import {
18
+ DROP_POSITION_BEFORE,
19
+ DROP_POSITION_AFTER,
20
+ } from '../../Constants/Tree.js';
21
+ import * as colourMixer from '@k-renwick/colour-mixer'
22
+ import UiGlobals from '../../UiGlobals.js';
23
+ import useForceUpdate from '../../Hooks/useForceUpdate.js';
24
+ import withContextMenu from '../Hoc/withContextMenu.js';
25
+ import withAlert from '../Hoc/withAlert.js';
26
+ import withData from '../Hoc/withData.js';
27
+ import withEvents from '../Hoc/withEvents.js';
28
+ import withSideEditor from '../Hoc/withSideEditor.js';
29
+ import withFilters from '../Hoc/withFilters.js';
30
+ import withPresetButtons from '../Hoc/withPresetButtons.js';
31
+ import withMultiSelection from '../Hoc/withMultiSelection.js';
32
+ import withSelection from '../Hoc/withSelection.js';
33
+ import withWindowedEditor from '../Hoc/withWindowedEditor.js';
34
+ import withInlineEditor from '../Hoc/withInlineEditor.js';
35
+ import testProps from '../../Functions/testProps.js';
36
+ import nbToRgb from '../../Functions/nbToRgb.js';
37
+ import TreeHeaderRow from './TreeHeaderRow.js';
38
+ import TreeNode, { ReorderableTreeNode } from './TreeNode.js';
39
+ import IconButton from '../Buttons/IconButton.js';
40
+ import PaginationToolbar from '../Toolbar/PaginationToolbar.js';
41
+ import NoRecordsFound from './NoRecordsFound.js';
42
+ import Toolbar from '../Toolbar/Toolbar.js';
43
+ import NoReorderRows from '../Icons/NoReorderRows.js';
44
+ import ReorderRows from '../Icons/ReorderRows.js';
45
+ import _ from 'lodash';
46
+
47
+
48
+ // Tree requires the use of HOC withSelection() whenever it's used.
49
+ // The default export is *with* the HOC. A separate *raw* component is
50
+ // exported which can be combined with many HOCs for various functionality.
51
+
52
+ export function Tree(props) {
53
+ const {
54
+ 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.
56
+ return {};
57
+ },
58
+ getNodeText = (item) => { // extracts model/data and decides what the row text should be
59
+ return item.displayValue;
60
+ },
61
+ getNodeType, // extracts model/data and decides what kind of node this should be. Helper for getNodeIcon
62
+ getNodeIcon,
63
+ nodeProps = (item) => {
64
+ return {};
65
+ },
66
+ noneFoundText,
67
+ disableLoadingIndicator = false,
68
+ disableSelectorSelected = false,
69
+ showHovers = true,
70
+ canNodesReorder = false,
71
+ allowToggleSelection = true, // i.e. single click with no shift key toggles the selection of the node clicked on
72
+ disableBottomToolbar = false,
73
+ bottomToolbar = null,
74
+ topToolbar = null,
75
+ additionalToolbarButtons = [],
76
+
77
+ // withEditor
78
+ onAdd,
79
+ onEdit,
80
+ onDelete,
81
+ onView,
82
+ onDuplicate,
83
+ onReset,
84
+ onContextMenu,
85
+
86
+ // withData
87
+ Repository,
88
+ data,
89
+ fields,
90
+ idField,
91
+ displayField,
92
+ idIx,
93
+ displayIx,
94
+
95
+ // withSelection
96
+ selection,
97
+ setSelection,
98
+ selectionMode,
99
+ removeFromSelection,
100
+ addToSelection,
101
+ deselectAll,
102
+ selectRangeTo,
103
+ isInSelection,
104
+ noSelectorMeansNoResults = false,
105
+
106
+ // DataMgt
107
+ selectorId,
108
+ selectorSelected,
109
+
110
+ } = props,
111
+ styles = UiGlobals.styles,
112
+ forceUpdate = useForceUpdate(),
113
+ treeRef = useRef(),
114
+ [isReady, setIsReady] = useState(false),
115
+ [isLoading, setIsLoading] = useState(false),
116
+ [isReorderMode, setIsReorderMode] = useState(false),
117
+ [treeNodeData, setTreeNodeData] = useState({}),
118
+ [dragNodeSlot, setDragNodeSlot] = useState(null),
119
+ [dragNodeIx, setDragNodeIx] = useState(),
120
+ onNodeClick = (item, e) => {
121
+ const
122
+ {
123
+ shiftKey,
124
+ metaKey,
125
+ } = e;
126
+
127
+ if (selectionMode === SELECTION_MODE_MULTI) {
128
+ if (shiftKey) {
129
+ if (isInSelection(item)) {
130
+ removeFromSelection(item);
131
+ } else {
132
+ selectRangeTo(item);
133
+ }
134
+ } else if (metaKey) {
135
+ if (isInSelection(item)) {
136
+ // Already selected
137
+ if (allowToggleSelection) {
138
+ removeFromSelection(item);
139
+ } else {
140
+ // Do nothing.
141
+ }
142
+ } else {
143
+ addToSelection(item);
144
+ }
145
+ } else {
146
+ if (isInSelection(item)) {
147
+ // Already selected
148
+ if (allowToggleSelection) {
149
+ removeFromSelection(item);
150
+ } else {
151
+ // Do nothing.
152
+ }
153
+ } else {
154
+ // select just this one
155
+ setSelection([item]);
156
+ }
157
+ }
158
+ } else {
159
+ // selectionMode is SELECTION_MODE_SINGLE
160
+ let newSelection = selection;
161
+ if (isInSelection(item)) {
162
+ // Already selected
163
+ if (allowToggleSelection) {
164
+ // Create empty selection
165
+ newSelection = [];
166
+ } else {
167
+ // Do nothing.
168
+ }
169
+ } else {
170
+ // Select it alone
171
+ newSelection = [item];
172
+ }
173
+ if (newSelection) {
174
+ setSelection(newSelection);
175
+ }
176
+ }
177
+ },
178
+ onRefresh = () => {
179
+ if (!Repository) {
180
+ return;
181
+ }
182
+ const promise = Repository.reload();
183
+ if (promise) { // Some repository types don't use promises
184
+ promise.then(() => {
185
+ setIsLoading(false);
186
+ forceUpdate();
187
+ });
188
+ }
189
+ },
190
+ getFooterToolbarItems = () => {
191
+ const
192
+ iconButtonProps = {
193
+ _hover: {
194
+ bg: 'trueGray.400',
195
+ },
196
+ mx: 1,
197
+ px: 3,
198
+ },
199
+ iconProps = {
200
+ alignSelf: 'center',
201
+ size: styles.TREE_TOOLBAR_ITEMS_ICON_SIZE,
202
+ h: 20,
203
+ 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
+ />);
234
+ }
235
+ return items;
236
+ },
237
+ renderNode = (itemData) => {
238
+ const item = itemData.item;
239
+ if (item.isDestroyed) {
240
+ return null;
241
+ }
242
+
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);
252
+
253
+ return <Pressable
254
+ // {...testProps(Repository ? Repository.schema.name + '-' + item.id : item.id)}
255
+ onPress={(e) => {
256
+ if (e.preventDefault && e.cancelable) {
257
+ e.preventDefault();
258
+ }
259
+ if (isReorderMode) {
260
+ return
261
+ }
262
+ switch (e.detail) {
263
+ case 1: // single click
264
+ onNodeClick(item, e); // sets selection
265
+ break;
266
+ case 2: // double click
267
+ if (!isSelected) { // If a row was already selected when double-clicked, the first click will deselect it,
268
+ onNodeClick(item, e); // so reselect it
269
+ }
270
+ if (onEdit) {
271
+ onEdit();
272
+ }
273
+ break;
274
+ case 3: // triple click
275
+ break;
276
+ default:
277
+ }
278
+ }}
279
+ onLongPress={(e) => {
280
+ if (e.preventDefault && e.cancelable) {
281
+ e.preventDefault();
282
+ }
283
+ if (isReorderMode) {
284
+ return
285
+ }
286
+
287
+ // context menu
288
+ const selection = [item];
289
+ setSelection(selection);
290
+ if (onContextMenu) {
291
+ onContextMenu(item, e, selection, setSelection);
292
+ }
293
+ }}
294
+ flexDirection="row"
295
+ flexGrow={1}
296
+ >
297
+ {({
298
+ isHovered,
299
+ isFocused,
300
+ isPressed,
301
+ }) => {
302
+ let bg = nodeProps.bg || styles.TREE_NODE_BG,
303
+ mixWith;
304
+ if (isSelected) {
305
+ if (showHovers && isHovered) {
306
+ mixWith = styles.TREE_NODE_SELECTED_HOVER_BG;
307
+ } else {
308
+ mixWith = styles.TREE_NODE_SELECTED_BG;
309
+ }
310
+ } else if (showHovers && isHovered) {
311
+ mixWith = styles.TREE_NODE_HOVER_BG;
312
+ }
313
+ if (mixWith) {
314
+ const
315
+ mixWithObj = nbToRgb(mixWith),
316
+ ratio = mixWithObj.alpha ? 1 - mixWithObj.alpha : 0.5;
317
+ bg = colourMixer.blend(bg, ratio, mixWithObj.color);
318
+ }
319
+ let WhichTreeNode = TreeNode,
320
+ rowReorderProps = {};
321
+ if (canNodesReorder && isReorderMode) {
322
+ WhichTreeNode = ReorderableTreeNode;
323
+ rowReorderProps = {
324
+ mode: VERTICAL,
325
+ onDragStart: onNodeReorderDragStart,
326
+ onDrag: onNodeReorderDrag,
327
+ onDragStop: onNodeReorderDragStop,
328
+ proxyParent: treeRef.current?.getScrollableNode().children[0],
329
+ proxyPositionRelativeToParent: true,
330
+ getParentNode: (node) => node.parentElement.parentElement.parentElement,
331
+ getProxy: getReorderProxy,
332
+ };
333
+ }
334
+
335
+ return <WhichTreeNode
336
+ nodeProps={nodeProps}
337
+ bg={bg}
338
+ itemData={itemData}
339
+
340
+ icon={icon}
341
+ onToggle={onToggle}
342
+
343
+ // fields={fields}
344
+ // hideNavColumn={hideNavColumn}
345
+ {...rowReorderProps}
346
+ />;
347
+ }}
348
+ </Pressable>;
349
+ },
350
+ buildTreeNode = (itemData) => {
351
+ // this one is to be used recursively on children
352
+
353
+ // isVisible // skip if not visible, just return keyed array
354
+
355
+ // renderNode(itemData);
356
+
357
+ },
358
+ buildTreeNodes = () => {
359
+
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
362
+
363
+ const
364
+ rootEntity = Repository.getRootEntity(),
365
+ rootNode = buildTreeNode(rootEntity);
366
+
367
+
368
+
369
+
370
+ return rootNode;
371
+ },
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)
378
+
379
+ },
380
+
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 = () => {},
387
+
388
+ // Drag/Drop
389
+ getReorderProxy = (node) => {
390
+ const
391
+ row = node.parentElement.parentElement,
392
+ rowRect = row.getBoundingClientRect(),
393
+ parent = row.parentElement,
394
+ parentRect = parent.getBoundingClientRect(),
395
+ proxy = row.cloneNode(true),
396
+ top = rowRect.top - parentRect.top,
397
+ dragNodeIx = Array.from(parent.children).indexOf(row)
398
+
399
+ setDragNodeIx(dragNodeIx); // the ix of which record is being dragged
400
+
401
+ proxy.style.top = top + 'px';
402
+ proxy.style.left = '20px';
403
+ proxy.style.height = rowRect.height + 'px';
404
+ proxy.style.width = rowRect.width + 'px';
405
+ proxy.style.display = 'flex';
406
+ // proxy.style.backgroundColor = '#ccc';
407
+ proxy.style.position = 'absolute';
408
+ proxy.style.border = '1px solid #000';
409
+ return proxy;
410
+ },
411
+ onNodeReorderDragStart = (info, e, proxy, node) => {
412
+ // console.log('onNodeReorderDragStart', info, e, proxy, node);
413
+ const
414
+ proxyRect = proxy.getBoundingClientRect(),
415
+ row = node.parentElement.parentElement,
416
+ parent = row.parentElement,
417
+ parentRect = parent.getBoundingClientRect(),
418
+ rows = _.filter(row.parentElement.children, (childNode) => {
419
+ return childNode.getBoundingClientRect().height !== 0; // Skip zero-height children
420
+ }),
421
+ currentY = proxyRect.top - parentRect.top, // top position of pointer, relative to page
422
+ headerNodeIx = showHeaders ? 0 : null,
423
+ firstActualNodeIx = showHeaders ? 1 : 0;
424
+
425
+ // Figure out which index the user wants
426
+ let newIx = 0;
427
+ _.each(rows, (child, ix, all) => {
428
+ const
429
+ rect = child.getBoundingClientRect(), // rect of the row of this iteration
430
+ {
431
+ top,
432
+ bottom,
433
+ height,
434
+ } = rect,
435
+ compensatedTop = top - parentRect.top,
436
+ compensatedBottom = bottom - parentRect.top,
437
+ halfHeight = height / 2;
438
+
439
+ if (ix === headerNodeIx || child === proxy) {
440
+ return;
441
+ }
442
+ if (ix === firstActualNodeIx) {
443
+ // first row
444
+ if (currentY < compensatedTop + halfHeight) {
445
+ newIx = firstActualNodeIx;
446
+ return false;
447
+ } else if (currentY < compensatedBottom) {
448
+ newIx = firstActualNodeIx + 1;
449
+ return false;
450
+ }
451
+ return;
452
+ } else if (ix === all.length -1) {
453
+ // last row
454
+ if (currentY < compensatedTop + halfHeight) {
455
+ newIx = ix;
456
+ return false;
457
+ }
458
+ newIx = ix +1;
459
+ return false;
460
+ }
461
+
462
+ // all other rows
463
+ if (compensatedTop <= currentY && currentY < compensatedTop + halfHeight) {
464
+ newIx = ix;
465
+ return false;
466
+ } else if (currentY < compensatedBottom) {
467
+ newIx = ix +1;
468
+ return false;
469
+ }
470
+ });
471
+
472
+ let useBottom = false;
473
+ if (!rows[newIx] || rows[newIx] === proxy) {
474
+ newIx--;
475
+ useBottom = true;
476
+ }
477
+
478
+ // Render marker showing destination location
479
+ const
480
+ rowContainerRect = rows[newIx].getBoundingClientRect(),
481
+ top = (useBottom ? rowContainerRect.bottom : rowContainerRect.top) - parentRect.top - parseInt(parent.style.borderWidth), // get relative Y position
482
+ treeNodesContainer = treeRef.current._listRef._scrollRef.childNodes[0],
483
+ treeNodesContainerRect = treeNodesContainer.getBoundingClientRect(),
484
+ marker = document.createElement('div');
485
+
486
+ marker.style.position = 'absolute';
487
+ marker.style.top = top -4 + 'px'; // -4 so it's always visible
488
+ marker.style.height = '4px';
489
+ marker.style.width = treeNodesContainerRect.width + 'px';
490
+ marker.style.backgroundColor = '#f00';
491
+
492
+ treeNodesContainer.appendChild(marker);
493
+
494
+ setDragNodeSlot({ ix: newIx, marker, useBottom, });
495
+ },
496
+ onNodeReorderDrag = (info, e, proxy, node) => {
497
+ // console.log('onNodeReorderDrag', info, e, proxy, node);
498
+ const
499
+ proxyRect = proxy.getBoundingClientRect(),
500
+ row = node.parentElement.parentElement,
501
+ parent = row.parentElement,
502
+ parentRect = parent.getBoundingClientRect(),
503
+ rows = _.filter(row.parentElement.children, (childNode) => {
504
+ return childNode.getBoundingClientRect().height !== 0; // Skip zero-height children
505
+ }),
506
+ currentY = proxyRect.top - parentRect.top, // top position of pointer, relative to page
507
+ headerNodeIx = showHeaders ? 0 : null,
508
+ firstActualNodeIx = showHeaders ? 1 : 0;
509
+
510
+ // Figure out which index the user wants
511
+ let newIx = 0;
512
+ _.each(rows, (child, ix, all) => {
513
+ const
514
+ rect = child.getBoundingClientRect(), // rect of the row of this iteration
515
+ {
516
+ top,
517
+ bottom,
518
+ height,
519
+ } = rect,
520
+ compensatedTop = top - parentRect.top,
521
+ compensatedBottom = bottom - parentRect.top,
522
+ halfHeight = height / 2;
523
+
524
+ if (ix === headerNodeIx || child === proxy) {
525
+ return;
526
+ }
527
+ if (ix === firstActualNodeIx) {
528
+ // first row
529
+ if (currentY < compensatedTop + halfHeight) {
530
+ newIx = firstActualNodeIx;
531
+ return false;
532
+ } else if (currentY < compensatedBottom) {
533
+ newIx = firstActualNodeIx + 1;
534
+ return false;
535
+ }
536
+ return;
537
+ } else if (ix === all.length -1) {
538
+ // last row
539
+ if (currentY < compensatedTop + halfHeight) {
540
+ newIx = ix;
541
+ return false;
542
+ }
543
+ newIx = ix +1;
544
+ return false;
545
+ }
546
+
547
+ // all other rows
548
+ if (compensatedTop <= currentY && currentY < compensatedTop + halfHeight) {
549
+ newIx = ix;
550
+ return false;
551
+ } else if (currentY < compensatedBottom) {
552
+ newIx = ix +1;
553
+ return false;
554
+ }
555
+ });
556
+
557
+ let useBottom = false;
558
+ if (!rows[newIx] || rows[newIx] === proxy) {
559
+ newIx--;
560
+ useBottom = true;
561
+ }
562
+
563
+ // Render marker showing destination location (can't use regular render cycle because this div is absolutely positioned on page)
564
+ const
565
+ rowContainerRect = rows[newIx].getBoundingClientRect(),
566
+ top = (useBottom ? rowContainerRect.bottom : rowContainerRect.top) - parentRect.top - parseInt(parent.style.borderWidth); // get relative Y position
567
+ let marker = dragNodeSlot && dragNodeSlot.marker;
568
+ if (marker) {
569
+ marker.style.top = top -4 + 'px'; // -4 so it's always visible
570
+ }
571
+
572
+ setDragNodeSlot({ ix: newIx, marker, useBottom, });
573
+ // console.log('onNodeReorderDrag', newIx);
574
+
575
+ },
576
+ onNodeReorderDragStop = (delta, e, config) => {
577
+ // console.log('onNodeReorderDragStop', delta, e, config);
578
+ const
579
+ dropIx = dragNodeSlot.ix,
580
+ compensatedDragIx = showHeaders ? dragNodeIx -1 : dragNodeIx, // ix, without taking header row into account
581
+ compensatedDropIx = showHeaders ? dropIx -1 : dropIx, // // ix, without taking header row into account
582
+ dropPosition = dragNodeSlot.useBottom ? DROP_POSITION_AFTER : DROP_POSITION_BEFORE;
583
+
584
+ let shouldMove = true,
585
+ finalDropIx = compensatedDropIx;
586
+
587
+ if (dropPosition === DROP_POSITION_BEFORE) {
588
+ if (dragNodeIx === dropIx || dragNodeIx === dropIx -1) { // basically before or after the drag row's origin
589
+ // Same as origin; don't do anything
590
+ shouldMove = false;
591
+ } else {
592
+ // Actually move it
593
+ if (!Repository) { // If we're just going to be switching rows, rather than telling server to reorder rows, so maybe adjust finalDropIx...
594
+ if (finalDropIx > compensatedDragIx) { // if we're dropping *before* the origin ix
595
+ finalDropIx = finalDropIx -1; // Because we're using BEFORE, we want to switch with the row *prior to* the ix we're dropping before
596
+ }
597
+ }
598
+ }
599
+ } else if (dropPosition === DROP_POSITION_AFTER) {
600
+ // Only happens on the very last row. Everything else is BEFORE...
601
+ if (dragNodeIx === dropIx) {
602
+ // Same as origin; don't do anything
603
+ shouldMove = false;
604
+ }
605
+ }
606
+
607
+ if (shouldMove) {
608
+ // Update the row with the new ix
609
+ let dragRecord,
610
+ dropRecord;
611
+ if (Repository) {
612
+ dragRecord = Repository.getByIx(compensatedDragIx);
613
+ dropRecord = Repository.getByIx(finalDropIx);
614
+
615
+ Repository.reorder(dragRecord, dropRecord, dropPosition);
616
+
617
+ } else {
618
+ function arrayMove(arr, fromIndex, toIndex) {
619
+ var element = arr[fromIndex];
620
+ arr.splice(fromIndex, 1);
621
+ arr.splice(toIndex, 0, element);
622
+ }
623
+ arrayMove(data, compensatedDragIx, finalDropIx);
624
+ }
625
+ }
626
+
627
+ if (dragNodeSlot) {
628
+ dragNodeSlot.marker.remove();
629
+ }
630
+ setDragNodeSlot(null);
631
+ };
632
+
633
+ useEffect(() => {
634
+
635
+
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
+ }
672
+
673
+ function buildAndSetTreeNodeData() {
674
+ setTreeNodeData(buildTreeNodeData());
675
+ }
676
+
677
+ if (!isReady) {
678
+ buildAndSetTreeNodeData();
679
+ setIsReady(true);
680
+ }
681
+ if (!Repository) {
682
+ return () => {};
683
+ }
684
+
685
+ // set up @onehat/data repository
686
+ const
687
+ setTrue = () => setIsLoading(true),
688
+ setFalse = () => setIsLoading(false),
689
+ onChangeFilters = () => {
690
+ if (!Repository.isAutoLoad) {
691
+ Repository.reload();
692
+ }
693
+ },
694
+ onChangeSorters = () => {
695
+ if (!Repository.isAutoLoad) {
696
+ Repository.reload();
697
+ }
698
+ };
699
+
700
+ Repository.on('beforeLoad', setTrue);
701
+ Repository.on('load', setFalse);
702
+ Repository.ons(['changePage', 'changePageSize',], deselectAll);
703
+ Repository.ons(['changeData', 'change'], buildAndSetTreeNodeData);
704
+ Repository.on('changeFilters', onChangeFilters);
705
+ Repository.on('changeSorters', onChangeSorters);
706
+
707
+
708
+ return () => {
709
+ Repository.off('beforeLoad', setTrue);
710
+ Repository.off('load', setFalse);
711
+ Repository.offs(['changePage', 'changePageSize',], deselectAll);
712
+ Repository.offs(['changeData', 'change'], buildAndSetTreeNodeData);
713
+ Repository.off('changeFilters', onChangeFilters);
714
+ Repository.off('changeSorters', onChangeSorters);
715
+ };
716
+ }, []);
717
+
718
+ useEffect(() => {
719
+ if (!Repository) {
720
+ return () => {};
721
+ }
722
+ if (!disableSelectorSelected && selectorId) {
723
+ let id = selectorSelected?.id;
724
+ if (_.isEmpty(selectorSelected)) {
725
+ id = noSelectorMeansNoResults ? 'NO_MATCHES' : null;
726
+ }
727
+ Repository.filter(selectorId, id, false); // so it doesn't clear existing filters
728
+ }
729
+
730
+ }, [selectorId, selectorSelected]);
731
+
732
+ const footerToolbarItemComponents = useMemo(() => getFooterToolbarItems(), [additionalToolbarButtons, isReorderMode]);
733
+
734
+ if (!isReady) {
735
+ return null;
736
+ }
737
+
738
+ // Actual TreeNodes
739
+ const treeNodes = buildTreeNodes();
740
+
741
+ // headers & footers
742
+ let treeFooterComponent = null;
743
+ if (!disableBottomToolbar) {
744
+ if (Repository && bottomToolbar === 'pagination' && !disablePagination && Repository.isPaginated) {
745
+ treeFooterComponent = <PaginationToolbar Repository={Repository} toolbarItems={footerToolbarItemComponents} />;
746
+ } else if (footerToolbarItemComponents.length) {
747
+ treeFooterComponent = <Toolbar>{footerToolbarItemComponents}</Toolbar>;
748
+ }
749
+ }
750
+
751
+ return <Column
752
+ {...testProps('Tree')}
753
+ flex={1}
754
+ w="100%"
755
+ >
756
+ {topToolbar}
757
+
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}
765
+ </Column>
766
+
767
+ {treeFooterComponent}
768
+
769
+ </Column>;
770
+
771
+ }
772
+
773
+ export const SideTreeEditor = withAlert(
774
+ withEvents(
775
+ withData(
776
+ // withMultiSelection(
777
+ withSelection(
778
+ withSideEditor(
779
+ withFilters(
780
+ withPresetButtons(
781
+ withContextMenu(
782
+ Tree
783
+ )
784
+ )
785
+ )
786
+ )
787
+ )
788
+ // )
789
+ )
790
+ )
791
+ );
792
+
793
+ export const WindowedTreeEditor = withAlert(
794
+ withEvents(
795
+ withData(
796
+ // withMultiSelection(
797
+ withSelection(
798
+ withWindowedEditor(
799
+ withFilters(
800
+ withPresetButtons(
801
+ withContextMenu(
802
+ Tree
803
+ )
804
+ )
805
+ )
806
+ )
807
+ )
808
+ // )
809
+ )
810
+ )
811
+ );
812
+
813
+ export default WindowedTreeEditor;