@onehat/ui 0.4.83 → 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
 
@@ -147,6 +148,7 @@ function GridComponent(props) {
147
148
  showSelectHandle = true,
148
149
  isRowSelectable = true,
149
150
  isRowHoverable = true,
151
+ isDisabled = false,
150
152
  canColumnsSort = true,
151
153
  canColumnsReorder = true,
152
154
  canColumnsResize = true,
@@ -522,6 +524,33 @@ function GridComponent(props) {
522
524
  onContextMenu(item, e, newSelection);
523
525
  }
524
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
+ }}
525
554
  className={clsx(
526
555
  'Pressable',
527
556
  'Row',
@@ -1679,51 +1708,55 @@ function GridComponent(props) {
1679
1708
  className += ' ' + props.className;
1680
1709
  }
1681
1710
 
1682
- grid = <VStackNative
1683
- {...testProps(self)}
1684
- ref={containerRef}
1685
- tabIndex={0}
1686
- onKeyDown={onGridKeyDown}
1687
- onLayout={(e) => debouncedAdjustPageSizeToHeight(e)}
1688
- className={className}
1689
- style={style}
1690
- >
1691
- {topToolbar &&
1692
- <VStack ref={topToolbarRef}>
1693
- {topToolbar}
1694
- </VStack>}
1695
-
1696
- <VStack
1697
- ref={gridContainerRef}
1698
- onClick={() => {
1699
- if (!isReorderMode && !isInlineEditorShown && deselectAll) {
1700
- deselectAll();
1701
- }
1702
- }}
1703
- className={clsx(
1704
- 'gridContainer',
1705
- 'w-full',
1706
- // 'h-full',
1707
- 'flex-1',
1708
- 'min-h-[40px]',
1709
- 'relative', // Enable positioning for overlay
1710
- gridContainerBorderClassName,
1711
- )}
1712
- >
1713
- {grid}
1714
- {/* Loading overlay during measurement phases to prevent visual flashing */}
1715
- {autoAdjustPageSizeToHeight &&
1716
- (getMeasurementPhase() === PHASES__INITIAL || getMeasurementPhase() === PHASES__MEASURING) &&
1717
- entities?.length > 0 && (
1718
- <VStack className="absolute inset-0 z-10 bg-white">
1719
- <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
+ )}
1720
1754
  </VStack>
1721
- )}
1722
- </VStack>
1723
1755
 
1724
- {listFooterComponent}
1756
+ {listFooterComponent}
1725
1757
 
1726
- </VStackNative>
1758
+ </VStackNative>;
1759
+ }
1727
1760
 
