@onehat/ui 0.4.82 → 0.4.83
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 +7 -6
- package/src/Components/Form/Field/Combo/Combo.js +84 -15
- package/src/Components/Form/Field/Json.js +2 -1
- package/src/Components/Form/Field/Tag/Tag.js +93 -75
- package/src/Components/Form/Field/Tag/ValueBox.js +2 -2
- package/src/Components/Form/Form.js +1 -0
- package/src/Components/Grid/Grid.js +4 -0
- package/src/Components/Grid/GridRow.js +3 -0
- package/src/Components/Hoc/Secondary/withSecondaryEditor.js +137 -43
- package/src/Components/Hoc/Secondary/withSecondarySideEditor.js +1 -1
- package/src/Components/Toolbar/Pagination.js +108 -106
- package/src/Components/Tree/TreeNode.js +2 -1
- package/src/Components/Viewer/Viewer.js +2 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onehat/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.83",
|
|
4
4
|
"description": "Base UI for OneHat apps",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -58,17 +58,18 @@
|
|
|
58
58
|
"@k-renwick/colour-mixer": "^1.2.1",
|
|
59
59
|
"@legendapp/motion": "^2.4.0",
|
|
60
60
|
"@onehat/data": "^1.22.0",
|
|
61
|
-
"clsx": "^2.1.1",
|
|
62
|
-
"nativewind": "4.1.23",
|
|
63
|
-
"normalize-css-color": "^1.0.2",
|
|
64
61
|
"@react-native-community/slider": "^4.5.2",
|
|
65
62
|
"@reduxjs/toolkit": "^2.6.1",
|
|
63
|
+
"clsx": "^2.1.1",
|
|
66
64
|
"decimal.js": "^10.5.0",
|
|
67
65
|
"inflector-js": "^1.0.1",
|
|
68
66
|
"js-cookie": "^3.0.5",
|
|
67
|
+
"nativewind": "4.1.23",
|
|
68
|
+
"normalize-css-color": "^1.0.2",
|
|
69
69
|
"react-hook-form": "^7.55.0",
|
|
70
70
|
"react-native-progress": "^5.0.1",
|
|
71
71
|
"react-redux": "^9.2.0",
|
|
72
|
+
"tailwind-scrollbar": "^3.1.0",
|
|
72
73
|
"tailwindcss": "^3.4.17",
|
|
73
74
|
"yup": "^1.6.1"
|
|
74
75
|
},
|
|
@@ -79,10 +80,10 @@
|
|
|
79
80
|
"react": "*",
|
|
80
81
|
"react-color": "^2.19.3",
|
|
81
82
|
"react-datetime": "^3.2.0",
|
|
82
|
-
"react-dom": "*",
|
|
83
83
|
"react-dnd": "^16.0.1",
|
|
84
84
|
"react-dnd-html5-backend": "^16.0.1",
|
|
85
|
-
"react-dnd-touch-backend":"16.0.1",
|
|
85
|
+
"react-dnd-touch-backend": "16.0.1",
|
|
86
|
+
"react-dom": "*",
|
|
86
87
|
"react-draggable": "^4.4.5",
|
|
87
88
|
"react-native": "*",
|
|
88
89
|
"react-native-draggable": "^3.3.0",
|
|
@@ -24,6 +24,7 @@ import testProps from '../../../../Functions/testProps.js';
|
|
|
24
24
|
import UiGlobals from '../../../../UiGlobals.js';
|
|
25
25
|
import Input from '../Input.js';
|
|
26
26
|
import { Grid, WindowedGridEditor } from '../../../Grid/Grid.js';
|
|
27
|
+
import useForceUpdate from '../../../../Hooks/useForceUpdate.js';
|
|
27
28
|
import withAlert from '../../../Hoc/withAlert.js';
|
|
28
29
|
import withComponent from '../../../Hoc/withComponent.js';
|
|
29
30
|
import withData from '../../../Hoc/withData.js';
|
|
@@ -121,13 +122,16 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
121
122
|
setValue,
|
|
122
123
|
} = props,
|
|
123
124
|
styles = UiGlobals.styles,
|
|
125
|
+
forceUpdate = useForceUpdate(),
|
|
124
126
|
inputRef = useRef(),
|
|
125
127
|
inputCloneRef = useRef(),
|
|
126
128
|
triggerRef = useRef(),
|
|
127
129
|
menuRef = useRef(),
|
|
128
130
|
displayValueRef = useRef(),
|
|
129
131
|
typingTimeout = useRef(),
|
|
130
|
-
|
|
132
|
+
isMenuShown = useRef(false),
|
|
133
|
+
isGridLayoutRunWithRender = useRef(false),
|
|
134
|
+
[isMenuAbove, setIsMenuAbove] = useState(false),
|
|
131
135
|
[isViewerShown, setIsViewerShown] = useState(false),
|
|
132
136
|
[viewerSelection, setViewerSelection] = useState([]),
|
|
133
137
|
[isRendered, setIsRendered] = useState(false),
|
|
@@ -140,15 +144,39 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
140
144
|
[newEntityDisplayValue, setNewEntityDisplayValue] = useState(null),
|
|
141
145
|
[filteredData, setFilteredData] = useState(data),
|
|
142
146
|
[inputHeight, setInputHeight] = useState(0),
|
|
147
|
+
[menuRenderedHeight, setMenuRenderedHeight] = useState(0),
|
|
143
148
|
[width, setWidth] = useState(0),
|
|
144
149
|
[top, setTop] = useState(0),
|
|
145
150
|
[left, setLeft] = useState(0),
|
|
151
|
+
getIsMenuShown = () => {
|
|
152
|
+
return isMenuShown.current;
|
|
153
|
+
},
|
|
154
|
+
setIsMenuShown = (bool) => {
|
|
155
|
+
isMenuShown.current = bool;
|
|
156
|
+
|
|
157
|
+
if (!bool) {
|
|
158
|
+
// The menu's onLayout runs every time there's a change in its size or position.
|
|
159
|
+
// We're only interested in the *first* time it runs with a rendered height.
|
|
160
|
+
// So if hiding the menu, reset isGridLayoutRunWithRender here and we'll set it to true
|
|
161
|
+
// the first time onLayout runs with a height.
|
|
162
|
+
setIsGridLayoutRunWithRender(false);
|
|
163
|
+
setIsMenuAbove(false); // reset this so the next time the menu opens, it starts below the input
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
forceUpdate();
|
|
167
|
+
},
|
|
168
|
+
getIsGridLayoutRunWithRender = () => {
|
|
169
|
+
return isGridLayoutRunWithRender.current;
|
|
170
|
+
},
|
|
171
|
+
setIsGridLayoutRunWithRender = (bool) => {
|
|
172
|
+
isGridLayoutRunWithRender.current = bool;
|
|
173
|
+
},
|
|
146
174
|
onLayout = (e) => {
|
|
147
175
|
setIsRendered(true);
|
|
148
176
|
setContainerWidth(e.nativeEvent.layout.width);
|
|
149
177
|
},
|
|
150
178
|
showMenu = async () => {
|
|
151
|
-
if (
|
|
179
|
+
if (getIsMenuShown()) {
|
|
152
180
|
return;
|
|
153
181
|
}
|
|
154
182
|
if (CURRENT_MODE === UI_MODE_WEB && inputRef.current?.getBoundingClientRect) {
|
|
@@ -181,13 +209,13 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
181
209
|
setIsMenuShown(true);
|
|
182
210
|
},
|
|
183
211
|
hideMenu = () => {
|
|
184
|
-
if (!
|
|
212
|
+
if (!getIsMenuShown()) {
|
|
185
213
|
return;
|
|
186
214
|
}
|
|
187
215
|
setIsMenuShown(false);
|
|
188
216
|
},
|
|
189
217
|
toggleMenu = () => {
|
|
190
|
-
setIsMenuShown(!
|
|
218
|
+
setIsMenuShown(!getIsMenuShown());
|
|
191
219
|
},
|
|
192
220
|
temporarilySetIsNavigatingViaKeyboard = () => {
|
|
193
221
|
setIsNavigatingViaKeyboard(true);
|
|
@@ -359,14 +387,14 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
359
387
|
if (reloadOnTrigger && Repository) {
|
|
360
388
|
await Repository.reload();
|
|
361
389
|
}
|
|
362
|
-
if (
|
|
390
|
+
if (getIsMenuShown()) {
|
|
363
391
|
hideMenu();
|
|
364
392
|
} else {
|
|
365
393
|
showMenu();
|
|
366
394
|
}
|
|
367
395
|
},
|
|
368
396
|
onTriggerBlur = (e) => {
|
|
369
|
-
if (!
|
|
397
|
+
if (!getIsMenuShown()) {
|
|
370
398
|
return;
|
|
371
399
|
}
|
|
372
400
|
|
|
@@ -409,6 +437,33 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
409
437
|
onCheckButtonPress = () => {
|
|
410
438
|
hideMenu();
|
|
411
439
|
},
|
|
440
|
+
onGridLayout = (e) => {
|
|
441
|
+
// This method is to determine if we need to flip the grid above the input
|
|
442
|
+
// because the menu is partially offscreen
|
|
443
|
+
|
|
444
|
+
if (CURRENT_MODE !== UI_MODE_WEB || !e.nativeEvent.layout.height) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// we reach this point only if the grid has rendered with a height.
|
|
449
|
+
|
|
450
|
+
if (!getIsGridLayoutRunWithRender()) {
|
|
451
|
+
// we reach this point only on the *first* time onGridLayout runs with a height.
|
|
452
|
+
// determine if the menu is partially offscreen
|
|
453
|
+
const
|
|
454
|
+
menuRect = menuRef.current.getBoundingClientRect(),
|
|
455
|
+
inputRect = inputRef.current.getBoundingClientRect(),
|
|
456
|
+
menuOverflows = menuRect.bottom > window.innerHeight;
|
|
457
|
+
if (menuOverflows) {
|
|
458
|
+
// flip it
|
|
459
|
+
setIsMenuAbove(true);
|
|
460
|
+
} else {
|
|
461
|
+
setIsMenuAbove(false);
|
|
462
|
+
}
|
|
463
|
+
setMenuRenderedHeight(e.nativeEvent.layout.height);
|
|
464
|
+
setIsGridLayoutRunWithRender(true);
|
|
465
|
+
}
|
|
466
|
+
},
|
|
412
467
|
isEventStillInComponent = (e) => {
|
|
413
468
|
const {
|
|
414
469
|
relatedTarget
|
|
@@ -518,7 +573,7 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
518
573
|
setFilteredData(found);
|
|
519
574
|
}
|
|
520
575
|
|
|
521
|
-
if (!
|
|
576
|
+
if (!getIsMenuShown()) {
|
|
522
577
|
showMenu();
|
|
523
578
|
}
|
|
524
579
|
setIsSearchMode(true);
|
|
@@ -734,7 +789,7 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
734
789
|
</Pressable>;
|
|
735
790
|
}
|
|
736
791
|
|
|
737
|
-
if (
|
|
792
|
+
if (getIsMenuShown()) {
|
|
738
793
|
const gridProps = _.pick(props, [
|
|
739
794
|
'Editor',
|
|
740
795
|
'model',
|
|
@@ -743,6 +798,9 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
743
798
|
'idIx',
|
|
744
799
|
'displayIx',
|
|
745
800
|
// 'value',
|
|
801
|
+
'disableAdd',
|
|
802
|
+
'disableEdit',
|
|
803
|
+
'disableDelete',
|
|
746
804
|
'disableView',
|
|
747
805
|
'disableCopy',
|
|
748
806
|
'disableDuplicate',
|
|
@@ -761,8 +819,9 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
761
819
|
if (!Repository) {
|
|
762
820
|
gridProps.data = filteredData;
|
|
763
821
|
}
|
|
764
|
-
const
|
|
765
|
-
|
|
822
|
+
const
|
|
823
|
+
WhichGrid = isEditor ? WindowedGridEditor : Grid,
|
|
824
|
+
gridStyle = {};
|
|
766
825
|
if (CURRENT_MODE === UI_MODE_WEB) {
|
|
767
826
|
gridStyle.height = menuHeight || styles.FORM_COMBO_MENU_HEIGHT;
|
|
768
827
|
}
|
|
@@ -786,6 +845,7 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
786
845
|
disablePresetButtons={!isEditor}
|
|
787
846
|
alternateRowBackgrounds={false}
|
|
788
847
|
showSelectHandle={false}
|
|
848
|
+
onLayout={onGridLayout}
|
|
789
849
|
onChangeSelection={(selection) => {
|
|
790
850
|
|
|
791
851
|
if (Repository && selection[0]?.isPhantom) {
|
|
@@ -938,7 +998,7 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
938
998
|
</Box>;
|
|
939
999
|
}
|
|
940
1000
|
dropdownMenu = <Popover
|
|
941
|
-
isOpen={
|
|
1001
|
+
isOpen={getIsMenuShown()}
|
|
942
1002
|
onClose={() => {
|
|
943
1003
|
hideMenu();
|
|
944
1004
|
}}
|
|
@@ -962,15 +1022,24 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
962
1022
|
'max-w-full',
|
|
963
1023
|
)}
|
|
964
1024
|
style={{
|
|
965
|
-
|
|
1025
|
+
// If flipped, position above input; otherwise, below
|
|
1026
|
+
top: isMenuAbove
|
|
1027
|
+
? (top - menuRenderedHeight) // above
|
|
1028
|
+
: top, // below
|
|
966
1029
|
left,
|
|
967
1030
|
width,
|
|
968
|
-
// height: (menuHeight || styles.FORM_COMBO_MENU_HEIGHT) + inputHeight,
|
|
969
1031
|
minWidth: 100,
|
|
970
1032
|
}}
|
|
971
1033
|
>
|
|
972
|
-
{
|
|
973
|
-
|
|
1034
|
+
{isMenuAbove ?
|
|
1035
|
+
<>
|
|
1036
|
+
{grid}
|
|
1037
|
+
{inputClone}
|
|
1038
|
+
</> :
|
|
1039
|
+
<>
|
|
1040
|
+
{inputClone}
|
|
1041
|
+
{grid}
|
|
1042
|
+
</>}
|
|
974
1043
|
</Box>
|
|
975
1044
|
</Popover>;
|
|
976
1045
|
}
|
|
@@ -21,6 +21,7 @@ export function JsonElement(props) {
|
|
|
21
21
|
tooltip = null,
|
|
22
22
|
isDisabled = false,
|
|
23
23
|
isViewOnly = false,
|
|
24
|
+
isCollapsed = true,
|
|
24
25
|
tooltipPlacement = 'bottom',
|
|
25
26
|
testID,
|
|
26
27
|
|
|
@@ -59,7 +60,7 @@ export function JsonElement(props) {
|
|
|
59
60
|
editable={!isViewOnly}
|
|
60
61
|
src={src}
|
|
61
62
|
enableClipboard={false}
|
|
62
|
-
collapsed={
|
|
63
|
+
collapsed={isCollapsed}
|
|
63
64
|
onEdit={(obj) => {
|
|
64
65
|
setValue(JSON.stringify(obj.updated_src));
|
|
65
66
|
}}
|
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
EDITOR_TYPE__PLAIN,
|
|
14
14
|
} from '../../../../Constants/Editor.js';
|
|
15
|
+
import Button from '../../../Buttons/Button.js';
|
|
16
|
+
import testProps from '../../../../Functions/testProps.js';
|
|
15
17
|
import Form from '../../Form.js';
|
|
16
18
|
import Viewer from '../../../Viewer/Viewer.js';
|
|
17
19
|
import withAlert from '../../../Hoc/withAlert.js';
|
|
@@ -36,11 +38,13 @@ function TagComponent(props) {
|
|
|
36
38
|
Editor,
|
|
37
39
|
_combo = {},
|
|
38
40
|
SourceRepository,
|
|
41
|
+
mustSaveBeforeEditingJoinData = false,
|
|
39
42
|
joinDataConfig,
|
|
40
|
-
|
|
43
|
+
getBaseParams, // See note in useEffect
|
|
44
|
+
outerValueId, // See note in useEffect
|
|
41
45
|
tooltip,
|
|
42
46
|
testID,
|
|
43
|
-
|
|
47
|
+
isDirty = false,
|
|
44
48
|
|
|
45
49
|
// parent Form
|
|
46
50
|
onChangeValue,
|
|
@@ -77,11 +81,11 @@ function TagComponent(props) {
|
|
|
77
81
|
}
|
|
78
82
|
return null;
|
|
79
83
|
}),
|
|
80
|
-
[isInited, setIsInited] = useState(
|
|
84
|
+
[isInited, setIsInited] = useState(_.isUndefined(getBaseParams)), // default to true unless getBaseParams is defined
|
|
81
85
|
modelFieldStartsWith = hasJoinData ? Inflector.underscore(JoinRepository.getSchema().name) + '__' : '',
|
|
82
86
|
valueRef = useRef(value),
|
|
83
87
|
onView = async (item, e) => {
|
|
84
|
-
//
|
|
88
|
+
// show the joined record's viewer
|
|
85
89
|
const
|
|
86
90
|
id = item.id,
|
|
87
91
|
repository = TargetRepository;
|
|
@@ -142,7 +146,7 @@ function TagComponent(props) {
|
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
// The value we get from combo is a simple int
|
|
145
|
-
// Convert this to id
|
|
149
|
+
// Convert this to { id, text} from either Repository or data array.
|
|
146
150
|
const
|
|
147
151
|
data = props.data,
|
|
148
152
|
idIx = props.idIx,
|
|
@@ -171,32 +175,36 @@ function TagComponent(props) {
|
|
|
171
175
|
|
|
172
176
|
let joinData = {};
|
|
173
177
|
if (hasJoinData) {
|
|
174
|
-
// build up the default starting values,
|
|
178
|
+
// build up the default starting values for joinData,
|
|
175
179
|
// first with schema defaultValues...
|
|
176
180
|
const
|
|
177
181
|
allSchemaDefaults = JoinRepository.getSchema().getDefaultValues(),
|
|
178
182
|
modelSchemaDefaults = _.pickBy(allSchemaDefaults, (value, key) => {
|
|
179
183
|
return key.startsWith(modelFieldStartsWith);
|
|
180
184
|
}),
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
// then with default values in joinDataConfig, if they exist
|
|
188
|
-
_.each(joinDataConfig, (fieldConfig) => {
|
|
189
|
-
if (!_.isNil(fieldConfig.defaultValue)) {
|
|
190
|
-
joinData[fieldConfig.name] = fieldConfig.defaultValue;
|
|
191
|
-
}
|
|
185
|
+
fullFieldNames = propertyDef.joinData.map((fieldName) => { // add the 'model_name__' prefix so we can get schema default values
|
|
186
|
+
return modelFieldStartsWith + fieldName;
|
|
187
|
+
}),
|
|
188
|
+
schemaDefaultValues = _.pick(modelSchemaDefaults, fullFieldNames);
|
|
189
|
+
joinData = _.mapKeys(schemaDefaultValues, (value, key) => { // strip out the 'model_name__' prefix from field names
|
|
190
|
+
return key.startsWith(modelFieldStartsWith) ? key.slice(modelFieldStartsWith.length) : key;
|
|
192
191
|
});
|
|
193
|
-
|
|
192
|
+
|
|
193
|
+
// then override with default values in joinDataConfig, if they exist
|
|
194
|
+
if (joinDataConfig) {
|
|
195
|
+
_.each(Object.keys(joinDataConfig), (fieldName) => {
|
|
196
|
+
const fieldConfig = joinDataConfig[fieldName];
|
|
197
|
+
if (!_.isUndefined(fieldConfig.defaultValue)) { // null in jsonDataConfig will override a default value in schema!
|
|
198
|
+
joinData[fieldName] = fieldConfig.defaultValue;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
194
202
|
}
|
|
195
203
|
|
|
196
204
|
|
|
197
205
|
// add new value
|
|
198
206
|
const
|
|
199
|
-
newValue = [...value], // clone, so we trigger a re-render
|
|
207
|
+
newValue = [...value], // clone Tag's full current value (array), so we trigger a re-render after adding the new value
|
|
200
208
|
newItem = {
|
|
201
209
|
id,
|
|
202
210
|
text: displayValue,
|
|
@@ -215,8 +223,8 @@ function TagComponent(props) {
|
|
|
215
223
|
});
|
|
216
224
|
setValue(newValue);
|
|
217
225
|
},
|
|
218
|
-
|
|
219
|
-
//
|
|
226
|
+
onViewEditJoinData = async (item, e) => {
|
|
227
|
+
// show the joinData viewer/editor
|
|
220
228
|
|
|
221
229
|
/* item format:
|
|
222
230
|
item = {
|
|
@@ -229,22 +237,27 @@ function TagComponent(props) {
|
|
|
229
237
|
}
|
|
230
238
|
*/
|
|
231
239
|
|
|
232
|
-
//
|
|
240
|
+
// Prepare Form to edit the joinData
|
|
233
241
|
const
|
|
234
|
-
|
|
242
|
+
// create the Form.record, format: { meters_pm_schedules__also_resets: null, meters_pm_schedules__hide_every_n: 5 }
|
|
243
|
+
record = _.mapKeys(item.joinData, (value, key) => { // add the 'model_name__' prefix so we can match JoinRepository property names
|
|
235
244
|
return modelFieldStartsWith + key;
|
|
236
245
|
}),
|
|
246
|
+
// create the Form.items
|
|
237
247
|
items = propertyDef.joinData.map((fieldName) => {
|
|
238
248
|
let obj = {
|
|
239
249
|
name: modelFieldStartsWith + fieldName,
|
|
240
250
|
};
|
|
241
|
-
|
|
251
|
+
|
|
252
|
+
// add in any specific config for joinData[fieldName]], if it exists
|
|
253
|
+
// (The outer *Editor can configure each Tag field's joinData Form item.
|
|
254
|
+
// This moves that configuration down and adds outerValueId)
|
|
242
255
|
if (joinDataConfig?.[fieldName]) {
|
|
243
|
-
const
|
|
244
|
-
|
|
256
|
+
const joinDataConfigFieldname = _.clone(joinDataConfig[fieldName]); // don't mutate original
|
|
257
|
+
joinDataConfigFieldname.outerValueId = item.id; // so that joinData can be aware of the value of the inspected ValueBox; see note in useEffect, below
|
|
245
258
|
obj = {
|
|
246
259
|
...obj,
|
|
247
|
-
...
|
|
260
|
+
...joinDataConfigFieldname,
|
|
248
261
|
};
|
|
249
262
|
}
|
|
250
263
|
|
|
@@ -253,6 +266,7 @@ function TagComponent(props) {
|
|
|
253
266
|
|
|
254
267
|
let height = 300;
|
|
255
268
|
let body;
|
|
269
|
+
const extraModalProps = {};
|
|
256
270
|
if (isViewOnly) {
|
|
257
271
|
// show Viewer
|
|
258
272
|
body = <Viewer
|
|
@@ -263,12 +277,17 @@ function TagComponent(props) {
|
|
|
263
277
|
labelWidth: 200,
|
|
264
278
|
}}
|
|
265
279
|
/>;
|
|
280
|
+
|
|
281
|
+
extraModalProps.customButtons = [
|
|
282
|
+
<Button
|
|
283
|
+
{...testProps('closeBtn')}
|
|
284
|
+
key="closeBtn"
|
|
285
|
+
onPress={hideModal}
|
|
286
|
+
text="Close"
|
|
287
|
+
className="text-white"
|
|
288
|
+
/>,
|
|
289
|
+
];
|
|
266
290
|
} else {
|
|
267
|
-
switch (items.length) {
|
|
268
|
-
case 1: height = 250; break;
|
|
269
|
-
case 2: height = 400; break;
|
|
270
|
-
default: height = 600; break;
|
|
271
|
-
}
|
|
272
291
|
body = <Form
|
|
273
292
|
editorType={EDITOR_TYPE__PLAIN}
|
|
274
293
|
isEditorViewOnly={false}
|
|
@@ -285,7 +304,7 @@ function TagComponent(props) {
|
|
|
285
304
|
]}
|
|
286
305
|
onSave={(values)=> {
|
|
287
306
|
|
|
288
|
-
// strip the '
|
|
307
|
+
// strip the 'model_name__' prefix from the field names
|
|
289
308
|
values = _.mapKeys(values, (value, key) => {
|
|
290
309
|
return key.startsWith(modelFieldStartsWith) ? key.slice(modelFieldStartsWith.length) : key;
|
|
291
310
|
});
|
|
@@ -303,6 +322,11 @@ function TagComponent(props) {
|
|
|
303
322
|
}}
|
|
304
323
|
/>;
|
|
305
324
|
}
|
|
325
|
+
switch (items.length) {
|
|
326
|
+
case 1: height = 250; break;
|
|
327
|
+
case 2: height = 400; break;
|
|
328
|
+
default: height = 600; break;
|
|
329
|
+
}
|
|
306
330
|
|
|
307
331
|
showModal({
|
|
308
332
|
title: 'Extra data for "' + item.text + '"',
|
|
@@ -312,6 +336,7 @@ function TagComponent(props) {
|
|
|
312
336
|
includeReset: false,
|
|
313
337
|
includeCancel: false,
|
|
314
338
|
body,
|
|
339
|
+
...extraModalProps,
|
|
315
340
|
});
|
|
316
341
|
},
|
|
317
342
|
onGridAdd = (selection) => {
|
|
@@ -381,54 +406,47 @@ function TagComponent(props) {
|
|
|
381
406
|
text={val.text}
|
|
382
407
|
onView={() => onView(val)}
|
|
383
408
|
showEye={showEye}
|
|
384
|
-
|
|
385
|
-
showJoin={hasJoinData}
|
|
409
|
+
onViewEditJoinData={() => onViewEditJoinData(val)}
|
|
410
|
+
showJoin={hasJoinData && (!mustSaveBeforeEditingJoinData || !isDirty)}
|
|
386
411
|
onDelete={!isViewOnly ? () => onDelete(val) : null}
|
|
387
412
|
minimizeForRow={minimizeForRow}
|
|
388
413
|
/>;
|
|
389
414
|
});
|
|
390
415
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
// return baseParams;
|
|
421
|
-
// },
|
|
422
|
-
// },
|
|
423
|
-
// },
|
|
424
|
-
// }
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if (getBaseParams) {
|
|
416
|
+
if (!_.isUndefined(getBaseParams) && outerValueId) {
|
|
417
|
+
useEffect(() => {
|
|
418
|
+
|
|
419
|
+
// NOTE: This useEffect is so we can dynamically set the TargetRepository's baseParams,
|
|
420
|
+
// based on outerValueId, before it loads.
|
|
421
|
+
// We did this for cases where the Tag field has joinData that's managing a nested Tag field.
|
|
422
|
+
// ... This deals with recursion, so gets "alice in wonderland" quickly!
|
|
423
|
+
// If that inner Tag field has getBaseParams defined on a joinDataConfig field of the outer Tag,
|
|
424
|
+
// then that means it needs to set its baseParams dynamically, based on the value of the outer ValueBox.
|
|
425
|
+
|
|
426
|
+
// For example: in the MetersEditor:
|
|
427
|
+
// {
|
|
428
|
+
// name: 'meters__pm_schedules',
|
|
429
|
+
// mustSaveBeforeEditingJoinData: true,
|
|
430
|
+
// joinDataConfig: {
|
|
431
|
+
// also_resets: {
|
|
432
|
+
// getBaseParams: (values, outerValueId) => {
|
|
433
|
+
// const baseParams = {
|
|
434
|
+
// 'conditions[MetersPmSchedules.meter_id]': meter_id, // limit also_resets to those MetersPmSchedules related to this meter
|
|
435
|
+
// };
|
|
436
|
+
// if (outerValueId) {
|
|
437
|
+
// baseParams['conditions[MetersPmSchedules.id <>]'] = outerValueId; // exclude the ValueBox that was clicked on
|
|
438
|
+
// }
|
|
439
|
+
// return baseParams;
|
|
440
|
+
// },
|
|
441
|
+
// },
|
|
442
|
+
// },
|
|
443
|
+
// }
|
|
444
|
+
|
|
428
445
|
TargetRepository.setBaseParams(getBaseParams(value, outerValueId));
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
446
|
+
setIsInited(true);
|
|
447
|
+
|
|
448
|
+
}, [value]);
|
|
449
|
+
}
|
|
432
450
|
|
|
433
451
|
if (!isInited) {
|
|
434
452
|
return null;
|
|
@@ -16,7 +16,7 @@ export default function ValueBox(props) {
|
|
|
16
16
|
text,
|
|
17
17
|
onView,
|
|
18
18
|
showEye = false,
|
|
19
|
-
|
|
19
|
+
onViewEditJoinData,
|
|
20
20
|
showJoin = false,
|
|
21
21
|
onDelete,
|
|
22
22
|
minimizeForRow = false,
|
|
@@ -60,7 +60,7 @@ export default function ValueBox(props) {
|
|
|
60
60
|
size: styles.FORM_TAG_VALUEBOX_ICON_SIZE,
|
|
61
61
|
className: 'text-grey-600',
|
|
62
62
|
}}
|
|
63
|
-
onPress={
|
|
63
|
+
onPress={onViewEditJoinData}
|
|
64
64
|
className={clsx(
|
|
65
65
|
'ValueBox-joinBtn',
|
|
66
66
|
'h-full',
|
|
@@ -816,6 +816,7 @@ function Form(props) {
|
|
|
816
816
|
{...testProps('field-' + name)}
|
|
817
817
|
name={name}
|
|
818
818
|
value={value}
|
|
819
|
+
isDirty={isDirty}
|
|
819
820
|
onChangeValue={(newValue) => {
|
|
820
821
|
if (newValue === undefined) {
|
|
821
822
|
newValue = null; // React Hook Form doesn't respond well when setting value to undefined
|
|
@@ -133,6 +133,7 @@ function GridComponent(props) {
|
|
|
133
133
|
flatListProps = {},
|
|
134
134
|
onRowPress,
|
|
135
135
|
onRender,
|
|
136
|
+
onLayout,
|
|
136
137
|
disableLoadOnRender = false,
|
|
137
138
|
forceLoadOnRender = false,
|
|
138
139
|
pullToRefresh = true,
|
|
@@ -1092,6 +1093,9 @@ function GridComponent(props) {
|
|
|
1092
1093
|
if (!Repository || Repository.isDestroyed) { // This method gets delayed, so it's possible for Repository to have been destroyed. Check for this
|
|
1093
1094
|
return;
|
|
1094
1095
|
}
|
|
1096
|
+
if (onLayout) {
|
|
1097
|
+
onLayout(e);
|
|
1098
|
+
}
|
|
1095
1099
|
if (DEBUG) {
|
|
1096
1100
|
console.log(`${getMeasurementPhase()}, adjustPageSizeToHeight A`);
|
|
1097
1101
|
}
|
|
@@ -285,6 +285,9 @@ const GridRow = forwardRef((props, ref) => {
|
|
|
285
285
|
'py-3',
|
|
286
286
|
'block',
|
|
287
287
|
areCellsScrollable ? 'overflow-auto' : 'overflow-hidden',
|
|
288
|
+
'[&::-webkit-scrollbar]:h-2',
|
|
289
|
+
'[&::-webkit-scrollbar-thumb]:bg-gray-400',
|
|
290
|
+
'[&::-webkit-scrollbar-thumb]:rounded-full',
|
|
288
291
|
colClassName,
|
|
289
292
|
styles.GRID_CELL_CLASSNAME,
|
|
290
293
|
styles.GRID_ROW_MAX_HEIGHT_NORMAL,
|
|
@@ -32,6 +32,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
32
32
|
secondaryUserCanEdit = true, // not permissions, but capability
|
|
33
33
|
secondaryUserCanView = true,
|
|
34
34
|
secondaryCanEditorViewOnly = false, // whether the editor can *ever* change state out of 'View' mode
|
|
35
|
+
secondaryCanProceedWithCrud, // fn returns bool on if the CRUD operation can proceed
|
|
35
36
|
secondaryDisableAdd = false,
|
|
36
37
|
secondaryDisableEdit = false,
|
|
37
38
|
secondaryDisableDelete = false,
|
|
@@ -47,12 +48,14 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
47
48
|
secondaryEditorType,
|
|
48
49
|
secondaryOnAdd,
|
|
49
50
|
secondaryOnChange, // any kind of crud change
|
|
51
|
+
secondaryOnBeforeDelete,
|
|
50
52
|
secondaryOnDelete,
|
|
51
53
|
secondaryOnSave, // this could also be called 'onEdit'
|
|
52
54
|
secondaryOnEditorClose,
|
|
53
55
|
secondaryNewEntityDisplayValue,
|
|
54
56
|
secondaryNewEntityDisplayProperty, // in case the field to set for newEntityDisplayValue is different from model
|
|
55
57
|
secondaryDefaultValues,
|
|
58
|
+
secondaryInitialEditorMode = EDITOR_MODE__VIEW,
|
|
56
59
|
secondaryStayInEditModeOnSelectionChange = false,
|
|
57
60
|
|
|
58
61
|
// withComponent
|
|
@@ -86,11 +89,11 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
86
89
|
secondaryNewEntityDisplayValueRef = useRef(),
|
|
87
90
|
secondaryEditorModeRef = useRef(EDITOR_MODE__VIEW),
|
|
88
91
|
secondaryIsIgnoreNextSelectionChangeRef = useRef(false),
|
|
92
|
+
secondaryIsEditorShownRef = useRef(false),
|
|
89
93
|
secondaryModel = SecondaryRepository?.schema?.name,
|
|
90
94
|
[secondaryCurrentRecord, secondarySetCurrentRecord] = useState(null),
|
|
91
95
|
[secondaryIsAdding, setIsAdding] = useState(false),
|
|
92
96
|
[secondaryIsSaving, setIsSaving] = useState(false),
|
|
93
|
-
[secondaryIsEditorShown, secondarySetIsEditorShownRaw] = useState(false),
|
|
94
97
|
[secondaryIsEditorViewOnly, setIsEditorViewOnly] = useState(secondaryCanEditorViewOnly), // current state of whether editor is in view-only mode
|
|
95
98
|
[secondaryLastSelection, setLastSelection] = useState(),
|
|
96
99
|
secondarySetIsIgnoreNextSelectionChange = (bool) => {
|
|
@@ -100,11 +103,24 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
100
103
|
return secondaryIsIgnoreNextSelectionChangeRef.current;
|
|
101
104
|
},
|
|
102
105
|
secondarySetIsEditorShown = (bool) => {
|
|
103
|
-
|
|
106
|
+
secondaryIsEditorShownRef.current = bool;
|
|
107
|
+
forceUpdate();
|
|
104
108
|
if (!bool && secondaryOnEditorClose) {
|
|
105
109
|
secondaryOnEditorClose();
|
|
106
110
|
}
|
|
107
111
|
},
|
|
112
|
+
secondaryGetIsEditorShown = () => {
|
|
113
|
+
return secondaryIsEditorShownRef.current;
|
|
114
|
+
},
|
|
115
|
+
secondarySetIsWaitModalShown = (bool) => {
|
|
116
|
+
const
|
|
117
|
+
dispatch = UiGlobals.redux?.dispatch,
|
|
118
|
+
setIsWaitModalShownAction = UiGlobals.systemReducer?.setIsWaitModalShownAction;
|
|
119
|
+
if (setIsWaitModalShownAction) {
|
|
120
|
+
console.log('withSecondaryEditor:setIsWaitModalShownAction', bool);
|
|
121
|
+
dispatch(setIsWaitModalShownAction(bool));
|
|
122
|
+
}
|
|
123
|
+
},
|
|
108
124
|
secondarySetSelectionDecorated = (newSelection) => {
|
|
109
125
|
function doIt() {
|
|
110
126
|
secondarySetSelection(newSelection);
|
|
@@ -139,7 +155,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
139
155
|
forceUpdate();
|
|
140
156
|
}
|
|
141
157
|
},
|
|
142
|
-
|
|
158
|
+
secondaryGetNewEntityDisplayValue = () => {
|
|
143
159
|
return secondaryNewEntityDisplayValueRef.current;
|
|
144
160
|
},
|
|
145
161
|
secondaryDoAdd = async (e, values) => {
|
|
@@ -147,6 +163,9 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
147
163
|
showPermissionsError(ADD, secondaryModel);
|
|
148
164
|
return;
|
|
149
165
|
}
|
|
166
|
+
if (secondaryCanProceedWithCrud && !secondaryCanProceedWithCrud()) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
150
169
|
|
|
151
170
|
const secondarySelection = secondaryGetSelection();
|
|
152
171
|
let addValues = values;
|
|
@@ -165,20 +184,20 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
165
184
|
// 1. directlty submit 'values' to use in secondaryDoAdd(), or
|
|
166
185
|
// 2. Use the repository's default values (defined on each property as 'defaultValue'), or
|
|
167
186
|
// 3. Individually override the repository's default values with submitted 'defaultValues' (given as a prop to this HOC)
|
|
168
|
-
let
|
|
187
|
+
let secondaryDefaultValuesToUse = SecondaryRepository.getSchema().getDefaultValues();
|
|
169
188
|
if (secondaryDefaultValues) {
|
|
170
|
-
_.merge(
|
|
189
|
+
_.merge(secondaryDefaultValuesToUse, secondaryDefaultValues);
|
|
171
190
|
}
|
|
172
|
-
addValues = {...
|
|
191
|
+
addValues = {...secondaryDefaultValuesToUse};
|
|
173
192
|
}
|
|
174
193
|
|
|
175
194
|
if (secondarySelectorId && !_.isEmpty(secondarySelectorSelected)) {
|
|
176
195
|
addValues[secondarySelectorId] = secondarySelectorSelected[secondarySelectorSelectedField];
|
|
177
196
|
}
|
|
178
197
|
|
|
179
|
-
if (
|
|
198
|
+
if (secondaryGetNewEntityDisplayValue()) {
|
|
180
199
|
const displayPropertyName = secondaryNewEntityDisplayProperty || SecondaryRepository.getSchema().model.displayProperty;
|
|
181
|
-
addValues[displayPropertyName] =
|
|
200
|
+
addValues[displayPropertyName] = secondaryGetNewEntityDisplayValue();
|
|
182
201
|
}
|
|
183
202
|
|
|
184
203
|
if (getListeners().onBeforeAdd) {
|
|
@@ -246,6 +265,9 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
246
265
|
showPermissionsError(EDIT, secondaryModel);
|
|
247
266
|
return;
|
|
248
267
|
}
|
|
268
|
+
if (secondaryCanProceedWithCrud && !secondaryCanProceedWithCrud()) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
249
271
|
const secondarySelection = secondaryGetSelection();
|
|
250
272
|
if (_.isEmpty(secondarySelection) || (_.isArray(secondarySelection) && (secondarySelection.length > 1 || secondarySelection[0]?.isDestroyed))) {
|
|
251
273
|
return;
|
|
@@ -265,6 +287,9 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
265
287
|
showPermissionsError(DELETE, secondaryModel);
|
|
266
288
|
return;
|
|
267
289
|
}
|
|
290
|
+
if (secondaryCanProceedWithCrud && !secondaryCanProceedWithCrud()) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
268
293
|
let cb = null;
|
|
269
294
|
if (_.isFunction(args)) {
|
|
270
295
|
cb = args;
|
|
@@ -273,7 +298,15 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
273
298
|
if (_.isEmpty(secondarySelection) || (_.isArray(secondarySelection) && (secondarySelection.length > 1 || secondarySelection[0]?.isDestroyed))) {
|
|
274
299
|
return;
|
|
275
300
|
}
|
|
301
|
+
if (secondaryOnBeforeDelete) {
|
|
302
|
+
// This listener is set by parent components using a prop
|
|
303
|
+
const listenerResult = await secondaryOnBeforeDelete(secondarySelection);
|
|
304
|
+
if (listenerResult === false) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
276
308
|
if (getListeners().onBeforeDelete) {
|
|
309
|
+
// This listener is set by child components using setWithEditListeners()
|
|
277
310
|
const listenerResult = await getListeners().onBeforeDelete();
|
|
278
311
|
if (listenerResult === false) {
|
|
279
312
|
return;
|
|
@@ -309,26 +342,33 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
309
342
|
});
|
|
310
343
|
} else
|
|
311
344
|
if (isSingle && isPhantom) {
|
|
312
|
-
|
|
345
|
+
secondaryDeleteRecord(cb);
|
|
313
346
|
} else {
|
|
314
347
|
const identifier = secondaryGetRecordIdentifier(secondarySelection);
|
|
315
|
-
confirm('Are you sure you want to delete the ' + identifier, () =>
|
|
348
|
+
confirm('Are you sure you want to delete the ' + identifier, () => secondaryDeleteRecord(null, cb));
|
|
316
349
|
}
|
|
317
350
|
},
|
|
318
351
|
secondaryDoMoveChildren = (cb) => {
|
|
319
352
|
hideAlert();
|
|
320
|
-
|
|
353
|
+
secondaryDeleteRecord(true, cb);
|
|
321
354
|
},
|
|
322
355
|
secondaryDoDeleteChildren = (cb) => {
|
|
323
356
|
hideAlert();
|
|
324
|
-
|
|
357
|
+
secondaryDeleteRecord(false, cb);
|
|
325
358
|
},
|
|
326
|
-
|
|
359
|
+
secondaryDeleteRecord = async (moveSubtreeUp, cb) => {
|
|
327
360
|
if (canUser && !canUser(DELETE, secondaryModel)) {
|
|
328
361
|
showPermissionsError(DELETE, secondaryModel);
|
|
329
362
|
return;
|
|
330
363
|
}
|
|
331
364
|
const secondarySelection = secondaryGetSelection();
|
|
365
|
+
if (secondaryOnBeforeDelete) {
|
|
366
|
+
// This listener is set by parent components using a prop
|
|
367
|
+
const listenerResult = await secondaryOnBeforeDelete(secondarySelection);
|
|
368
|
+
if (listenerResult === false) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
332
372
|
if (getListeners().onBeforeDelete) {
|
|
333
373
|
const listenerResult = await getListeners().onBeforeDelete(secondarySelection);
|
|
334
374
|
if (listenerResult === false) {
|
|
@@ -362,6 +402,9 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
362
402
|
showPermissionsError(VIEW, secondaryModel);
|
|
363
403
|
return;
|
|
364
404
|
}
|
|
405
|
+
if (secondaryCanProceedWithCrud && !secondaryCanProceedWithCrud()) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
365
408
|
if (secondaryEditorType === EDITOR_TYPE__INLINE) {
|
|
366
409
|
alert('Cannot view in inline editor.');
|
|
367
410
|
return; // inline editor doesn't have a view mode
|
|
@@ -389,37 +432,70 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
389
432
|
showPermissionsError(DUPLICATE, secondaryModel);
|
|
390
433
|
return;
|
|
391
434
|
}
|
|
392
|
-
|
|
393
|
-
|
|
435
|
+
if (secondaryCanProceedWithCrud && !secondaryCanProceedWithCrud()) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
394
438
|
|
|
395
439
|
const secondarySelection = secondaryGetSelection();
|
|
396
440
|
if (secondarySelection.length !== 1) {
|
|
397
441
|
return;
|
|
398
442
|
}
|
|
443
|
+
|
|
399
444
|
if (secondaryUseRemoteDuplicate) {
|
|
400
|
-
|
|
401
|
-
|
|
445
|
+
return await secondaryOnRemoteDuplicate();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
let isSuccess = false,
|
|
449
|
+
duplicateEntity;
|
|
450
|
+
try {
|
|
451
|
+
const
|
|
452
|
+
entity = secondarySelection[0],
|
|
453
|
+
idProperty = SecondaryRepository.getSchema().model.idProperty,
|
|
454
|
+
rawValues = _.omit(entity.getOriginalData(), idProperty);
|
|
455
|
+
rawValues.id = null; // unset the id of the duplicate
|
|
456
|
+
|
|
457
|
+
setIsWaitModalShown(true);
|
|
458
|
+
|
|
459
|
+
duplicateEntity = await SecondaryRepository.add(rawValues, false, true);
|
|
460
|
+
isSuccess = true;
|
|
461
|
+
|
|
462
|
+
} catch(err) {
|
|
463
|
+
// do nothing
|
|
464
|
+
} finally {
|
|
465
|
+
setIsWaitModalShown(false);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (isSuccess) {
|
|
469
|
+
secondarySetIsIgnoreNextSelectionChange(true);
|
|
470
|
+
secondarySetSelection([duplicateEntity]);
|
|
471
|
+
secondarySetEditorMode(EDITOR_MODE__EDIT);
|
|
472
|
+
secondarySetIsEditorShown(true);
|
|
402
473
|
}
|
|
403
|
-
const
|
|
404
|
-
entity = secondarySelection[0],
|
|
405
|
-
idProperty = SecondaryRepository.getSchema().model.idProperty,
|
|
406
|
-
rawValues = _.omit(entity.getOriginalData(), idProperty);
|
|
407
|
-
rawValues.id = null; // unset the id of the duplicate
|
|
408
|
-
const duplicate = await SecondaryRepository.add(rawValues, false, true);
|
|
409
|
-
secondarySetIsIgnoreNextSelectionChange(true);
|
|
410
|
-
secondarySetSelection([duplicate]);
|
|
411
|
-
secondarySetEditorMode(EDITOR_MODE__EDIT);
|
|
412
|
-
secondarySetIsEditorShown(true);
|
|
413
474
|
},
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
475
|
+
secondaryOnRemoteDuplicate = async () => {
|
|
476
|
+
let isSuccess = false,
|
|
477
|
+
duplicateEntity;
|
|
478
|
+
try {
|
|
479
|
+
const
|
|
480
|
+
secondarySelection = secondaryGetSelection(),
|
|
481
|
+
entity = secondarySelection[0];
|
|
482
|
+
|
|
483
|
+
setIsWaitModalShown(true);
|
|
419
484
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
485
|
+
duplicateEntity = await SecondaryRepository.remoteDuplicate(entity);
|
|
486
|
+
isSuccess = true;
|
|
487
|
+
|
|
488
|
+
} catch(err) {
|
|
489
|
+
// do nothing
|
|
490
|
+
} finally {
|
|
491
|
+
setIsWaitModalShown(false);
|
|
492
|
+
}
|
|
493
|
+
if (isSuccess) {
|
|
494
|
+
secondarySetIsIgnoreNextSelectionChange(true);
|
|
495
|
+
secondarySetSelection([duplicateEntity]);
|
|
496
|
+
secondaryDoEdit();
|
|
497
|
+
return duplicateEntity;
|
|
498
|
+
}
|
|
423
499
|
},
|
|
424
500
|
secondaryDoEditorSave = async (data, e) => {
|
|
425
501
|
let mode = secondaryGetEditorMode() === EDITOR_MODE__ADD ? ADD : EDIT;
|
|
@@ -516,7 +592,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
516
592
|
isSingle = secondarySelection.length === 1,
|
|
517
593
|
isPhantom = secondarySelection[0] && !secondarySelection[0]?.isDestroyed && secondarySelection[0].isPhantom;
|
|
518
594
|
if (isSingle && isPhantom) {
|
|
519
|
-
await
|
|
595
|
+
await secondaryDeleteRecord();
|
|
520
596
|
}
|
|
521
597
|
|
|
522
598
|
setIsAdding(false);
|
|
@@ -551,7 +627,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
551
627
|
secondarySetIsEditorShown(false);
|
|
552
628
|
});
|
|
553
629
|
},
|
|
554
|
-
|
|
630
|
+
secondaryCalculateEditorMode = () => {
|
|
555
631
|
|
|
556
632
|
let secondaryIsIgnoreNextSelectionChange = secondaryGetIsIgnoreNextSelectionChange(),
|
|
557
633
|
doStayInEditModeOnSelectionChange = secondaryStayInEditModeOnSelectionChange;
|
|
@@ -563,7 +639,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
563
639
|
secondaryIsIgnoreNextSelectionChange = true;
|
|
564
640
|
}
|
|
565
641
|
|
|
566
|
-
//
|
|
642
|
+
// secondaryCalculateEditorMode gets called only on selection changes
|
|
567
643
|
const secondarySelection = secondaryGetSelection();
|
|
568
644
|
let mode;
|
|
569
645
|
if (secondaryEditorType === EDITOR_TYPE__SIDE && !_.isNil(UiGlobals.isSideEditorAlwaysEditMode) && UiGlobals.isSideEditorAlwaysEditMode) {
|
|
@@ -617,10 +693,26 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
617
693
|
};
|
|
618
694
|
|
|
619
695
|
useEffect(() => {
|
|
620
|
-
secondarySetEditorMode(calculateEditorMode());
|
|
621
696
|
|
|
622
|
-
|
|
697
|
+
if (secondaryEditorType === EDITOR_TYPE__SIDE) {
|
|
698
|
+
if (secondarySelection?.length) { // || isAdding
|
|
699
|
+
// there is a selection, so show the editor
|
|
700
|
+
secondarySetIsEditorShown(true);
|
|
701
|
+
} else {
|
|
702
|
+
// no selection, so close the editor
|
|
703
|
+
secondarySetIsEditorShown(false);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
secondarySetEditorMode(secondaryCalculateEditorMode());
|
|
623
708
|
setLastSelection(secondarySelection);
|
|
709
|
+
|
|
710
|
+
// Push isIgnoreNextSelectionChange until after a microtask to ensure all
|
|
711
|
+
// synchronous operations (including listener callbacks) are complete
|
|
712
|
+
// (this is to prevent the editor from immediately switching modes on doAdd in Tree)
|
|
713
|
+
Promise.resolve().then(() => {
|
|
714
|
+
secondarySetIsIgnoreNextSelectionChange(false);
|
|
715
|
+
});
|
|
624
716
|
}, [secondarySelection]);
|
|
625
717
|
|
|
626
718
|
if (self) {
|
|
@@ -638,21 +730,23 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
638
730
|
// NOTE: If I don't calculate this on the fly for selection changes,
|
|
639
731
|
// we see a flash of the previous state, since useEffect hasn't yet run.
|
|
640
732
|
// (basically redo what's in the useEffect, above)
|
|
641
|
-
secondarySetEditorMode(
|
|
733
|
+
secondarySetEditorMode(secondaryCalculateEditorMode());
|
|
642
734
|
}
|
|
643
735
|
|
|
644
736
|
return <WrappedComponent
|
|
645
737
|
{...props}
|
|
738
|
+
ref={ref}
|
|
646
739
|
secondaryDisableWithEditor={false}
|
|
647
740
|
secondaryAlreadyHasWithEditor={true}
|
|
648
|
-
ref={ref}
|
|
649
741
|
secondaryCurrentRecord={secondaryCurrentRecord}
|
|
650
742
|
secondarySetCurrentRecord={secondarySetCurrentRecord}
|
|
651
|
-
secondaryIsEditorShown={
|
|
743
|
+
secondaryIsEditorShown={secondaryGetIsEditorShown()}
|
|
744
|
+
secondaryGetIsEditorShown={secondaryGetIsEditorShown}
|
|
652
745
|
secondaryIsEditorViewOnly={secondaryIsEditorViewOnly}
|
|
653
746
|
secondaryIsAdding={secondaryIsAdding}
|
|
654
747
|
secondaryIsSaving={secondaryIsSaving}
|
|
655
748
|
secondaryEditorMode={secondaryGetEditorMode()}
|
|
749
|
+
secondaryGetEditorMode={secondaryGetEditorMode}
|
|
656
750
|
secondaryOnEditMode={secondarySetEditMode}
|
|
657
751
|
secondaryOnViewMode={secondarySetViewMode}
|
|
658
752
|
secondaryEditorStateRef={secondaryEditorStateRef}
|
|
@@ -68,7 +68,7 @@ export default function withSecondarySideEditor(WrappedComponent, isTree = false
|
|
|
68
68
|
isSideEditor={true}
|
|
69
69
|
{...props}
|
|
70
70
|
/>}
|
|
71
|
-
east={<Editor
|
|
71
|
+
east={props.secondaryIsEditorShown && <Editor
|
|
72
72
|
{...propsToPass}
|
|
73
73
|
editorType={EDITOR_TYPE__SIDE}
|
|
74
74
|
{...secondaryEditorProps}
|
|
@@ -56,8 +56,8 @@ export default function Pagination(props) {
|
|
|
56
56
|
let items = [],
|
|
57
57
|
isDisabled = false;
|
|
58
58
|
if (showMoreOnly) {
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
if (totalPages > 1 && showPagination) {
|
|
60
|
+
isDisabled = (pageEnd === total);
|
|
61
61
|
items.push(<Button
|
|
62
62
|
{...testProps('showMoreBtn')}
|
|
63
63
|
key="showMoreBtn"
|
|
@@ -88,120 +88,122 @@ export default function Pagination(props) {
|
|
|
88
88
|
self={self}
|
|
89
89
|
/>);
|
|
90
90
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
91
|
+
if (totalPages > 1) {
|
|
92
|
+
isDisabled = page === 1;
|
|
93
|
+
if (showPagination) {
|
|
94
|
+
items.push(<IconButton
|
|
95
|
+
{...testProps('firstPageBtn')}
|
|
96
|
+
key="firstPageBtn"
|
|
97
|
+
reference="firstPageBtn"
|
|
98
|
+
className="Pagination-firstPageBtn"
|
|
99
|
+
parent={self}
|
|
100
|
+
isDisabled={isDisabled}
|
|
101
|
+
icon={AnglesLeft}
|
|
102
|
+
_icon={iconProps}
|
|
103
|
+
onPress={() => Repository.setPage(1)}
|
|
104
|
+
tooltip="First Page"
|
|
105
|
+
/>);
|
|
106
|
+
items.push(<IconButton
|
|
107
|
+
{...testProps('prevPageBtn')}
|
|
108
|
+
key="prevPageBtn"
|
|
109
|
+
reference="prevPageBtn"
|
|
110
|
+
className="Pagination-prevPageBtn"
|
|
111
|
+
parent={self}
|
|
112
|
+
isDisabled={isDisabled}
|
|
113
|
+
icon={AngleLeft}
|
|
114
|
+
_icon={iconProps}
|
|
115
|
+
onPress={() => Repository.prevPage()}
|
|
116
|
+
tooltip="Previous Page"
|
|
117
|
+
/>);
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
parent={self}
|
|
125
|
-
isDisabled={isDisabled}
|
|
126
|
-
icon={AngleRight}
|
|
127
|
-
_icon={iconProps}
|
|
128
|
-
onPress={() => Repository.nextPage()}
|
|
129
|
-
tooltip="Next Page"
|
|
130
|
-
/>);
|
|
131
|
-
items.push(<IconButton
|
|
132
|
-
{...testProps('lastPageBtn')}
|
|
133
|
-
key="lastPageBtn"
|
|
134
|
-
reference="lastPageBtn"
|
|
135
|
-
className="Pagination-lastPageBtn"
|
|
136
|
-
parent={self}
|
|
137
|
-
isDisabled={isDisabled}
|
|
138
|
-
icon={AnglesRight}
|
|
139
|
-
_icon={iconProps}
|
|
140
|
-
onPress={() => Repository.setPage(totalPages)}
|
|
141
|
-
tooltip="Last Page"
|
|
142
|
-
/>);
|
|
143
|
-
if (!minimize) {
|
|
144
|
-
items.push(<Text
|
|
145
|
-
key="page"
|
|
146
|
-
className="Pagination-page mx-1"
|
|
147
|
-
>Page</Text>);
|
|
148
|
-
items.push(<Input
|
|
149
|
-
{...testProps('pageInput')}
|
|
150
|
-
key="pageInput"
|
|
151
|
-
reference="pageInput"
|
|
119
|
+
isDisabled = page === totalPages || totalPages <= 1;
|
|
120
|
+
items.push(<IconButton
|
|
121
|
+
{...testProps('nextPageBtn')}
|
|
122
|
+
key="nextPageBtn"
|
|
123
|
+
reference="nextPageBtn"
|
|
124
|
+
className="Pagination-nextPageBtn"
|
|
152
125
|
parent={self}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
126
|
+
isDisabled={isDisabled}
|
|
127
|
+
icon={AngleRight}
|
|
128
|
+
_icon={iconProps}
|
|
129
|
+
onPress={() => Repository.nextPage()}
|
|
130
|
+
tooltip="Next Page"
|
|
131
|
+
/>);
|
|
132
|
+
items.push(<IconButton
|
|
133
|
+
{...testProps('lastPageBtn')}
|
|
134
|
+
key="lastPageBtn"
|
|
135
|
+
reference="lastPageBtn"
|
|
136
|
+
className="Pagination-lastPageBtn"
|
|
137
|
+
parent={self}
|
|
138
|
+
isDisabled={isDisabled}
|
|
139
|
+
icon={AnglesRight}
|
|
140
|
+
_icon={iconProps}
|
|
141
|
+
onPress={() => Repository.setPage(totalPages)}
|
|
142
|
+
tooltip="Last Page"
|
|
143
|
+
/>);
|
|
144
|
+
if (!minimize) {
|
|
145
|
+
items.push(<Text
|
|
146
|
+
key="page"
|
|
147
|
+
className="Pagination-page mx-1"
|
|
148
|
+
>Page</Text>);
|
|
149
|
+
items.push(<Input
|
|
150
|
+
{...testProps('pageInput')}
|
|
151
|
+
key="pageInput"
|
|
152
|
+
reference="pageInput"
|
|
153
|
+
parent={self}
|
|
154
|
+
keyboardType="numeric"
|
|
155
|
+
value={page?.toString()}
|
|
156
|
+
onChangeValue={(value) => Repository.setPage(value)}
|
|
157
|
+
maxValue={totalPages}
|
|
158
|
+
isDisabled={totalPages === 1}
|
|
159
|
+
className={clsx(
|
|
160
|
+
'Pagination-pageInput',
|
|
161
|
+
'min-w-[40px]',
|
|
162
|
+
'w-[40px]',
|
|
163
|
+
'text-center',
|
|
164
|
+
'bg-grey-100',
|
|
165
|
+
)}
|
|
166
|
+
textAlignIsCenter={true}
|
|
167
|
+
tooltip="Set Page"
|
|
168
|
+
tooltipClassName="w-[40px]"
|
|
169
|
+
/>);
|
|
170
|
+
items.push(<Text
|
|
171
|
+
key="totalPages"
|
|
172
|
+
className={clsx(
|
|
173
|
+
'Pagination-totalPages',
|
|
174
|
+
'whitespace-nowrap',
|
|
175
|
+
'inline-flex',
|
|
176
|
+
'mx-1',
|
|
177
|
+
)}
|
|
178
|
+
>{`of ${totalPages}`}</Text>);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (showPagination && !minimize && !disablePageSize) {
|
|
183
|
+
items.push(<PageSizeSelect
|
|
184
|
+
{...testProps('pageSize')}
|
|
185
|
+
key="pageSize"
|
|
186
|
+
reference="pageSize"
|
|
187
|
+
parent={self}
|
|
188
|
+
pageSize={pageSize}
|
|
189
|
+
Repository={Repository}
|
|
168
190
|
/>);
|
|
191
|
+
}
|
|
192
|
+
if (showPagination && !minimize) {
|
|
193
|
+
let pageSpan = `${pageStart} – ${pageEnd}`;
|
|
194
|
+
if (pageStart === pageEnd) {
|
|
195
|
+
pageSpan = pageStart;
|
|
196
|
+
}
|
|
169
197
|
items.push(<Text
|
|
170
|
-
key="
|
|
198
|
+
key="pageDisplay"
|
|
171
199
|
className={clsx(
|
|
172
|
-
'Pagination-
|
|
200
|
+
'Pagination-pageDisplay',
|
|
173
201
|
'whitespace-nowrap',
|
|
174
202
|
'inline-flex',
|
|
175
203
|
'mx-1',
|
|
176
204
|
)}
|
|
177
|
-
>{`of ${
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (showPagination && !minimize && !disablePageSize) {
|
|
182
|
-
items.push(<PageSizeSelect
|
|
183
|
-
{...testProps('pageSize')}
|
|
184
|
-
key="pageSize"
|
|
185
|
-
reference="pageSize"
|
|
186
|
-
parent={self}
|
|
187
|
-
pageSize={pageSize}
|
|
188
|
-
Repository={Repository}
|
|
189
|
-
/>);
|
|
190
|
-
}
|
|
191
|
-
if (showPagination && !minimize) {
|
|
192
|
-
let pageSpan = `${pageStart} – ${pageEnd}`;
|
|
193
|
-
if (pageStart === pageEnd) {
|
|
194
|
-
pageSpan = pageStart;
|
|
205
|
+
>{`Displaying ${pageSpan} of ${total}`}</Text>);
|
|
195
206
|
}
|
|
196
|
-
items.push(<Text
|
|
197
|
-
key="pageDisplay"
|
|
198
|
-
className={clsx(
|
|
199
|
-
'Pagination-pageDisplay',
|
|
200
|
-
'whitespace-nowrap',
|
|
201
|
-
'inline-flex',
|
|
202
|
-
'mx-1',
|
|
203
|
-
)}
|
|
204
|
-
>{`Displaying ${pageSpan} of ${total}`}</Text>);
|
|
205
207
|
}
|
|
206
208
|
}
|
|
207
209
|
return <HStack
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useMemo, useEffect, } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Box,
|
|
4
|
+
BoxNative,
|
|
4
5
|
HStackNative,
|
|
5
6
|
Icon,
|
|
6
7
|
Spinner,
|
|
@@ -185,7 +186,7 @@ export default function TreeNode(props) {
|
|
|
185
186
|
styles.TREE_NODE_EXPAND_BTN_CLASSNAME,
|
|
186
187
|
)}
|
|
187
188
|
/> :
|
|
188
|
-
<
|
|
189
|
+
<BoxNative
|
|
189
190
|
{...testProps('spacer')}
|
|
190
191
|
className={clsx(
|
|
191
192
|
'TreeNode-spacer',
|
|
@@ -161,11 +161,8 @@ function Viewer(props) {
|
|
|
161
161
|
viewerTypeProps.isViewOnly = true;
|
|
162
162
|
viewerTypeProps.SourceRepository = Repository;
|
|
163
163
|
}
|
|
164
|
-
if (type?.match(/(GridEditor)$/)) {
|
|
165
|
-
viewerTypeProps.
|
|
166
|
-
viewerTypeProps.disableEdit = true;
|
|
167
|
-
viewerTypeProps.disableDelete = true;
|
|
168
|
-
viewerTypeProps.disableDuplicate = true;
|
|
164
|
+
if (type?.match(/(Grid|GridEditor)$/)) {
|
|
165
|
+
viewerTypeProps.canEditorViewOnly = true;
|
|
169
166
|
}
|
|
170
167
|
const Element = getComponentFromType(type);
|
|
171
168
|
|