@onehat/ui 0.3.11 → 0.3.13

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.11",
3
+ "version": "0.3.13",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -29,10 +29,10 @@ export default function Editor(props) {
29
29
 
30
30
  if (_.isEmpty(selection)) {
31
31
  return null; // hide the editor when no selection
32
- return <Box {...props} bg="#ddd" />;
33
32
  }
34
33
 
35
- if (Repository?.isRemotePhantomMode && selection.length === 1 && editorMode === EDITOR_MODE__VIEW) {
34
+ // Repository?.isRemotePhantomMode && selection.length === 1 &&
35
+ if (editorMode === EDITOR_MODE__VIEW) {
36
36
  return <Viewer
37
37
  {...props}
38
38
  record={selection[0]}
@@ -6,6 +6,9 @@ import {
6
6
  Row,
7
7
  Text,
8
8
  } from 'native-base';
9
+ import {
10
+ EDITOR_TYPE__SIDE,
11
+ } from '../../Constants/Editor.js';
9
12
  import UiGlobals from '../../UiGlobals.js';
10
13
  import getComponentFromType from '../../Functions/getComponentFromType.js';
11
14
  import Label from '../Form/Label.js';
@@ -29,10 +32,12 @@ export default function Viewer(props) {
29
32
  selectorSelected,
30
33
 
31
34
  // withEditor
35
+ editorType,
32
36
  onEditMode,
33
37
  onClose,
34
38
  onDelete,
35
39
  } = props,
40
+ isSideEditor = editorType === EDITOR_TYPE__SIDE,
36
41
  styles = UiGlobals.styles,
37
42
  flex = props.flex || 1,
38
43
  buildAncillary = () => {
@@ -45,12 +50,17 @@ export default function Viewer(props) {
45
50
  selectorId = null,
46
51
  ...propsToPass
47
52
  } = item;
53
+ if (!propsToPass.h) {
54
+ propsToPass.h = 400;
55
+ }
48
56
  const
49
57
  Element = getComponentFromType(type),
50
58
  element = <Element
51
59
  selectorId={selectorId}
52
60
  selectorSelected={selectorSelected || record}
53
61
  flex={1}
62
+ h={350}
63
+ canEditorViewOnly={true}
54
64
  {...propsToPass}
55
65
  />;
56
66
  if (title) {
@@ -65,6 +75,10 @@ export default function Viewer(props) {
65
75
  return components;
66
76
  };
67
77
 
78
+ const
79
+ showDeleteBtn = onDelete && viewerCanDelete,
80
+ showCloseBtn = !isSideEditor;
81
+
68
82
  return <Column flex={flex} {...props}>
69
83
  <ScrollView width="100%" _web={{ height: 1 }}>
70
84
  <Column m={2}>
@@ -88,26 +102,28 @@ export default function Viewer(props) {
88
102
 
89
103
  </Column>
90
104
  </ScrollView>
91
- <Footer justifyContent="flex-end">
92
- {onDelete && viewerCanDelete &&
93
- <Row flex={1} justifyContent="flex-start">
94
- <Button
95
- key="deleteBtn"
96
- onPress={onDelete}
97
- bg="warning"
98
- _hover={{
99
- bg: 'warningHover',
100
- }}
101
- color="#fff"
102
- >Delete</Button>
103
- </Row>}
104
- <Button.Group space={2}>
105
- <Button
106
- key="closeBtn"
107
- onPress={onClose}
108
- color="#fff"
109
- >Close</Button>
110
- </Button.Group>
111
- </Footer>
105
+ {(showDeleteBtn || showCloseBtn) &&
106
+ <Footer justifyContent="flex-end">
107
+ {showDeleteBtn &&
108
+ <Row flex={1} justifyContent="flex-start">
109
+ <Button
110
+ key="deleteBtn"
111
+ onPress={onDelete}
112
+ bg="warning"
113
+ _hover={{
114
+ bg: 'warningHover',
115
+ }}
116
+ color="#fff"
117
+ >Delete</Button>
118
+ </Row>}
119
+ {showCloseBtn &&
120
+ <Button.Group space={2}>
121
+ <Button
122
+ key="closeBtn"
123
+ onPress={onClose}
124
+ color="#fff"
125
+ >Close</Button>
126
+ </Button.Group>}
127
+ </Footer>}
112
128
  </Column>;
113
129
  }
@@ -34,7 +34,7 @@ export function ComboComponent(props) {
34
34
  tooltip = null,
35
35
  menuMinWidth = 150,
36
36
  disableDirectEntry = false,
37
- disablePagination = true,
37
+ disablePagination = false,
38
38
  hideMenuOnSelection = true,
39
39
  _input = {},
40
40
  isEditor = false,
@@ -90,16 +90,12 @@ export function ComboComponent(props) {
90
90
  if (rectTop !== top) {
91
91
  setTop(rectTop);
92
92
  }
93
- setHeight(null);
94
93
  } else {
95
94
  // Menu is above the combo
96
-
97
- const rectTop = rect.top -200;
95
+ const rectTop = rect.top - styles.FORM_COMBO_MENU_HEIGHT;
98
96
  if (rectTop !== top) {
99
97
  setTop(rectTop);
100
98
  }
101
-
102
- setHeight(200);
103
99
  }
104
100
  if (rect.left !== left) {
105
101
  setLeft(rect.left);
@@ -489,14 +485,12 @@ export function ComboComponent(props) {
489
485
  top={top + 'px'}
490
486
  left={left + 'px'}
491
487
  w={width + 'px'}
492
- h={height ? height + 'px' : null}
493
488
  minWidth={menuMinWidth}
494
489
  overflow="auto"
495
490
  bg="#fff"
496
491
  >
497
492
  <Popover.Body
498
493
  ref={menuRef}
499
- maxHeight={200}
500
494
  borderWidth={1}
501
495
  borderColor='trueGray.400'
502
496
  borderTopWidth={0}
@@ -505,8 +499,6 @@ export function ComboComponent(props) {
505
499
  <WhichGrid
506
500
  showHeaders={false}
507
501
  showHovers={true}
508
- pageSize={100}
509
- disableAdjustingPageSizeToHeight={true}
510
502
  shadow={1}
511
503
  getRowProps={() => {
512
504
  return {
@@ -519,6 +511,7 @@ export function ComboComponent(props) {
519
511
  };
520
512
  }}
521
513
  {...props}
514
+ h={styles.FORM_COMBO_MENU_HEIGHT + 'px'}
522
515
  disablePresetButtons={!isEditor}
523
516
  disablePagination={disablePagination}
524
517
  setSelection={(selection) => {
@@ -102,6 +102,7 @@ function Form(props) {
102
102
  isMultiple = _.isArray(record),
103
103
  isSingle = !isMultiple, // for convenience
104
104
  forceUpdate = useForceUpdate(),
105
+ [previousRecord, setPreviousRecord] = useState(record),
105
106
  initialValues = _.merge(startingValues, (record && !record.isDestroyed ? record.submitValues : {})),
106
107
  defaultValues = isMultiple ? getNullFieldValues(initialValues, Repository) : initialValues, // when multiple entities, set all default values to null
107
108
  {
@@ -420,6 +421,9 @@ function Form(props) {
420
421
  selectorId,
421
422
  ...propsToPass
422
423
  } = item;
424
+ if (!propsToPass.h) {
425
+ propsToPass.h = 400;
426
+ }
423
427
  const
424
428
  Element = getComponentFromType(type),
425
429
  element = <Element
@@ -444,8 +448,23 @@ function Form(props) {
444
448
  if (editorType === EDITOR_TYPE__INLINE) {
445
449
  alert(errors.message);
446
450
  }
451
+ },
452
+ onSaveDecorated = async (data, e) => {
453
+ // reset the form after a save
454
+ const result = await onSave(data, e);
455
+ if (result) {
456
+ const values = record.submitValues;
457
+ reset(values);
458
+ }
447
459
  };
448
460
 
461
+ useEffect(() => {
462
+ if (record !== previousRecord) {
463
+ setPreviousRecord(record);
464
+ reset(defaultValues);
465
+ }
466
+ }, [record]);
467
+
449
468
  useEffect(() => {
450
469
  if (!Repository) {
451
470
  return () => {};
@@ -603,11 +622,11 @@ function Form(props) {
603
622
  >Cancel</Button>}
604
623
  {!isEditorViewOnly && onSave && <Button
605
624
  key="saveBtn"
606
- onPress={(e) => handleSubmit(onSave, onSubmitError)(e)}
625
+ onPress={(e) => handleSubmit(onSaveDecorated, onSubmitError)(e)}
607
626
  isDisabled={isSaveDisabled}
608
627
  color="#fff"
609
628
  >{editorMode === EDITOR_MODE__ADD ? 'Add' : 'Save'}</Button>}
610
- {isEditorViewOnly && onClose && <Button
629
+ {isEditorViewOnly && onClose && editorType !== EDITOR_TYPE__SIDE && <Button
611
630
  key="closeBtn"
612
631
  onPress={onClose}
613
632
  color="#fff"
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useRef, useMemo, } from 'react';
1
+ import React, { useState, useEffect, useRef, useMemo, useCallback, } from 'react';
2
2
  import {
3
3
  Column,
4
4
  FlatList,
@@ -104,6 +104,7 @@ function GridComponent(props) {
104
104
  onDuplicate,
105
105
  onReset,
106
106
  onContextMenu,
107
+ isAdding,
107
108
 
108
109
  // withData
109
110
  Repository,
@@ -141,6 +142,7 @@ function GridComponent(props) {
141
142
  styles = UiGlobals.styles,
142
143
  forceUpdate = useForceUpdate(),
143
144
  gridRef = useRef(),
145
+ isAddingRef = useRef(),
144
146
  [isReady, setIsReady] = useState(false),
145
147
  [isLoading, setIsLoading] = useState(false),
146
148
  [localColumnsConfig, setLocalColumnsConfigRaw] = useState([]),
@@ -616,41 +618,34 @@ function GridComponent(props) {
616
618
  setDragRowSlot(null);
617
619
  },
618
620
  onLayout = (e) => {
619
- if (disableAdjustingPageSizeToHeight || !Repository) {
621
+ if (disableAdjustingPageSizeToHeight || !Repository || CURRENT_MODE !== UI_MODE_WEB || !gridRef.current || isAddingRef.current) {
620
622
  return;
621
623
  }
622
- const {
623
- nativeEvent: {
624
- layout,
625
- target,
626
- },
627
- } = e;
628
- let pageSize;
629
- if (CURRENT_MODE === UI_MODE_WEB) {
630
- const
631
- targetBoundingBox = target.getBoundingClientRect(),
632
- targetHeight = targetBoundingBox.height,
633
- firstRow = target.children[0]?.children[0]?.children[0]?.children[0]?.children[0];
634
- if (firstRow) {
635
- const
636
- rowBoundingBox = firstRow.getBoundingClientRect(),
637
- rowHeight = rowBoundingBox.height,
638
- rowsPerTarget = Math.floor(targetHeight / rowHeight);
639
- pageSize = rowsPerTarget;
640
- if (showHeaders) {
641
- pageSize--;
642
- }
643
- if (bottomToolbar) {
644
- pageSize--;
645
- }
646
- }
624
+
625
+ const
626
+ gr = gridRef.current,
627
+ scrollableNode = gr.getScrollableNode(),
628
+ scrollableNodeBoundingBox = scrollableNode.getBoundingClientRect(),
629
+ scrollableNodeHeight = scrollableNodeBoundingBox.height,
630
+ firstRow = scrollableNode.children[0].children[showHeaders ? 1: 0];
631
+
632
+ if (!firstRow) {
633
+ return;
647
634
  }
648
-
649
- if (pageSize) {
635
+
636
+ const
637
+ rowHeight = firstRow.getBoundingClientRect().height,
638
+ rowsPerContainer = Math.floor(scrollableNodeHeight / rowHeight);
639
+ let pageSize = rowsPerContainer;
640
+ if (showHeaders) {
641
+ pageSize--;
642
+ }
643
+ if (pageSize !== Repository.pageSize) {
650
644
  Repository.setPageSize(pageSize);
651
645
  }
652
- };
653
-
646
+ },
647
+ debouncedOnLayout = useCallback(_.debounce(onLayout, 500), []);
648
+
654
649
  useEffect(() => {
655
650
 
656
651
  const calculateLocalColumnsConfig = () => {
@@ -778,6 +773,9 @@ function GridComponent(props) {
778
773
 
779
774
  }, [selectorId, selectorSelected]);
780
775
 
776
+
777
+ isAddingRef.current = isAdding;
778
+
781
779
  const footerToolbarItemComponents = useMemo(() => getFooterToolbarItems(), [additionalToolbarButtons, isDragMode]);
782
780
 
783
781
  if (!isReady) {
@@ -811,18 +809,19 @@ function GridComponent(props) {
811
809
  } else {
812
810
  sizeProps.flex = flex ?? 1;
813
811
  }
812
+
814
813
  return <Column
815
814
  {...testProps('Grid')}
816
815
  w="100%"
817
816
  bg={bg}
818
817
  borderWidth={styles.GRID_BORDER_WIDTH}
819
818
  borderColor={styles.GRID_BORDER_COLOR}
820
- onLayout={onLayout}
819
+ onLayout={debouncedOnLayout}
821
820
  {...sizeProps}
822
821
  >
823
822
  {topToolbar}
824
823
 
825
- <Column w="100%" flex={1} borderTopWidth={isLoading ? 2 : 1} borderTopColor={isLoading ? '#f00' : 'trueGray.300'} onClick={() => {
824
+ <Column w="100%" flex={1} minHeight={40} borderTopWidth={isLoading ? 2 : 1} borderTopColor={isLoading ? '#f00' : 'trueGray.300'} onClick={() => {
826
825
  if (!isDragMode && !isInlineEditorShown) {
827
826
  deselectAll();
828
827
  }
@@ -16,7 +16,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
16
16
  const {
17
17
  userCanEdit = true,
18
18
  userCanView = true,
19
- canEditorViewOnly = false,
19
+ canEditorViewOnly = false, // whether the editor can *ever* change state out of 'View' mode
20
20
  disableAdd = false,
21
21
  disableEdit = false,
22
22
  disableDelete = false,
@@ -49,8 +49,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
49
49
  listeners = useRef({}),
50
50
  editorStateRef = useRef(),
51
51
  [currentRecord, setCurrentRecord] = useState(null),
52
+ [isAdding, setIsAdding] = useState(false),
52
53
  [isEditorShown, setIsEditorShown] = useState(false),
53
- [isEditorViewOnly, setIsEditorViewOnly] = useState(canEditorViewOnly),
54
+ [isEditorViewOnly, setIsEditorViewOnly] = useState(canEditorViewOnly), // current state of whether editor is in view-only mode
54
55
  [lastSelection, setLastSelection] = useState(),
55
56
  setSelectionDecorated = (newSelection) => {
56
57
  function doIt() {
@@ -71,7 +72,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
71
72
  // forceUpdate(); // we don't want to get into an infinite loop of renders. Simply directly assign the listeners in every child render
72
73
  },
73
74
  onAdd = async () => {
74
- const defaultValues = Repository.getSchema().model.defaultValues;
75
+ const defaultValues = Repository.getSchema().getDefaultValues();
75
76
  let addValues = _.clone(defaultValues);
76
77
 
77
78
  if (selectorId && !_.isEmpty(selectorSelected)) {
@@ -105,6 +106,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
105
106
  // Unmap the values, so we can input true originalData
106
107
  addValues = Repository.unmapData(addValues);
107
108
 
109
+ setIsAdding(true);
108
110
  const entity = await Repository.add(addValues, false, true);
109
111
  setSelection([entity]);
110
112
  setIsEditorViewOnly(false);
@@ -245,11 +247,15 @@ export default function withEditor(WrappedComponent, isTree = false) {
245
247
  }
246
248
 
247
249
  await Repository.save();
250
+
251
+ setIsAdding(false);
248
252
  setIsEditorShown(false);
249
253
 
250
254
  if (getListeners().onAfterEdit) {
251
255
  await getListeners().onAfterEdit(what);
252
256
  }
257
+
258
+ return true;
253
259
  },
254
260
  onEditorCancel = () => {
255
261
  async function doIt() {
@@ -259,6 +265,8 @@ export default function withEditor(WrappedComponent, isTree = false) {
259
265
  if (isSingle && isPhantom) {
260
266
  await deleteRecord();
261
267
  }
268
+
269
+ setIsAdding(false);
262
270
  setEditorMode(EDITOR_MODE__VIEW);
263
271
  setIsEditorShown(false);
264
272
  }
@@ -330,6 +338,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
330
338
  setCurrentRecord={setCurrentRecord}
331
339
  isEditorShown={isEditorShown}
332
340
  isEditorViewOnly={isEditorViewOnly}
341
+ isAdding={isAdding}
333
342
  editorMode={editorMode}
334
343
  onEditMode={onEditMode}
335
344
  onViewMode={onViewMode}
@@ -62,7 +62,6 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
62
62
 
63
63
  // withSelection
64
64
  selection,
65
- setSelection,
66
65
 
67
66
  // parent container
68
67
  selectorId,
@@ -87,7 +86,7 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
87
86
  }
88
87
  break;
89
88
  case 'edit':
90
- if (disableEdit || canEditorViewOnly) {
89
+ if (disableEdit || canEditorViewOnly || isSideEditor) {
91
90
  isDisabled = true;
92
91
  }
93
92
  break;
@@ -251,7 +250,7 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
251
250
  }
252
251
 
253
252
  // Send it to clipboard
254
- navigator.clipboard.writeText(text);
253
+ navigator?.clipboard.writeText(text);
255
254
  };
256
255
  // onPrint = () => {
257
256
  // debugger;
@@ -213,7 +213,7 @@ export default function withSelection(WrappedComponent) {
213
213
  setValue(localValue);
214
214
  }
215
215
  },
216
- conformSelectionToValue = () => {
216
+ conformSelectionToValue = async () => {
217
217
  // adjust the selection to match the value
218
218
  let newSelection = [];
219
219
  if (Repository) {
@@ -222,9 +222,20 @@ export default function withSelection(WrappedComponent) {
222
222
  if (_.isArray(value)) {
223
223
  newSelection = Repository.getBy((entity) => inArray(entity.id, value));
224
224
  } else {
225
- const found = Repository.getById(value);
225
+ let found = Repository.getById(value);
226
226
  if (found) {
227
227
  newSelection.push(found);
228
+ } else if (Repository?.isRemote && Repository?.entities.length) {
229
+
230
+ // Value cannot be found in Repository, but actually exists on server
231
+ // Try to get this value from the server directly
232
+ Repository.filter(Repository.schema.model.idProperty, value);
233
+ await Repository.load();
234
+ found = Repository.getById(value);
235
+ if (found) {
236
+ newSelection.push(found);
237
+ }
238
+
228
239
  }
229
240
  }
230
241
  }
@@ -270,7 +281,7 @@ export default function withSelection(WrappedComponent) {
270
281
 
271
282
  if (usesWithValue && !_.isNil(value)) {
272
283
 
273
- conformSelectionToValue();
284
+ await conformSelectionToValue();
274
285
 
275
286
  } else if (autoSelectFirstItem) {
276
287
  let newSelection = [];
@@ -9,6 +9,7 @@ import {
9
9
  HORIZONTAL,
10
10
  VERTICAL,
11
11
  } from '../../Constants/Directions.js';
12
+ import Inflector from 'inflector-js';
12
13
  import Header from './Header.js';
13
14
  import Mask from './Mask.js';
14
15
  import withCollapsible from '../Hoc/withCollapsible.js';
@@ -33,7 +34,7 @@ function Panel(props) {
33
34
  onLayout = null,
34
35
 
35
36
  // Header
36
- title = props.model,
37
+ title = Inflector.humanize(Inflector.underscore(props.model)),
37
38
  showHeader = true,
38
39
  header = null,
39
40
  isClosable = false,
@@ -60,7 +60,7 @@ export default function ManagerScreen(props) {
60
60
  whichComponent = sideModeComponent;
61
61
  }
62
62
 
63
- return <Column flex={1} w="100%">
63
+ return <Column maxHeight="100vh" overflow="hidden" flex={1} w="100%">
64
64
  <Row
65
65
  h="80px"
66
66
  py={2}
@@ -89,6 +89,8 @@ export default function ManagerScreen(props) {
89
89
  tooltip="Side Editor"
90
90
  />
91
91
  </Row>
92
+
92
93
  {whichComponent}
94
+
93
95
  </Column>;
94
96
  }
@@ -16,6 +16,7 @@ const defaults = {
16
16
  FORM_COMBO_INPUT_FONTSIZE: DEFAULT_FONTSIZE,
17
17
  FORM_COMBO_INPUT_BG: WHITE,
18
18
  FORM_COMBO_INPUT_FOCUS_BG: FOCUS,
19
+ FORM_COMBO_MENU_HEIGHT: 250,
19
20
  FORM_COMBO_TRIGGER_BG: WHITE,
20
21
  FORM_COMBO_TRIGGER_HOVER_BG: 'trueGray.300',
21
22
  FORM_DATE_ICON_BG: 'primary.200',
@@ -49,7 +49,7 @@ function AttachmentsElement(props) {
49
49
 
50
50
  } = props,
51
51
  styles = UiGlobals.styles,
52
- model = selectorSelected?.repository.name,
52
+ model = selectorSelected?.repository?.name,
53
53
  modelid = selectorSelected?.id,
54
54
  [isReady, setIsReady] = useState(false),
55
55
  [isUploading, setIsUploading] = useState(false),