@onehat/ui 0.4.82 → 0.4.84

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.
@@ -77,6 +77,7 @@ import Toolbar from '../Toolbar/Toolbar.js';
77
77
  import NoReorderRows from '../Icons/NoReorderRows.js';
78
78
  import ReorderRows from '../Icons/ReorderRows.js';
79
79
  import Unauthorized from '../Messages/Unauthorized.js';
80
+ import Mask from '../Panel/Mask.js';
80
81
  import _ from 'lodash';
81
82
 
82
83
 
@@ -133,6 +134,7 @@ function GridComponent(props) {
133
134
  flatListProps = {},
134
135
  onRowPress,
135
136
  onRender,
137
+ onLayout,
136
138
  disableLoadOnRender = false,
137
139
  forceLoadOnRender = false,
138
140
  pullToRefresh = true,
@@ -146,6 +148,7 @@ function GridComponent(props) {
146
148
  showSelectHandle = true,
147
149
  isRowSelectable = true,
148
150
  isRowHoverable = true,
151
+ isDisabled = false,
149
152
  canColumnsSort = true,
150
153
  canColumnsReorder = true,
151
154
  canColumnsResize = true,
@@ -521,6 +524,33 @@ function GridComponent(props) {
521
524
  onContextMenu(item, e, newSelection);
522
525
  }
523
526
  }}
