@onehat/ui 0.4.41 → 0.4.43

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/ui",
3
- "version": "0.4.41",
3
+ "version": "0.4.43",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -30,7 +30,6 @@ export default function ReloadButton(props) {
30
30
  {...propsToPass}
31
31
  icon={Rotate}
32
32
  _icon={_icon}
33
- className="ml-2"
34
33
  tooltip="Reload"
35
34
  />;
36
35
  }
@@ -28,15 +28,17 @@ const
28
28
  const checkboxProps = {
29
29
  };
30
30
  if (Repository) {
31
- const entities = Repository.getEntitiesOnPage();
32
- checkboxes = _.map(entities, (entity, ix) => {
33
- return <Checkbox
34
- {...testProps('checkbox-' + entity.id)}
35
- key={ix}
36
- value={entity.id}
37
- {...checkboxProps}
38
- >{entity.displayValue}</Checkbox>;
39
- });
31
+ if (!Repository.isDestroyed) {
32
+ const entities = Repository.getEntitiesOnPage();
33
+ checkboxes = _.map(entities, (entity, ix) => {
34
+ return <Checkbox
35
+ {...testProps('checkbox-' + entity.id)}
36
+ key={ix}
37
+ value={entity.id}
38
+ {...checkboxProps}
39
+ >{entity.displayValue}</Checkbox>;
40
+ });
41
+ }
40
42
  } else {
41
43
  checkboxes = _.map(data, (datum, ix) => {
42
44
  return <Checkbox
@@ -36,6 +36,21 @@ import _ from 'lodash';
36
36
 
37
37
  const FILTER_NAME = 'q';
38
38
 
39
+ /**
40
+ * isEmptyValue
41
+ * _.isEmpty returns true for all integers, so we need this instead
42
+ * @param {*} value
43
+ * @returns boolean
44
+ */
45
+ function isEmptyValue(value) {
46
+ //
47
+ return value === null ||
48
+ value === undefined ||
49
+ value === '' ||
50
+ value === 0 ||
51
+ (_.isObject(value) && _.isEmpty(value));
52
+ };
53
+
39
54
  export const ComboComponent = forwardRef((props, ref) => {
40
55
 
41
56
  const {
@@ -170,19 +185,20 @@ export const ComboComponent = forwardRef((props, ref) => {
170
185
  } else if (_.isArray(value)) {
171
186
  displayValue = [];
172
187
  if (Repository) {
173
- if (!Repository.isLoaded) {
174
- debugger;
175
- throw Error('Not yet implemented'); // Would a Combo ever have multiple remote selections? Shouldn't that be a Tag field??
176
- }
177
- if (Repository.isLoading) {
178
- await Repository.waitUntilDoneLoading();
179
- }
180
- displayValue = _.each(value, (id) => {
181
- const entity = Repository.getById(id);
182
- if (entity) {
183
- displayValue.push(entity.displayValue);
188
+ if (!Repository.isDestroyed) {
189
+ if (!Repository.isLoaded) {
190
+ throw Error('Not yet implemented'); // Would a Combo ever have multiple remote selections? Shouldn't that be a Tag field??
184
191
  }
185
- });
192
+ if (Repository.isLoading) {
193
+ await Repository.waitUntilDoneLoading();
194
+ }
195
+ displayValue = _.each(value, (id) => {
196
+ const entity = Repository.getById(id);
197
+ if (entity) {
198
+ displayValue.push(entity.displayValue);
199
+ }
200
+ });
201
+ }
186
202
  } else {
187
203
  displayValue = _.each(value, (id) => {
188
204
  const item = _.find(data, (datum) => datum[idIx] === id);
@@ -194,25 +210,33 @@ export const ComboComponent = forwardRef((props, ref) => {
194
210
  displayValue = displayValue.join(', ');
195
211
  } else {
196
212
  if (Repository) {
197
- let entity;
198
- if (!Repository.isLoaded) {
199
- entity = await Repository.getSingleEntityFromServer(value);
200
- } else {
201
- if (Repository.isLoading) {
202
- await Repository.waitUntilDoneLoading();
203
- }
204
- entity = Repository.getById(value);
205
- if (!entity) {
206
- entity = await Repository.getSingleEntityFromServer(value);
213
+ if (!Repository.isDestroyed) {
214
+ let entity;
215
+ if (!isEmptyValue(value)) {
216
+ if (!Repository.isLoaded) {
217
+ entity = await Repository.getSingleEntityFromServer(value);
218
+ } else {
219
+ if (Repository.isLoading) {
220
+ await Repository.waitUntilDoneLoading();
221
+ }
222
+ entity = Repository.getById(value);
223
+ if (!entity) {
224
+ entity = await Repository.getSingleEntityFromServer(value);
225
+ }
226
+ }
207
227
  }
228
+ displayValue = entity?.displayValue || '';
208
229
  }
209
- displayValue = entity?.displayValue || '';
210
230
  } else {
211
231
  const item = _.find(data, (datum) => datum[idIx] === value);
212
232
  displayValue = (item && item[displayIx]) || '';
213
233
  }
214
234
  }
215
235
 
236
+ if (isInTag) {
237
+ displayValue = '';
238
+ }
239
+
216
240
  displayValueRef.current = displayValue;
217
241
  resetTextInputValue();
218
242
  },
@@ -399,14 +423,16 @@ export const ComboComponent = forwardRef((props, ref) => {
399
423
  },
400
424
  clearGridFilters = async () => {
401
425
  if (Repository) {
402
- if (Repository.isLoading) {
403
- await Repository.waitUntilDoneLoading();
404
- }
405
- const filterName = getFilterName();
406
- if (Repository.hasFilter(filterName)) {
407
- Repository.clearFilters(filterName);
408
- if (Repository.isRemote && !Repository.isAutoLoad) {
409
- await Repository.reload();
426
+ if (!Repository.isDestroyed) {
427
+ if (Repository.isLoading) {
428
+ await Repository.waitUntilDoneLoading();
429
+ }
430
+ const filterName = getFilterName();
431
+ if (Repository.hasFilter(filterName)) {
432
+ Repository.clearFilters(filterName);
433
+ if (Repository.isRemote && !Repository.isAutoLoad) {
434
+ await Repository.reload();
435
+ }
410
436
  }
411
437
  }
412
438
  } else {
@@ -421,42 +447,44 @@ export const ComboComponent = forwardRef((props, ref) => {
421
447
  searchForMatches = async (value) => {
422
448
  let found;
423
449
  if (Repository) {
424
- if (Repository.isLoading) {
425
- await Repository.waitUntilDoneLoading();
426
- }
450
+ if (!Repository.isDestroyed) {
451
+ if (Repository.isLoading) {
452
+ await Repository.waitUntilDoneLoading();
453
+ }
427
454
 
428
- if (_.isEmpty(value)) {
429
- clearGridFilters();
430
- return;
431
- }
455
+ if (_.isEmpty(value)) {
456
+ clearGridFilters();
457
+ return;
458
+ }
432
459
 
433
- // Set filter
434
- const
435
- idRegex = /^id:(.*)$/,
436
- isId = _.isString(value) && !!value.match(idRegex),
437
- filterName = getFilterName(isId);
438
- if (Repository.isRemote) {
439
- // remote
440
- const filterValue = _.isEmpty(value) ? null : (isId ? value.match(idRegex)[1] : value + '%');
441
- await Repository.filter(filterName, filterValue);
442
- if (!Repository.isAutoLoad) {
443
- await Repository.reload();
460
+ // Set filter
461
+ const
462
+ idRegex = /^id:(.*)$/,
463
+ isId = _.isString(value) && !!value.match(idRegex),
464
+ filterName = getFilterName(isId);
465
+ if (Repository.isRemote) {
466
+ // remote
467
+ const filterValue = _.isEmpty(value) ? null : (isId ? value.match(idRegex)[1] : value + '%');
468
+ await Repository.filter(filterName, filterValue);
469
+ if (!Repository.isAutoLoad) {
470
+ await Repository.reload();
471
+ }
472
+ } else {
473
+ // local
474
+ Repository.filter({
475
+ name: filterName,
476
+ fn: (entity) => {
477
+ const
478
+ displayValue = entity.displayValue,
479
+ regex = new RegExp('^' + value, 'i'); // case-insensitive
480
+ return displayValue.match(regex);
481
+ },
482
+ });
444
483
  }
445
- } else {
446
- // local
447
- Repository.filter({
448
- name: filterName,
449
- fn: (entity) => {
450
- const
451
- displayValue = entity.displayValue,
452
- regex = new RegExp('^' + value, 'i'); // case-insensitive
453
- return displayValue.match(regex);
454
- },
455
- });
456
- }
457
484
 
458
- if (!isId) {
459
- setNewEntityDisplayValue(value); // capture the search query so we can tell Grid what to use for a new entity's displayValue
485
+ if (!isId) {
486
+ setNewEntityDisplayValue(value); // capture the search query so we can tell Grid what to use for a new entity's displayValue
487
+ }
460
488
  }
461
489
  } else {
462
490
  // Search through data
@@ -164,6 +164,7 @@ function NumberElement(props) {
164
164
  isDisabled={isDisabled}
165
165
  tooltip={tooltip}
166
166
  tooltipPlacement={tooltipPlacement}
167
+ tooltipClassName="flex-1"
167
168
  className={`
168
169
  h-full
169
170
  text-center
@@ -30,15 +30,17 @@ const
30
30
  my: '2px',
31
31
  };
32
32
  if (Repository) {
33
- const entities = Repository.getEntitiesOnPage();
34
- radios = _.map(entities, (entity, ix) => {
35
- return <Radio
36
- {...testProps('radio-' + entity.id)}
37
- key={ix}
38
- value={entity.id}
39
- {...radioProps}
40
- >{entity.displayValue}</Radio>;
41
- });
33
+ if (!Repository.isDestroyed) {
34
+ const entities = Repository.getEntitiesOnPage();
35
+ radios = _.map(entities, (entity, ix) => {
36
+ return <Radio
37
+ {...testProps('radio-' + entity.id)}
38
+ key={ix}
39
+ value={entity.id}
40
+ {...radioProps}
41
+ >{entity.displayValue}</Radio>;
42
+ });
43
+ }
42
44
  } else {
43
45
  radios = _.map(data, (datum, ix) => {
44
46
  return <Radio
@@ -11,7 +11,7 @@ export default function PageSizeSelect(props) {
11
11
  } = props;
12
12
 
13
13
  return useMemo(() => {
14
- return <HStack className="PageSizeSelect-HStack w-[70px] ml-3">
14
+ return <HStack className="PageSizeSelect-HStack w-[70px]">
15
15
  <Select
16
16
  data={[
17
17
  // [ 1, '1', ],
@@ -124,11 +124,13 @@ function TagComponent(props) {
124
124
  if (!id) {
125
125
  displayValue = '';
126
126
  } else if (Repository) {
127
- item = Repository.getById(id);
128
- if (!item) {
129
- throw Error('item not found');
127
+ if (!Repository.isDestroyed) {
128
+ item = Repository.getById(id);
129
+ if (!item) {
130
+ throw Error('item not found');
131
+ }
132
+ displayValue = item.displayValue;
130
133
  }
131
- displayValue = item.displayValue;
132
134
  } else {
133
135
  item = _.find(data, (datum) => datum[idIx] === id);
134
136
  if (!item) {
@@ -248,19 +250,19 @@ function TagComponent(props) {
248
250
  className += ' ' + props.className;
249
251
  }
250
252
  const style = {};
251
- if (!props.flex && !props.w) {
253
+ if (props.style) {
254
+ _.assign(style, props.style); // needed for grid; otherwise valuebox width can be too wide
255
+ }
256
+ if (!props.flex && !props.w && !style.width) {
252
257
  style.flex = 1;
253
258
  } else {
254
- if (props.w) {
259
+ if (props.w && !style.width) {
255
260
  style.width = props.w;
256
261
  }
257
- if (props.flex) {
262
+ if (props.flex && !style.width) {
258
263
  style.flex = props.flex;
259
264
  }
260
265
  }
261
- if (props.style) {
262
- _.assign(style, props.style); // needed for grid; otherwise valuebox width can be too wide
263
- }
264
266
  let valueBoxesClassName = `
265
267
  Tag-valueBoxes-container
266
268
  w-full
@@ -966,7 +966,7 @@ function Form(props) {
966
966
  doReset = (values) => {
967
967
  reset(values);
968
968
  if (onReset) {
969
- onReset(values, formSetValue, formGetValues);
969
+ onReset(values, formSetValue, formGetValues, trigger);
970
970
  }
971
971
  },
972
972
  onSaveDecorated = async (data, e) => {
@@ -998,14 +998,14 @@ function Form(props) {
998
998
  }
999
999
  if (record === previousRecord) {
1000
1000
  if (onInit) {
1001
- onInit(initialValues, formSetValue, formGetValues);
1001
+ onInit(initialValues, formSetValue, formGetValues, trigger);
1002
1002
  }
1003
1003
  } else {
1004
1004
  setPreviousRecord(record);
1005
1005
  doReset(defaultValues);
1006
1006
  }
1007
1007
  if (formSetup) {
1008
- formSetup(formSetValue, formGetValues, formState);
1008
+ formSetup(formSetValue, formGetValues, formState, trigger);
1009
1009
  }
1010
1010
  }, [record]);
1011
1011
 
@@ -146,6 +146,7 @@ function GridComponent(props) {
146
146
  disableBottomToolbar = false,
147
147
  disablePagination = false,
148
148
  bottomToolbar = 'pagination',
149
+ _paginationToolbarProps = {},
149
150
  topToolbar = null,
150
151
  additionalToolbarButtons = [],
151
152
  bg = '#fff',
@@ -744,10 +745,12 @@ function GridComponent(props) {
744
745
  let dragRecord,
745
746
  dropRecord;
746
747
  if (Repository) {
747
- dragRecord = Repository.getByIx(dragIx);
748
- dropRecord = Repository.getByIx(dropIx);
749
- if (dropRecord) {
750
- Repository.reorder(dragRecord, dropRecord, useBottom ? DROP_POSITION_AFTER : DROP_POSITION_BEFORE);
748
+ if (!Repository.isDestroyed) {
749
+ dragRecord = Repository.getByIx(dragIx);
750
+ dropRecord = Repository.getByIx(dropIx);
751
+ if (dropRecord) {
752
+ Repository.reorder(dragRecord, dropRecord, useBottom ? DROP_POSITION_AFTER : DROP_POSITION_BEFORE);
753
+ }
751
754
  }
752
755
  } else {
753
756
  function arrayMove(arr, fromIndex, toIndex) {
@@ -766,21 +769,13 @@ function GridComponent(props) {
766
769
  const
767
770
  headerHeight = showHeaders ? 50 : 0,
768
771
  footerHeight = !disablePagination ? 50 : 0,
769
- height = containerHeight - headerHeight - footerHeight;
770
-
771
- const rowsPerContainer = Math.floor(height / defaultRowHeight);
772
-
773
- // // Get the total height of all rows
774
- // const rows = gridRef.current._listRef._scrollRef.childNodes[0].childNodes;
775
- // let totalRowHeight = 0;
776
- // rows.forEach((row) => {
777
- // totalRowHeight += row.getBoundingClientRect().height;
778
- // });
779
- // const rowsPerContainer = Math.floor(height / (totalRowHeight / rows.length));
780
-
781
- let pageSize = rowsPerContainer;
782
- if (showHeaders) {
783
- pageSize--;
772
+ availableHeight = containerHeight - headerHeight - footerHeight,
773
+ maxClassNormal = styles.GRID_ROW_MAX_HEIGHT_NORMAL, // e.g. max-h-[40px]
774
+ rowNormalHeight = parseInt(maxClassNormal.match(/\d+/)[0]);
775
+
776
+ let pageSize = Math.floor(availableHeight / rowNormalHeight);
777
+ if (pageSize < 1) {
778
+ pageSize = 1;
784
779
  }
785
780
  return pageSize;
786
781
  },
@@ -1162,6 +1157,7 @@ function GridComponent(props) {
1162
1157
  toolbarItems={footerToolbarItemComponents}
1163
1158
  disablePageSize={disablePageSize}
1164
1159
  showMoreOnly={showMoreOnly}
1160
+ {..._paginationToolbarProps}
1165
1161
  />;
1166
1162
  } else if (footerToolbarItemComponents.length) {
1167
1163
  listFooterComponent = <Toolbar>
@@ -92,8 +92,8 @@ function GridRow(props) {
92
92
  justify-center
93
93
  border-r-black-100
94
94
  block
95
- max-h-[40px]
96
- overflow-scroll
95
+ overflow-auto
96
+ ${styles.GRID_ROW_MAX_HEIGHT_EXTRA}
97
97
  `;
98
98
  if (isOnlyOneVisibleColumn) {
99
99
  colClassName = ' w-full';
@@ -167,8 +167,9 @@ function GridRow(props) {
167
167
  if (type.match(/(Tag|TagEditor|Json)$/)) {
168
168
  elementProps.isViewOnly = true; // TODO: this won't work for InlineGridEditor, bc that Grid can't use isViewOnly when actually editing
169
169
  }
170
+ let cellProps = {};
170
171
  if (config.getCellProps) {
171
- _.assign(elementProps, config.getCellProps(item));
172
+ _.assign(cellProps, config.getCellProps(item));
172
173
  }
173
174
  let elementClassName = `
174
175
  GridRow-Element
@@ -177,16 +178,19 @@ function GridRow(props) {
177
178
  px-2
178
179
  py-3
179
180
  block
180
- max-h-[40px]
181
181
  overflow-scroll
182
182
  ${colClassName}
183
183
  ${styles.GRID_CELL_CLASSNAME}
184
+ ${styles.GRID_ROW_MAX_HEIGHT_NORMAL}
184
185
  `;
185
186
  if (config.className) {
186
187
  elementClassName += ' ' + config.className;
187
188
  }
189
+ if (cellProps.className) {
190
+ elementClassName += ' ' + cellProps.className;
191
+ }
188
192
  if (type.match(/(Tag|TagEditor)$/)) {
189
- elementClassName += ' max-h-[80px]';
193
+ elementClassName += ' ' + styles.GRID_ROW_MAX_HEIGHT_EXTRA;
190
194
  }
191
195
  return <Element
192
196
  {...testProps('cell-' + config.fieldName)}
@@ -232,14 +236,9 @@ function GridRow(props) {
232
236
  GridRow-TextNative
233
237
  self-center
234
238
  overflow-hidden
235
- text-ellipsis
236
- truncate
237
- whitespace-nowrap
238
- overflow-hidden
239
239
  ${colClassName}
240
- ${styles.GRID_CELL_CLASSNAME}
241
- ${styles.GRID_CELL_PX}
242
- ${styles.GRID_CELL_PY}
240
+ ${styles.GRID_CELL_CLASSNAME}
241
+ ${styles.GRID_ROW_MAX_HEIGHT_EXTRA}
243
242
  `;
244
243
  if (config.className) {
245
244
  textClassName += ' ' + config.className;
@@ -101,6 +101,15 @@ export default function withEditor(WrappedComponent, isTree = false) {
101
101
  onEditorClose();
102
102
  }
103
103
  },
104
+ setIsWaitModalShown = (bool) => {
105
+ const
106
+ dispatch = UiGlobals.redux?.dispatch,
107
+ setIsWaitModalShownAction = UiGlobals.debugReducer?.setIsWaitModalShownAction;
108
+ if (setIsWaitModalShownAction) {
109
+ console.log('withEditor:setIsWaitModalShownAction', bool);
110
+ dispatch(setIsWaitModalShownAction(bool));
111
+ }
112
+ },
104
113
  setSelectionDecorated = (newSelection) => {
105
114
  function doIt() {
106
115
  setSelection(newSelection);
@@ -386,36 +395,66 @@ export default function withEditor(WrappedComponent, isTree = false) {
386
395
  return;
387
396
  }
388
397
 
389
- // check permissions for duplicate
390
-
391
398
  const selection = getSelection();
392
399
  if (selection.length !== 1) {
393
400
  return;
394
401
  }
402
+
395
403
  if (useRemoteDuplicate) {
396
- const results = await onRemoteDuplicate();
397
- return results;
404
+ return await onRemoteDuplicate();
405
+ }
406
+
407
+ let isSuccess = false,
408
+ duplicateEntity;
409
+ try {
410
+ const
411
+ entity = selection[0],
412
+ idProperty = Repository.getSchema().model.idProperty,
413
+ rawValues = _.omit(entity.getOriginalData(), idProperty);
414
+ rawValues.id = null; // unset the id of the duplicate
415
+
416
+ setIsWaitModalShown(true);
417
+
418
+ duplicateEntity = await Repository.add(rawValues, false, true);
419
+ isSuccess = true;
420
+
421
+ } catch(err) {
422
+ // do nothing
423
+ } finally {
424
+ setIsWaitModalShown(false);
425
+ }
426
+
427
+ if (isSuccess) {
428
+ setIsIgnoreNextSelectionChange(true);
429
+ setSelection([duplicateEntity]);
430
+ setEditorMode(EDITOR_MODE__EDIT);
431
+ setIsEditorShown(true);
398
432
  }
399
- const
400
- entity = selection[0],
401
- idProperty = Repository.getSchema().model.idProperty,
402
- rawValues = _.omit(entity.getOriginalData(), idProperty);
403
- rawValues.id = null; // unset the id of the duplicate
404
- const duplicate = await Repository.add(rawValues, false, true);
405
- setIsIgnoreNextSelectionChange(true);
406
- setSelection([duplicate]);
407
- setEditorMode(EDITOR_MODE__EDIT);
408
- setIsEditorShown(true);
409
433
  },
410
434
  onRemoteDuplicate = async () => {
411
- const
412
- selection = getSelection(),
413
- entity = selection[0],
414
- duplicateEntity = await Repository.remoteDuplicate(entity);
435
+ let isSuccess = false,
436
+ duplicateEntity;
437
+ try {
438
+ const
439
+ selection = getSelection(),
440
+ entity = selection[0];
441
+
442
+ setIsWaitModalShown(true);
415
443
 
416
- setIsIgnoreNextSelectionChange(true);
417
- setSelection([duplicateEntity]);
418
- doEdit();
444
+ duplicateEntity = await Repository.remoteDuplicate(entity);
445
+ isSuccess = true;
446
+
447
+ } catch(err) {
448
+ // do nothing
449
+ } finally {
450
+ setIsWaitModalShown(false);
451
+ }
452
+ if (isSuccess) {
453
+ setIsIgnoreNextSelectionChange(true);
454
+ setSelection([duplicateEntity]);
455
+ doEdit();
456
+ return duplicateEntity;
457
+ }
419
458
  },
420
459
  doEditorSave = async (data, e) => {
421
460
  let mode = getEditorMode() === EDITOR_MODE__ADD ? ADD : EDIT;
@@ -654,7 +654,7 @@ export default function withFilters(WrappedComponent) {
654
654
  <HStack className="withFilters-scrollViewContainer flex-1 items-center">
655
655
  <ScrollView
656
656
  ref={scrollViewRef}
657
- className={`withFilters-ScrollView ${scrollViewClass}`}
657
+ className={`withFilters-ScrollView ${scrollViewClass} pb-1`}
658
658
  horizontal={true}
659
659
  contentContainerStyle={{ alignItems: 'center' }}
660
660
  onContentSizeChange={onContentSizeChange}
@@ -189,7 +189,7 @@ export default function withModal(WrappedComponent) {
189
189
  {isModalShown &&
190
190
  <Modal
191
191
  isOpen={true}
192
- onClose={onCancel}
192
+ onClose={onCancel || (canClose ? hideModal : null)}
193
193
  className="withModal-Modal"
194
194
  {...testProps(testID)}
195
195
  >
@@ -141,6 +141,9 @@ export default function withPdfButtons(WrappedComponent) {
141
141
  if (!_.isEmpty(items)) {
142
142
  const defaults = item.defaults;
143
143
  item.items = _.map(items, (item, ix) => {
144
+ if (!item){
145
+ return null;
146
+ }
144
147
  return buildNextLayer(item, ix, defaults);
145
148
  });
146
149
  }
@@ -150,7 +150,9 @@ export default function withSelection(WrappedComponent) {
150
150
  currentlySelectedRowIndices = [];
151
151
  const Repository = getRepository();
152
152
  if (Repository) {
153
- items = Repository.getEntitiesOnPage();
153
+ if (!Repository.isDestroyed) {
154
+ items = Repository.getEntitiesOnPage();
155
+ }
154
156
  } else {
155
157
  items = data;
156
158
  }
@@ -214,8 +216,12 @@ export default function withSelection(WrappedComponent) {
214
216
 
215
217
  // Gets ix of entity on page, or element in data array
216
218
  if (Repository) {
217
- const entities = Repository.getEntitiesOnPage();
218
- return entities.indexOf(item);
219
+ if (!Repository.isDestroyed) {
220
+ const entities = Repository.getEntitiesOnPage();
221
+ return entities.indexOf(item);
222
+ } else {
223
+ return -1;
224
+ }
219
225
  }
220
226
 
221
227
  let found;
@@ -272,28 +278,30 @@ export default function withSelection(WrappedComponent) {
272
278
  const Repository = getRepository();
273
279
  let newSelection = [];
274
280
  if (Repository) {
275
- if (Repository.isLoading) {
276
- await Repository.waitUntilDoneLoading();
277
- }
278
- // Get entity or entities that match value
279
- if ((_.isArray(value) && !_.isEmpty(value)) || !!value) {
280
- if (_.isArray(value)) {
281
- newSelection = Repository.getBy((entity) => inArray(entity.id, value));
282
- } else {
283
- let found = Repository.getById(value);
284
- if (found) {
285
- newSelection.push(found);
286
- // } else if (Repository?.isRemote && Repository?.entities.length) {
287
-
288
- // // Value cannot be found in Repository, but actually exists on server
289
- // // Try to get this value from the server directly
290
- // Repository.filter(Repository.schema.model.idProperty, value);
291
- // await Repository.load();
292
- // found = Repository.getById(value);
293
- // if (found) {
294
- // newSelection.push(found);
295
- // }
296
-
281
+ if (!Repository.isDestroyed) {
282
+ if (Repository.isLoading) {
283
+ await Repository.waitUntilDoneLoading();
284
+ }
285
+ // Get entity or entities that match value
286
+ if ((_.isArray(value) && !_.isEmpty(value)) || !!value) {
287
+ if (_.isArray(value)) {
288
+ newSelection = Repository.getBy((entity) => inArray(entity.id, value));
289
+ } else {
290
+ let found = Repository.getById(value);
291
+ if (found) {
292
+ newSelection.push(found);
293
+ // } else if (Repository?.isRemote && Repository?.entities.length) {
294
+
295
+ // // Value cannot be found in Repository, but actually exists on server
296
+ // // Try to get this value from the server directly
297
+ // Repository.filter(Repository.schema.model.idProperty, value);
298
+ // await Repository.load();
299
+ // found = Repository.getById(value);
300
+ // if (found) {
301
+ // newSelection.push(found);
302
+ // }
303
+
304
+ }
297
305
  }
298
306
  }
299
307
  }
@@ -326,6 +334,9 @@ export default function withSelection(WrappedComponent) {
326
334
 
327
335
  if (Repository) {
328
336
  useEffect(() => {
337
+ if (Repository.isDestroyed) {
338
+ return null;
339
+ }
329
340
  Repository.on('load', refreshSelection);
330
341
  return () => {
331
342
  Repository.off('load', refreshSelection);
@@ -356,8 +367,10 @@ export default function withSelection(WrappedComponent) {
356
367
  } else if (autoSelectFirstItem) {
357
368
  let newSelection = [];
358
369
  if (Repository) {
359
- const entitiesOnPage = Repository.getEntitiesOnPage();
360
- newSelection = entitiesOnPage[0] ? [entitiesOnPage[0]] : [];
370
+ if (!Repository.isDestroyed) {
371
+ const entitiesOnPage = Repository.getEntitiesOnPage();
372
+ newSelection = entitiesOnPage[0] ? [entitiesOnPage[0]] : [];
373
+ }
361
374
  } else {
362
375
  newSelection = data[0] ? [data[0]] : [];
363
376
  }
@@ -70,14 +70,23 @@ export default function Pagination(props) {
70
70
  }
71
71
  if (!Repository.isLocal) {
72
72
  items.push(<ReloadButton
73
- key="reloadPageBtn"
74
- className="Pagination-reloadPageBtn"
75
- _icon={iconProps}
76
- Repository={Repository}
77
- self={self}
78
- />);
73
+ key="reloadPageBtn"
74
+ className="Pagination-reloadPageBtn"
75
+ _icon={iconProps}
76
+ Repository={Repository}
77
+ self={self}
78
+ />);
79
79
  }
80
80
  } else {
81
+ if (!Repository.isLocal) {
82
+ items.push(<ReloadButton
83
+ key="reloadPageBtn"
84
+ className="Pagination-reloadPageBtn"
85
+ _icon={iconProps}
86
+ Repository={Repository}
87
+ self={self}
88
+ />);
89
+ }
81
90
  isDisabled = page === 1;
82
91
  if (showPagination) {
83
92
  items.push(<IconButton
@@ -104,42 +113,6 @@ export default function Pagination(props) {
104
113
  onPress={() => Repository.prevPage()}
105
114
  tooltip="Previous Page"
106
115
  />);
107
- if (!minimize) {
108
- items.push(<Text
109
- key="page"
110
- className="Pagination-page mx-1"
111
- >Page</Text>);
112
- items.push(<Input
113
- {...testProps('pageInput')}
114
- key="pageInput"
115
- reference="pageInput"
116
- parent={self}
117
- keyboardType="numeric"
118
- value={page?.toString()}
119
- onChangeValue={(value) => Repository.setPage(value)}
120
- maxValue={totalPages}
121
- isDisabled={totalPages === 1}
122
- className={`
123
- Pagination-pageInput
124
- min-w-[40px]
125
- w-[40px]
126
- text-center
127
- bg-grey-100
128
- `}
129
- textAlignIsCenter={true}
130
- tooltip="Set Page"
131
- tooltipClassName="w-[40px]"
132
- />);
133
- items.push(<Text
134
- key="totalPages"
135
- className={`
136
- Pagination-totalPages
137
- whitespace-nowrap
138
- inline-flex
139
- mx-1
140
- `}
141
- >{`of ${totalPages}`}</Text>);
142
- }
143
116
 
144
117
  isDisabled = page === totalPages || totalPages <= 1;
145
118
  items.push(<IconButton
@@ -166,17 +139,44 @@ export default function Pagination(props) {
166
139
  onPress={() => Repository.setPage(totalPages)}
167
140
  tooltip="Last Page"
168
141
  />);
142
+ if (!minimize) {
143
+ items.push(<Text
144
+ key="page"
145
+ className="Pagination-page mx-1"
146
+ >Page</Text>);
147
+ items.push(<Input
148
+ {...testProps('pageInput')}
149
+ key="pageInput"
150
+ reference="pageInput"
151
+ parent={self}
152
+ keyboardType="numeric"
153
+ value={page?.toString()}
154
+ onChangeValue={(value) => Repository.setPage(value)}
155
+ maxValue={totalPages}
156
+ isDisabled={totalPages === 1}
157
+ className={`
158
+ Pagination-pageInput
159
+ min-w-[40px]
160
+ w-[40px]
161
+ text-center
162
+ bg-grey-100
163
+ `}
164
+ textAlignIsCenter={true}
165
+ tooltip="Set Page"
166
+ tooltipClassName="w-[40px]"
167
+ />);
168
+ items.push(<Text
169
+ key="totalPages"
170
+ className={`
171
+ Pagination-totalPages
172
+ whitespace-nowrap
173
+ inline-flex
174
+ mx-1
175
+ `}
176
+ >{`of ${totalPages}`}</Text>);
177
+ }
169
178
  }
170
179
 
171
- if (!Repository.isLocal) {
172
- items.push(<ReloadButton
173
- key="reloadPageBtn"
174
- className="Pagination-reloadPageBtn"
175
- _icon={iconProps}
176
- Repository={Repository}
177
- self={self}
178
- />);
179
- }
180
180
  if (showPagination && !minimize && !disablePageSize) {
181
181
  items.push(<PageSizeSelect
182
182
  {...testProps('pageSize')}
@@ -204,15 +204,14 @@ export default function Pagination(props) {
204
204
  }
205
205
  }
206
206
  return <HStack
207
- style={{ userSelect: 'none', }}
207
+ style={{
208
+ userSelect: 'none',
209
+ }}
208
210
  className={`
209
211
  Pagination
210
- flex-none
211
- gap-1
212
- justify-start
213
212
  items-center
214
- px-2
215
- mr-3
213
+ shrink-0
214
+ gap-2
216
215
  `}
217
216
  >
218
217
  {items}
@@ -10,11 +10,15 @@ export default function PaginationToolbar(props) {
10
10
  const {
11
11
  toolbarItems = [],
12
12
  disablePageSize = false,
13
+ minimize,
13
14
  } = props,
14
- [minimize, setMinimize] = useState(false),
15
+ [minimizeLocal, setMinimizeLocal] = useState(minimize),
15
16
  propsToPass = _.omit(props, 'toolbarItems'),
16
17
  showPagination = true,//props.Repository?.totalPages > 1,
17
18
  onLayout = (e) => {
19
+ if (minimize) {
20
+ return; // skip if already minimized
21
+ }
18
22
  // Note to future self: this is using hard-coded values.
19
23
  // Eventually might want to make it responsive to actual sizes
20
24
 
@@ -28,7 +32,7 @@ export default function PaginationToolbar(props) {
28
32
  shouldMinimize = width < threshold;
29
33
 
30
34
  if (shouldMinimize !== minimize) {
31
- setMinimize(shouldMinimize);
35
+ setMinimizeLocal(shouldMinimize);
32
36
  }
33
37
  };
34
38
 
@@ -36,27 +40,26 @@ export default function PaginationToolbar(props) {
36
40
  className={`
37
41
  border-t
38
42
  border-t-grey-400
39
- w-full
40
43
  `}
41
44
  onLayout={(e) => onLayout(e)}
42
45
  >
46
+ {toolbarItems.length ?
47
+ <HStack
48
+ className={`
49
+ PaginationToolbar-HStack
50
+ shrink-0
51
+ border-r
52
+ border-r-grey-400
53
+ mr-3
54
+ pr-3
55
+ `}
56
+ >{toolbarItems}</HStack> : null}
43
57
  <Pagination
44
58
  {...propsToPass}
45
59
  showPagination={showPagination}
46
60
  w={toolbarItems.length ? null : '100%'}
47
- minimize={minimize}
61
+ minimize={minimizeLocal}
48
62
  disablePageSize={disablePageSize}
49
63
  />
50
- {toolbarItems.length ?
51
- <HStack className={`
52
- PaginationToolbar-HStack
53
- flex-1
54
- space-x-1
55
- border-l
56
- border-l-grey-400
57
- ml-3
58
- pl-3
59
- `}
60
- >{toolbarItems}</HStack> : null}
61
64
  </Toolbar>;
62
65
  };
@@ -9,14 +9,11 @@ export default function Toolbar(props) {
9
9
 
10
10
  let className = `
11
11
  Toolbar
12
- flex
13
- w-full
14
- justify-start
15
- items-center
16
12
  overflow-auto
17
- space-x-2
18
- px-2
19
- py-2
13
+ items-center
14
+ justify-start
15
+ gap-2
16
+ p-2
20
17
  border-b
21
18
  border-solid
22
19
  border-b-grey-400
@@ -534,10 +534,12 @@ function TreeComponent(props) {
534
534
  buildAndSetTreeNodeData = async () => {
535
535
  let nodes = [];
536
536
  if (Repository) {
537
- if (!Repository.areRootNodesLoaded) {
538
- nodes = await Repository.loadRootNodes(1);
539
- } else {
540
- nodes = Repository.getRootNodes();
537
+ if (!Repository.isDestroyed) {
538
+ if (!Repository.areRootNodesLoaded) {
539
+ nodes = await Repository.loadRootNodes(1);
540
+ } else {
541
+ nodes = Repository.getRootNodes();
542
+ }
541
543
  }
542
544
  } else {
543
545
  nodes = assembleDataTreeNodes();
@@ -611,7 +613,9 @@ function TreeComponent(props) {
611
613
  },
612
614
  getTreeNodeByNodeId = (node_id) => {
613
615
  if (Repository) {
614
- return Repository.getById(node_id);
616
+ if (!Repository.isDestroyed) {
617
+ return Repository.getById(node_id);
618
+ }
615
619
  }
616
620
  return data[node_id]; // TODO: This is probably not right!
617
621
  },
@@ -732,8 +736,8 @@ function TreeComponent(props) {
732
736
  }
733
737
 
734
738
  const
735
- depthChildren = await datum.item.loadChildren(depth),
736
- directChildren = _.filter(depthChildren, (child) => { // narrow list to only direct descendants, so buildTreeNodeData can work correctly
739
+ node = await datum.item.loadChildren(depth),
740
+ directChildren = _.filter(node.children, (child) => { // narrow list to only direct descendants, so buildTreeNodeData can work correctly
737
741
  return child.depth === datum.item.depth + 1;
738
742
  });
739
743
 
@@ -1217,11 +1221,11 @@ function TreeComponent(props) {
1217
1221
  dropRowRecord = dropRowDatum.item;
1218
1222
 
1219
1223
  if (Repository) {
1220
-
1221
- const commonAncestorId = await Repository.moveTreeNode(dragRowRecord, dropRowRecord.id);
1222
- const commonAncestorDatum = getDatumById(commonAncestorId);
1223
- reloadNode(commonAncestorDatum.item);
1224
-
1224
+ if (!Repository.isDestroyed) {
1225
+ const commonAncestorId = await Repository.moveTreeNode(dragRowRecord, dropRowRecord.id);
1226
+ const commonAncestorDatum = getDatumById(commonAncestorId);
1227
+ reloadNode(commonAncestorDatum.item);
1228
+ }
1225
1229
  } else {
1226
1230
 
1227
1231
  throw Error('Not yet implemented');
@@ -78,6 +78,8 @@ const defaults = {
78
78
  GRID_ROW_BG_HOVER: '#ccc', // must be hex
79
79
  GRID_ROW_SELECTED_BG: '#ff0', // must be hex
80
80
  GRID_ROW_SELECTED_BG_HOVER: '#cc0', // must be hex
81
+ GRID_ROW_MAX_HEIGHT_NORMAL: 'max-h-[40px]',
82
+ GRID_ROW_MAX_HEIGHT_EXTRA: 'max-h-[80px]',
81
83
  GRID_REORDER_BORDER_COLOR: 'border-[#23d9ea]',
82
84
  GRID_REORDER_BORDER_WIDTH: 'border-4',
83
85
  GRID_REORDER_BORDER_STYLE: 'border-dashed',