1728
1761
  if (isDropTarget) {
1729
1762
  grid = <VStackNative
@@ -286,7 +286,7 @@ const GridRow = forwardRef((props, ref) => {
286
286
  'block',
287
287
  areCellsScrollable ? 'overflow-auto' : 'overflow-hidden',
288
288
  '[&::-webkit-scrollbar]:h-2',
289
- '[&::-webkit-scrollbar-thumb]:bg-gray-400',
289
+ '[&::-webkit-scrollbar-thumb]:bg-gray-300',
290
290
  '[&::-webkit-scrollbar-thumb]:rounded-full',
291
291
  colClassName,
292
292
  styles.GRID_CELL_CLASSNAME,
@@ -350,10 +350,17 @@ const GridRow = forwardRef((props, ref) => {
350
350
  if (config.getCellProps) {
351
351
  _.assign(elementProps, config.getCellProps(item));
352
352
  }
353
+
354
+ // TODO: incorporate better scrollbar formatting with
355
+ // tailwind plugin 'tailwind-scrollbar' (already installed, just not yet used here)
356
+
353
357
  let textClassName = clsx(
354
358
  'GridRow-TextNative',
355
359
  'self-center',
356
360
  areCellsScrollable ? 'overflow-auto' : 'overflow-hidden',
361
+ '[&::-webkit-scrollbar]:h-2',
362
+ '[&::-webkit-scrollbar-thumb]:bg-gray-300',
363
+ '[&::-webkit-scrollbar-thumb]:rounded-full',
357
364
  colClassName,
358
365
  styles.GRID_CELL_CLASSNAME,
359
366
  styles.GRID_ROW_MAX_HEIGHT_EXTRA,
@@ -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) {
@@ -75,6 +76,7 @@ export default function withSecondarySideEditor(WrappedComponent, isTree = false
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();
@@ -45,11 +45,12 @@ export default function withSideEditor(WrappedComponent, isTree = false) {
45
45
  throw Error('Editor is not defined');
46
46
  }
47
47
 
48
+ const containerProps = {};
48
49
  if (isResizable) {
49
- _editor.w = 500;
50
- _editor.isResizable = true;
50
+ containerProps.eastIsResizable = true;
51
+ containerProps.eastInitialWidth = 500;
51
52
  } else {
52
- _editor.flex = sideFlex;
53
+ containerProps.eastInitialFlex = sideFlex;
53
54
  }
54
55
 
55
56
  if (!_editor.className) {
@@ -61,10 +62,10 @@ export default function withSideEditor(WrappedComponent, isTree = false) {
61
62
  parent={self}
62
63
  reference="SideEditor"
63
64
  center={<WrappedComponent
65
+ {...props}
64
66
  ref={ref}
65
67
  isTree={isTree}
66
68
  isSideEditor={true}
67
- {...props}
68
69
  />}
69
70
  east={props.isEditorShown && <Editor
70
71
  {...propsToPass}
@@ -73,6 +74,8 @@ export default function withSideEditor(WrappedComponent, isTree = false) {
73
74
  parent={self}
74
75
  reference="editor"
75
76
  />}
77
+ {...containerProps}
78
+ isDisabled={props.isDisabled}
76
79
  />;
77
80
  });
78
81
  return withAdditionalProps(withEditor(SideEditor, isTree));
@@ -9,6 +9,9 @@ import {
9
9
  import clsx from 'clsx';
10
10
  import * as Progress from 'react-native-progress';
11
11
  import useForceUpdate from '../../Hooks/useForceUpdate';
12
+ import {
13
+ EDITOR_TYPE__PLAIN,
14
+ } from '../../Constants/Editor.js';
12
15
  import {
13
16
  PROGRESS__NONE_FOUND,
14
17
  PROGRESS__IN_PROCESS,
@@ -16,10 +19,15 @@ import {
16
19
  PROGRESS__FAILED,
17
20
  PROGRESS__STUCK,
18
21
  PROGRESS__UNSTUCK,
22
+ ASYNC_OPERATION_MODES__INIT,
23
+ ASYNC_OPERATION_MODES__START,
24
+ ASYNC_OPERATION_MODES__PROCESSING,
25
+ ASYNC_OPERATION_MODES__RESULTS,
19
26
  } from '../../Constants/Progress.js';
20
27
  import {
21
28
  MOMENT_DATE_FORMAT_2,
22
29
  } from '../../Constants/Dates.js';
30
+ import inArray from '../../Functions/inArray.js';
23
31
  import isJson from '../../Functions/isJson.js';
24
32
  import Form from '../Form/Form.js';
25
33
  import Button from '../Buttons/Button.js';
@@ -38,11 +46,12 @@ import Toolbar from '../Toolbar/Toolbar.js';
38
46
  import moment from 'moment';
39
47
  import _ from 'lodash';
40
48
 
41
- const
42
- INIT = 'INIT', // no footer shown; used when component initially loads, to see if operation is already in progress
43
- START = 'START', // shows the start form
44
- PROCESSING = 'PROCESSING', // shows the loading indicator while starting the operation
45
- RESULTS = 'RESULTS'; // shows the results of the operation, or any in-progress updates
49
+ // MODES:
50
+ // ASYNC_OPERATION_MODES__INIT - no footer shown; used when component initially loads, to see if operation is already in progress
51
+ // ASYNC_OPERATION_MODES__START - shows the start form
52
+ // ASYNC_OPERATION_MODES__PROCESSING - shows the loading indicator while starting the operation
53
+ // ASYNC_OPERATION_MODES__RESULTS - shows the results of the operation, or any in-progress updates
54
+
46
55
 
47
56
  // If getProgressUpdates is false, the component will show the start form initially.
48
57
  // If getProgressUpdates is true, the component will initially query the server to see if
@@ -63,9 +72,11 @@ function AsyncOperation(props) {
63
72
  formStartingValues = {},
64
73
  _form = {},
65
74
  getProgressUpdates = false,
75
+ getInitialProgress = true, // applies only if getProgressUpdates is true
66
76
  parseProgress, // optional fn, accepts 'response' as arg and returns an object like this: { status, errors, started, lastUpdated, timeElapsed, count, current, total, percentage }
67
77
  updateInterval = 10000, // ms
68
78
  progressColor = '#666',
79
+ onChangeMode,
69
80
 
70
81
  // withComponent
71
82
  self,
@@ -81,15 +92,18 @@ function AsyncOperation(props) {
81
92
  getIsValid = () => {
82
93
  return isValid.current;
83
94
  },
84
- mode = useRef(INIT),
85
- setMode = (newMode) => {
86
- mode.current = newMode;
95
+ modeRef = useRef(ASYNC_OPERATION_MODES__INIT),
96
+ setMode = (mode) => {
97
+ modeRef.current = mode;
98
+
99
+ if (onChangeMode) {
100
+ onChangeMode(mode);
101
+ }
87
102
  },
88
103
  getMode = () => {
89
- return mode.current;
104
+ return modeRef.current;
90
105
  },
91
- isInProcess = getMode() === PROCESSING,
92
- currentTabIx = (getMode() === PROCESSING ? 1 : (getMode() === RESULTS ? 2 : 0)),
106
+ isInProcess = getMode() === ASYNC_OPERATION_MODES__PROCESSING,
93
107
  intervalRef = useRef(null),
94
108
  getInterval = () => {
95
109
  return intervalRef.current;
@@ -113,9 +127,9 @@ function AsyncOperation(props) {
113
127
  },
114
128
  getFooter = () => {
115
129
  switch(getMode()) {
116
- case INIT:
130
+ case ASYNC_OPERATION_MODES__INIT:
117
131
  return null;
118
- case START:
132
+ case ASYNC_OPERATION_MODES__START:
119
133
  return <Toolbar>
120
134
  <Button
121
135
  text="Start"
@@ -124,7 +138,7 @@ function AsyncOperation(props) {
124
138
  isDisabled={!getIsValid()}
125
139
  />
126
140
  </Toolbar>;
127
- case PROCESSING:
141
+ case ASYNC_OPERATION_MODES__PROCESSING:
128
142
  // TODO: Add a cancellation option to the command.
129
143
  // would require a backend controller action to support it
130
144
  return null;
@@ -135,7 +149,7 @@ function AsyncOperation(props) {
135
149
  // variant="link"
136
150
  // />
137
151
  // </Toolbar>;
138
- case RESULTS:
152
+ case ASYNC_OPERATION_MODES__RESULTS:
139
153
  let button;
140
154
  if (getIsStuck()) {
141
155
  button = <Button
@@ -160,13 +174,13 @@ function AsyncOperation(props) {
160
174
  [progress, setProgress] = useState(null),
161
175
  [isReady, setIsReady] = useState(false),
162
176
  showResults = (results) => {
163
- setMode(RESULTS);
177
+ setMode(ASYNC_OPERATION_MODES__RESULTS);
164
178
  setFooter(getFooter());
165
179
  setResults(results);
166
180
  },
167
181
  startProcess = async () => {
168
182
  stopGettingProgress();
169
- setMode(PROCESSING);
183
+ setMode(ASYNC_OPERATION_MODES__PROCESSING);
170
184
  setFooter(getFooter());
171
185
 
172
186
  const
@@ -264,11 +278,11 @@ function AsyncOperation(props) {
264
278
  statusMessage = '',
265
279
  errorMessage = null;
266
280
  if (status === PROGRESS__IN_PROCESS) {
267
- setMode(PROCESSING);
281
+ setMode(ASYNC_OPERATION_MODES__PROCESSING);
268
282
  color = 'text-green-600';
269
283
  statusMessage = 'In process...';
270
284
  } else {
271
- setMode(RESULTS);
285
+ setMode(ASYNC_OPERATION_MODES__RESULTS);
272
286
  stopGettingProgress();
273
287
  if (status === PROGRESS__COMPLETED) {
274
288
  statusMessage = 'Completed';
@@ -328,7 +342,7 @@ function AsyncOperation(props) {
328
342
  })}
329
343
  </VStack>);
330
344
  }
331
- if (getMode() === PROCESSING) {
345
+ if (getMode() === ASYNC_OPERATION_MODES__PROCESSING) {
332
346
  setProgress(renderItems);
333
347
  } else {
334
348
  setResults(renderItems);
@@ -351,7 +365,7 @@ function AsyncOperation(props) {
351
365
  },
352
366
  unstick = async () => {
353
367
  stopGettingProgress();
354
- setMode(PROCESSING);
368
+ setMode(ASYNC_OPERATION_MODES__PROCESSING);
355
369
  setFooter(getFooter());
356
370
 
357
371
  const
@@ -379,7 +393,7 @@ function AsyncOperation(props) {
379
393
  resetToInitialState();
380
394
  },
381
395
  resetToInitialState = () => {
382
- setMode(START);
396
+ setMode(ASYNC_OPERATION_MODES__START);
383
397
  setFooter(getFooter());
384
398
  setIsStuck(false);
385
399
  stopGettingProgress();
@@ -394,10 +408,10 @@ function AsyncOperation(props) {
394
408
  };
395
409
 
396
410
  useEffect(() => {
397
- if (getProgressUpdates) {
411
+ if (getProgressUpdates && getInitialProgress) {
398
412
  getProgress(true);
399
413
  } else {
400
- setMode(START);
414
+ setMode(ASYNC_OPERATION_MODES__START);
401
415
  setIsReady(true);
402
416
  }
403
417
  return () => {
@@ -409,6 +423,20 @@ function AsyncOperation(props) {
409
423
  };
410
424
  }, []);
411
425
 
426
+ let currentTabIx = 0;
427
+ switch(getMode()) {
428
+ case ASYNC_OPERATION_MODES__INIT:
429
+ case ASYNC_OPERATION_MODES__START:
430
+ currentTabIx = 0;
431
+ break;
432
+ case ASYNC_OPERATION_MODES__PROCESSING:
433
+ currentTabIx = 1;
434
+ break;
435
+ case ASYNC_OPERATION_MODES__RESULTS:
436
+ currentTabIx = 2;
437
+ break;
438
+ }
439
+
412
440
  return <Panel
413
441
  {...props}
414
442
  footer={footer}
@@ -421,10 +449,11 @@ function AsyncOperation(props) {
421
449
  title: 'Start',
422
450
  icon: Play,
423
451
  isDisabled: currentTabIx !== 0,
424
- content: getMode() === INIT ?
452
+ content: inArray(getMode(), [ASYNC_OPERATION_MODES__INIT, ASYNC_OPERATION_MODES__PROCESSING, ASYNC_OPERATION_MODES__RESULTS]) ?
425
453
  <Loading /> :
426
454
  <ScrollView className="ScrollView h-full w-full">
427
455
  <Form
456
+ editorType={EDITOR_TYPE__PLAIN}
428
457
  reference="form"
429
458
  parent={self}
430
459
  className="w-full h-full flex-1"
@@ -2,20 +2,7 @@ import {
2
2
  Box,
3
3
  } from '@project-components/Gluestack';
4
4
  import clsx from 'clsx';
5
- import {
6
- CURRENT_MODE,
7
- UI_MODE_WEB,
8
- UI_MODE_NATIVE,
9
- } from '../../Constants/UiModes.js';
10
5
 
11
6
  export default function Mask(props) {
12
- if (CURRENT_MODE === UI_MODE_WEB) {
13
-
14
- return <div className="mask"></div>;
15
-
16
- } else if (CURRENT_MODE === UI_MODE_NATIVE) {
17
-
18
- return <Box className="absolute h-full w-full bg-grey-400:alpha.20 z-100000"></Box>;
19
-
20
- }
7
+ return <Box className="mask flex-none absolute h-full w-full bg-grey-400/20 z-[100000]"></Box>;
21
8
  }
@@ -1,8 +1,8 @@
1
+ import { cloneElement, useState, useEffect, } from 'react';
1
2
  import {
2
3
  VStackNative,
3
4
  } from '@project-components/Gluestack';
4
5
  import clsx from 'clsx';
5
- import React, { useState, useEffect, } from 'react';
6
6
  import {
7
7
  SCREEN_MODES__FULL,
8
8
  SCREEN_MODES__SIDE,
@@ -21,10 +21,9 @@ function ManagerScreen(props) {
21
21
  sideModeComponent,
22
22
  fullModeComponent,
23
23
  onChangeMode,
24
-
25
- // withComponent
26
- self,
24
+ ...propsToPass
27
25
  } = props,
26
+ self = props.self,
28
27
  id = props.id || props.self?.path,
29
28
  [isRendered, setIsRendered] = useState(false),
30
29
  [isModeSet, setIsModeSet] = useState(false),
@@ -79,7 +78,7 @@ function ManagerScreen(props) {
79
78
  self.mode = actualMode;
80
79
  }
81
80
 
82
- const whichComponent = actualMode === SCREEN_MODES__FULL ? fullModeComponent : sideModeComponent;
81
+ const whichComponent = cloneElement((actualMode === SCREEN_MODES__FULL ? fullModeComponent : sideModeComponent), propsToPass);
83
82
 
84
83
  return <VStackNative
85
84
  {...testProps(self)}
@@ -1106,6 +1106,30 @@ function TreeComponent(props) {
1106
1106
  onContextMenu(item, e, selection);
1107
1107
  }
1108
1108
  }}
1109
+ onContextMenu={(e) => {
1110
+ // web only; happens before onLongPress triggers
1111
+ // different behavior here than onLongPress:
1112
+ // if user clicks on a phantom record, or if onContextMenu is not set, pass to the browser's context menu
1113
+ if (selection && selection[0] && selection[0].isRemotePhantom) {
1114
+ return; // block context menu or changing selection when a remote phantom is already selected
1115
+ }
1116
+ if (onContextMenu) {
1117
+ e.preventDefault();
1118
+ e.stopPropagation(); // disallow browser's default behavior for context menu
1119
+
1120
+ // if the right-clicked item is not in the current selection,
1121
+ // set the selection only to this one item.
1122
+ let newSelection = selection;
1123
+ if (!isInSelection(item)) {
1124
+ newSelection = [item];
1125
+ setSelection(newSelection);
1126
+ }
1127
+
1128
+ if (onContextMenu) {
1129
+ onContextMenu(item, e, newSelection);
1130
+ }
1131
+ }
1132
+ }}
1109
1133
  className={clsx(
1110
1134
  'Pressable',
1111
1135
  'Node',
@@ -264,6 +264,7 @@ import FiltersForm from './Form/FiltersForm.js';
264
264
  import Form from './Form/Form.js';
265
265
  import Grid from './Grid/Grid.js';
266
266
  import GridPanel from './Panel/GridPanel.js';
267
+ import Hidden from './Form/Field/Hidden.js';
267
268
  import IconButton from './Buttons/IconButton.js';
268
269
  import Input from './Form/Field/Input.js';
269
270
  import IntervalsCombo from './Form/Field/Combo/IntervalsCombo.js';
@@ -558,6 +559,7 @@ const components = {
558
559
  Form,
559
560
  Grid,
560
561
  GridPanel,
562
+ Hidden,
561
563
  IconButton,
562
564
  Input,
563
565
  IntervalsCombo,
@@ -3,4 +3,9 @@ export const PROGRESS__IN_PROCESS = 'IN_PROCESS';
3
3
  export const PROGRESS__COMPLETED = 'COMPLETED';
4
4
  export const PROGRESS__FAILED = 'FAILED';
5
5
  export const PROGRESS__STUCK = 'STUCK';
6
- export const PROGRESS__UNSTUCK = 'UNSTUCK';
6
+ export const PROGRESS__UNSTUCK = 'UNSTUCK';
7
+
8
+ export const ASYNC_OPERATION_MODES__INIT = 'INIT';
9
+ export const ASYNC_OPERATION_MODES__START = 'START';
10
+ export const ASYNC_OPERATION_MODES__PROCESSING = 'PROCESSING';
11
+ export const ASYNC_OPERATION_MODES__RESULTS = 'RESULTS';
@@ -474,9 +474,9 @@ function AttachmentsElement(props) {
474
474
  },
475
475
  isPdf = currentFile.attachments__mimetype === 'application/pdf';
476
476
 
477
- let url = currentFile.attachments__uri;
477
+ let url = encodeURI(currentFile.attachments__uri);
478
478
  try {
479
- const response = await fetch(currentFile.attachments__uri, {
479
+ const response = await fetch(url, {
480
480
  headers: Attachments.headers // Use your repository's headers
481
481
  });
482
482
 
@@ -1019,7 +1019,7 @@ function AttachmentsElement(props) {
1019
1019
  },
1020
1020
  {
1021
1021
  "id": "attachments__size_formatted",
1022
- "header": "Size Formatted",
1022
+ "header": "Size",
1023
1023
  "fieldName": "attachments__size_formatted",
1024
1024
  "isSortable": false,
1025
1025
  "isEditable": false,