@onehat/ui 0.3.32 → 0.3.33

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.3.32",
3
+ "version": "0.3.33",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -77,6 +77,7 @@ function Form(props) {
77
77
  // sizing of outer container
78
78
  h,
79
79
  maxHeight,
80
+ minHeight = 0,
80
81
  w,
81
82
  maxWidth,
82
83
  flex,
@@ -617,7 +618,7 @@ function Form(props) {
617
618
  } else {
618
619
  formComponents = buildFromItems();
619
620
  const formAncillaryComponents = buildAncillary();
620
- editor = <ScrollView _web={{ height: 1 }} width="100%" pb={1}>
621
+ editor = <ScrollView _web={{ minHeight, }} width="100%" pb={1}>
621
622
  <Column p={4}>{formComponents}</Column>
622
623
  <Column m={2} pt={4}>{formAncillaryComponents}</Column>
623
624
  </ScrollView>;
@@ -10,6 +10,7 @@ export default function withComponent(WrappedComponent) {
10
10
  const {
11
11
  // self: parent,
12
12
  parent,
13
+ componentMethods,
13
14
  ...propsToPass
14
15
  } = props,
15
16
  { reference } = props,
@@ -38,6 +39,11 @@ export default function withComponent(WrappedComponent) {
38
39
  });
39
40
 
40
41
  useEffect(() => {
42
+ if (componentMethods) {
43
+ _.each(componentMethods, (method, name) => {
44
+ selfRef.current[name] = method;
45
+ });
46
+ }
41
47
  if (parent && reference) {
42
48
  parent.registerChild(selfRef.current);
43
49
  }
@@ -28,6 +28,9 @@ export default function withData(WrappedComponent) {
28
28
  displayField = 'value',
29
29
  idIx,
30
30
  displayIx,
31
+
32
+ // withComponent
33
+ self,
31
34
  } = props,
32
35
  propsToPass = _.omit(props, ['model']), // passing 'model' would mess things up if withData gets called twice (e.g. withData(...withData(...)) ), as we'd be trying to recreate Repository twice
33
36
  localIdIx = idIx || (fields && idField ? fields.indexOf(idField) : null),
@@ -64,6 +67,9 @@ export default function withData(WrappedComponent) {
64
67
  if (setRepository) { // pass it on up to higher components
65
68
  setRepository(Repository);
66
69
  }
70
+ if (self) {
71
+ self.repository = Repository;
72
+ }
67
73
  setIsReady(true);
68
74
  })();
69
75
 
@@ -22,6 +22,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
22
22
  disableDelete = false,
23
23
  disableDuplicate = false,
24
24
  disableView = false,
25
+ useRemoteDuplicate = false, // call specific copyToNew function on server, rather than simple duplicate on client
25
26
  getRecordIdentifier = (selection) => {
26
27
  if (selection.length > 1) {
27
28
  return 'records?';
@@ -224,6 +225,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
224
225
  if (selection.length !== 1) {
225
226
  return;
226
227
  }
228
+ if (useRemoteDuplicate) {
229
+ return onRemoteDuplicate();
230
+ }
227
231
  const
228
232
  entity = selection[0],
229
233
  idProperty = Repository.getSchema().model.idProperty,
@@ -233,6 +237,40 @@ export default function withEditor(WrappedComponent, isTree = false) {
233
237
  setEditorMode(EDITOR_MODE__EDIT);
234
238
  setIsEditorShown(true);
235
239
  },
240
+ onRemoteDuplicate = async () => {
241
+
242
+ // Call /duplicate on server
243
+ const
244
+ Model = Repository.getSchema().name,
245
+ entity = selection[0],
246
+ id = entity.id;
247
+ const result = await Repository._send('POST', Model + '/duplicate', { id });
248
+ const {
249
+ root,
250
+ success,
251
+ total,
252
+ message
253
+ } = Repository._processServerResponse(result);
254
+
255
+ if (!success) {
256
+ throw Error(message);
257
+ }
258
+
259
+ const duplicateId = root.id;
260
+
261
+ // Filter the grid with only the duplicate's ID, and open it for editing.
262
+ self.filterById(duplicateId, () => { // because of the way useFilters is made, we have to use a callback, not await a Promise.
263
+
264
+ // Select the only node
265
+ const duplicateEntity = Repository.getById(duplicateId);
266
+ self.setSelection([duplicateEntity]);
267
+
268
+ // edit it
269
+ onEdit();
270
+
271
+ });
272
+
273
+ },
236
274
  onEditorSave = async (data, e) => {
237
275
  const
238
276
  what = record || selection,
@@ -343,12 +381,12 @@ export default function withEditor(WrappedComponent, isTree = false) {
343
381
  }, [selection]);
344
382
 
345
383
  if (self) {
346
- self.onAdd = onAdd;
347
- self.onEdit = onEdit;
348
- self.onDelete = onDelete;
349
- self.onMoveChildren = onMoveChildren;
350
- self.onDeleteChildren = onDeleteChildren;
351
- self.onDuplicate = onDuplicate;
384
+ self.add = onAdd;
385
+ self.edit = onEdit;
386
+ self.delete = onDelete;
387
+ self.moveChildren = onMoveChildren;
388
+ self.deleteChildren = onDeleteChildren;
389
+ self.duplicate = onDuplicate;
352
390
  }
353
391
 
354
392
  if (lastSelection !== selection) {
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useId, } from 'react';
1
+ import { useState, useEffect, useId, useRef, } from 'react';
2
2
  import {
3
3
  Column,
4
4
  Modal,
@@ -46,6 +46,9 @@ export default function withFilters(WrappedComponent) {
46
46
 
47
47
  // withData
48
48
  Repository,
49
+
50
+ // withComponent
51
+ self,
49
52
  } = props;
50
53
 
51
54
  let modal = null,
@@ -59,7 +62,7 @@ export default function withFilters(WrappedComponent) {
59
62
  defaultFilters: modelDefaultFilters,
60
63
  ancillaryFilters: modelAncillaryFilters,
61
64
  } = Repository.getSchema().model,
62
- id = props.id || useId(),
65
+ id = props.id || props.reference || useId(),
63
66
 
64
67
  // determine the starting filters
65
68
  startingFilters = !_.isEmpty(customFilters) ? customFilters : // custom filters override component filters
@@ -124,6 +127,7 @@ export default function withFilters(WrappedComponent) {
124
127
  }
125
128
 
126
129
  const
130
+ filterCallbackRef = useRef(),
127
131
  [filters, setFiltersRaw] = useState(formattedStartingFilters), // array of formatted filters
128
132
  [slots, setSlots] = useState(startingSlots), // array of field names user is currently filtering on; blank slots have a null entry in array
129
133
  [modalFilters, setModalFilters] = useState([]),
@@ -230,6 +234,21 @@ export default function withFilters(WrappedComponent) {
230
234
  }
231
235
  return inArray(filterType, ['NumberRange', 'DateRange']);
232
236
  },
237
+ filterById = (id, cb) => {
238
+ onClearFilters();
239
+ filterCallbackRef.current = cb; // store the callback, so we can call it the next time this HOC renders with new filters
240
+ const newFilters = _.clone(filters);
241
+ _.remove(newFilters, (filter) => {
242
+ return filter.field === 'q';
243
+ });
244
+ newFilters.unshift({
245
+ field: 'q',
246
+ title: 'Search all text fields',
247
+ type: 'Input',
248
+ value: 'id:' + id,
249
+ });
250
+ setFilters(newFilters, false, false);
251
+ },
233
252
  renderFilters = () => {
234
253
  const
235
254
  filterProps = {
@@ -347,7 +366,7 @@ export default function withFilters(WrappedComponent) {
347
366
  } else {
348
367
  const
349
368
  isAncillary = type === FILTER_TYPE_ANCILLARY,
350
- filterName = (isAncillary ? 'ancillary___' : '') + field;
369
+ filterName = (isAncillary ? 'ancillary___' : '') + field;
351
370
  newFilterNames.push(filterName);
352
371
  newRepoFilters.push({ name: filterName, value, });
353
372
  }
@@ -362,12 +381,17 @@ export default function withFilters(WrappedComponent) {
362
381
  setPreviousFilterNames(newFilterNames);
363
382
  }
364
383
 
365
- Repository.filter(newRepoFilters, null, false); // false so other filters remain
366
-
367
384
  if (searchAllText && Repository.searchAncillary && !Repository.hasBaseParam('searchAncillary')) {
368
385
  Repository.setBaseParam('searchAncillary', true);
369
386
  }
370
387
 
388
+ await Repository.filter(newRepoFilters, null, false); // false so other filters remain
389
+
390
+ if (filterCallbackRef.current) {
391
+ filterCallbackRef.current(); // call the callback
392
+ filterCallbackRef.current = null; // clear the callback
393
+ }
394
+
371
395
  if (!isReady) {
372
396
  setIsReady(true);
373
397
  }
@@ -378,6 +402,11 @@ export default function withFilters(WrappedComponent) {
378
402
  return null;
379
403
  }
380
404
 
405
+ if (self) {
406
+ self.filterById = filterById;
407
+ self.setFilters = setFilters;
408
+ }
409
+
381
410
  const
382
411
  renderedFilters = renderFilters(),
383
412
  hasFilters = !!renderedFilters.length;
@@ -499,6 +528,7 @@ export default function withFilters(WrappedComponent) {
499
528
  editorType={EDITOR_TYPE__PLAIN}
500
529
  flex={1}
501
530
  startingValues={formStartingValues}
531
+ minHeight={minHeight}
502
532
  items={[
503
533
  {
504
534
  type: 'Column',
@@ -27,6 +27,9 @@ export default function withSelection(WrappedComponent) {
27
27
  autoSelectFirstItem = false,
28
28
  fireEvent,
29
29
 
30
+ // withComponent
31
+ self,
32
+
30
33
  // withValue
31
34
  value,
32
35
  setValue,
@@ -309,6 +312,19 @@ export default function withSelection(WrappedComponent) {
309
312
 
310
313
  }, []);
311
314
 
315
+ if (self) {
316
+ self.setSelection = setSelection;
317
+ self.selectNext = selectNext;
318
+ self.selectPrev = selectPrev;
319
+ self.addToSelection = addToSelection;
320
+ self.removeFromSelection = removeFromSelection;
321
+ self.deselectAll = deselectAll;
322
+ self.selectRangeTo = selectRangeTo;
323
+ self.isInSelection = isInSelection;
324
+ self.getIdsFromLocalSelection = getIdsFromLocalSelection;
325
+ self.getDisplayValuesFromSelection = getDisplayValuesFromLocalSelection;
326
+ }
327
+
312
328
  if (usesWithValue) {
313
329
  useEffect(() => {
314
330
  if (!isReady) {