@onehat/ui 0.2.41 → 0.2.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.2.41",
3
+ "version": "0.2.43",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -5,10 +5,13 @@ import {
5
5
  import UiGlobals from '../../../UiGlobals.js';
6
6
  import withTooltip from '../../Hoc/withTooltip.js';
7
7
  import withValue from '../../Hoc/withValue.js';
8
+ import _ from 'lodash';
8
9
 
9
10
  const
10
11
  TextAreaElement = (props) => {
11
- const styles = UiGlobals.styles;
12
+ const
13
+ styles = UiGlobals.styles,
14
+ value = _.isNil(props.value) ? '' : props.value; // null value may not actually reset this TextArea, so set it explicitly to empty string
12
15
  return <TextArea
13
16
  ref={props.outerRef}
14
17
  onChangeText={props.setValue}
@@ -17,6 +20,7 @@ const
17
20
  fontSize={styles.FORM_TEXTAREA_FONTSIZE}
18
21
  h={styles.FORM_TEXTAREA_HEIGHT}
19
22
  {...props}
23
+ value={value}
20
24
  />;
21
25
  },
22
26
  TextAreaField = withValue(TextAreaElement);
@@ -14,6 +14,9 @@ import {
14
14
  EDITOR_TYPE__SIDE,
15
15
  EDITOR_TYPE__SMART,
16
16
  EDITOR_TYPE__PLAIN,
17
+ EDITOR_MODE__VIEW,
18
+ EDITOR_MODE__ADD,
19
+ EDITOR_MODE__EDIT,
17
20
  } from '../../Constants/Editor.js';
18
21
  import { useForm, Controller } from 'react-hook-form'; // https://react-hook-form.com/api/
19
22
  import * as yup from 'yup'; // https://github.com/jquense/yup#string
@@ -74,12 +77,13 @@ function Form(props) {
74
77
 
75
78
  // withEditor
76
79
  isViewOnly = false,
80
+ editorMode,
77
81
  onCancel,
78
82
  onEditorSave,
79
83
  onSave = onEditorSave,
80
84
  onClose,
81
85
 
82
- // withSelection
86
+ // DataMgt
83
87
  selectorId,
84
88
  selectorSelected,
85
89
 
@@ -456,18 +460,32 @@ function Form(props) {
456
460
  <Row flex={1}>{formComponents}</Row>
457
461
  </ScrollView>;
458
462
  }
463
+
464
+ let editorModeF;
465
+ switch(editorMode) {
466
+ case EDITOR_MODE__VIEW:
467
+ editorModeF = 'View';
468
+ break;
469
+ case EDITOR_MODE__ADD:
470
+ editorModeF = 'Add';
471
+ break;
472
+ case EDITOR_MODE__EDIT:
473
+ editorModeF = isMultiple ? 'Edit Multiple' : 'Edit';
474
+ break;
475
+ }
459
476
 
460
477
  return <Column {...sizeProps} onLayout={onLayout}>
461
478
 
462
- {onBack && <Row p={2} alignItems="center">
463
- <Button
464
- key="backBtn"
465
- onPress={onBack}
466
- leftIcon={<Icon as={AngleLeft} color="#fff" size="sm" />}
467
- color="#fff"
468
- >Back</Button>
469
- <Text ml={2} fontSize={18}>Edit Mode</Text>
470
- </Row>}
479
+ <Row p={2} alignItems="center">
480
+ {isSingle && editorMode === EDITOR_MODE__EDIT && onBack &&
481
+ <Button
482
+ key="backBtn"
483
+ onPress={onBack}
484
+ leftIcon={<Icon as={AngleLeft} color="#fff" size="sm" />}
485
+ color="#fff"
486
+ >Back</Button>}
487
+ <Text ml={2} fontSize={18}>{editorModeF} Mode</Text>
488
+ </Row>
471
489
 
472
490
  {editor}
473
491
 
@@ -475,7 +493,7 @@ function Form(props) {
475
493
  <Button.Group space={2} {...buttonGroupProps}>
476
494
  {!isViewOnly && <IconButton
477
495
  key="resetBtn"
478
- onPress={reset}
496
+ onPress={() => reset()}
479
497
  icon={<Rotate color="#fff" />}
480
498
  />}
481
499
  {!isViewOnly && onCancel && <Button
@@ -495,7 +513,7 @@ function Form(props) {
495
513
  onPress={(e) => handleSubmit(onSave, onSubmitError)(e)}
496
514
  isDisabled={!_.isEmpty(formState.errors) || (!isSingle && !record?.isPhantom && !formState.isDirty)}
497
515
  color="#fff"
498
- >Save</Button>}
516
+ >{editorMode === EDITOR_MODE__ADD ? 'Add' : 'Save'}</Button>}
499
517
  {isViewOnly && onClose && <Button
500
518
  key="closeBtn"
501
519
  onPress={onClose}
@@ -82,6 +82,7 @@ export function Grid(props) {
82
82
  hideNavColumn = true,
83
83
  noneFoundText,
84
84
  disableLoadingIndicator = false,
85
+ disableSelectorSelected = false,
85
86
  showRowExpander = false,
86
87
  rowExpanderTpl = '',
87
88
  showHeaders = true,
@@ -129,10 +130,9 @@ export function Grid(props) {
129
130
  isInSelection,
130
131
  noSelectorMeansNoResults = false,
131
132
 
132
- // selectorSelected
133
+ // DataMgt
133
134
  selectorId,
134
135
  selectorSelected,
135
- disableSelectorSelected = false,
136
136
 
137
137
  // withInlineEditor
138
138
  inlineEditorRef,
@@ -8,6 +8,8 @@ import _ from 'lodash';
8
8
 
9
9
  export default function withEditor(WrappedComponent) {
10
10
  return (props) => {
11
+
12
+ let [editorMode, setEditorMode] = useState(EDITOR_MODE__VIEW); // Can change below, so use 'let'
11
13
  const {
12
14
  useEditor = false,
13
15
  userCanEdit = true,
@@ -25,6 +27,10 @@ export default function withEditor(WrappedComponent) {
25
27
  },
26
28
  record,
27
29
 
30
+ // DataMgt
31
+ selectorId,
32
+ selectorSelected,
33
+
28
34
  // withData
29
35
  Repository,
30
36
 
@@ -38,21 +44,27 @@ export default function withEditor(WrappedComponent) {
38
44
  [currentRecord, setCurrentRecord] = useState(null),
39
45
  [isEditorShown, setIsEditorShown] = useState(false),
40
46
  [isEditorViewOnly, setIsEditorViewOnly] = useState(false),
41
- [editorMode, setEditorMode] = useState(EDITOR_MODE__VIEW),
47
+ [lastSelection, setLastSelection] = useState(),
42
48
  addRecord = async () => {
43
- if (!userCanEdit) {
49
+ if (!userCanEdit || disableAdd) {
44
50
  return;
45
51
  }
46
52
  const
47
53
  defaultValues = Repository.getSchema().model.defaultValues,
48
- entity = await Repository.add(defaultValues, false, true, true);
54
+ addValues = _.clone(defaultValues);
55
+
56
+ if (selectorId && !_.isEmpty(selectorSelected)) {
57
+ addValues[selectorId] = selectorSelected.id;
58
+ }
59
+
60
+ const entity = await Repository.add(addValues, false, true, true);
49
61
  setSelection([entity]);
50
62
  setIsEditorViewOnly(false);
51
63
  setEditorMode(EDITOR_MODE__ADD);
52
64
  setIsEditorShown(true);
53
65
  },
54
66
  editRecord = () => {
55
- if (!userCanEdit) {
67
+ if (!userCanEdit || disableEdit) {
56
68
  return;
57
69
  }
58
70
  setIsEditorViewOnly(false);
@@ -60,7 +72,7 @@ export default function withEditor(WrappedComponent) {
60
72
  setIsEditorShown(true);
61
73
  },
62
74
  deleteRecord = (e) => {
63
- if (!userCanEdit) {
75
+ if (!userCanEdit || disableDelete) {
64
76
  return;
65
77
  }
66
78
  const
@@ -74,11 +86,9 @@ export default function withEditor(WrappedComponent) {
74
86
  confirm('Are you sure you want to delete the ' + identifier, onDelete);
75
87
  }
76
88
  },
77
- onDelete = () => {
89
+ onDelete = async () => {
78
90
  Repository.delete(selection);
79
- if (!Repository.isAutoSave) {
80
- Repository.save();
81
- }
91
+ await Repository.save();
82
92
  },
83
93
  viewRecord = () => {
84
94
  if (!userCanView) {
@@ -92,7 +102,7 @@ export default function withEditor(WrappedComponent) {
92
102
  setIsEditorShown(true);
93
103
  },
94
104
  duplicateRecord = async () => {
95
- if (!userCanEdit) {
105
+ if (!userCanEdit || disableDuplicate) {
96
106
  return;
97
107
  }
98
108
  if (selection.length !== 1) {
@@ -104,9 +114,10 @@ export default function withEditor(WrappedComponent) {
104
114
  rawValues = _.omit(entity.rawValues, idProperty),
105
115
  duplicate = await Repository.add(rawValues, false, true);
106
116
  setSelection([duplicate]);
117
+ setEditorMode(EDITOR_MODE__EDIT);
107
118
  setIsEditorShown(true);
108
119
  },
109
- onEditorSave = (data, e) => {
120
+ onEditorSave = async (data, e) => {
110
121
  const
111
122
  what = record || selection,
112
123
  isSingle = what.length === 1;
@@ -117,42 +128,63 @@ export default function withEditor(WrappedComponent) {
117
128
  } else if (selection.length > 1) {
118
129
  // Edit multiple entities
119
130
 
120
-
121
- debugger;
122
-
123
-
124
-
125
- }
126
- if (!Repository.isAutoSave) {
127
- Repository.save();
131
+ // Loop through all entities and change fields that are not null
132
+ const propertyNames = Object.getOwnPropertyNames(data);
133
+ _.each(propertyNames, (propertyName) => {
134
+ if (!_.isNil(data[propertyName])) {
135
+ _.each(what, (rec) => {
136
+ rec[propertyName] = data[propertyName]
137
+ });
138
+ }
139
+ });
128
140
  }
141
+ await Repository.save();
129
142
  setIsEditorShown(false);
130
143
  },
131
- onEditorCancel = () => {
144
+ onEditorCancel = async () => {
132
145
  const
133
146
  isSingle = selection.length === 1,
134
147
  isPhantom = selection[0] && selection[0].isPhantom;
135
148
  if (isSingle && isPhantom) {
136
- onDelete();
149
+ await onDelete();
137
150
  }
151
+ setEditorMode(EDITOR_MODE__VIEW);
138
152
  setIsEditorShown(false);
139
153
  },
140
154
  onEditorClose = () => {
141
155
  setIsEditorShown(false);
156
+ },
157
+ calculateEditorMode = () => {
158
+ let mode = EDITOR_MODE__VIEW;
159
+ if (userCanEdit) {
160
+ if (selection.length > 1) {
161
+ if (!disableEdit) {
162
+ // For multiple entities selected, change it to edit multiple mode
163
+ mode = EDITOR_MODE__EDIT;
164
+ }
165
+ } else if (selection.length === 1 && selection.isPhantom) {
166
+ if (!disableAdd) {
167
+ // When a phantom entity is selected, change it to add mode.
168
+ mode = EDITOR_MODE__ADD;
169
+ }
170
+ }
171
+ }
172
+ return mode;
142
173
  };
143
174
 
144
175
  useEffect(() => {
145
- if (selection.length === 1 && selection.isPhantom && userCanEdit) {
146
- if (editorMode !== EDITOR_MODE__ADD) {
147
- setEditorMode(EDITOR_MODE__ADD);
148
- }
149
- } else {
150
- if (editorMode !== EDITOR_MODE__VIEW) {
151
- setEditorMode(EDITOR_MODE__VIEW);
152
- }
153
- }
176
+ // When selection changes, set the mode appropriately
177
+ const mode = calculateEditorMode();
178
+ setEditorMode(mode);
179
+ setLastSelection(selection);
154
180
  }, [selection]);
155
181
 
182
+ if (lastSelection !== selection) {
183
+ // NOTE: If I don't calculate this on the fly for selection changes,
184
+ // we see a flash of the previous state, since useEffect hasn't yet run.
185
+ editorMode = calculateEditorMode();
186
+ }
187
+
156
188
  return <WrappedComponent
157
189
  {...props}
158
190
  currentRecord={currentRecord}
@@ -33,12 +33,6 @@ export default function withPresetButtons(WrappedComponent) {
33
33
  } = props,
34
34
  {
35
35
  // for local use
36
- selection,
37
- onAdd,
38
- onEdit,
39
- onDelete,
40
- onView,
41
- onDuplicate,
42
36
  useEditor = true,
43
37
  disableAdd = false,
44
38
  disableEdit = false,
@@ -51,6 +45,19 @@ export default function withPresetButtons(WrappedComponent) {
51
45
  // withEditor
52
46
  userCanEdit = true,
53
47
  userCanView = true,
48
+ onAdd,
49
+ onEdit,
50
+ onDelete,
51
+ onView,
52
+ onDuplicate,
53
+
54
+ // withSelection
55
+ selection,
56
+ setSelection,
57
+
58
+ // DataMgt
59
+ selectorId,
60
+ selectorSelected,
54
61
  } = props,
55
62
  [isReady, setIsReady] = useState(false),
56
63
  [localContextMenuItems, setLocalContextMenuItems] = useState([]),
@@ -210,7 +217,7 @@ export default function withPresetButtons(WrappedComponent) {
210
217
  if (!isReady) {
211
218
  setIsReady(true);
212
219
  }
213
- }, [selection, localColumnsConfig]);
220
+ }, [selection, selectorSelected, localColumnsConfig]);
214
221
 
215
222
  if (!isReady) {
216
223
  return null;
@@ -229,6 +236,7 @@ export default function withPresetButtons(WrappedComponent) {
229
236
  if (additionalToolbarButtons) {
230
237
  additionalToolbarButtonsToPass.concat(additionalToolbarButtons);
231
238
  }
239
+
232
240
  return <WrappedComponent
233
241
  {...propsToPass}
234
242
  contextMenuItems={contextMenuItemsToPass}
@@ -44,8 +44,8 @@ export default function DataMgt(props) {
44
44
  [isWestCollapsed, setIsWestCollapsed] = useState(westStartsCollapsed),
45
45
  [isEastCollapsed, setIsEastCollapsed] = useState(eastStartsCollapsed),
46
46
  [isFullscreen, setIsFullscreen] = useState(false),
47
- [westSelected, setWestSelectedRaw] = useState([]),
48
- [centerSelected, setCenterSelected] = useState([]),
47
+ [westSelected, setWestSelectedRaw] = useState(),
48
+ [centerSelected, setCenterSelected] = useState(),
49
49
  setWestSelected = (selected) => {
50
50
  setWestSelectedRaw(selected);
51
51
  setCenterSelected(); // clear selection in center
@@ -156,7 +156,7 @@ export default function DataMgt(props) {
156
156
  autoLoad={!showSelector}
157
157
  uniqueRepository={true}
158
158
  selectorId={showSelector ? westSelector_id : null}
159
- selectorSelected={westSelected}
159
+ selectorSelected={westSelected?.[0]}
160
160
  noSelectorMeansNoResults={centerNoSelectorMeansNoResults}
161
161
  onChangeSelection={setCenterSelected}
162
162
  onEvent={onEvent}
@@ -167,7 +167,7 @@ export default function DataMgt(props) {
167
167
  isFullscreen,
168
168
  showSelector,
169
169
  westSelected,
170
- westSelected?.hash,
170
+ westSelected?.[0].hash,
171
171
  centerNoSelectorMeansNoResults,
172
172
  // {...centerProps}
173
173
  ])
@@ -191,7 +191,7 @@ export default function DataMgt(props) {
191
191
  controlledByCenter = typeof associatedPanel.props.controlledByCenter === 'undefined' ? true : associatedPanel.props.controlledByCenter,
192
192
  thisAssociatedPanelProps = {
193
193
  selectorId: controlledByCenter ? centerSelector_id : westSelector_id,
194
- selectorSelected: controlledByCenter ? centerSelected : westSelected,
194
+ selectorSelected: controlledByCenter ? centerSelected?.[0] : westSelected?.[0],
195
195
  ...associatedPanel.props,
196
196
  };
197
197
  return React.cloneElement(associatedPanel, { key: ix, reference: 'associatedPanel' + ix, ...allAssociatedPanelProps, ...thisAssociatedPanelProps, });