527
+ onContextMenu={(e) => {
528
+ // web only; happens before onLongPress triggers
529
+ // different behavior here than onLongPress:
530
+ // if user clicks on a header row or phantom record, or if onContextMenu is not set, pass to the browser's context menu
531
+ if (isHeaderRow || isReorderMode) {
532
+ return
533
+ }
534
+ if (selection && selection[0] && selection[0].isRemotePhantom) {
535
+ return; // block context menu or changing selection when a remote phantom is already selected
536
+ }
537
+ if (onContextMenu) {
538
+ e.preventDefault();
539
+ e.stopPropagation(); // disallow browser's default behavior for context menu
540
+
541
+ // if the right-clicked item is not in the current selection,
542
+ // set the selection only to this one item.
543
+ let newSelection = selection;
544
+ if (!isInSelection(item)) {
545
+ newSelection = [item];
546
+ if (!disableWithSelection) {
547
+ setSelection(newSelection);
548
+ }
549
+ }
550
+
551
+ onContextMenu(item, e, newSelection);
552
+ }
553
+ }}
524
554
  className={clsx(
525
555
  'Pressable',
526
556
  'Row',
@@ -1092,6 +1122,9 @@ function GridComponent(props) {
1092
1122
  if (!Repository || Repository.isDestroyed) { // This method gets delayed, so it's possible for Repository to have been destroyed. Check for this
1093
1123
  return;
1094
1124
  }
1125
+ if (onLayout) {
1126
+ onLayout(e);
1127
+ }
1095
1128
  if (DEBUG) {
1096
1129
  console.log(`${getMeasurementPhase()}, adjustPageSizeToHeight A`);
1097
1130
  }
@@ -1675,51 +1708,55 @@ function GridComponent(props) {
1675
1708
  className += ' ' + props.className;
1676
1709
  }
1677
1710
 
1678
- grid = <VStackNative
1679
- {...testProps(self)}
1680
- ref={containerRef}
1681
- tabIndex={0}
1682
- onKeyDown={onGridKeyDown}
1683
- onLayout={(e) => debouncedAdjustPageSizeToHeight(e)}
1684
- className={className}
1685
- style={style}
1686
- >
1687
- {topToolbar &&
1688
- <VStack ref={topToolbarRef}>
1689
- {topToolbar}
1690
- </VStack>}
1691
-
1692
- <VStack
1693
- ref={gridContainerRef}
1694
- onClick={() => {
1695
- if (!isReorderMode && !isInlineEditorShown && deselectAll) {
1696
- deselectAll();
1697
- }
1698
- }}
1699
- className={clsx(
1700
- 'gridContainer',
1701
- 'w-full',
1702
- // 'h-full',
1703
- 'flex-1',
1704
- 'min-h-[40px]',
1705
- 'relative', // Enable positioning for overlay
1706
- gridContainerBorderClassName,
1707
- )}
1708
- >
1709
- {grid}
1710
- {/* Loading overlay during measurement phases to prevent visual flashing */}
1711
- {autoAdjustPageSizeToHeight &&
1712
- (getMeasurementPhase() === PHASES__INITIAL || getMeasurementPhase() === PHASES__MEASURING) &&
1713
- entities?.length > 0 && (
1714
- <VStack className="absolute inset-0 z-10 bg-white">
1715
- <Loading isScreen={true} />
1711
+ if (isDisabled) {
1712
+ grid = <Mask />;
1713
+ } else {
1714
+ grid = <VStackNative
1715
+ {...testProps(self)}
1716
+ ref={containerRef}
1717
+ tabIndex={0}
1718
+ onKeyDown={onGridKeyDown}
1719
+ onLayout={(e) => debouncedAdjustPageSizeToHeight(e)}
1720
+ className={className}
1721
+ style={style}
1722
+ >
1723
+ {topToolbar &&
1724
+ <VStack ref={topToolbarRef}>
1725
+ {topToolbar}
1726
+ </VStack>}
1727
+
1728
+ <VStack
1729
+ ref={gridContainerRef}
1730
+ onClick={() => {
1731
+ if (!isReorderMode && !isInlineEditorShown && deselectAll) {
1732
+ deselectAll();
1733
+ }
1734
+ }}
1735
+ className={clsx(
1736
+ 'gridContainer',
1737
+ 'w-full',
1738
+ // 'h-full',
1739
+ 'flex-1',
1740
+ 'min-h-[40px]',
1741
+ 'relative', // Enable positioning for overlay
1742
+ gridContainerBorderClassName,
1743
+ )}
1744
+ >
1745
+ {grid}
1746
+ {/* Loading overlay during measurement phases to prevent visual flashing */}
1747
+ {autoAdjustPageSizeToHeight &&
1748
+ (getMeasurementPhase() === PHASES__INITIAL || getMeasurementPhase() === PHASES__MEASURING) &&
1749
+ entities?.length > 0 && (
1750
+ <VStack className="absolute inset-0 z-10 bg-white">
1751
+ <Loading isScreen={true} />
1752
+ </VStack>
1753
+ )}
1716
1754
  </VStack>
1717
- )}
1718
- </VStack>
1719
1755
 
1720
- {listFooterComponent}
1756
+ {listFooterComponent}
1721
1757
 
1722
- </VStackNative>
1758
+ </VStackNative>;
1759
+ }
1723
1760
 
1724
1761
  if (isDropTarget) {
1725
1762
  grid = <VStackNative
@@ -285,6 +285,9 @@ const GridRow = forwardRef((props, ref) => {
285
285
  'py-3',
286
286
  'block',
287
287
  areCellsScrollable ? 'overflow-auto' : 'overflow-hidden',
288
+ '[&::-webkit-scrollbar]:h-2',
289
+ '[&::-webkit-scrollbar-thumb]:bg-gray-300',
290
+ '[&::-webkit-scrollbar-thumb]:rounded-full',
288
291
  colClassName,
289
292
  styles.GRID_CELL_CLASSNAME,
290
293
  styles.GRID_ROW_MAX_HEIGHT_NORMAL,
@@ -347,10 +350,17 @@ const GridRow = forwardRef((props, ref) => {
347
350
  if (config.getCellProps) {
348
351
  _.assign(elementProps, config.getCellProps(item));
349
352
  }
353
+
354
+ // TODO: incorporate better scrollbar formatting with
355
+ // tailwind plugin 'tailwind-scrollbar' (already installed, just not yet used here)
356
+
350
357
  let textClassName = clsx(
351
358
  'GridRow-TextNative',
352
359
  'self-center',
353
360
  areCellsScrollable ? 'overflow-auto' : 'overflow-hidden',
361
+ '[&::-webkit-scrollbar]:h-2',
362
+ '[&::-webkit-scrollbar-thumb]:bg-gray-300',
363
+ '[&::-webkit-scrollbar-thumb]:rounded-full',
354
364
  colClassName,
355
365
  styles.GRID_CELL_CLASSNAME,
356
366
  styles.GRID_ROW_MAX_HEIGHT_EXTRA,
@@ -32,6 +32,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
32
32
  secondaryUserCanEdit = true, // not permissions, but capability
33
33
  secondaryUserCanView = true,
34
34
  secondaryCanEditorViewOnly = false, // whether the editor can *ever* change state out of 'View' mode
35
+ secondaryCanProceedWithCrud, // fn returns bool on if the CRUD operation can proceed
35
36
  secondaryDisableAdd = false,
36
37
  secondaryDisableEdit = false,
37
38
  secondaryDisableDelete = false,
@@ -47,12 +48,14 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
47
48
  secondaryEditorType,
48
49
  secondaryOnAdd,
49
50
  secondaryOnChange, // any kind of crud change
51
+ secondaryOnBeforeDelete,
50
52
  secondaryOnDelete,
51
53
  secondaryOnSave, // this could also be called 'onEdit'
52
54
  secondaryOnEditorClose,
53
55
  secondaryNewEntityDisplayValue,
54
56
  secondaryNewEntityDisplayProperty, // in case the field to set for newEntityDisplayValue is different from model
55
57
  secondaryDefaultValues,
58
+ secondaryInitialEditorMode = EDITOR_MODE__VIEW,
56
59
  secondaryStayInEditModeOnSelectionChange = false,
57
60
 
58
61
  // withComponent
@@ -86,11 +89,11 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
86
89
  secondaryNewEntityDisplayValueRef = useRef(),
87
90
  secondaryEditorModeRef = useRef(EDITOR_MODE__VIEW),
88
91
  secondaryIsIgnoreNextSelectionChangeRef = useRef(false),
92
+ secondaryIsEditorShownRef = useRef(false),
89
93
  secondaryModel = SecondaryRepository?.schema?.name,
90
94
  [secondaryCurrentRecord, secondarySetCurrentRecord] = useState(null),
91
95
  [secondaryIsAdding, setIsAdding] = useState(false),
92
96
  [secondaryIsSaving, setIsSaving] = useState(false),
93
- [secondaryIsEditorShown, secondarySetIsEditorShownRaw] = useState(false),
94
97
  [secondaryIsEditorViewOnly, setIsEditorViewOnly] = useState(secondaryCanEditorViewOnly), // current state of whether editor is in view-only mode
95
98
  [secondaryLastSelection, setLastSelection] = useState(),
96
99
  secondarySetIsIgnoreNextSelectionChange = (bool) => {
@@ -100,11 +103,24 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
100
103
  return secondaryIsIgnoreNextSelectionChangeRef.current;
101
104
  },
102
105
  secondarySetIsEditorShown = (bool) => {
103
- secondarySetIsEditorShownRaw(bool);
106
+ secondaryIsEditorShownRef.current = bool;
107
+ forceUpdate();
104
108
  if (!bool && secondaryOnEditorClose) {
105
109
  secondaryOnEditorClose();
106
110
  }
107
111
  },
112
+ secondaryGetIsEditorShown = () => {
113
+ return secondaryIsEditorShownRef.current;
114
+ },
115
+ secondarySetIsWaitModalShown = (bool) => {
116
+ const
117
+ dispatch = UiGlobals.redux?.dispatch,
118
+ setIsWaitModalShownAction = UiGlobals.systemReducer?.setIsWaitModalShownAction;
119
+ if (setIsWaitModalShownAction) {
120
+ console.log('withSecondaryEditor:setIsWaitModalShownAction', bool);
121
+ dispatch(setIsWaitModalShownAction(bool));
122
+ }
123
+ },
108
124
  secondarySetSelectionDecorated = (newSelection) => {
109
125
  function doIt() {
110
126
  secondarySetSelection(newSelection);
@@ -139,7 +155,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
139
155
  forceUpdate();
140
156
  }
141
157
  },
142
- getNewEntityDisplayValue = () => {
158
+ secondaryGetNewEntityDisplayValue = () => {
143
159
  return secondaryNewEntityDisplayValueRef.current;
144
160
  },
145
161
  secondaryDoAdd = async (e, values) => {
@@ -147,6 +163,9 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
147
163
  showPermissionsError(ADD, secondaryModel);
148
164
  return;
149
165
  }
166
+ if (secondaryCanProceedWithCrud && !secondaryCanProceedWithCrud()) {
167
+ return;
168
+ }
150
169
 
151
170
  const secondarySelection = secondaryGetSelection();
152
171
  let addValues = values;
@@ -165,20 +184,20 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
165
184
  // 1. directlty submit 'values' to use in secondaryDoAdd(), or
166
185
  // 2. Use the repository's default values (defined on each property as 'defaultValue'), or
167
186
  // 3. Individually override the repository's default values with submitted 'defaultValues' (given as a prop to this HOC)
168
- let defaultValuesToUse = SecondaryRepository.getSchema().getDefaultValues();
187
+ let secondaryDefaultValuesToUse = SecondaryRepository.getSchema().getDefaultValues();
169
188
  if (secondaryDefaultValues) {
170
- _.merge(defaultValuesToUse, secondaryDefaultValues);
189
+ _.merge(secondaryDefaultValuesToUse, secondaryDefaultValues);
171
190
  }
172
- addValues = {...defaultValuesToUse};
191
+ addValues = {...secondaryDefaultValuesToUse};
173
192
  }
174
193
 
175
194
  if (secondarySelectorId && !_.isEmpty(secondarySelectorSelected)) {
176
195
  addValues[secondarySelectorId] = secondarySelectorSelected[secondarySelectorSelectedField];
177
196
  }
178
197
 
179
- if (getNewEntityDisplayValue()) {
198
+ if (secondaryGetNewEntityDisplayValue()) {
180
199
  const displayPropertyName = secondaryNewEntityDisplayProperty || SecondaryRepository.getSchema().model.displayProperty;
181
- addValues[displayPropertyName] = getNewEntityDisplayValue();
200
+ addValues[displayPropertyName] = secondaryGetNewEntityDisplayValue();
182
201
  }
183
202
 
184
203
  if (getListeners().onBeforeAdd) {
@@ -246,6 +265,9 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
246
265
  showPermissionsError(EDIT, secondaryModel);
247
266
  return;
248
267
  }
268
+ if (secondaryCanProceedWithCrud && !secondaryCanProceedWithCrud()) {
269
+ return;
270
+ }
249
271
  const secondarySelection = secondaryGetSelection();
250
272
  if (_.isEmpty(secondarySelection) || (_.isArray(secondarySelection) && (secondarySelection.length > 1 || secondarySelection[0]?.isDestroyed))) {
251
273
  return;
@@ -265,6 +287,9 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
265
287
  showPermissionsError(DELETE, secondaryModel);
266
288
  return;
267
289
  }
290
+ if (secondaryCanProceedWithCrud && !secondaryCanProceedWithCrud()) {
291
+ return;
292
+ }
268
293
  let cb = null;
269
294
  if (_.isFunction(args)) {
270
295
  cb = args;
@@ -273,7 +298,15 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
273
298
  if (_.isEmpty(secondarySelection) || (_.isArray(secondarySelection) && (secondarySelection.length > 1 || secondarySelection[0]?.isDestroyed))) {
274
299
  return;
275
300
  }
301
+ if (secondaryOnBeforeDelete) {
302
+ // This listener is set by parent components using a prop
303
+ const listenerResult = await secondaryOnBeforeDelete(secondarySelection);
304
+ if (listenerResult === false) {
305
+ return;
306
+ }
307
+ }
276
308
  if (getListeners().onBeforeDelete) {
309
+ // This listener is set by child components using setWithEditListeners()
277
310
  const listenerResult = await getListeners().onBeforeDelete();
278
311
  if (listenerResult === false) {
279
312
  return;
@@ -309,26 +342,33 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
309
342
  });
310
343
  } else
311
344
  if (isSingle && isPhantom) {
312
- deleteRecord(cb);
345
+ secondaryDeleteRecord(cb);
313
346
  } else {
314
347
  const identifier = secondaryGetRecordIdentifier(secondarySelection);
315
- confirm('Are you sure you want to delete the ' + identifier, () => deleteRecord(null, cb));
348
+ confirm('Are you sure you want to delete the ' + identifier, () => secondaryDeleteRecord(null, cb));
316
349
  }
317
350
  },
318
351
  secondaryDoMoveChildren = (cb) => {
319
352
  hideAlert();
320
- deleteRecord(true, cb);
353
+ secondaryDeleteRecord(true, cb);
321
354
  },
322
355
  secondaryDoDeleteChildren = (cb) => {
323
356
  hideAlert();
324
- deleteRecord(false, cb);
357
+ secondaryDeleteRecord(false, cb);
325
358
  },
326
- deleteRecord = async (moveSubtreeUp, cb) => {
359
+ secondaryDeleteRecord = async (moveSubtreeUp, cb) => {
327
360
  if (canUser && !canUser(DELETE, secondaryModel)) {
328
361
  showPermissionsError(DELETE, secondaryModel);
329
362
  return;
330
363
  }
331
364
  const secondarySelection = secondaryGetSelection();
365
+ if (secondaryOnBeforeDelete) {
366
+ // This listener is set by parent components using a prop
367
+ const listenerResult = await secondaryOnBeforeDelete(secondarySelection);
368
+ if (listenerResult === false) {
369
+ return;
370
+ }
371
+ }
332
372
  if (getListeners().onBeforeDelete) {
333
373
  const listenerResult = await getListeners().onBeforeDelete(secondarySelection);
334
374
  if (listenerResult === false) {
@@ -362,6 +402,9 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
362
402
  showPermissionsError(VIEW, secondaryModel);
363
403
  return;
364
404
  }
405
+ if (secondaryCanProceedWithCrud && !secondaryCanProceedWithCrud()) {
406
+ return;
407
+ }
365
408
  if (secondaryEditorType === EDITOR_TYPE__INLINE) {
366
409
  alert('Cannot view in inline editor.');
367
410
  return; // inline editor doesn't have a view mode
@@ -389,37 +432,70 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
389
432
  showPermissionsError(DUPLICATE, secondaryModel);
390
433
  return;
391
434
  }
392
-
393
- // check permissions for duplicate
435
+ if (secondaryCanProceedWithCrud && !secondaryCanProceedWithCrud()) {
436
+ return;
437
+ }
394
438
 
395
439
  const secondarySelection = secondaryGetSelection();
396
440
  if (secondarySelection.length !== 1) {
397
441
  return;
398
442
  }
443
+
399
444
  if (secondaryUseRemoteDuplicate) {
400
- const results = await onRemoteDuplicate();
401
- return results;
445
+ return await secondaryOnRemoteDuplicate();
446
+ }
447
+
448
+ let isSuccess = false,
449
+ duplicateEntity;
450
+ try {
451
+ const
452
+ entity = secondarySelection[0],
453
+ idProperty = SecondaryRepository.getSchema().model.idProperty,
454
+ rawValues = _.omit(entity.getOriginalData(), idProperty);
455
+ rawValues.id = null; // unset the id of the duplicate
456
+
457
+ setIsWaitModalShown(true);
458
+
459
+ duplicateEntity = await SecondaryRepository.add(rawValues, false, true);
460
+ isSuccess = true;
461
+
462
+ } catch(err) {
463
+ // do nothing
464
+ } finally {
465
+ setIsWaitModalShown(false);
466
+ }
467
+
468
+ if (isSuccess) {
469
+ secondarySetIsIgnoreNextSelectionChange(true);
470
+ secondarySetSelection([duplicateEntity]);
471
+ secondarySetEditorMode(EDITOR_MODE__EDIT);
472
+ secondarySetIsEditorShown(true);
402
473
  }
403
- const
404
- entity = secondarySelection[0],
405
- idProperty = SecondaryRepository.getSchema().model.idProperty,
406
- rawValues = _.omit(entity.getOriginalData(), idProperty);
407
- rawValues.id = null; // unset the id of the duplicate
408
- const duplicate = await SecondaryRepository.add(rawValues, false, true);
409
- secondarySetIsIgnoreNextSelectionChange(true);
410
- secondarySetSelection([duplicate]);
411
- secondarySetEditorMode(EDITOR_MODE__EDIT);
412
- secondarySetIsEditorShown(true);
413
474
  },
414
- onRemoteDuplicate = async () => {
415
- const
416
- secondarySelection = secondaryGetSelection(),
417
- entity = secondarySelection[0],
418
- duplicateEntity = await SecondaryRepository.remoteDuplicate(entity);
475
+ secondaryOnRemoteDuplicate = async () => {
476
+ let isSuccess = false,
477
+ duplicateEntity;
478
+ try {
479
+ const
480
+ secondarySelection = secondaryGetSelection(),
481
+ entity = secondarySelection[0];
482
+
483
+ setIsWaitModalShown(true);
419
484
 
420
- secondarySetIsIgnoreNextSelectionChange(true);
421
- secondarySetSelection([duplicateEntity]);
422
- secondaryDoEdit();
485
+ duplicateEntity = await SecondaryRepository.remoteDuplicate(entity);
486
+ isSuccess = true;
487
+
488
+ } catch(err) {
489
+ // do nothing
490
+ } finally {
491
+ setIsWaitModalShown(false);
492
+ }
493
+ if (isSuccess) {
494
+ secondarySetIsIgnoreNextSelectionChange(true);
495
+ secondarySetSelection([duplicateEntity]);
496
+ secondaryDoEdit();
497
+ return duplicateEntity;
498
+ }
423
499
  },
424
500
  secondaryDoEditorSave = async (data, e) => {
425
501
  let mode = secondaryGetEditorMode() === EDITOR_MODE__ADD ? ADD : EDIT;
@@ -516,7 +592,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
516
592
  isSingle = secondarySelection.length === 1,
517
593
  isPhantom = secondarySelection[0] && !secondarySelection[0]?.isDestroyed && secondarySelection[0].isPhantom;
518
594
  if (isSingle && isPhantom) {
519
- await deleteRecord();
595
+ await secondaryDeleteRecord();
520
596
  }
521
597
 
522
598
  setIsAdding(false);
@@ -551,7 +627,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
551
627
  secondarySetIsEditorShown(false);
552
628
  });
553
629
  },
554
- calculateEditorMode = () => {
630
+ secondaryCalculateEditorMode = () => {
555
631
 
556
632
  let secondaryIsIgnoreNextSelectionChange = secondaryGetIsIgnoreNextSelectionChange(),
557
633
  doStayInEditModeOnSelectionChange = secondaryStayInEditModeOnSelectionChange;
@@ -563,7 +639,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
563
639
  secondaryIsIgnoreNextSelectionChange = true;
564
640
  }
565
641
 
566
- // calculateEditorMode gets called only on selection changes
642
+ // secondaryCalculateEditorMode gets called only on selection changes
567
643
  const secondarySelection = secondaryGetSelection();
568
644
  let mode;
569
645
  if (secondaryEditorType === EDITOR_TYPE__SIDE && !_.isNil(UiGlobals.isSideEditorAlwaysEditMode) && UiGlobals.isSideEditorAlwaysEditMode) {
@@ -617,10 +693,26 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
617
693
  };
618
694
 
619
695
  useEffect(() => {
620
- secondarySetEditorMode(calculateEditorMode());
621
696
 
622
- secondarySetIsIgnoreNextSelectionChange(false);
697
+ if (secondaryEditorType === EDITOR_TYPE__SIDE) {
698
+ if (secondarySelection?.length) { // || isAdding
699
+ // there is a selection, so show the editor
700
+ secondarySetIsEditorShown(true);
701
+ } else {
702
+ // no selection, so close the editor
703
+ secondarySetIsEditorShown(false);
704
+ }
705
+ }
706
+
707
+ secondarySetEditorMode(secondaryCalculateEditorMode());
623
708
  setLastSelection(secondarySelection);
709
+
710
+ // Push isIgnoreNextSelectionChange until after a microtask to ensure all
711
+ // synchronous operations (including listener callbacks) are complete
712
+ // (this is to prevent the editor from immediately switching modes on doAdd in Tree)
713
+ Promise.resolve().then(() => {
714
+ secondarySetIsIgnoreNextSelectionChange(false);
715
+ });
624
716
  }, [secondarySelection]);
625
717
 
626
718
  if (self) {
@@ -638,21 +730,23 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
638
730
  // NOTE: If I don't calculate this on the fly for selection changes,
639
731
  // we see a flash of the previous state, since useEffect hasn't yet run.
640
732
  // (basically redo what's in the useEffect, above)
641
- secondarySetEditorMode(calculateEditorMode());
733
+ secondarySetEditorMode(secondaryCalculateEditorMode());
642
734
  }
643
735
 
644
736
  return <WrappedComponent
645
737
  {...props}
738
+ ref={ref}
646
739
  secondaryDisableWithEditor={false}
647
740
  secondaryAlreadyHasWithEditor={true}
648
- ref={ref}
649
741
  secondaryCurrentRecord={secondaryCurrentRecord}
650
742
  secondarySetCurrentRecord={secondarySetCurrentRecord}
651
- secondaryIsEditorShown={secondaryIsEditorShown}
743
+ secondaryIsEditorShown={secondaryGetIsEditorShown()}
744
+ secondaryGetIsEditorShown={secondaryGetIsEditorShown}
652
745
  secondaryIsEditorViewOnly={secondaryIsEditorViewOnly}
653
746
  secondaryIsAdding={secondaryIsAdding}
654
747
  secondaryIsSaving={secondaryIsSaving}
655
748
  secondaryEditorMode={secondaryGetEditorMode()}
749
+ secondaryGetEditorMode={secondaryGetEditorMode}
656
750
  secondaryOnEditMode={secondarySetEditMode}
657
751
  secondaryOnViewMode={secondarySetViewMode}
658
752
  secondaryEditorStateRef={secondaryEditorStateRef}
@@ -47,11 +47,12 @@ export default function withSecondarySideEditor(WrappedComponent, isTree = false
47
47
  throw Error('SecondaryEditor is not defined');
48
48
  }
49
49
 
50
+ const containerProps = {};
50
51
  if (isResizable) {
51
- secondaryEditorProps.w = 500;
52
- secondaryEditorProps.isResizable = true;
52
+ containerProps.eastIsResizable = true;
53
+ containerProps.eastInitialWidth = 500;
53
54
  } else {
54
- secondaryEditorProps.flex = secondarySideFlex;
55
+ containerProps.eastInitialFlex = secondarySideFlex;
55
56
  }
56
57
 
57
58
  if (!secondaryEditorProps.className) {
@@ -68,13 +69,14 @@ export default function withSecondarySideEditor(WrappedComponent, isTree = false
68
69
  isSideEditor={true}
69
70
  {...props}
70
71
  />}
71
- east={<Editor
72
+ east={props.secondaryIsEditorShown && <Editor
72
73
  {...propsToPass}
73
74
  editorType={EDITOR_TYPE__SIDE}
74
75
  {...secondaryEditorProps}
75
76
  parent={self}
76
77
  reference="secondaryEditor"
77
78
  />}
79
+ {...containerProps}
78
80
  />;
79
81
  });
80
82
  return withAdditionalProps(withSecondaryEditor(SideEditor, isTree));
@@ -95,9 +95,8 @@ export default function withContextMenu(WrappedComponent) {
95
95
  action="secondary"
96
96
  />;
97
97
  });
98
- const showId = true; // TODO: This should only be for local dev
99
- if (showId) {
100
- contextMenuItemComponents.push(<Text key="idViewer" className="flex-1 py-2 px-4 select-none">id: {selection?.[0]?.id}</Text>);
98
+ if (UiGlobals.isLocal) {
99
+ contextMenuItemComponents.push(<Text key="idViewer" className="flex-1 py-2 px-4 select-none">id: {selection?.[0]?.actualId || selection?.[0]?.id}</Text>);
101
100
  }
102
101
  return contextMenuItemComponents;
103
102
  };
@@ -122,9 +121,19 @@ export default function withContextMenu(WrappedComponent) {
122
121
  // may change based on the selection; and this is why we're using
123
122
  // useEffect to show the context menu instead of onContextMenu.
124
123
 
124
+ // TODO: There might be a bug here. As the comment above suggests, useEffect()
125
+ // was running if contextMenuItems changed. But the comment next to the args
126
+ // for useEffect() says we're not including contextMenuItems in the args
127
+ // to avoid infinite loops. Is this a problem??
128
+
129
+ const contextMenuItemComponents = createContextMenuItemComponents();
130
+ if (contextMenuItemComponents.length === 0) {
131
+ // No items to show
132
+ return;
133
+ }
134
+
125
135
  // show context menu
126
136
  const
127
- contextMenuItemComponents = createContextMenuItemComponents(),
128
137
  className = clsx(
129
138
  'context-menu-container',
130
139
  'absolute',
@@ -14,8 +14,6 @@ import {
14
14
  } from 'uuid';
15
15
  import getComponentFromType from '../../Functions/getComponentFromType.js';
16
16
 
17
-
18
-
19
17
  // Note on modes:
20
18
  // HORIZONTAL means the component moves along the X axis.
21
19
  // VERTICAL means the component moves along the Y axis.
@@ -62,6 +60,8 @@ export default function withDraggable(WrappedComponent) {
62
60
  if (isDragging) {
63
61
  return;
64
62
  }
63
+
64
+ // console.log('start x', info.x);
65
65
 
66
66
  const
67
67
  node = getDraggableNodeFromNode(info.node),
@@ -132,6 +132,9 @@ export default function withDraggable(WrappedComponent) {
132
132
  deltaY,
133
133
  } = info;
134
134
 
135
+
136
+ // console.log('drag x', info.x);
137
+
135
138
  // Move the proxy to where it should be
136
139
  const
137
140
  proxy = document.getElementById('dragproxy'),
@@ -157,7 +160,8 @@ export default function withDraggable(WrappedComponent) {
157
160
  return;
158
161
  }
159
162
 
160
- // console.log('end', info);
163
+ // console.log('end x', info.x);
164
+
161
165
  // remove proxy
162
166
  const proxy = document.getElementById('dragproxy');
163
167
  proxy.remove();