@onehat/ui 0.3.57 → 0.3.58
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 +1 -1
- package/src/Components/Form/Field/Combo/Combo.js +279 -185
- package/src/Components/Form/Field/Tag/Tag.js +46 -20
- package/src/Components/Form/FieldSet.js +5 -4
- package/src/Components/Form/Form.js +76 -26
- package/src/Components/Hoc/withEditor.js +32 -4
- package/src/Components/Hoc/withSelection.js +14 -14
- package/src/Components/Hoc/withValue.js +1 -1
- package/src/Components/Toolbar/Pagination.js +12 -0
- package/src/Components/Viewer/Viewer.js +1 -0
- package/src/Components/index.js +0 -2
- package/src/Components/Viewer/TagViewer.js +0 -30
package/package.json
CHANGED
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
} from '../../../../Constants/UiModes.js';
|
|
13
13
|
import UiGlobals from '../../../../UiGlobals.js';
|
|
14
14
|
import Input from '../Input.js';
|
|
15
|
+
import withAlert from '../../../Hoc/withAlert.js';
|
|
15
16
|
import withComponent from '../../../Hoc/withComponent.js';
|
|
16
17
|
import withData from '../../../Hoc/withData.js';
|
|
17
|
-
import withSelection from '../../../Hoc/withSelection.js';
|
|
18
18
|
import withValue from '../../../Hoc/withValue.js';
|
|
19
19
|
import emptyFn from '../../../../Functions/emptyFn.js';
|
|
20
20
|
import { Grid, WindowedGridEditor } from '../../../Grid/Grid.js';
|
|
@@ -22,15 +22,10 @@ import IconButton from '../../../Buttons/IconButton.js';
|
|
|
22
22
|
import CaretDown from '../../../Icons/CaretDown.js';
|
|
23
23
|
import _ from 'lodash';
|
|
24
24
|
|
|
25
|
-
// Combo requires the use of HOC withSelection() whenever it's used.
|
|
26
|
-
// The default export is *with* the HOC. A separate *raw* component is
|
|
27
|
-
// exported which can be combined with many HOCs for various functionality.
|
|
28
|
-
|
|
29
25
|
export function ComboComponent(props) {
|
|
30
26
|
const {
|
|
31
27
|
additionalButtons,
|
|
32
28
|
autoFocus = false,
|
|
33
|
-
forceSelection = true,
|
|
34
29
|
tooltipRef = null,
|
|
35
30
|
tooltip = null,
|
|
36
31
|
menuMinWidth = 150,
|
|
@@ -39,10 +34,14 @@ export function ComboComponent(props) {
|
|
|
39
34
|
_input = {},
|
|
40
35
|
isEditor = false,
|
|
41
36
|
isDisabled = false,
|
|
37
|
+
tooltipPlacement = 'bottom',
|
|
38
|
+
onRowPress,
|
|
42
39
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
// withComponent
|
|
41
|
+
self,
|
|
42
|
+
|
|
43
|
+
// withAlert
|
|
44
|
+
confirm,
|
|
46
45
|
|
|
47
46
|
// withData
|
|
48
47
|
Repository,
|
|
@@ -50,32 +49,28 @@ export function ComboComponent(props) {
|
|
|
50
49
|
idIx,
|
|
51
50
|
displayIx,
|
|
52
51
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
setSelection,
|
|
57
|
-
selectionMode,
|
|
58
|
-
selectNext,
|
|
59
|
-
selectPrev,
|
|
60
|
-
getDisplayValuesFromSelection,
|
|
61
|
-
|
|
62
|
-
tooltipPlacement = 'bottom',
|
|
52
|
+
// withValue
|
|
53
|
+
value,
|
|
54
|
+
setValue,
|
|
63
55
|
} = props,
|
|
64
56
|
styles = UiGlobals.styles,
|
|
65
57
|
inputRef = useRef(),
|
|
66
58
|
triggerRef = useRef(),
|
|
67
59
|
menuRef = useRef(),
|
|
68
|
-
|
|
60
|
+
displayValueRef = useRef(null),
|
|
69
61
|
savedSearch = useRef(null),
|
|
70
62
|
typingTimeout = useRef(),
|
|
71
63
|
[isMenuShown, setIsMenuShown] = useState(false),
|
|
72
64
|
[isRendered, setIsRendered] = useState(false),
|
|
73
|
-
[
|
|
65
|
+
[isReady, setIsReady] = useState(false),
|
|
66
|
+
[isSearchMode, setIsSearchMode] = useState(false),
|
|
67
|
+
[gridSelection, setGridSelection] = useState(null),
|
|
68
|
+
[textInputValue, setTextInputValue] = useState(''),
|
|
69
|
+
[newEntityDisplayValue, setNewEntityDisplayValue] = useState(null),
|
|
74
70
|
[width, setWidth] = useState(0),
|
|
75
|
-
[height, setHeight] = useState(null),
|
|
76
71
|
[top, setTop] = useState(0),
|
|
77
72
|
[left, setLeft] = useState(0),
|
|
78
|
-
showMenu = () => {
|
|
73
|
+
showMenu = async () => {
|
|
79
74
|
if (isMenuShown) {
|
|
80
75
|
return;
|
|
81
76
|
}
|
|
@@ -107,7 +102,7 @@ export function ComboComponent(props) {
|
|
|
107
102
|
}
|
|
108
103
|
}
|
|
109
104
|
if (Repository && !Repository.isLoaded) {
|
|
110
|
-
Repository.
|
|
105
|
+
await Repository.load();
|
|
111
106
|
}
|
|
112
107
|
setIsMenuShown(true);
|
|
113
108
|
},
|
|
@@ -117,11 +112,8 @@ export function ComboComponent(props) {
|
|
|
117
112
|
}
|
|
118
113
|
setIsMenuShown(false);
|
|
119
114
|
},
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
},
|
|
123
|
-
setIsManuallyEnteringText = (bool) => {
|
|
124
|
-
isManuallyEnteringText.current = bool;
|
|
115
|
+
toggleMenu = () => {
|
|
116
|
+
setIsMenuShown(!isMenuShown);
|
|
125
117
|
},
|
|
126
118
|
getSavedSearch = () => {
|
|
127
119
|
return savedSearch.current;
|
|
@@ -129,8 +121,8 @@ export function ComboComponent(props) {
|
|
|
129
121
|
setSavedSearch = (val) => {
|
|
130
122
|
savedSearch.current = val;
|
|
131
123
|
},
|
|
132
|
-
|
|
133
|
-
|
|
124
|
+
resetInputTextValue = () => {
|
|
125
|
+
setTextInputValue(getDisplayValue());
|
|
134
126
|
},
|
|
135
127
|
onInputKeyPress = (e, inputValue) => {
|
|
136
128
|
if (disableDirectEntry) {
|
|
@@ -138,6 +130,8 @@ export function ComboComponent(props) {
|
|
|
138
130
|
}
|
|
139
131
|
switch(e.key) {
|
|
140
132
|
case 'Escape':
|
|
133
|
+
setIsSearchMode(false);
|
|
134
|
+
resetInputTextValue();
|
|
141
135
|
hideMenu();
|
|
142
136
|
break;
|
|
143
137
|
case 'Enter':
|
|
@@ -145,24 +139,42 @@ export function ComboComponent(props) {
|
|
|
145
139
|
if (_.isEmpty(inputValue) && !_.isNull(value)) {
|
|
146
140
|
// User pressed Enter on an empty text field, but value is set to something
|
|
147
141
|
// This means the user cleared the input and pressed enter, meaning he wants to clear the value
|
|
148
|
-
|
|
149
|
-
// clear the value
|
|
150
142
|
setValue(null);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
143
|
+
hideMenu();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (_.isEmpty(gridSelection)) {
|
|
148
|
+
confirm('You have nothing selected in the dropdown menu. Clear value?', doIt, true);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
doIt();
|
|
153
|
+
|
|
154
|
+
function doIt() {
|
|
155
|
+
setValue(gridSelection?.id);
|
|
156
|
+
hideMenu();
|
|
156
157
|
}
|
|
157
158
|
break;
|
|
158
|
-
case 'ArrowDown':
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
159
|
+
// case 'ArrowDown':
|
|
160
|
+
// e.preventDefault();
|
|
161
|
+
// showMenu();
|
|
162
|
+
// selectNext();
|
|
163
|
+
// setTimeout(() => {
|
|
164
|
+
// if (!self.children?.dropdownGrid?.selectPrev) {
|
|
165
|
+
// debugger;
|
|
166
|
+
// }
|
|
167
|
+
// self.children.dropdownGrid.selectNext();
|
|
168
|
+
// }, 10);
|
|
169
|
+
// break;
|
|
170
|
+
// case 'ArrowUp':
|
|
171
|
+
// e.preventDefault();
|
|
172
|
+
// showMenu();
|
|
173
|
+
// selectPrev();
|
|
174
|
+
// setTimeout(() => {
|
|
175
|
+
// self.children.dropdownGrid.selectPrev();
|
|
176
|
+
// }, 10);
|
|
177
|
+
// break;
|
|
166
178
|
default:
|
|
167
179
|
}
|
|
168
180
|
},
|
|
@@ -170,140 +182,138 @@ export function ComboComponent(props) {
|
|
|
170
182
|
if (disableDirectEntry) {
|
|
171
183
|
return;
|
|
172
184
|
}
|
|
173
|
-
setTextValue(value);
|
|
174
185
|
|
|
175
|
-
|
|
186
|
+
if (_.isEmpty(value)) {
|
|
187
|
+
// text input is cleared
|
|
188
|
+
hideMenu();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
setTextInputValue(value);
|
|
193
|
+
showMenu();
|
|
194
|
+
|
|
176
195
|
clearTimeout(typingTimeout.current);
|
|
177
196
|
typingTimeout.current = setTimeout(() => {
|
|
178
197
|
searchForMatches(value);
|
|
179
198
|
}, 300);
|
|
180
199
|
},
|
|
200
|
+
onInputFocus = (e) => {
|
|
201
|
+
inputRef.current.select();
|
|
202
|
+
},
|
|
181
203
|
onInputBlur = (e) => {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
} = e;
|
|
185
|
-
|
|
186
|
-
setIsManuallyEnteringText(false);
|
|
187
|
-
|
|
188
|
-
// If user focused on the trigger and text is blank, clear the selection and close the menu
|
|
189
|
-
if ((triggerRef.current === relatedTarget || triggerRef.current.contains(relatedTarget)) && (_.isEmpty(textValue) || _.isNil(textValue))) {
|
|
190
|
-
if (!disableWithSelection) {
|
|
191
|
-
setSelection([]); // delete current selection
|
|
192
|
-
}
|
|
193
|
-
hideMenu();
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// If user focused on the menu or trigger, ignore this blur
|
|
198
|
-
if (triggerRef.current === relatedTarget ||
|
|
199
|
-
triggerRef.current.contains(relatedTarget) ||
|
|
200
|
-
menuRef.current=== relatedTarget ||
|
|
201
|
-
menuRef.current?.contains(relatedTarget)) {
|
|
204
|
+
if (isEventStillInComponent(e)) {
|
|
205
|
+
// ignore the blur
|
|
202
206
|
return;
|
|
203
207
|
}
|
|
204
208
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
triggerRef.current !== relatedTarget &&
|
|
209
|
-
(!menuRef.current || !menuRef.current.contains(relatedTarget))
|
|
210
|
-
)
|
|
211
|
-
) {
|
|
212
|
-
hideMenu();
|
|
213
|
-
}
|
|
214
|
-
if (_.isEmpty(textValue) || _.isNil(textValue)) {
|
|
215
|
-
|
|
216
|
-
if (!disableWithSelection) {
|
|
217
|
-
setSelection([]); // delete current selection
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
} else if (getIsManuallyEnteringText()) {
|
|
221
|
-
if (forceSelection) {
|
|
222
|
-
if (!disableWithSelection) {
|
|
223
|
-
setSelection([]); // delete current selection
|
|
224
|
-
} else {
|
|
225
|
-
setValue(textValue);
|
|
226
|
-
}
|
|
227
|
-
hideMenu();
|
|
228
|
-
} else {
|
|
229
|
-
setValue(textValue);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (!disableWithSelection) {
|
|
234
|
-
if (_.isEmpty(selection)) {
|
|
235
|
-
setTextValue('');
|
|
236
|
-
}
|
|
237
|
-
}
|
|
209
|
+
setIsSearchMode(false);
|
|
210
|
+
resetInputTextValue();
|
|
211
|
+
hideMenu();
|
|
238
212
|
},
|
|
239
|
-
|
|
213
|
+
onTriggerPress = (e) => {
|
|
240
214
|
if (!isRendered) {
|
|
241
215
|
return;
|
|
242
216
|
}
|
|
217
|
+
clearGridFilters();
|
|
243
218
|
showMenu();
|
|
244
219
|
},
|
|
245
|
-
|
|
246
|
-
if (!
|
|
220
|
+
onTriggerBlur = (e) => {
|
|
221
|
+
if (!isMenuShown) {
|
|
247
222
|
return;
|
|
248
223
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
224
|
+
|
|
225
|
+
if (isEventStillInComponent(e)) {
|
|
226
|
+
// ignore the blur
|
|
227
|
+
return;
|
|
253
228
|
}
|
|
254
|
-
|
|
229
|
+
|
|
230
|
+
setIsSearchMode(false);
|
|
231
|
+
resetInputTextValue();
|
|
232
|
+
hideMenu();
|
|
255
233
|
},
|
|
256
|
-
|
|
234
|
+
isEventStillInComponent = (e) => {
|
|
257
235
|
const {
|
|
258
236
|
relatedTarget
|
|
259
237
|
} = e;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
238
|
+
return !relatedTarget ||
|
|
239
|
+
!menuRef.current ||
|
|
240
|
+
!triggerRef.current ||
|
|
241
|
+
triggerRef.current === relatedTarget ||
|
|
242
|
+
triggerRef.current.contains(relatedTarget) ||
|
|
243
|
+
menuRef.current === relatedTarget ||
|
|
244
|
+
menuRef.current?.contains(relatedTarget);
|
|
245
|
+
},
|
|
246
|
+
clearGridFilters = async () => {
|
|
247
|
+
if (Repository) {
|
|
248
|
+
if (Repository.isLoading) {
|
|
249
|
+
await Repository.waitUntilDoneLoading();
|
|
264
250
|
}
|
|
265
|
-
|
|
251
|
+
|
|
252
|
+
// clear filter
|
|
253
|
+
if (Repository.isRemote) {
|
|
254
|
+
let searchField = 'q';
|
|
255
|
+
const searchValue = null;
|
|
266
256
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
257
|
+
// Check to see if displayField is a real field
|
|
258
|
+
const
|
|
259
|
+
schema = Repository.getSchema(),
|
|
260
|
+
displayFieldName = schema.model.displayProperty,
|
|
261
|
+
displayFieldDef = schema.getPropertyDefinition(displayFieldName);
|
|
262
|
+
if (!displayFieldDef.isVirtual) {
|
|
263
|
+
searchField = displayFieldName + ' LIKE';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
Repository.clear();
|
|
267
|
+
await Repository.filter(searchField, searchValue);
|
|
268
|
+
if (!this.isAutoLoad) {
|
|
269
|
+
await Repository.reload();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
} else {
|
|
273
|
+
throw Error('Not yet implemented');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
setSavedSearch(null);
|
|
277
|
+
|
|
278
|
+
} else {
|
|
279
|
+
throw Error('Not yet implemented');
|
|
273
280
|
}
|
|
274
281
|
},
|
|
275
282
|
searchForMatches = async (value) => {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
return;
|
|
283
|
+
if (!isMenuShown) {
|
|
284
|
+
showMenu();
|
|
279
285
|
}
|
|
280
286
|
|
|
287
|
+
setIsSearchMode(true);
|
|
288
|
+
|
|
281
289
|
let found;
|
|
282
290
|
if (Repository) {
|
|
291
|
+
if (Repository.isLoading) {
|
|
292
|
+
await Repository.waitUntilDoneLoading();
|
|
293
|
+
}
|
|
283
294
|
|
|
284
295
|
// Set filter
|
|
285
296
|
let filter = {};
|
|
286
297
|
if (Repository.isRemote) {
|
|
287
298
|
let searchField = 'q';
|
|
299
|
+
const searchValue = _.isEmpty(value) ? null : value + '%';
|
|
288
300
|
|
|
289
301
|
// Check to see if displayField is a real field
|
|
290
302
|
const
|
|
291
303
|
schema = Repository.getSchema(),
|
|
292
|
-
displayFieldName = schema.model.displayProperty
|
|
304
|
+
displayFieldName = schema.model.displayProperty,
|
|
293
305
|
displayFieldDef = schema.getPropertyDefinition(displayFieldName);
|
|
294
306
|
if (!displayFieldDef.isVirtual) {
|
|
295
307
|
searchField = displayFieldName + ' LIKE';
|
|
296
308
|
}
|
|
297
309
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
await Repository.filter(searchField, value);
|
|
310
|
+
await Repository.filter(searchField, searchValue);
|
|
301
311
|
if (!this.isAutoLoad) {
|
|
302
312
|
await Repository.reload();
|
|
303
313
|
}
|
|
304
314
|
|
|
305
315
|
} else {
|
|
306
|
-
throw Error('Not
|
|
316
|
+
throw Error('Not yet implemented');
|
|
307
317
|
|
|
308
318
|
// Fuzzy search with getBy filter function
|
|
309
319
|
filter = (entity) => {
|
|
@@ -316,18 +326,11 @@ export function ComboComponent(props) {
|
|
|
316
326
|
}
|
|
317
327
|
|
|
318
328
|
setSavedSearch(value);
|
|
319
|
-
|
|
320
|
-
const numResults = Repository.entities.length;
|
|
321
|
-
if (!numResults) {
|
|
322
|
-
setSelection([]);
|
|
323
|
-
} else if (numResults === 1) {
|
|
324
|
-
const selection = Repository.entities[0];
|
|
325
|
-
setSelection([selection]);
|
|
326
|
-
setSavedSearch(null);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
+
setNewEntityDisplayValue(value); // capture the search query so we can tell Grid what to use for a new entity's displayValue
|
|
329
330
|
|
|
330
331
|
} else {
|
|
332
|
+
throw Error('Not yet implemented');
|
|
333
|
+
|
|
331
334
|
// Search through data
|
|
332
335
|
found = _.find(data, (item) => {
|
|
333
336
|
if (_.isString(item[displayIx]) && _.isString(value)) {
|
|
@@ -335,23 +338,64 @@ export function ComboComponent(props) {
|
|
|
335
338
|
}
|
|
336
339
|
return item[displayIx] === value;
|
|
337
340
|
});
|
|
338
|
-
if (found) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
newTextValue = getDisplayValuesFromSelection(newSelection);
|
|
341
|
+
// if (found) {
|
|
342
|
+
// const
|
|
343
|
+
// newSelection = [found];
|
|
342
344
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
345
|
+
// setTextInputValue(newTextValue);
|
|
346
|
+
// }
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
getDisplayValue = () => {
|
|
350
|
+
return displayValueRef.current;
|
|
351
|
+
},
|
|
352
|
+
setDisplayValue = async (value) => {
|
|
353
|
+
let displayValue = '';
|
|
354
|
+
if (_.isNil(value)) {
|
|
355
|
+
// do nothing
|
|
356
|
+
} else if (_.isArray(value)) {
|
|
357
|
+
displayValue = [];
|
|
358
|
+
if (Repository) {
|
|
359
|
+
if (!Repository.isLoaded) {
|
|
360
|
+
throw Error('Not yet implemented'); // Would a Combo ever have multiple remote selections? Shouldn't that be a Tag field??
|
|
361
|
+
}
|
|
362
|
+
if (Repository.isLoading) {
|
|
363
|
+
await Repository.waitUntilDoneLoading();
|
|
346
364
|
}
|
|
365
|
+
displayValue = _.each(value, (id) => {
|
|
366
|
+
const entity = Repository.getById(id);
|
|
367
|
+
if (entity) {
|
|
368
|
+
displayValue.push(entity.displayValue)
|
|
369
|
+
}
|
|
370
|
+
});
|
|
347
371
|
} else {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
372
|
+
displayValue = _.each(value, (id) => {
|
|
373
|
+
const item = _.find(data, (datum) => datum[idIx] === id);
|
|
374
|
+
if (item) {
|
|
375
|
+
displayValue.push(item[displayIx]);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
displayValue = displayValue.join(', ');
|
|
380
|
+
} else {
|
|
381
|
+
if (Repository) {
|
|
382
|
+
let entity;
|
|
383
|
+
if (!Repository.isLoaded) {
|
|
384
|
+
entity = await Repository.getSingleEntityFromServer(value);
|
|
385
|
+
} else {
|
|
386
|
+
if (Repository.isLoading) {
|
|
387
|
+
await Repository.waitUntilDoneLoading();
|
|
351
388
|
}
|
|
389
|
+
entity = Repository.getById(value);
|
|
352
390
|
}
|
|
391
|
+
displayValue = entity?.displayValue || '';
|
|
392
|
+
} else {
|
|
393
|
+
const item = _.find(data, (datum) => datum[idIx] === id);
|
|
394
|
+
displayValue = (item && item[displayIx]) || '';
|
|
353
395
|
}
|
|
354
396
|
}
|
|
397
|
+
|
|
398
|
+
displayValueRef.current = displayValue;
|
|
355
399
|
};
|
|
356
400
|
|
|
357
401
|
useEffect(() => {
|
|
@@ -365,27 +409,40 @@ export function ComboComponent(props) {
|
|
|
365
409
|
|
|
366
410
|
}, [isRendered]);
|
|
367
411
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
412
|
+
useEffect(() => {
|
|
413
|
+
(async () => {
|
|
414
|
+
setIsSearchMode(false);
|
|
415
|
+
await setDisplayValue(value);
|
|
416
|
+
resetInputTextValue();
|
|
417
|
+
if (!isReady) {
|
|
418
|
+
setIsReady(true);
|
|
372
419
|
}
|
|
420
|
+
})();
|
|
421
|
+
}, [value]);
|
|
373
422
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (!_.isEqual(localTextValue, textValue)) {
|
|
377
|
-
setTextValue(localTextValue);
|
|
378
|
-
}
|
|
379
|
-
setIsManuallyEnteringText(false);
|
|
380
|
-
}, [selection]);
|
|
423
|
+
if (!isReady) {
|
|
424
|
+
return null;
|
|
381
425
|
}
|
|
382
426
|
|
|
383
|
-
|
|
384
427
|
const refProps = {};
|
|
385
428
|
if (tooltipRef) {
|
|
386
429
|
refProps.ref = tooltipRef;
|
|
387
430
|
}
|
|
388
431
|
|
|
432
|
+
const gridProps = _.pick(props, [
|
|
433
|
+
'Editor',
|
|
434
|
+
'model',
|
|
435
|
+
'Repository',
|
|
436
|
+
'data',
|
|
437
|
+
'idIx',
|
|
438
|
+
'displayIx',
|
|
439
|
+
'value',
|
|
440
|
+
'disableView',
|
|
441
|
+
'disableCopy',
|
|
442
|
+
'disableDuplicate',
|
|
443
|
+
'disablePrint',
|
|
444
|
+
]);
|
|
445
|
+
|
|
389
446
|
const WhichGrid = isEditor ? WindowedGridEditor : Grid;
|
|
390
447
|
|
|
391
448
|
let comboComponent = <Row {...refProps} justifyContent="center" alignItems="center" h={styles.FORM_COMBO_HEIGHT} flex={1} onLayout={() => setIsRendered(true)}>
|
|
@@ -424,17 +481,17 @@ export function ComboComponent(props) {
|
|
|
424
481
|
_focus={{
|
|
425
482
|
bg: styles.FORM_COMBO_INPUT_FOCUS_BG,
|
|
426
483
|
}}
|
|
427
|
-
>{
|
|
484
|
+
>{textInputValue}</Text>
|
|
428
485
|
</Pressable> :
|
|
429
486
|
<Input
|
|
430
487
|
ref={inputRef}
|
|
431
|
-
value={
|
|
488
|
+
value={textInputValue}
|
|
432
489
|
autoSubmit={true}
|
|
433
490
|
isDisabled={isDisabled}
|
|
434
491
|
onChangeValue={onInputChangeText}
|
|
435
492
|
onKeyPress={onInputKeyPress}
|
|
493
|
+
onFocus={onInputFocus}
|
|
436
494
|
onBlur={onInputBlur}
|
|
437
|
-
onClick={onInputClick}
|
|
438
495
|
onLayout={(e) => {
|
|
439
496
|
// On web, this is not needed, but on RN it might be, so leave it in for now
|
|
440
497
|
const {
|
|
@@ -447,15 +504,6 @@ export function ComboComponent(props) {
|
|
|
447
504
|
setTop(top + height);
|
|
448
505
|
setLeft(left);
|
|
449
506
|
}}
|
|
450
|
-
// onFocus={(e) => {
|
|
451
|
-
// if (isBlocked.current) {
|
|
452
|
-
// return;
|
|
453
|
-
// }
|
|
454
|
-
// if (!isRendered) {
|
|
455
|
-
// return;
|
|
456
|
-
// }
|
|
457
|
-
// showMenu();
|
|
458
|
-
// }}
|
|
459
507
|
flex={1}
|
|
460
508
|
h="100%"
|
|
461
509
|
m={0}
|
|
@@ -535,20 +583,66 @@ export function ComboComponent(props) {
|
|
|
535
583
|
};
|
|
536
584
|
}}
|
|
537
585
|
autoAdjustPageSizeToHeight={false}
|
|
538
|
-
{...
|
|
586
|
+
{...gridProps}
|
|
587
|
+
reference="dropdownGrid"
|
|
588
|
+
parent={self}
|
|
539
589
|
h={styles.FORM_COMBO_MENU_HEIGHT + 'px'}
|
|
590
|
+
newEntityDisplayValue={newEntityDisplayValue}
|
|
540
591
|
disablePresetButtons={!isEditor}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
592
|
+
onChangeSelection={(selection) => {
|
|
593
|
+
if (selection[0]?.isPhantom) {
|
|
594
|
+
// do nothing
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
setGridSelection(selection);
|
|
599
|
+
|
|
600
|
+
// When we first open the menu, we try to match the selection to the value, ignore this
|
|
601
|
+
if (selection[0]?.displayValue === getDisplayValue()) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// when user selected the record matching the current value, kill search mode
|
|
606
|
+
if (selection[0]?.id === value) {
|
|
607
|
+
setIsSearchMode(false);
|
|
608
|
+
resetInputTextValue();
|
|
609
|
+
if (hideMenuOnSelection) {
|
|
610
|
+
hideMenu();
|
|
611
|
+
}
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
setValue(selection[0]?.id);
|
|
616
|
+
|
|
617
|
+
if (_.isEmpty(selection)) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (hideMenuOnSelection && !isEditor) {
|
|
546
622
|
hideMenu();
|
|
547
623
|
}
|
|
624
|
+
|
|
548
625
|
}}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
626
|
+
onSave={(selection) => {
|
|
627
|
+
const entity = selection[0];
|
|
628
|
+
if (entity?.id !== value) {
|
|
629
|
+
// Either a phantom record was just solidified into a real record, or a new (non-phantom) record was added.
|
|
630
|
+
// Select it and set the value of the combo.
|
|
631
|
+
setGridSelection([entity]);
|
|
632
|
+
const id = entity.id;
|
|
633
|
+
setValue(id);
|
|
634
|
+
}
|
|
635
|
+
}}
|
|
636
|
+
onRowPress={(item, e) => {
|
|
637
|
+
if (onRowPress) {
|
|
638
|
+
onRowPress(item, e);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const id = Repository ? item.id : item[idIx];
|
|
642
|
+
if (id === value) {
|
|
643
|
+
hideMenu();
|
|
644
|
+
onInputFocus();
|
|
645
|
+
}
|
|
552
646
|
}}
|
|
553
647
|
/>
|
|
554
648
|
</Popover.Body>
|
|
@@ -565,9 +659,9 @@ export function ComboComponent(props) {
|
|
|
565
659
|
}
|
|
566
660
|
|
|
567
661
|
export const Combo = withComponent(
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
662
|
+
withAlert(
|
|
663
|
+
withData(
|
|
664
|
+
withValue(
|
|
571
665
|
ComboComponent
|
|
572
666
|
)
|
|
573
667
|
)
|
|
@@ -25,7 +25,6 @@ function ValueBox(props) {
|
|
|
25
25
|
onView,
|
|
26
26
|
onDelete,
|
|
27
27
|
} = props;
|
|
28
|
-
|
|
29
28
|
return <Row
|
|
30
29
|
borderWidth={1}
|
|
31
30
|
borderColor="trueGray.400"
|
|
@@ -43,7 +42,7 @@ function ValueBox(props) {
|
|
|
43
42
|
onPress={onView}
|
|
44
43
|
h="100%"
|
|
45
44
|
/>
|
|
46
|
-
<Text color="trueGray.600">{text}</Text>
|
|
45
|
+
<Text color="trueGray.600" mr={onDelete ? 0 : 2}>{text}</Text>
|
|
47
46
|
{onDelete &&
|
|
48
47
|
<IconButton
|
|
49
48
|
_icon={{
|
|
@@ -83,9 +82,15 @@ function TagComponent(props) {
|
|
|
83
82
|
const
|
|
84
83
|
id = item.id,
|
|
85
84
|
repository = propsToPass.Repository;
|
|
85
|
+
if (!repository.isLoaded) {
|
|
86
|
+
await repository.load();
|
|
87
|
+
}
|
|
88
|
+
if (repository.isLoading) {
|
|
89
|
+
await repository.waitUntilDoneLoading();
|
|
90
|
+
}
|
|
86
91
|
let record = repository.getById(id); // first try to get from entities in memory
|
|
87
92
|
if (!record && repository.getSingleEntityFromServer) {
|
|
88
|
-
record = await repository.getSingleEntityFromServer(id);
|
|
93
|
+
record = await repository.getSingleEntityFromServer(id);
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
if (!record) {
|
|
@@ -135,24 +140,42 @@ function TagComponent(props) {
|
|
|
135
140
|
}),
|
|
136
141
|
WhichCombo = isEditor ? ComboEditor : Combo;
|
|
137
142
|
|
|
143
|
+
const sizeProps = {};
|
|
144
|
+
if (!props.flex && !props.w) {
|
|
145
|
+
sizeProps.flex = 1;
|
|
146
|
+
} else {
|
|
147
|
+
if (props.w) {
|
|
148
|
+
sizeProps.w = props.w;
|
|
149
|
+
}
|
|
150
|
+
if (props.flex) {
|
|
151
|
+
sizeProps.flex = props.flex;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
138
155
|
return <>
|
|
139
|
-
<Column
|
|
140
|
-
{
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
+
<Column
|
|
157
|
+
{...props}
|
|
158
|
+
{...sizeProps}
|
|
159
|
+
px={0}
|
|
160
|
+
py={0}
|
|
161
|
+
>
|
|
162
|
+
<Row
|
|
163
|
+
w="100%"
|
|
164
|
+
borderWidth={1}
|
|
165
|
+
borderColor="trueGray.300"
|
|
166
|
+
borderRadius="md"
|
|
167
|
+
bg="trueGray.100"
|
|
168
|
+
p={1}
|
|
169
|
+
mb={1}
|
|
170
|
+
minHeight={10}
|
|
171
|
+
flexWrap="wrap"
|
|
172
|
+
>{valueBoxes}</Row>
|
|
173
|
+
{isEditor &&
|
|
174
|
+
<WhichCombo
|
|
175
|
+
Repository={props.Repository}
|
|
176
|
+
Editor={props.Editor}
|
|
177
|
+
onRowPress={onAdd}
|
|
178
|
+
/>}
|
|
156
179
|
</Column>
|
|
157
180
|
{isViewerShown &&
|
|
158
181
|
<Modal
|
|
@@ -162,6 +185,9 @@ function TagComponent(props) {
|
|
|
162
185
|
<Editor
|
|
163
186
|
editorType={EDITOR_TYPE__WINDOWED}
|
|
164
187
|
{...propsToPass}
|
|
188
|
+
px={0}
|
|
189
|
+
py={0}
|
|
190
|
+
w="100%"
|
|
165
191
|
parent={self}
|
|
166
192
|
reference="viewer"
|
|
167
193
|
|
|
@@ -29,7 +29,7 @@ export default function FieldSet(props) {
|
|
|
29
29
|
forceUpdate = useForceUpdate(),
|
|
30
30
|
childRefs = useRef([]),
|
|
31
31
|
isAllCheckedRef = useRef(false),
|
|
32
|
-
[
|
|
32
|
+
[isLocalCollapsed, setIsLocalCollapsed] = useState(isCollapsed),
|
|
33
33
|
getIsAllChecked = () => {
|
|
34
34
|
return isAllCheckedRef.current;
|
|
35
35
|
},
|
|
@@ -38,7 +38,7 @@ export default function FieldSet(props) {
|
|
|
38
38
|
forceUpdate();
|
|
39
39
|
},
|
|
40
40
|
onToggleCollapse = () => {
|
|
41
|
-
|
|
41
|
+
setIsLocalCollapsed(!isLocalCollapsed);
|
|
42
42
|
},
|
|
43
43
|
onToggleAllChecked = () => {
|
|
44
44
|
const bool = !getIsAllChecked();
|
|
@@ -82,6 +82,7 @@ export default function FieldSet(props) {
|
|
|
82
82
|
bg={styles.FORM_FIELDSET_BG}
|
|
83
83
|
mb={4}
|
|
84
84
|
pb={1}
|
|
85
|
+
pr={4}
|
|
85
86
|
{...propsToPass}
|
|
86
87
|
>
|
|
87
88
|
{title &&
|
|
@@ -119,7 +120,7 @@ export default function FieldSet(props) {
|
|
|
119
120
|
</Row>}
|
|
120
121
|
{isCollapsible && <IconButton
|
|
121
122
|
_icon={{
|
|
122
|
-
as:
|
|
123
|
+
as: isLocalCollapsed ? <CaretDown /> : <CaretUp />,
|
|
123
124
|
size: 'sm',
|
|
124
125
|
color: 'trueGray.300',
|
|
125
126
|
}}
|
|
@@ -127,7 +128,7 @@ export default function FieldSet(props) {
|
|
|
127
128
|
/>}
|
|
128
129
|
</Row>}
|
|
129
130
|
{helpText && <Text>{helpText}</Text>}
|
|
130
|
-
{!
|
|
131
|
+
{!isLocalCollapsed && <FieldSetContext.Provider value={{ registerChild, onChangeValue, }}>
|
|
131
132
|
{children}
|
|
132
133
|
</FieldSetContext.Provider>}
|
|
133
134
|
</Box>;
|
|
@@ -439,6 +439,9 @@ function Form(props) {
|
|
|
439
439
|
name={name}
|
|
440
440
|
value={value}
|
|
441
441
|
onChangeValue={(newValue) => {
|
|
442
|
+
if (newValue === undefined) {
|
|
443
|
+
newValue = null; // React Hook Form doesn't respond well when setting value to undefined
|
|
444
|
+
}
|
|
442
445
|
onChange(newValue);
|
|
443
446
|
if (onEditorChange) {
|
|
444
447
|
onEditorChange(newValue, formSetValue, formGetValues, formState);
|
|
@@ -452,21 +455,21 @@ function Form(props) {
|
|
|
452
455
|
{...propsToPass}
|
|
453
456
|
{...editorTypeProps}
|
|
454
457
|
/>;
|
|
455
|
-
if (
|
|
456
|
-
|
|
457
|
-
|
|
458
|
+
if (editorType !== EDITOR_TYPE__INLINE) {
|
|
459
|
+
let message = null;
|
|
460
|
+
if (error) {
|
|
461
|
+
message = error.message;
|
|
458
462
|
if (label) {
|
|
459
463
|
message = message.replace(error.ref.name, label);
|
|
460
464
|
}
|
|
461
|
-
element = <Column pt={1} flex={1}>
|
|
462
|
-
{element}
|
|
463
|
-
<Text color="#f00">{message}</Text>
|
|
464
|
-
</Column>;
|
|
465
|
-
} else {
|
|
466
|
-
debugger;
|
|
467
|
-
|
|
468
|
-
|
|
469
465
|
}
|
|
466
|
+
if (message) {
|
|
467
|
+
message = <Text color="#f00">{message}</Text>;
|
|
468
|
+
}
|
|
469
|
+
element = <Column pt={1} flex={1}>
|
|
470
|
+
{element}
|
|
471
|
+
{message}
|
|
472
|
+
</Column>;
|
|
470
473
|
}
|
|
471
474
|
|
|
472
475
|
if (item.additionalEditButtons) {
|
|
@@ -573,17 +576,24 @@ function Form(props) {
|
|
|
573
576
|
|
|
574
577
|
useEffect(() => {
|
|
575
578
|
if (!Repository) {
|
|
576
|
-
return () => {
|
|
579
|
+
return () => {
|
|
580
|
+
if (!_.isNil(editorStateRef)) {
|
|
581
|
+
editorStateRef.current = null; // clean up the editorStateRef on unmount
|
|
582
|
+
}
|
|
583
|
+
};
|
|
577
584
|
}
|
|
578
585
|
|
|
579
586
|
Repository.ons(['changeData', 'change'], forceUpdate);
|
|
580
587
|
|
|
581
588
|
return () => {
|
|
582
589
|
Repository.offs(['changeData', 'change'], forceUpdate);
|
|
590
|
+
if (!_.isNil(editorStateRef)) {
|
|
591
|
+
editorStateRef.current = null; // clean up the editorStateRef on unmount
|
|
592
|
+
}
|
|
583
593
|
};
|
|
584
594
|
}, [Repository]);
|
|
585
595
|
|
|
586
|
-
// if (Repository && (!record || _.isEmpty(record))) {
|
|
596
|
+
// if (Repository && (!record || _.isEmpty(record) || record.isDestroyed)) {
|
|
587
597
|
// return null;
|
|
588
598
|
// }
|
|
589
599
|
|
|
@@ -622,7 +632,14 @@ function Form(props) {
|
|
|
622
632
|
additionalButtons,
|
|
623
633
|
isSaveDisabled = false,
|
|
624
634
|
isSubmitDisabled = false,
|
|
625
|
-
savingProps = {}
|
|
635
|
+
savingProps = {},
|
|
636
|
+
|
|
637
|
+
showDeleteBtn = false,
|
|
638
|
+
showResetBtn = false,
|
|
639
|
+
showCloseBtn = false,
|
|
640
|
+
showCancelBtn = false,
|
|
641
|
+
showSaveBtn = false,
|
|
642
|
+
showSubmitBtn = false;
|
|
626
643
|
|
|
627
644
|
if (containerWidth) { // we need to render this component twice in order to get the container width. Skip this on first render
|
|
628
645
|
|
|
@@ -678,7 +695,7 @@ function Form(props) {
|
|
|
678
695
|
isSaveDisabled = true;
|
|
679
696
|
isSubmitDisabled = true;
|
|
680
697
|
}
|
|
681
|
-
if (_.isEmpty(formState.dirtyFields) && !record?.
|
|
698
|
+
if (_.isEmpty(formState.dirtyFields) && !record?.isPhantom) {
|
|
682
699
|
isSaveDisabled = true;
|
|
683
700
|
}
|
|
684
701
|
|
|
@@ -688,6 +705,34 @@ function Form(props) {
|
|
|
688
705
|
footerProps.alignItems = 'flex-start';
|
|
689
706
|
}
|
|
690
707
|
|
|
708
|
+
if (onDelete && editorMode === EDITOR_MODE__EDIT && isSingle) {
|
|
709
|
+
showDeleteBtn = true;
|
|
710
|
+
}
|
|
711
|
+
if (!isEditorViewOnly) {
|
|
712
|
+
showResetBtn = true;
|
|
713
|
+
}
|
|
714
|
+
if (editorType !== EDITOR_TYPE__SIDE) { // side editor won't show either close or cancel buttons!
|
|
715
|
+
// determine whether we should show the close or cancel button
|
|
716
|
+
if (isEditorViewOnly) {
|
|
717
|
+
showCloseBtn = true;
|
|
718
|
+
} else {
|
|
719
|
+
if (formState.isDirty || record?.isPhantom) {
|
|
720
|
+
if (isSingle && onCancel) {
|
|
721
|
+
showCancelBtn = true;
|
|
722
|
+
}
|
|
723
|
+
} else {
|
|
724
|
+
if (onClose) {
|
|
725
|
+
showCloseBtn = true;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
if (!isEditorViewOnly && onSave) {
|
|
731
|
+
showSaveBtn = true;
|
|
732
|
+
}
|
|
733
|
+
if (!!onSubmit) {
|
|
734
|
+
showSubmitBtn = true;
|
|
735
|
+
}
|
|
691
736
|
}
|
|
692
737
|
|
|
693
738
|
return <Column {...sizeProps} onLayout={onLayoutDecorated} ref={formRef}>
|
|
@@ -712,6 +757,7 @@ function Form(props) {
|
|
|
712
757
|
|
|
713
758
|
<Footer justifyContent="flex-end" {...footerProps} {...savingProps}>
|
|
714
759
|
{onDelete && editorMode === EDITOR_MODE__EDIT && isSingle &&
|
|
760
|
+
|
|
715
761
|
<Row flex={1} justifyContent="flex-start">
|
|
716
762
|
<Button
|
|
717
763
|
key="deleteBtn"
|
|
@@ -724,7 +770,7 @@ function Form(props) {
|
|
|
724
770
|
>Delete</Button>
|
|
725
771
|
</Row>}
|
|
726
772
|
|
|
727
|
-
{
|
|
773
|
+
{showResetBtn &&
|
|
728
774
|
<IconButton
|
|
729
775
|
key="resetBtn"
|
|
730
776
|
onPress={() => {
|
|
@@ -735,34 +781,38 @@ function Form(props) {
|
|
|
735
781
|
}}
|
|
736
782
|
icon={<Rotate color="#fff" />}
|
|
737
783
|
/>}
|
|
738
|
-
|
|
784
|
+
|
|
785
|
+
{showCancelBtn &&
|
|
739
786
|
<Button
|
|
740
787
|
key="cancelBtn"
|
|
741
788
|
variant="ghost"
|
|
742
789
|
onPress={onCancel}
|
|
743
790
|
color="#fff"
|
|
744
791
|
>Cancel</Button>}
|
|
745
|
-
|
|
792
|
+
|
|
793
|
+
{showCloseBtn &&
|
|
794
|
+
<Button
|
|
795
|
+
key="closeBtn"
|
|
796
|
+
variant="ghost"
|
|
797
|
+
onPress={onClose}
|
|
798
|
+
color="#fff"
|
|
799
|
+
>Close</Button>}
|
|
800
|
+
|
|
801
|
+
{showSaveBtn &&
|
|
746
802
|
<Button
|
|
747
803
|
key="saveBtn"
|
|
748
804
|
onPress={(e) => handleSubmit(onSaveDecorated, onSubmitError)(e)}
|
|
749
805
|
isDisabled={isSaveDisabled}
|
|
750
806
|
color="#fff"
|
|
751
807
|
>{editorMode === EDITOR_MODE__ADD ? 'Add' : 'Save'}</Button>}
|
|
752
|
-
|
|
808
|
+
|
|
809
|
+
{showSubmitBtn &&
|
|
753
810
|
<Button
|
|
754
811
|
key="submitBtn"
|
|
755
812
|
onPress={(e) => handleSubmit(onSubmitDecorated, onSubmitError)(e)}
|
|
756
813
|
isDisabled={isSubmitDisabled}
|
|
757
814
|
color="#fff"
|
|
758
815
|
>{submitBtnLabel || 'Submit'}</Button>}
|
|
759
|
-
|
|
760
|
-
{isEditorViewOnly && onClose && editorType !== EDITOR_TYPE__SIDE &&
|
|
761
|
-
<Button
|
|
762
|
-
key="closeBtn"
|
|
763
|
-
onPress={onClose}
|
|
764
|
-
color="#fff"
|
|
765
|
-
>Close</Button>}
|
|
766
816
|
|
|
767
817
|
{additionalFooterButtons && _.map(additionalFooterButtons, (props) => {
|
|
768
818
|
return <Button
|
|
@@ -35,6 +35,8 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
35
35
|
},
|
|
36
36
|
record,
|
|
37
37
|
onChange,
|
|
38
|
+
onSave,
|
|
39
|
+
newEntityDisplayValue,
|
|
38
40
|
|
|
39
41
|
// withComponent
|
|
40
42
|
self,
|
|
@@ -57,6 +59,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
57
59
|
} = props,
|
|
58
60
|
listeners = useRef({}),
|
|
59
61
|
editorStateRef = useRef(),
|
|
62
|
+
newEntityDisplayValueRef = useRef(),
|
|
60
63
|
[currentRecord, setCurrentRecord] = useState(null),
|
|
61
64
|
[isAdding, setIsAdding] = useState(false),
|
|
62
65
|
[isSaving, setIsSaving] = useState(false),
|
|
@@ -81,6 +84,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
81
84
|
listeners.current = obj;
|
|
82
85
|
// forceUpdate(); // we don't want to get into an infinite loop of renders. Simply directly assign the listeners in every child render
|
|
83
86
|
},
|
|
87
|
+
getNewEntityDisplayValue = () => {
|
|
88
|
+
return newEntityDisplayValueRef.current;
|
|
89
|
+
},
|
|
84
90
|
onAdd = async () => {
|
|
85
91
|
const defaultValues = Repository.getSchema().getDefaultValues();
|
|
86
92
|
let addValues = _.clone(defaultValues);
|
|
@@ -89,6 +95,11 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
89
95
|
addValues[selectorId] = selectorSelected.id;
|
|
90
96
|
}
|
|
91
97
|
|
|
98
|
+
if (getNewEntityDisplayValue()) {
|
|
99
|
+
const displayPropertyName = Repository.getSchema().model.displayProperty;
|
|
100
|
+
addValues[displayPropertyName] = getNewEntityDisplayValue();
|
|
101
|
+
}
|
|
102
|
+
|
|
92
103
|
if (getListeners().onBeforeAdd) {
|
|
93
104
|
const listenerResult = await getListeners().onBeforeAdd();
|
|
94
105
|
if (listenerResult === false) {
|
|
@@ -116,6 +127,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
116
127
|
// Unmap the values, so we can input true originalData
|
|
117
128
|
addValues = Repository.unmapData(addValues);
|
|
118
129
|
|
|
130
|
+
|
|
119
131
|
setIsAdding(true);
|
|
120
132
|
setIsSaving(true);
|
|
121
133
|
const entity = await Repository.add(addValues, false, true);
|
|
@@ -260,6 +272,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
260
272
|
entity = selection[0],
|
|
261
273
|
id = entity.id;
|
|
262
274
|
const result = await Repository._send('POST', Model + '/duplicate', { id });
|
|
275
|
+
if (!result) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
263
278
|
const {
|
|
264
279
|
root,
|
|
265
280
|
success,
|
|
@@ -273,15 +288,22 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
273
288
|
|
|
274
289
|
const duplicateId = root.id;
|
|
275
290
|
|
|
291
|
+
// TODO: I don't like this.
|
|
292
|
+
// Currently, we filter the repository by only the new Entity, then select the entity for editing.
|
|
293
|
+
// There is a 2-second delay between filtering and being able to select, and this is unacceptable.
|
|
294
|
+
// Why do we filter for just the new entity? Because it's not guaranteed to show up in the grid based on sorting.
|
|
295
|
+
// Can't we just manually add this record to the repository at the top and then edit it?
|
|
296
|
+
|
|
276
297
|
// Filter the grid with only the duplicate's ID, and open it for editing.
|
|
277
298
|
self.filterById(duplicateId, () => { // because of the way useFilters is made, we have to use a callback, not await a Promise.
|
|
278
299
|
|
|
279
300
|
// Select the only node
|
|
280
301
|
const duplicateEntity = Repository.getById(duplicateId);
|
|
281
|
-
|
|
302
|
+
setTimeout(() => {
|
|
303
|
+
setSelection([duplicateEntity]);
|
|
282
304
|
|
|
283
|
-
|
|
284
|
-
|
|
305
|
+
onEdit();
|
|
306
|
+
}, 2000); // we need this delay!
|
|
285
307
|
|
|
286
308
|
});
|
|
287
309
|
|
|
@@ -326,6 +348,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
326
348
|
if (onChange) {
|
|
327
349
|
onChange();
|
|
328
350
|
}
|
|
351
|
+
if (onSave) {
|
|
352
|
+
onSave(what);
|
|
353
|
+
}
|
|
329
354
|
|
|
330
355
|
return true;
|
|
331
356
|
},
|
|
@@ -339,7 +364,6 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
339
364
|
}
|
|
340
365
|
|
|
341
366
|
setIsAdding(false);
|
|
342
|
-
setEditorMode(EDITOR_MODE__VIEW);
|
|
343
367
|
setIsEditorShown(false);
|
|
344
368
|
}
|
|
345
369
|
const formState = editorStateRef.current;
|
|
@@ -350,6 +374,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
350
374
|
}
|
|
351
375
|
},
|
|
352
376
|
onEditorClose = () => {
|
|
377
|
+
if (isAdding) {
|
|
378
|
+
onEditorCancel();
|
|
379
|
+
}
|
|
353
380
|
setIsEditorShown(false);
|
|
354
381
|
},
|
|
355
382
|
onEditorDelete = async () => {
|
|
@@ -406,6 +433,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
406
433
|
self.deleteChildren = onDeleteChildren;
|
|
407
434
|
self.duplicate = onDuplicate;
|
|
408
435
|
}
|
|
436
|
+
newEntityDisplayValueRef.current = newEntityDisplayValue;
|
|
409
437
|
|
|
410
438
|
if (lastSelection !== selection) {
|
|
411
439
|
// NOTE: If I don't calculate this on the fly for selection changes,
|
|
@@ -228,6 +228,9 @@ export default function withSelection(WrappedComponent) {
|
|
|
228
228
|
conformSelectionToValue = async () => {
|
|
229
229
|
let newSelection = [];
|
|
230
230
|
if (Repository) {
|
|
231
|
+
if (Repository.isLoading) {
|
|
232
|
+
await Repository.waitUntilDoneLoading();
|
|
233
|
+
}
|
|
231
234
|
// Get entity or entities that match value
|
|
232
235
|
if ((_.isArray(value) && !_.isEmpty(value)) || !!value) {
|
|
233
236
|
if (_.isArray(value)) {
|
|
@@ -236,16 +239,16 @@ export default function withSelection(WrappedComponent) {
|
|
|
236
239
|
let found = Repository.getById(value);
|
|
237
240
|
if (found) {
|
|
238
241
|
newSelection.push(found);
|
|
239
|
-
} else if (Repository?.isRemote && Repository?.entities.length) {
|
|
242
|
+
// } else if (Repository?.isRemote && Repository?.entities.length) {
|
|
240
243
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
244
|
+
// // Value cannot be found in Repository, but actually exists on server
|
|
245
|
+
// // Try to get this value from the server directly
|
|
246
|
+
// Repository.filter(Repository.schema.model.idProperty, value);
|
|
247
|
+
// await Repository.load();
|
|
248
|
+
// found = Repository.getById(value);
|
|
249
|
+
// if (found) {
|
|
250
|
+
// newSelection.push(found);
|
|
251
|
+
// }
|
|
249
252
|
|
|
250
253
|
}
|
|
251
254
|
}
|
|
@@ -278,9 +281,6 @@ export default function withSelection(WrappedComponent) {
|
|
|
278
281
|
};
|
|
279
282
|
|
|
280
283
|
useEffect(() => {
|
|
281
|
-
if (isReady) {
|
|
282
|
-
return () => {};
|
|
283
|
-
}
|
|
284
284
|
|
|
285
285
|
(async () => {
|
|
286
286
|
|
|
@@ -291,7 +291,7 @@ export default function withSelection(WrappedComponent) {
|
|
|
291
291
|
await Repository.load();
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
if (
|
|
294
|
+
if (!_.isNil(value)) {
|
|
295
295
|
|
|
296
296
|
await conformSelectionToValue();
|
|
297
297
|
|
|
@@ -314,7 +314,7 @@ export default function withSelection(WrappedComponent) {
|
|
|
314
314
|
|
|
315
315
|
})();
|
|
316
316
|
|
|
317
|
-
}, []);
|
|
317
|
+
}, [value]);
|
|
318
318
|
|
|
319
319
|
if (self) {
|
|
320
320
|
self.selection = localSelection;
|
|
@@ -69,6 +69,18 @@ export default function Pagination(props) {
|
|
|
69
69
|
isDisabled={isDisabled}
|
|
70
70
|
tooltip="Show More"
|
|
71
71
|
>Show More</Button>);
|
|
72
|
+
if (!Repository.isLocal) {
|
|
73
|
+
items.push(<IconButton
|
|
74
|
+
key="reload"
|
|
75
|
+
parent={self}
|
|
76
|
+
reference="reloadPageBtn"
|
|
77
|
+
{...iconButtonProps}
|
|
78
|
+
icon={<Icon as={Rotate} {...iconProps} color="trueGray.600" />}
|
|
79
|
+
onPress={() => Repository.reload()}
|
|
80
|
+
tooltip="Reload"
|
|
81
|
+
ml={2}
|
|
82
|
+
/>);
|
|
83
|
+
}
|
|
72
84
|
} else {
|
|
73
85
|
isDisabled = page === 1;
|
|
74
86
|
items.push(<IconButton
|
package/src/Components/index.js
CHANGED
|
@@ -41,7 +41,6 @@ import RadioGroup from './Form/Field/RadioGroup/RadioGroup.js';
|
|
|
41
41
|
import SquareButton from './Buttons/SquareButton.js';
|
|
42
42
|
import TabPanel from './Panel/TabPanel.js';
|
|
43
43
|
import Tag from './Form/Field/Tag/Tag.js';
|
|
44
|
-
import TagViewer from './Viewer/TagViewer.js';
|
|
45
44
|
import TextArea from './Form/Field/TextArea.js';
|
|
46
45
|
import Text from './Form/Field/Text.js';
|
|
47
46
|
import TimezonesCombo from './Form/Field/Combo/TimezonesCombo.js';
|
|
@@ -94,7 +93,6 @@ const components = {
|
|
|
94
93
|
SquareButton,
|
|
95
94
|
TabPanel,
|
|
96
95
|
Tag,
|
|
97
|
-
TagViewer,
|
|
98
96
|
Text,
|
|
99
97
|
TextArea,
|
|
100
98
|
TimezonesCombo,
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Text,
|
|
3
|
-
} from 'native-base';
|
|
4
|
-
import UiGlobals from '../../UiGlobals.js';
|
|
5
|
-
import withComponent from '../Hoc/withComponent.js';
|
|
6
|
-
import _ from 'lodash';
|
|
7
|
-
|
|
8
|
-
function TagViewer(props) {
|
|
9
|
-
const {
|
|
10
|
-
value,
|
|
11
|
-
} = props,
|
|
12
|
-
parsedValue = value ? JSON.parse(value) : null,
|
|
13
|
-
values = parsedValue ? _.map(parsedValue, (val) => {
|
|
14
|
-
const ret = val?.text;
|
|
15
|
-
return ret;
|
|
16
|
-
}).join(', ') : [],
|
|
17
|
-
styles = UiGlobals.styles;
|
|
18
|
-
|
|
19
|
-
return <Text
|
|
20
|
-
numberOfLines={1}
|
|
21
|
-
ellipsizeMode="head"
|
|
22
|
-
fontSize={styles.FORM_TEXT_FONTSIZE}
|
|
23
|
-
minHeight='40px'
|
|
24
|
-
px={3}
|
|
25
|
-
py={2}
|
|
26
|
-
{...props}
|
|
27
|
-
>{values}</Text>;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export default withComponent(TagViewer);
|