@onehat/ui 0.3.124 → 0.3.126

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.124",
3
+ "version": "0.3.126",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -12,6 +12,9 @@ import {
12
12
  UI_MODE_REACT_NATIVE,
13
13
  UI_MODE_WEB,
14
14
  } from '../../../../Constants/UiModes.js';
15
+ import {
16
+ EDITOR_TYPE__WINDOWED,
17
+ } from '../../../../Constants/Editor.js';
15
18
  import UiGlobals from '../../../../UiGlobals.js';
16
19
  import Input from '../Input.js';
17
20
  import withAlert from '../../../Hoc/withAlert.js';
@@ -47,11 +50,13 @@ export function ComboComponent(props) {
47
50
  placeholder,
48
51
  onRowPress,
49
52
  icon,
53
+ Editor, // only used for the eyeButton
50
54
 
51
55
  // withComponent
52
56
  self,
53
57
 
54
58
  // withAlert
59
+ alert,
55
60
  confirm,
56
61
 
57
62
  // withData
@@ -317,6 +322,7 @@ export function ComboComponent(props) {
317
322
  setViewerSelection([record]);
318
323
  setIsViewerShown(true);
319
324
  },
325
+ onViewerClose = () => setIsViewerShown(false),
320
326
  onCheckButtonPress = () => {
321
327
  hideMenu();
322
328
  },
@@ -482,7 +488,7 @@ export function ComboComponent(props) {
482
488
  mr={1}
483
489
  />;
484
490
  }
485
- if (showEyeButton && !_.isNil(value)) {
491
+ if (showEyeButton && Editor && !_.isNil(value)) {
486
492
  eyeButton = <IconButton
487
493
  _icon={{
488
494
  as: Eye,
@@ -898,10 +904,35 @@ export function ComboComponent(props) {
898
904
  {dropdownMenu}
899
905
  </Row>;
900
906
 
907
+ if (isViewerShown && Editor) {
908
+ assembledComponents =
909
+ <>
910
+ {assembledComponents}
911
+ <Modal
912
+ isOpen={true}
913
+ onClose={onViewerClose}
914
+ >
915
+ <Editor
916
+ {...props}
917
+ editorType={EDITOR_TYPE__WINDOWED}
918
+ px={0}
919
+ py={0}
920
+ w="100%"
921
+ parent={self}
922
+ reference="viewer"
923
+
924
+ isEditorViewOnly={true}
925
+ selection={viewerSelection}
926
+ onEditorClose={onViewerClose}
927
+ />
928
+ </Modal>
929
+ </>;
930
+ }
931
+
901
932
  if (tooltip) {
902
933
  assembledComponents = <Tooltip label={tooltip} placement={tooltipPlacement}>
903
- {assembledComponents}
904
- </Tooltip>;
934
+ {assembledComponents}
935
+ </Tooltip>;
905
936
  }
906
937
 
907
938
  return assembledComponents;
@@ -346,7 +346,7 @@ function Form(props) {
346
346
  }
347
347
  }
348
348
  if (isCombo) {
349
- editorTypeProps.showEyeButton = true;
349
+ // editorTypeProps.showEyeButton = true;
350
350
  if (_.isNil(propsToPass.showXButton)) {
351
351
  editorTypeProps.showXButton = true;
352
352
  }
@@ -0,0 +1,117 @@
1
+ import { useState, useEffect, } from 'react';
2
+ import oneHatData from '@onehat/data';
3
+ import _ from 'lodash';
4
+
5
+ // NOTE: This is a modified version of @onehat/ui/src/Hoc/withData
6
+ // This HOC will eventually get out of sync with that one, and may need to be updated.
7
+
8
+ export default function withSecondaryData(WrappedComponent) {
9
+ return (props) => {
10
+
11
+ if (props.secondaryDisableWithData) {
12
+ return <WrappedComponent {...props} />;
13
+ }
14
+
15
+ const
16
+ {
17
+ // For @onehat/data repositories
18
+ SecondaryRepository,
19
+ setSecondaryRepository,
20
+ uniqueSecondaryRepository = false,
21
+ secondaryModel,
22
+ secondaryAutoLoad, // bool
23
+ secondaryPageSize,
24
+ secondaryBaseParams,
25
+
26
+ // For plain JS data
27
+ secondaryData,
28
+ secondaryFields = ['id', 'value'],
29
+ secondaryIdField = 'id',
30
+ secondaryDisplayField = 'value',
31
+ secondaryIdIx,
32
+ secondaryDisplayIx,
33
+
34
+ // withComponent
35
+ self,
36
+ } = props,
37
+ propsToPass = _.omit(props, ['secondaryModel']), // passing 'secondaryModel' would mess things up if withData gets called twice (e.g. withData(...withData(...)) ), as we'd be trying to recreate SecondaryRepository twice
38
+ localIdIx = secondaryIdIx || (secondaryFields && secondaryIdField ? secondaryFields.indexOf(secondaryIdField) : null),
39
+ localDisplayIx = secondaryDisplayIx || (secondaryFields && secondaryDisplayField ? secondaryFields?.indexOf(secondaryDisplayField) : null),
40
+ [LocalSecondaryRepository, setLocalSecondaryRepository] = useState(SecondaryRepository || null), // simply pass on SecondaryRepository if it's already supplied
41
+ [isReady, setIsReady] = useState(!!LocalSecondaryRepository || !!secondaryData); // It's already ready if a LocalSecondaryRepository or secondaryData array is already set. Otherwise, we need to create the repository
42
+
43
+ // Create LocalSecondaryRepository
44
+ // If SecondaryRepository was submitted to this withData(), the useEffect has no effect.
45
+ // If it's empty, it tries to create a LocalSecondaryRepository
46
+ useEffect(() => {
47
+ if (!!LocalSecondaryRepository || !!secondaryData) {
48
+ return () => {};
49
+ }
50
+
51
+ let repositoryId;
52
+
53
+ (async () => {
54
+ let SecondaryRepository;
55
+ if (uniqueSecondaryRepository) {
56
+ const schema = oneHatData.getSchema(secondaryModel);
57
+ SecondaryRepository = await oneHatData.createRepository({ schema });
58
+ repositoryId = SecondaryRepository.id;
59
+ } else {
60
+ SecondaryRepository = oneHatData.getRepository(secondaryModel);
61
+ }
62
+
63
+ if (secondaryPageSize) {
64
+ SecondaryRepository.setPageSize(secondaryPageSize);
65
+ }
66
+
67
+ if (secondaryBaseParams) {
68
+ SecondaryRepository.setBaseParams(secondaryBaseParams);
69
+ }
70
+
71
+
72
+ if (SecondaryRepository && !SecondaryRepository.isLoaded && SecondaryRepository.isRemote && !SecondaryRepository.isAutoLoad && !SecondaryRepository.isLoading) {
73
+ let doAutoLoad = SecondaryRepository.autoLoad;
74
+ if (!_.isNil(secondaryAutoLoad)) { // prop can override schema setting for secondaryAutoLoad
75
+ doAutoLoad = secondaryAutoLoad;
76
+ }
77
+ if (doAutoLoad) {
78
+ await SecondaryRepository.load();
79
+ }
80
+ }
81
+
82
+ setLocalSecondaryRepository(SecondaryRepository);
83
+ if (setSecondaryRepository) { // pass it on up to higher components
84
+ setSecondaryRepository(SecondaryRepository);
85
+ }
86
+ if (self) {
87
+ self.repository = SecondaryRepository;
88
+ }
89
+ setIsReady(true);
90
+ })();
91
+
92
+ return () => {
93
+ if (repositoryId) {
94
+ oneHatData.deleteRepository(repositoryId);
95
+ }
96
+ }
97
+
98
+ }, []);
99
+
100
+ if (!isReady) {
101
+ return null;
102
+ }
103
+
104
+ return <WrappedComponent
105
+ {...propsToPass}
106
+ secondaryDisableWithData={false}
107
+ SecondaryRepository={LocalSecondaryRepository}
108
+ secondaryModel={secondaryModel}
109
+ secondaryData={secondaryData}
110
+ secondaryFields={secondaryFields}
111
+ secondaryIdField={secondaryIdField}
112
+ secondaryDisplayField={secondaryDisplayField}
113
+ secondaryIdIx={localIdIx}
114
+ secondaryDisplayIx={localDisplayIx}
115
+ />;
116
+ };
117
+ }
@@ -0,0 +1,455 @@
1
+ import { useEffect, useState, useRef, } from 'react';
2
+ import {
3
+ Button,
4
+ } from 'native-base';
5
+ import {
6
+ EDITOR_MODE__VIEW,
7
+ EDITOR_MODE__ADD,
8
+ EDITOR_MODE__EDIT,
9
+ } from '../../../Constants/Editor.js';
10
+ import _ from 'lodash';
11
+
12
+ // NOTE: This is a modified version of @onehat/ui/src/Hoc/withEditor
13
+ // This HOC will eventually get out of sync with that one, and may need to be updated.
14
+
15
+ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
16
+ return (props) => {
17
+
18
+ if (props.secondaryDisableWithEditor) {
19
+ return <WrappedComponent {...props} />;
20
+ }
21
+
22
+ let [secondaryEditorMode, secondarySetEditorMode] = useState(EDITOR_MODE__VIEW); // Can change below, so use 'let'
23
+ const {
24
+ secondaryUserCanEdit = true,
25
+ secondaryUserCanView = true,
26
+ secondaryCanEditorViewOnly = false, // whether the editor can *ever* change state out of 'View' mode
27
+ secondaryDisableAdd = false,
28
+ secondaryDisableEdit = false,
29
+ secondaryDisableDelete = false,
30
+ secondaryDisableDuplicate = false,
31
+ secondaryDisableView = false,
32
+ secondaryUseRemoteDuplicate = false, // call specific copyToNew function on server, rather than simple duplicate on client
33
+ secondaryGetRecordIdentifier = (secondarySelection) => {
34
+ if (secondarySelection.length > 1) {
35
+ return 'records?';
36
+ }
37
+ return 'record' + (secondarySelection[0].displayValue ? ' "' + secondarySelection[0].displayValue + '"' : '') + '?';
38
+ },
39
+ secondaryRecord,
40
+ secondaryOnChange,
41
+ secondaryOnSave,
42
+ secondaryNewEntityDisplayValue,
43
+
44
+ // withComponent
45
+ self,
46
+
47
+ // parent container
48
+ secondarySelectorId,
49
+ secondarySelectorSelected,
50
+
51
+ // withSecondaryData
52
+ SecondaryRepository,
53
+
54
+ // withSecondarySelection
55
+ secondarySelection,
56
+ secondarySetSelection,
57
+
58
+ // withAlert
59
+ alert,
60
+ confirm,
61
+ hideAlert,
62
+ } = props,
63
+ secondaryListeners = useRef({}),
64
+ secondaryEditorStateRef = useRef(),
65
+ secondaryNewEntityDisplayValueRef = useRef(),
66
+ [secondaryCurrentRecord, secondarySetCurrentRecord] = useState(null),
67
+ [secondaryIsAdding, setIsAdding] = useState(false),
68
+ [secondaryIsSaving, setIsSaving] = useState(false),
69
+ [secondaryIsEditorShown, secondarySetIsEditorShown] = useState(false),
70
+ [secondaryIsEditorViewOnly, setIsEditorViewOnly] = useState(secondaryCanEditorViewOnly), // current state of whether editor is in view-only mode
71
+ [secondaryLastSelection, setLastSelection] = useState(),
72
+ secondarySetSelectionDecorated = (newSelection) => {
73
+ function doIt() {
74
+ secondarySetSelection(newSelection);
75
+ }
76
+ const formState = secondaryEditorStateRef.current;
77
+ if (!_.isEmpty(formState?.dirtyFields) && newSelection !== secondarySelection && secondaryEditorMode === EDITOR_MODE__EDIT) {
78
+ confirm('This record has unsaved changes. Are you sure you want to cancel editing? Changes will be lost.', doIt);
79
+ } else {
80
+ doIt();
81
+ }
82
+ },
83
+ getListeners = () => {
84
+ return secondaryListeners.current;
85
+ },
86
+ setListeners = (obj) => {
87
+ secondaryListeners.current = obj;
88
+ // forceUpdate(); // we don't want to get into an infinite loop of renders. Simply directly assign the secondaryListeners in every child render
89
+ },
90
+ getNewEntityDisplayValue = () => {
91
+ return secondaryNewEntityDisplayValueRef.current;
92
+ },
93
+ secondaryOnAdd = async (e, values) => {
94
+ const defaultValues = SecondaryRepository.getSchema().getDefaultValues();
95
+ let addValues = values || _.clone(defaultValues);
96
+
97
+ if (secondarySelectorId && !_.isEmpty(secondarySelectorSelected)) {
98
+ addValues[secondarySelectorId] = secondarySelectorSelected.id;
99
+ }
100
+
101
+ if (!values && getNewEntityDisplayValue()) {
102
+ const displayPropertyName = SecondaryRepository.getSchema().model.displayProperty;
103
+ addValues[displayPropertyName] = getNewEntityDisplayValue();
104
+ }
105
+
106
+ if (getListeners().onBeforeAdd) {
107
+ const listenerResult = await getListeners().onBeforeAdd();
108
+ if (listenerResult === false) {
109
+ return;
110
+ }
111
+ }
112
+
113
+ if (isTree) {
114
+ if (!secondarySelection[0]) {
115
+ throw Error('Must select a parent node.');
116
+ }
117
+ addValues.parentId = secondarySelection[0].id;
118
+ } else {
119
+ // Set repository to sort by id DESC and switch to page 1, so this new entity is guaranteed to show up on the current page, even after saving
120
+ const currentSorter = SecondaryRepository.sorters[0];
121
+ if (currentSorter.name !== SecondaryRepository.schema.model.idProperty || currentSorter.direction !== 'DESC') {
122
+ SecondaryRepository.pauseEvents();
123
+ SecondaryRepository.sort(SecondaryRepository.schema.model.idProperty, 'DESC');
124
+ SecondaryRepository.setPage(1);
125
+ SecondaryRepository.resumeEvents();
126
+ await SecondaryRepository.reload();
127
+ }
128
+ }
129
+
130
+ // Unmap the values, so we can input true originalData
131
+ addValues = SecondaryRepository.unmapData(addValues);
132
+
133
+
134
+ setIsAdding(true);
135
+ setIsSaving(true);
136
+ const entity = await SecondaryRepository.add(addValues, false, true);
137
+ setIsSaving(false);
138
+ secondarySetSelection([entity]);
139
+ setIsEditorViewOnly(false);
140
+ secondarySetEditorMode(EDITOR_MODE__ADD);
141
+ secondarySetIsEditorShown(true);
142
+
143
+ if (getListeners().onAfterAdd) {
144
+ await getListeners().onAfterAdd(entity);
145
+ }
146
+ if (secondaryOnChange) {
147
+ secondaryOnChange();
148
+ }
149
+ },
150
+ secondaryOnEdit = async () => {
151
+ if (_.isEmpty(secondarySelection) || (_.isArray(secondarySelection) && (secondarySelection.length > 1 || secondarySelection[0]?.isDestroyed))) {
152
+ return;
153
+ }
154
+ if (getListeners().onBeforeEdit) {
155
+ const listenerResult = await getListeners().onBeforeEdit();
156
+ if (listenerResult === false) {
157
+ return;
158
+ }
159
+ }
160
+ setIsEditorViewOnly(false);
161
+ secondarySetEditorMode(EDITOR_MODE__EDIT);
162
+ secondarySetIsEditorShown(true);
163
+ },
164
+ secondaryOnDelete = async (args) => {
165
+ let cb = null;
166
+ if (_.isFunction(args)) {
167
+ cb = args;
168
+ }
169
+ if (_.isEmpty(secondarySelection) || (_.isArray(secondarySelection) && (secondarySelection.length > 1 || secondarySelection[0]?.isDestroyed))) {
170
+ return;
171
+ }
172
+ if (getListeners().onBeforeDelete) {
173
+ const listenerResult = await getListeners().onBeforeDelete();
174
+ if (listenerResult === false) {
175
+ return;
176
+ }
177
+ }
178
+ const
179
+ isSingle = secondarySelection.length === 1,
180
+ firstSelection = secondarySelection[0],
181
+ isTree = firstSelection?.isTree,
182
+ hasChildren = isTree ? firstSelection?.hasChildren : false,
183
+ isPhantom = firstSelection?.isPhantom;
184
+
185
+ if (isSingle && isTree && hasChildren) {
186
+ alert({
187
+ title: 'Move up children?',
188
+ message: 'The node you have selected for deletion has children. ' +
189
+ 'Should these children be moved up to this node\'s parent, or be deleted?',
190
+ buttons: [
191
+ <Button colorScheme="danger" onPress={() => secondaryOnMoveChildren(cb)} key="moveBtn">
192
+ Move Children
193
+ </Button>,
194
+ <Button colorScheme="danger" onPress={() => secondaryOnDeleteChildren(cb)} key="deleteBtn">
195
+ Delete Children
196
+ </Button>
197
+ ],
198
+ includeCancel: true,
199
+ });
200
+ } else
201
+ if (isSingle && isPhantom) {
202
+ deleteRecord(cb);
203
+ } else {
204
+ const identifier = secondaryGetRecordIdentifier(secondarySelection);
205
+ confirm('Are you sure you want to delete the ' + identifier, () => deleteRecord(null, cb));
206
+ }
207
+ },
208
+ secondaryOnMoveChildren = (cb) => {
209
+ hideAlert();
210
+ deleteRecord(true, cb);
211
+ },
212
+ secondaryOnDeleteChildren = (cb) => {
213
+ hideAlert();
214
+ deleteRecord(false, cb);
215
+ },
216
+ deleteRecord = async (moveSubtreeUp, cb) => {
217
+ if (getListeners().onBeforeDeleteSave) {
218
+ await getListeners().onBeforeDeleteSave(secondarySelection);
219
+ }
220
+
221
+ await SecondaryRepository.delete(secondarySelection, moveSubtreeUp);
222
+ if (!SecondaryRepository.isAutoSave) {
223
+ await SecondaryRepository.save();
224
+ }
225
+ if (getListeners().onAfterDelete) {
226
+ await getListeners().onAfterDelete(secondarySelection);
227
+ }
228
+ secondarySetSelection([]);
229
+ if (cb) {
230
+ cb();
231
+ }
232
+ if (secondaryOnChange) {
233
+ secondaryOnChange();
234
+ }
235
+ },
236
+ secondaryOnView = async () => {
237
+ if (!secondaryUserCanView) {
238
+ return;
239
+ }
240
+ if (secondarySelection.length !== 1) {
241
+ return;
242
+ }
243
+ setIsEditorViewOnly(true);
244
+ secondarySetEditorMode(EDITOR_MODE__VIEW);
245
+ secondarySetIsEditorShown(true);
246
+
247
+ if (getListeners().onAfterView) {
248
+ await getListeners().onAfterView();
249
+ }
250
+ },
251
+ secondaryOnDuplicate = async () => {
252
+ if (!secondaryUserCanEdit || secondaryDisableDuplicate) {
253
+ return;
254
+ }
255
+ if (secondarySelection.length !== 1) {
256
+ return;
257
+ }
258
+ if (secondaryUseRemoteDuplicate) {
259
+ return onRemoteDuplicate();
260
+ }
261
+ const
262
+ entity = secondarySelection[0],
263
+ idProperty = SecondaryRepository.getSchema().model.idProperty,
264
+ rawValues = _.omit(entity.rawValues, idProperty),
265
+ duplicate = await SecondaryRepository.add(rawValues, false, true);
266
+ secondarySetSelection([duplicate]);
267
+ secondarySetEditorMode(EDITOR_MODE__EDIT);
268
+ secondarySetIsEditorShown(true);
269
+ },
270
+ onRemoteDuplicate = async () => {
271
+ const
272
+ entity = secondarySelection[0],
273
+ duplicateEntity = await SecondaryRepository.remoteDuplicate(entity);
274
+
275
+ secondarySetSelection([duplicateEntity]);
276
+ secondaryOnEdit();
277
+ },
278
+ secondaryOnEditorSave = async (data, e) => {
279
+ const
280
+ what = secondaryRecord || secondarySelection,
281
+ isSingle = what.length === 1;
282
+ if (isSingle) {
283
+ // just update this one entity
284
+ what[0].setValues(data);
285
+
286
+ } else if (secondarySelection.length > 1) {
287
+ // Edit multiple entities
288
+
289
+ // Loop through all entities and change fields that are not null
290
+ const propertyNames = Object.getOwnPropertyNames(data);
291
+ _.each(propertyNames, (propertyName) => {
292
+ if (!_.isNil(data[propertyName])) {
293
+ _.each(what, (rec) => {
294
+ rec[propertyName] = data[propertyName]
295
+ });
296
+ }
297
+ });
298
+ }
299
+
300
+ if (getListeners().onBeforeEditSave) {
301
+ await getListeners().onBeforeEditSave(what);
302
+ }
303
+
304
+ setIsSaving(true);
305
+ let success;
306
+ try {
307
+ await SecondaryRepository.save();
308
+ success = true;
309
+ } catch (e) {
310
+ success = false;
311
+ }
312
+ setIsSaving(false);
313
+
314
+ if (success) {
315
+ setIsAdding(false);
316
+
317
+ secondarySetEditorMode(EDITOR_MODE__EDIT);
318
+ // secondarySetIsEditorShown(false);
319
+
320
+ if (getListeners().onAfterEdit) {
321
+ await getListeners().onAfterEdit(what);
322
+ }
323
+ if (secondaryOnChange) {
324
+ secondaryOnChange();
325
+ }
326
+ if (secondaryOnSave) {
327
+ secondaryOnSave(what);
328
+ }
329
+ }
330
+
331
+ return success;
332
+ },
333
+ secondaryOnEditorCancel = () => {
334
+ async function doIt() {
335
+ const
336
+ isSingle = secondarySelection.length === 1,
337
+ isPhantom = secondarySelection[0] && !secondarySelection[0]?.isDestroyed && secondarySelection[0].isPhantom;
338
+ if (isSingle && isPhantom) {
339
+ await deleteRecord();
340
+ }
341
+
342
+ setIsAdding(false);
343
+ secondarySetIsEditorShown(false);
344
+ }
345
+ const formState = secondaryEditorStateRef.current;
346
+ if (!_.isEmpty(formState.dirtyFields)) {
347
+ confirm('This record has unsaved changes. Are you sure you want to cancel editing? Changes will be lost.', doIt);
348
+ } else {
349
+ doIt();
350
+ }
351
+ },
352
+ secondaryOnEditorClose = () => {
353
+ if (secondaryIsAdding) {
354
+ secondaryOnEditorCancel();
355
+ }
356
+ secondarySetIsEditorShown(false);
357
+ },
358
+ secondaryOnEditorDelete = async () => {
359
+ secondaryOnDelete(() => {
360
+ secondarySetEditorMode(EDITOR_MODE__VIEW);
361
+ secondarySetIsEditorShown(false);
362
+ });
363
+ },
364
+ calculateEditorMode = () => {
365
+ let mode = secondaryEditorMode;
366
+ if (!secondaryCanEditorViewOnly && secondaryUserCanEdit) {
367
+ if (secondarySelection.length > 1) {
368
+ if (!secondaryDisableEdit) {
369
+ // For multiple entities selected, change it to edit multiple mode
370
+ mode = EDITOR_MODE__EDIT;
371
+ }
372
+ } else if (secondarySelection.length === 1 && !secondarySelection[0].isDestroyed && secondarySelection[0].isPhantom) {
373
+ if (!secondaryDisableAdd) {
374
+ // When a phantom entity is selected, change it to add mode.
375
+ mode = EDITOR_MODE__ADD;
376
+ }
377
+ }
378
+ }
379
+ return mode;
380
+ },
381
+ secondaryOnEditMode = () => {
382
+ secondarySetEditorMode(EDITOR_MODE__EDIT);
383
+ },
384
+ secondaryOnViewMode = () => {
385
+ function doIt() {
386
+ secondarySetEditorMode(EDITOR_MODE__VIEW);
387
+ }
388
+ const formState = secondaryEditorStateRef.current;
389
+ if (!_.isEmpty(formState.dirtyFields)) {
390
+ confirm('This record has unsaved changes. Are you sure you want to switch to "View" mode? Changes will be lost.', doIt);
391
+ } else {
392
+ doIt();
393
+ }
394
+ };
395
+
396
+ useEffect(() => {
397
+ // When secondarySelection changes, set the mode appropriately
398
+ const mode = calculateEditorMode();
399
+ secondarySetEditorMode(mode);
400
+
401
+ setLastSelection(secondarySelection);
402
+ }, [secondarySelection]);
403
+
404
+ if (self) {
405
+ self.secondaryAdd = secondaryOnAdd;
406
+ self.secondaryEdit = secondaryOnEdit;
407
+ self.secondaryDelete = secondaryOnDelete;
408
+ self.secondarnMoveChildren = secondaryOnMoveChildren;
409
+ self.secondaryDeleteChildren = secondaryOnDeleteChildren;
410
+ self.secondaryDuplicate = secondaryOnDuplicate;
411
+ }
412
+ secondaryNewEntityDisplayValueRef.current = secondaryNewEntityDisplayValue;
413
+
414
+ if (secondaryLastSelection !== secondarySelection) {
415
+ // NOTE: If I don't calculate this on the fly for secondarySelection changes,
416
+ // we see a flash of the previous state, since useEffect hasn't yet run.
417
+ secondaryEditorMode = calculateEditorMode();
418
+ }
419
+
420
+ return <WrappedComponent
421
+ {...props}
422
+ secondaryDisableWithEditor={false}
423
+ secondaryCurrentRecord={secondaryCurrentRecord}
424
+ secondarySetCurrentRecord={secondarySetCurrentRecord}
425
+ secondaryIsEditorShown={secondaryIsEditorShown}
426
+ secondaryIsEditorViewOnly={secondaryIsEditorViewOnly}
427
+ secondaryIsAdding={secondaryIsAdding}
428
+ secondaryIsSaving={secondaryIsSaving}
429
+ secondaryEditorMode={secondaryEditorMode}
430
+ secondaryOnEditMode={secondaryOnEditMode}
431
+ secondaryOnViewMode={secondaryOnViewMode}
432
+ secondaryEditorStateRef={secondaryEditorStateRef}
433
+ secondarySetIsEditorShown={secondarySetIsEditorShown}
434
+ secondaryOnAdd={(!secondaryUserCanEdit || secondaryDisableAdd) ? null : secondaryOnAdd}
435
+ secondaryOnEdit={(!secondaryUserCanEdit || secondaryDisableEdit) ? null : secondaryOnEdit}
436
+ secondaryOnDelete={(!secondaryUserCanEdit || secondaryDisableDelete) ? null : secondaryOnDelete}
437
+ secondaryOnView={secondaryOnView}
438
+ secondaryOnDuplicate={secondaryOnDuplicate}
439
+ secondaryOnEditorSave={secondaryOnEditorSave}
440
+ secondaryOnEditorCancel={secondaryOnEditorCancel}
441
+ secondaryOnEditorDelete={(!secondaryUserCanEdit || secondaryDisableDelete) ? null : secondaryOnEditorDelete}
442
+ secondaryOnEditorClose={secondaryOnEditorClose}
443
+ secondarySetWithEditListeners={setListeners}
444
+ secondaryIsEditor={true}
445
+ secondaryUserCanEdit={secondaryUserCanEdit}
446
+ secondaryUserCanView={secondaryUserCanView}
447
+ secondaryDisableAdd={secondaryDisableAdd}
448
+ secondaryDisableEdit={secondaryDisableEdit}
449
+ secondaryDisableDelete={secondaryDisableDelete}
450
+ secondaryDisableDuplicate={secondaryDisableDuplicate}
451
+ secondaryDisableView ={secondaryDisableView}
452
+ secondarySetSelection={secondarySetSelectionDecorated}
453
+ />;
454
+ };
455
+ }
@@ -0,0 +1,377 @@
1
+ import { useState, useEffect, } from 'react';
2
+ import {
3
+ SELECTION_MODE_SINGLE,
4
+ SELECTION_MODE_MULTI,
5
+ SELECT_UP,
6
+ SELECT_DOWN,
7
+ } from '../../../Constants/Selection.js';
8
+ import inArray from '../../../Functions/inArray.js';
9
+ import _ from 'lodash';
10
+
11
+ // NOTE: This is a modified version of @onehat/ui/src/Hoc/withSelection
12
+ // This HOC will eventually get out of sync with that one, and may need to be updated.
13
+
14
+ export default function withSelection(WrappedComponent) {
15
+ return (props) => {
16
+
17
+ if (props.secondaryDisableWithSelection) {
18
+ return <WrappedComponent {...props} />;
19
+ }
20
+
21
+ if (props.secondarySetSelection) {
22
+ // bypass everything, since we're already using withSelection() in hierarchy.
23
+ // For example, Combo has withSelection(), and intenally it uses Grid which also has withSelection(),
24
+ // but we only need it defined once for the whole thing.
25
+ return <WrappedComponent {...props} />;
26
+ }
27
+
28
+ const
29
+ {
30
+ secondarySelection,
31
+ secondaryDefaultSelection,
32
+ secondaryOnChangeSelection,
33
+ secondarySelectionMode = SELECTION_MODE_SINGLE, // SELECTION_MODE_MULTI, SELECTION_MODE_SINGLE
34
+ secondaryAutoSelectFirstItem = false,
35
+ fireEvent,
36
+
37
+ // withComponent
38
+ self,
39
+
40
+ // withSecondaryValue
41
+ secondaryValue,
42
+ secondarySetValue,
43
+
44
+ // withSecondaryData
45
+ SecondaryRepository,
46
+ secondaryData,
47
+ secondaryIdIx,
48
+ secondaryDisplayIx,
49
+ } = props,
50
+ usesWithValue = !!secondarySetValue,
51
+ initialSelection = secondarySelection || secondaryDefaultSelection || [],
52
+ [secondaryLocalSelection, setLocalSelection] = useState(initialSelection),
53
+ [isReady, setIsReady] = useState(secondarySelection || false), // if secondarySelection is already defined, or secondaryValue is not null and we don't need to load repository, it's ready
54
+ secondarySetSelection = (secondarySelection) => {
55
+ if (_.isEqual(secondarySelection, secondaryLocalSelection)) {
56
+ return;
57
+ }
58
+
59
+ setLocalSelection(secondarySelection);
60
+ if (secondaryOnChangeSelection) {
61
+ secondaryOnChangeSelection(secondarySelection);
62
+ }
63
+ if (fireEvent) {
64
+ fireEvent('secondaryChangeSelection', secondarySelection);
65
+ }
66
+ },
67
+ secondarySelectNext = () => {
68
+ secondarySelectDirection(SELECT_DOWN);
69
+ },
70
+ secondarySelectPrev = () => {
71
+ secondarySelectDirection(SELECT_UP);
72
+ },
73
+ secondarySelectDirection = (which) => {
74
+ const { items, max, min, noSelection, } = getMaxMinSelectionIndices();
75
+ let newIx;
76
+ if (which === SELECT_DOWN) {
77
+ if (noSelection || max === items.length -1) {
78
+ // select first
79
+ newIx = 0;
80
+ } else {
81
+ newIx = max +1;
82
+ }
83
+ } else if (which === SELECT_UP) {
84
+ if (noSelection || min === 0) {
85
+ // select last
86
+ newIx = items.length -1;
87
+ } else {
88
+ newIx = min -1;
89
+ }
90
+ }
91
+ if (items[newIx]) {
92
+ secondarySetSelection([items[newIx]]);
93
+ }
94
+ },
95
+ secondaryAddToSelection = (item) => {
96
+ let newSelection = [];
97
+ newSelection = _.clone(secondaryLocalSelection); // so we get a new object, so descendants rerender
98
+ newSelection.push(item);
99
+ secondarySetSelection(newSelection);
100
+ },
101
+ secondaryRemoveFromSelection = (item) => {
102
+ let newSelection = [];
103
+ if (SecondaryRepository) {
104
+ newSelection = _.remove(secondaryLocalSelection, (sel) => sel !== item);
105
+ } else {
106
+ newSelection = _.remove(secondaryLocalSelection, (sel) => sel[secondaryIdIx] !== item[secondaryIdIx]);
107
+ }
108
+ secondarySetSelection(newSelection);
109
+ },
110
+ secondaryDeselectAll = () => {
111
+ if (!_.isEmpty(secondaryLocalSelection)) {
112
+ secondarySetSelection([]);
113
+ }
114
+ },
115
+ getMaxMinSelectionIndices = () => {
116
+ let items,
117
+ currentlySelectedRowIndices = [];
118
+ if (SecondaryRepository) {
119
+ items = SecondaryRepository.getEntitiesOnPage();
120
+ } else {
121
+ items = secondaryData;
122
+ }
123
+ _.each(items, (item, ix) => {
124
+ if (secondaryIsInSelection(item)) {
125
+ currentlySelectedRowIndices.push(ix);
126
+ }
127
+ });
128
+ if (currentlySelectedRowIndices.length === 0) {
129
+ return { items, noSelection: true, };
130
+ }
131
+ const
132
+ max = Math.max(...currentlySelectedRowIndices),
133
+ min = Math.min(...currentlySelectedRowIndices);
134
+
135
+ return { items, max, min, noSelection: false, };
136
+ },
137
+ secondarySelectRangeTo = (item) => {
138
+ // Select above max or below min to this one
139
+ const
140
+ currentSelectionLength = secondaryLocalSelection.length,
141
+ index = getIndexOfSelectedItem(item);
142
+ let newSelection = _.clone(secondaryLocalSelection); // so we get a new object, so descendants rerender
143
+
144
+ if (currentSelectionLength) {
145
+ const { items, max, min, } = getMaxMinSelectionIndices();
146
+ let i,
147
+ itemAtIx;
148
+ if (max < index) {
149
+ // all other secondarySelections are below the current;
150
+ // Range is from max+1 up to index
151
+ for (i = max +1; i < index; i++) {
152
+ itemAtIx = items[i];
153
+ newSelection.push(itemAtIx);
154
+ }
155
+ } else if (min > index) {
156
+ // all other secondarySelections are above the current;
157
+ // Range is from min-1 down to index
158
+ for (i = min -1; i > index; i--) {
159
+ itemAtIx = items[i];
160
+ newSelection.push(itemAtIx);
161
+ }
162
+ }
163
+ }
164
+ newSelection.push(item);
165
+ secondarySetSelection(newSelection);
166
+ },
167
+ secondaryIsInSelection = (item) => {
168
+ if (SecondaryRepository) {
169
+ return inArray(item, secondaryLocalSelection);
170
+ }
171
+
172
+ const found = _.find(secondaryLocalSelection, (selectedItem) => {
173
+ return selectedItem[secondaryIdIx] === item[secondaryIdIx];
174
+ });
175
+ return !!found;
176
+ },
177
+ getIndexOfSelectedItem = (item) => {
178
+ // Gets ix of entity on page, or element in secondaryData array
179
+ if (SecondaryRepository) {
180
+ const entities = SecondaryRepository.getEntitiesOnPage();
181
+ return entities.indexOf(item);
182
+ }
183
+
184
+ let found;
185
+ _.each(secondaryData, (datum, ix) => {
186
+ if (datum[secondaryIdIx] === item[secondaryIdIx]) {
187
+ found = ix;
188
+ return false; // break loop
189
+ }
190
+ });
191
+ return found;
192
+ },
193
+ secondaryGetIdsFromLocalSelection = () => {
194
+ if (!secondaryLocalSelection[0]) {
195
+ return null;
196
+ }
197
+ const secondaryValues = _.map(secondaryLocalSelection, (item) => {
198
+ if (SecondaryRepository) {
199
+ return item.id;
200
+ }
201
+ return item[secondaryIdIx];
202
+ });
203
+ if (secondaryValues.length === 1) {
204
+ return secondaryValues[0];
205
+ }
206
+ return secondaryValues;
207
+ },
208
+ secondaryGetDisplayValuesFromLocalSelection = (secondarySelection) => {
209
+ if (!secondarySelection[0]) {
210
+ return '';
211
+ }
212
+
213
+ return _.map(secondarySelection, (item) => {
214
+ if (SecondaryRepository) {
215
+ return item.displayValue;
216
+ }
217
+ return item[secondaryDisplayIx];
218
+ })
219
+ .join(', ');
220
+ },
221
+ conformValueToLocalSelection = () => {
222
+ if (!secondarySetValue) {
223
+ return;
224
+ }
225
+
226
+ const localValue = secondaryGetIdsFromLocalSelection();
227
+ if (!_.isEqual(localValue, secondaryValue)) {
228
+ secondarySetValue(localValue);
229
+ }
230
+ },
231
+ conformSelectionToValue = async () => {
232
+ let newSelection = [];
233
+ if (SecondaryRepository) {
234
+ if (SecondaryRepository.isLoading) {
235
+ await SecondaryRepository.waitUntilDoneLoading();
236
+ }
237
+ // Get entity or entities that match secondaryValue
238
+ if ((_.isArray(secondaryValue) && !_.isEmpty(secondaryValue)) || !!secondaryValue) {
239
+ if (_.isArray(secondaryValue)) {
240
+ newSelection = SecondaryRepository.getBy((entity) => inArray(entity.id, secondaryValue));
241
+ } else {
242
+ let found = SecondaryRepository.getById(secondaryValue);
243
+ if (found) {
244
+ newSelection.push(found);
245
+ // } else if (SecondaryRepository?.isRemote && SecondaryRepository?.entities.length) {
246
+
247
+ // // Value cannot be found in SecondaryRepository, but actually exists on server
248
+ // // Try to get this secondaryValue from the server directly
249
+ // SecondaryRepository.filter(SecondaryRepository.schema.model.idProperty, secondaryValue);
250
+ // await SecondaryRepository.load();
251
+ // found = SecondaryRepository.getById(secondaryValue);
252
+ // if (found) {
253
+ // newSelection.push(found);
254
+ // }
255
+
256
+ }
257
+ }
258
+ }
259
+ } else {
260
+ // Get secondaryData item or items that match secondaryValue
261
+ if (!_.isNil(secondaryValue) && (_.isBoolean(secondaryValue) || _.isNumber(secondaryValue) || !_.isEmpty(secondaryValue))) {
262
+ let currentValue = secondaryValue;
263
+ if (!_.isArray(currentValue)) {
264
+ currentValue = [currentValue];
265
+ }
266
+ _.each(currentValue, (val) => {
267
+ // Search through secondaryData
268
+ const found = _.find(secondaryData, (item) => {
269
+ if (_.isString(item[secondaryIdIx]) && _.isString(val)) {
270
+ return item[secondaryIdIx].toLowerCase() === val.toLowerCase();
271
+ }
272
+ return item[secondaryIdIx] === val;
273
+ });
274
+ if (found) {
275
+ newSelection.push(found);
276
+ }
277
+ });
278
+ }
279
+ }
280
+
281
+ if (!_.isEqual(newSelection, secondaryLocalSelection)) {
282
+ secondarySetSelection(newSelection);
283
+ }
284
+ };
285
+
286
+ useEffect(() => {
287
+
288
+ (async () => {
289
+
290
+ if (usesWithValue && SecondaryRepository?.isRemote
291
+ && !SecondaryRepository.isAutoLoad && !SecondaryRepository.isLoaded && !SecondaryRepository.isLoading && (!_.isNil(secondaryValue) || !_.isEmpty(secondarySelection)) || secondaryAutoSelectFirstItem) {
292
+ // on initialization, we can't conformSelectionToValue if the repository is not yet loaded,
293
+ // so first load repo, then conform to secondaryValue
294
+ await SecondaryRepository.load();
295
+ }
296
+
297
+ if (!_.isNil(secondaryValue)) {
298
+
299
+ await conformSelectionToValue();
300
+
301
+ } else if (!_.isEmpty(secondarySelection)) {
302
+
303
+ conformValueToLocalSelection();
304
+
305
+ } else if (secondaryAutoSelectFirstItem) {
306
+ let newSelection = [];
307
+ if (SecondaryRepository) {
308
+ const entitiesOnPage = SecondaryRepository.getEntitiesOnPage();
309
+ newSelection = entitiesOnPage[0] ? [entitiesOnPage[0]] : [];
310
+ } else {
311
+ newSelection = secondaryData[0] ? [secondaryData[0]] : [];
312
+ }
313
+ secondarySetSelection(newSelection);
314
+ }
315
+
316
+ setIsReady(true);
317
+
318
+ })();
319
+
320
+ }, [secondaryValue]);
321
+
322
+ if (self) {
323
+ self.secondarySelection = secondaryLocalSelection;
324
+ self.secondarySetSelection = secondarySetSelection;
325
+ self.secondarySelectNext = secondarySelectNext;
326
+ self.secondarySelectPrev = secondarySelectPrev;
327
+ self.secondaryAddToSelection = secondaryAddToSelection;
328
+ self.secondaryRemoveFromSelection = secondaryRemoveFromSelection;
329
+ self.secondaryDeselectAll = secondaryDeselectAll;
330
+ self.secondarySelectRangeTo = secondarySelectRangeTo;
331
+ self.secondaryIsInSelection = secondaryIsInSelection;
332
+ self.secondaryGetIdsFromLocalSelection = secondaryGetIdsFromLocalSelection;
333
+ self.secondaryGetDisplayValuesFromSelection = secondaryGetDisplayValuesFromLocalSelection;
334
+ }
335
+
336
+ if (usesWithValue) {
337
+ useEffect(() => {
338
+ if (!isReady) {
339
+ return () => {};
340
+ }
341
+
342
+ conformSelectionToValue();
343
+
344
+ }, [secondaryValue]);
345
+
346
+ useEffect(() => {
347
+ if (!isReady) {
348
+ return () => {};
349
+ }
350
+
351
+ conformValueToLocalSelection();
352
+
353
+ }, [secondarySelection]);
354
+ }
355
+
356
+ if (!isReady) {
357
+ return null;
358
+ }
359
+
360
+ return <WrappedComponent
361
+ {...props}
362
+ secondaryDisableWithSelection={false}
363
+ secondarySelection={secondaryLocalSelection}
364
+ secondarySetSelection={secondarySetSelection}
365
+ secondarySelectionMode={secondarySelectionMode}
366
+ secondarySelectNext={secondarySelectNext}
367
+ secondarySelectPrev={secondarySelectPrev}
368
+ secondaryRemoveFromSelection={secondaryRemoveFromSelection}
369
+ secondaryAddToSelection={secondaryAddToSelection}
370
+ secondaryDeselectAll={secondaryDeselectAll}
371
+ secondarySelectRangeTo={secondarySelectRangeTo}
372
+ secondaryIsInSelection={secondaryIsInSelection}
373
+ secondaryGetIdsFromSelection={secondaryGetIdsFromLocalSelection}
374
+ secondaryGetDisplayValuesFromSelection={secondaryGetDisplayValuesFromLocalSelection}
375
+ />;
376
+ };
377
+ }
@@ -0,0 +1,50 @@
1
+ import {
2
+ EDITOR_TYPE__SIDE,
3
+ } from '@onehat/ui/src/Constants/Editor.js';
4
+ import Container from '../../Container/Container.js';
5
+ import withSecondaryEditor from './withSecondaryEditor.js';
6
+ import _ from 'lodash';
7
+
8
+ // NOTE: This is a modified version of @onehat/ui/src/Hoc/withSideEditor
9
+ // This HOC will eventually get out of sync with that one, and may need to be updated.
10
+
11
+ export default function withSideEditor(WrappedComponent, isTree = false) {
12
+ return withSecondaryEditor((props) => {
13
+ const {
14
+ SecondaryEditor,
15
+ secondaryEditorProps = {},
16
+ secondarySideFlex = 100,
17
+
18
+ // withComponent
19
+ self,
20
+
21
+ // pull these out, as we don't want them going to the Editor
22
+ secondarySelectorId,
23
+ secondarySelectorSelected,
24
+
25
+ ...propsToPass
26
+ } = props;
27
+
28
+ if (!SecondaryEditor) {
29
+ throw Error('SecondaryEditor is not defined');
30
+ }
31
+
32
+ return <Container
33
+ center={<WrappedComponent
34
+ isTree={isTree}
35
+ isSideEditor={true}
36
+ {...props}
37
+ />}
38
+ east={<Editor
39
+ {...propsToPass}
40
+ editorType={EDITOR_TYPE__SIDE}
41
+ flex={secondarySideFlex}
42
+ borderLeftWidth={1}
43
+ borderLeftColor="#ccc"
44
+ {...secondaryEditorProps}
45
+ parent={self}
46
+ reference="secondaryEditor"
47
+ />}
48
+ />;
49
+ });
50
+ }
@@ -0,0 +1,154 @@
1
+ import { useState, useEffect, useRef, useContext, useCallback, } from 'react';
2
+ import natsort from 'natsort';
3
+ import useForceUpdate from '../../../Hooks/useForceUpdate.js';
4
+ import FieldSetContext from '../../../Contexts/FieldSetContext.js';
5
+ import _ from 'lodash';
6
+
7
+ // NOTE: This is a modified version of @onehat/ui/src/Hoc/withValue
8
+ // This HOC will eventually get out of sync with that one, and may need to be updated.
9
+
10
+ export default function withSecondaryValue(WrappedComponent) {
11
+ return (props) => {
12
+
13
+ if (props.secondaryDisableWithValue) {
14
+ return <WrappedComponent {...props} />;
15
+ }
16
+
17
+ if (props.secondarySetValue) {
18
+ // bypass everything, since we're already using withSecondaryValue() in hierarchy.
19
+ // For example, Combo has withSecondaryValue(), and intenally it uses Input which also has withSecondaryValue(),
20
+ // but we only need it defined once for the whole thing.
21
+ return <WrappedComponent {...props} />;
22
+ }
23
+
24
+ const
25
+ {
26
+ secondaryOnChangeValue,
27
+ secondaryValue,
28
+ secondaryStartingValue = null,
29
+ secondaryIsValueAlwaysArray = false,
30
+ secondaryIsValueAsStringifiedJson = false,
31
+
32
+ // withComponent
33
+ self,
34
+
35
+ // withData
36
+ SecondaryRepository,
37
+ secondaryIdIx,
38
+ } = props,
39
+ forceUpdate = useForceUpdate(),
40
+ childRef = useRef({}),
41
+ secondaryOnChangeValueRef = useRef(),
42
+ localValueRef = useRef(secondaryStartingValue || secondaryValue),
43
+ fieldSetOnChangeValueRef = useRef(),
44
+ fieldSetContext = useContext(FieldSetContext),
45
+ fieldSetRegisterChild = fieldSetContext?.registerChild,
46
+ fieldSetOnChangeValue = fieldSetContext?.secondaryOnChangeValue,
47
+ getLocalValue = () => {
48
+ return localValueRef.current;
49
+ },
50
+ secondarySetLocalValue = (secondaryValue) => {
51
+ localValueRef.current = secondaryValue;
52
+ forceUpdate();
53
+ },
54
+ secondarySetValueRef = useRef((newValue) => {
55
+ // NOTE: We useRef so that this function stays current after renders
56
+ if (secondaryIsValueAlwaysArray && !_.isArray(newValue)) {
57
+ newValue = _.isNil(newValue) ? [] : [newValue];
58
+ }
59
+ if (_.isArray(newValue)) {
60
+ const sortFn = natsort.default || natsort; // was having trouble with webpack and this solves it
61
+
62
+ // TODO: sort by the sortProperty, whatever that is, instead of just value
63
+ newValue.sort(sortFn()); // Only sort if we're using id/text arrangement. Otherwise, keep sort order as specified in SecondaryRepository.
64
+ }
65
+ if (secondaryIsValueAsStringifiedJson) {
66
+ newValue = JSON.stringify(newValue);
67
+ }
68
+
69
+ if (newValue === getLocalValue()) {
70
+ return;
71
+ }
72
+
73
+ secondarySetLocalValue(newValue);
74
+
75
+ if (secondaryOnChangeValueRef.current) {
76
+ secondaryOnChangeValueRef.current(newValue, childRef.current);
77
+ }
78
+ if (fieldSetOnChangeValueRef.current) {
79
+ fieldSetOnChangeValueRef.current(newValue, childRef.current);
80
+ }
81
+ }),
82
+ secondarySetValue = (args) => {
83
+ secondarySetValueRef.current(args);
84
+ },
85
+ secondaryOnChangeSelection = (selection) => {
86
+ let secondaryValue = null,
87
+ secondaryValues;
88
+ if (selection.length) {
89
+ if (SecondaryRepository) {
90
+ if (selection.length === 1) {
91
+ secondaryValue = selection[0].id;
92
+ } else {
93
+ secondaryValues = _.map(selection, (entity) => entity.id);
94
+ }
95
+ } else {
96
+ if (selection.length === 1) {
97
+ secondaryValue = selection[0][secondaryIdIx];
98
+ } else {
99
+ secondaryValues = _.map(selection, (item) => item[secondaryIdIx]);
100
+ }
101
+ }
102
+ }
103
+ if (secondaryValues) {
104
+ secondaryValue = secondaryValues;
105
+ }
106
+ secondarySetValue(secondaryValue);
107
+ };
108
+
109
+ // Ensure these passed functions stay current after render
110
+ secondaryOnChangeValueRef.current = secondaryOnChangeValue;
111
+ fieldSetOnChangeValueRef.current = fieldSetOnChangeValue;
112
+
113
+ useEffect(() => {
114
+ if (!_.isEqual(secondaryValue, getLocalValue())) {
115
+ secondarySetLocalValue(secondaryValue);
116
+ }
117
+ }, [secondaryValue]);
118
+
119
+ if (fieldSetRegisterChild) {
120
+ useEffect(() => {
121
+ fieldSetRegisterChild({
122
+ childRef: childRef.current,
123
+ value: secondaryValue,
124
+ setValue: secondarySetValueRef.current,
125
+ });
126
+ }, []);
127
+ }
128
+
129
+ if (self) {
130
+ self.secondarySetValue = secondarySetValue;
131
+ self.secondaryValue = getLocalValue();
132
+ }
133
+
134
+
135
+ // Convert localValue to normal JS primitives for field components
136
+ let convertedValue = getLocalValue();
137
+ if (_.isString(convertedValue) && secondaryIsValueAsStringifiedJson && !_.isNil(convertedValue)) {
138
+ convertedValue = JSON.parse(convertedValue);
139
+ }
140
+ if (secondaryIsValueAlwaysArray) {
141
+ if (_.isEmpty(convertedValue) || _.isNil(convertedValue)) {
142
+ convertedValue = [];
143
+ }
144
+ }
145
+
146
+ return <WrappedComponent
147
+ {...props}
148
+ secondaryDisableWithValue={false}
149
+ secondaryValue={convertedValue}
150
+ secondarySetValue={secondarySetValue}
151
+ secondaryOnChangeSelection={secondaryOnChangeSelection}
152
+ />;
153
+ };
154
+ }
@@ -0,0 +1,71 @@
1
+ import {
2
+ Column,
3
+ Modal,
4
+ Text,
5
+ } from 'native-base';
6
+ import {
7
+ EDITOR_TYPE__WINDOWED,
8
+ } from '../../../Constants/Editor.js';
9
+ import withSecondaryEditor from './withSecondaryEditor.js';
10
+ // import withDraggable from './withDraggable.js';
11
+ import _ from 'lodash';
12
+
13
+ // NOTE: This is a modified version of @onehat/ui/src/Hoc/withWindowedEditor
14
+ // This HOC will eventually get out of sync with that one, and may need to be updated.
15
+
16
+ export default function withSecondaryWindowedEditor(WrappedComponent, isTree = false) {
17
+ return withSecondaryEditor((props) => {
18
+ const {
19
+ secondaryIsEditorShown = false,
20
+ secondarySetIsEditorShown,
21
+ SecondaryEditor,
22
+ secondaryEditorProps = {},
23
+
24
+ // withComponent
25
+ self,
26
+
27
+ // pull these out, as we don't want them going to the SecondaryEditor
28
+ secondarySelectorId,
29
+ secondarySelectorSelected,
30
+ h,
31
+
32
+ ...propsToPass
33
+ } = props;
34
+
35
+ if (!SecondaryEditor) {
36
+ throw Error('SecondaryEditor is not defined');
37
+ }
38
+
39
+ if (secondaryIsEditorShown) {
40
+ // Move the 'secondary' props over to primary
41
+ // for the sake of the Editor
42
+ function lcfirst(str) {
43
+ return str.charAt(0).toLowerCase() + str.slice(1);
44
+ }
45
+ _.each(props, (prop, ix) => {
46
+ if (ix.match(/^secondary/)) {
47
+ const name = lcfirst(ix.replace(/^secondary/, ''));
48
+ secondaryEditorProps[name] = prop;
49
+ }
50
+ });
51
+ secondaryEditorProps.Repository = props.SecondaryRepository;
52
+ }
53
+
54
+ return <>
55
+ <WrappedComponent {...props} />
56
+ {secondaryIsEditorShown &&
57
+ <Modal
58
+ isOpen={true}
59
+ onClose={() => secondarySetIsEditorShown(false)}
60
+ >
61
+ <SecondaryEditor
62
+ editorType={EDITOR_TYPE__WINDOWED}
63
+ {...propsToPass}
64
+ {...secondaryEditorProps}
65
+ parent={self}
66
+ reference="secondaryEditor"
67
+ />
68
+ </Modal>}
69
+ </>;
70
+ }, isTree);
71
+ }
@@ -242,7 +242,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
242
242
  setIsEditorShown(true);
243
243
 
244
244
  if (getListeners().onAfterView) {
245
- await getListeners().onAfterView(entity);
245
+ await getListeners().onAfterView();
246
246
  }
247
247
  },
248
248
  onDuplicate = async () => {