@onehat/ui 0.2.77 → 0.2.79
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/Editor/Editor.js +3 -12
- package/src/Components/Editor/Viewer.js +17 -13
- package/src/Components/Form/Field/Combo/Combo.js +36 -9
- package/src/Components/Form/Form.js +36 -35
- package/src/Components/Grid/Grid.js +25 -14
- package/src/Components/Grid/GridHeaderRow.js +6 -0
- package/src/Components/Grid/GridRow.js +19 -1
- package/src/Components/Hoc/withContextMenu.js +15 -0
- package/src/Components/Hoc/withEditor.js +52 -32
- package/src/Components/Hoc/withInlineEditor.js +50 -22
- package/src/Components/Hoc/withPresetButtons.js +6 -0
- package/src/Components/index.js +1 -1
- package/src/Constants/Styles.js +1 -0
- package/src/Components/Form/Field/Combo/ComboEditor.js +0 -22
package/package.json
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EDITOR_MODE__VIEW,
|
|
3
|
-
EDITOR_MODE__ADD,
|
|
4
|
-
EDITOR_MODE__EDIT,
|
|
5
3
|
} from '../../Constants/Editor.js';
|
|
6
4
|
import _ from 'lodash';
|
|
7
5
|
|
|
@@ -16,7 +14,7 @@ export default function Editor(props) {
|
|
|
16
14
|
onEditorClose: onClose,
|
|
17
15
|
onEditorDelete: onDelete,
|
|
18
16
|
editorMode,
|
|
19
|
-
|
|
17
|
+
onEditMode,
|
|
20
18
|
|
|
21
19
|
// withData
|
|
22
20
|
Repository,
|
|
@@ -24,13 +22,7 @@ export default function Editor(props) {
|
|
|
24
22
|
// withSelection
|
|
25
23
|
selection,
|
|
26
24
|
|
|
27
|
-
} = props
|
|
28
|
-
onEditMode = () => {
|
|
29
|
-
setEditorMode(EDITOR_MODE__EDIT);
|
|
30
|
-
},
|
|
31
|
-
onViewMode = () => {
|
|
32
|
-
setEditorMode(EDITOR_MODE__VIEW);
|
|
33
|
-
};
|
|
25
|
+
} = props;
|
|
34
26
|
|
|
35
27
|
if (_.isEmpty(selection)) {
|
|
36
28
|
return null;
|
|
@@ -42,7 +34,7 @@ export default function Editor(props) {
|
|
|
42
34
|
record={selection[0]}
|
|
43
35
|
onEditMode={isViewOnly ? null : onEditMode}
|
|
44
36
|
onClose={onClose}
|
|
45
|
-
|
|
37
|
+
onDelete={onDelete}
|
|
46
38
|
/>;
|
|
47
39
|
}
|
|
48
40
|
|
|
@@ -52,7 +44,6 @@ export default function Editor(props) {
|
|
|
52
44
|
return <Form
|
|
53
45
|
{...props}
|
|
54
46
|
record={selection}
|
|
55
|
-
onViewMode={onViewMode}
|
|
56
47
|
onCancel={onCancel}
|
|
57
48
|
onSave={onSave}
|
|
58
49
|
onClose={onClose}
|
|
@@ -20,7 +20,12 @@ export default function Viewer(props) {
|
|
|
20
20
|
const {
|
|
21
21
|
additionalViewButtons = [],
|
|
22
22
|
ancillaryItems = [],
|
|
23
|
+
viewerCanDelete = false,
|
|
24
|
+
|
|
25
|
+
// withData
|
|
23
26
|
record,
|
|
27
|
+
|
|
28
|
+
// withEditor
|
|
24
29
|
onEditMode,
|
|
25
30
|
onClose,
|
|
26
31
|
onDelete,
|
|
@@ -80,19 +85,18 @@ export default function Viewer(props) {
|
|
|
80
85
|
</Column>
|
|
81
86
|
</ScrollView>
|
|
82
87
|
<Footer justifyContent="flex-end">
|
|
83
|
-
{onDelete &&
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
</Row>}
|
|
88
|
+
{onDelete && viewerCanDelete &&
|
|
89
|
+
<Row flex={1} justifyContent="flex-start">
|
|
90
|
+
<Button
|
|
91
|
+
key="deleteBtn"
|
|
92
|
+
onPress={onDelete}
|
|
93
|
+
bg="warning"
|
|
94
|
+
_hover={{
|
|
95
|
+
bg: 'warningHover',
|
|
96
|
+
}}
|
|
97
|
+
color="#fff"
|
|
98
|
+
>Delete</Button>
|
|
99
|
+
</Row>}
|
|
96
100
|
<Button.Group space={2}>
|
|
97
101
|
<Button
|
|
98
102
|
key="closeBtn"
|
|
@@ -12,12 +12,15 @@ 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 withData from '../../../Hoc/withData.js';
|
|
16
17
|
import withEvents from '../../../Hoc/withEvents.js';
|
|
18
|
+
import withPresetButtons from '../../../Hoc/withPresetButtons.js';
|
|
17
19
|
import withSelection from '../../../Hoc/withSelection.js';
|
|
18
20
|
import withValue from '../../../Hoc/withValue.js';
|
|
21
|
+
import withWindowedEditor from '../../../Hoc/withWindowedEditor.js';
|
|
19
22
|
import emptyFn from '../../../../Functions/emptyFn.js';
|
|
20
|
-
import { Grid } from '../../../Grid/Grid.js';
|
|
23
|
+
import { Grid, WindowedGridEditor } from '../../../Grid/Grid.js';
|
|
21
24
|
import IconButton from '../../../Buttons/IconButton.js';
|
|
22
25
|
import CaretDown from '../../../Icons/CaretDown.js';
|
|
23
26
|
import _ from 'lodash';
|
|
@@ -26,7 +29,7 @@ import _ from 'lodash';
|
|
|
26
29
|
// The default export is *with* the HOC. A separate *raw* component is
|
|
27
30
|
// exported which can be combined with many HOCs for various functionality.
|
|
28
31
|
|
|
29
|
-
export function
|
|
32
|
+
export function ComboComponent(props) {
|
|
30
33
|
const {
|
|
31
34
|
additionalButtons,
|
|
32
35
|
autoFocus = false,
|
|
@@ -36,7 +39,9 @@ export function Combo(props) {
|
|
|
36
39
|
menuMinWidth = 150,
|
|
37
40
|
disableDirectEntry = false,
|
|
38
41
|
disablePagination = true,
|
|
42
|
+
hideMenuOnSelection = true,
|
|
39
43
|
_input = {},
|
|
44
|
+
isEditor = false,
|
|
40
45
|
|
|
41
46
|
// withValue
|
|
42
47
|
value,
|
|
@@ -341,6 +346,8 @@ export function Combo(props) {
|
|
|
341
346
|
if (tooltipRef) {
|
|
342
347
|
refProps.ref = tooltipRef;
|
|
343
348
|
}
|
|
349
|
+
|
|
350
|
+
const WhichGrid = isEditor ? WindowedGridEditor : Grid;
|
|
344
351
|
|
|
345
352
|
let comboComponent = <Row {...refProps} justifyContent="center" alignItems="center" h={styles.FORM_COMBO_HEIGHT} flex={1} onLayout={() => setIsRendered(true)}>
|
|
346
353
|
{disableDirectEntry ?
|
|
@@ -474,7 +481,7 @@ export function Combo(props) {
|
|
|
474
481
|
borderTopWidth={0}
|
|
475
482
|
p={0}
|
|
476
483
|
>
|
|
477
|
-
<
|
|
484
|
+
<WhichGrid
|
|
478
485
|
showHeaders={false}
|
|
479
486
|
showHovers={true}
|
|
480
487
|
shadow={1}
|
|
@@ -489,13 +496,16 @@ export function Combo(props) {
|
|
|
489
496
|
};
|
|
490
497
|
}}
|
|
491
498
|
{...props}
|
|
499
|
+
disablePresetButtons={!isEditor}
|
|
492
500
|
disablePagination={disablePagination}
|
|
493
501
|
fireEvent={onEvent}
|
|
494
502
|
setSelection={(selection) => {
|
|
495
503
|
// Decorator fn to add local functionality
|
|
496
504
|
// Close the menu when row is selected on grid
|
|
497
505
|
setSelection(selection);
|
|
498
|
-
|
|
506
|
+
if (hideMenuOnSelection) {
|
|
507
|
+
hideMenu();
|
|
508
|
+
}
|
|
499
509
|
}}
|
|
500
510
|
selectionMode={selectionMode}
|
|
501
511
|
setValue={(value) => {
|
|
@@ -515,13 +525,30 @@ export function Combo(props) {
|
|
|
515
525
|
return comboComponent;
|
|
516
526
|
}
|
|
517
527
|
|
|
518
|
-
export
|
|
519
|
-
// withEvents(
|
|
520
|
-
withData(
|
|
528
|
+
export const Combo = withData(
|
|
521
529
|
withValue(
|
|
522
530
|
withSelection(
|
|
523
|
-
|
|
531
|
+
ComboComponent
|
|
524
532
|
)
|
|
525
533
|
)
|
|
526
534
|
);
|
|
527
|
-
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
function withAdditionalProps(WrappedComponent) {
|
|
539
|
+
return (props) => {
|
|
540
|
+
return <WrappedComponent
|
|
541
|
+
isEditor={true}
|
|
542
|
+
hideMenuOnSelection={false}
|
|
543
|
+
disableView={true}
|
|
544
|
+
disableCopy={true}
|
|
545
|
+
disableDuplicate={true}
|
|
546
|
+
disablePrint={true}
|
|
547
|
+
{...props}
|
|
548
|
+
/>;
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export const ComboEditor = withAdditionalProps(Combo);
|
|
553
|
+
|
|
554
|
+
export default Combo;
|
|
@@ -87,6 +87,7 @@ function Form(props) {
|
|
|
87
87
|
onSave,
|
|
88
88
|
onClose,
|
|
89
89
|
onDelete,
|
|
90
|
+
editorStateRef,
|
|
90
91
|
|
|
91
92
|
// DataMgt
|
|
92
93
|
selectorId,
|
|
@@ -148,6 +149,11 @@ function Form(props) {
|
|
|
148
149
|
borderRightColor: 'trueGray.200',
|
|
149
150
|
px: 1,
|
|
150
151
|
};
|
|
152
|
+
|
|
153
|
+
if (editorType === EDITOR_TYPE__INLINE) {
|
|
154
|
+
columnProps.minWidth = styles.INLINE_EDITOR_MIN_WIDTH;
|
|
155
|
+
}
|
|
156
|
+
|
|
151
157
|
_.each(columnsConfig, (config, ix) => {
|
|
152
158
|
let {
|
|
153
159
|
fieldName,
|
|
@@ -159,7 +165,11 @@ function Form(props) {
|
|
|
159
165
|
} = config;
|
|
160
166
|
|
|
161
167
|
if (!isEditable) {
|
|
162
|
-
|
|
168
|
+
let renderedValue = renderer ? renderer(record) : record[fieldName];
|
|
169
|
+
if (_.isBoolean(renderedValue)) {
|
|
170
|
+
renderedValue = renderedValue.toString();
|
|
171
|
+
}
|
|
172
|
+
renderedValue += "\n(not editable)";
|
|
163
173
|
elements.push(<Box key={ix} w={w} flex={flex} {...columnProps}>
|
|
164
174
|
<Text numberOfLines={1} ellipsizeMode="head">{renderedValue}</Text>
|
|
165
175
|
</Box>);
|
|
@@ -200,13 +210,7 @@ function Form(props) {
|
|
|
200
210
|
}
|
|
201
211
|
}
|
|
202
212
|
const Element = getComponentFromType(editor);
|
|
203
|
-
|
|
204
|
-
debugger;
|
|
205
|
-
// LEFT OFF HERE
|
|
206
|
-
// Trying inline editor, based on columnsConfig
|
|
207
|
-
// Getting an error that the OrdersEditor is missing users__email.
|
|
208
|
-
// Why is this even on the OrdersEditor? Runner?
|
|
209
|
-
}
|
|
213
|
+
|
|
210
214
|
let element = <Element
|
|
211
215
|
name={name}
|
|
212
216
|
value={value}
|
|
@@ -450,6 +454,10 @@ function Form(props) {
|
|
|
450
454
|
// if (Repository && (!record || _.isEmpty(record))) {
|
|
451
455
|
// return null;
|
|
452
456
|
// }
|
|
457
|
+
|
|
458
|
+
if (!_.isNil(editorStateRef)) {
|
|
459
|
+
editorStateRef.current = formState; // Update state so HOC can know what's going on
|
|
460
|
+
}
|
|
453
461
|
|
|
454
462
|
const sizeProps = {};
|
|
455
463
|
if (!flex && !h && !w) {
|
|
@@ -517,6 +525,12 @@ function Form(props) {
|
|
|
517
525
|
if (_.isEmpty(formState.dirtyFields) && !record?.isRemotePhantom) {
|
|
518
526
|
isSaveDisabled = true;
|
|
519
527
|
}
|
|
528
|
+
|
|
529
|
+
if (editorType === EDITOR_TYPE__INLINE) {
|
|
530
|
+
buttonGroupProps.position = 'fixed';
|
|
531
|
+
buttonGroupProps.left = 10; // TODO: I would prefer to have this be centered, but it's a lot more complex than just making it stick to the left
|
|
532
|
+
footerProps.alignItems = 'flex-start';
|
|
533
|
+
}
|
|
520
534
|
|
|
521
535
|
return <Column {...sizeProps} onLayout={onLayout}>
|
|
522
536
|
|
|
@@ -532,13 +546,7 @@ function Form(props) {
|
|
|
532
546
|
{isSingle && editorMode === EDITOR_MODE__EDIT && onViewMode &&
|
|
533
547
|
<Button
|
|
534
548
|
key="viewBtn"
|
|
535
|
-
onPress={
|
|
536
|
-
if (!_.isEmpty(formState.dirtyFields)) {
|
|
537
|
-
confirm('This record has unsaved changes. Are you sure you want to switch to "View" mode? Changes will be lost.', onViewMode);
|
|
538
|
-
} else {
|
|
539
|
-
onViewMode();
|
|
540
|
-
}
|
|
541
|
-
}}
|
|
549
|
+
onPress={onViewMode}
|
|
542
550
|
leftIcon={<Icon as={Eye} color="#fff" size="sm" />}
|
|
543
551
|
color="#fff"
|
|
544
552
|
>To View</Button>}
|
|
@@ -551,19 +559,18 @@ function Form(props) {
|
|
|
551
559
|
{editor}
|
|
552
560
|
|
|
553
561
|
<Footer justifyContent="flex-end" {...footerProps}>
|
|
554
|
-
{onDelete &&
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
</Row>}
|
|
562
|
+
{onDelete && editorMode === EDITOR_MODE__EDIT &&
|
|
563
|
+
<Row flex={1} justifyContent="flex-start">
|
|
564
|
+
<Button
|
|
565
|
+
key="deleteBtn"
|
|
566
|
+
onPress={onDelete}
|
|
567
|
+
bg="warning"
|
|
568
|
+
_hover={{
|
|
569
|
+
bg: 'warningHover',
|
|
570
|
+
}}
|
|
571
|
+
color="#fff"
|
|
572
|
+
>Delete</Button>
|
|
573
|
+
</Row>}
|
|
567
574
|
<Button.Group space={2} {...buttonGroupProps}>
|
|
568
575
|
|
|
569
576
|
{!isViewOnly && <IconButton
|
|
@@ -579,13 +586,7 @@ function Form(props) {
|
|
|
579
586
|
{!isViewOnly && onCancel && <Button
|
|
580
587
|
key="cancelBtn"
|
|
581
588
|
variant="ghost"
|
|
582
|
-
onPress={
|
|
583
|
-
if (!_.isEmpty(formState.dirtyFields)) {
|
|
584
|
-
confirm('This record has unsaved changes. Are you sure you want to cancel editing? Changes will be lost.', onCancel);
|
|
585
|
-
} else {
|
|
586
|
-
onCancel();
|
|
587
|
-
}
|
|
588
|
-
}}
|
|
589
|
+
onPress={onCancel}
|
|
589
590
|
color="#fff"
|
|
590
591
|
>Cancel</Button>}
|
|
591
592
|
{!isViewOnly && onSave && <Button
|
|
@@ -127,8 +127,8 @@ function GridComponent(props) {
|
|
|
127
127
|
selectorSelected,
|
|
128
128
|
|
|
129
129
|
// withInlineEditor
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
inlineEditor = null,
|
|
131
|
+
isInlineEditorShown = false,
|
|
132
132
|
onEditorRowClick,
|
|
133
133
|
|
|
134
134
|
} = props,
|
|
@@ -137,8 +137,8 @@ function GridComponent(props) {
|
|
|
137
137
|
gridRef = useRef(),
|
|
138
138
|
[isReady, setIsReady] = useState(false),
|
|
139
139
|
[isLoading, setIsLoading] = useState(false),
|
|
140
|
-
[isReorderMode, setIsReorderMode] = useState(false),
|
|
141
140
|
[localColumnsConfig, setLocalColumnsConfigRaw] = useState([]),
|
|
141
|
+
[isDragMode, setIsDragMode] = useState(false),
|
|
142
142
|
[dragRowSlot, setDragRowSlot] = useState(null),
|
|
143
143
|
[dragRowIx, setDragRowIx] = useState(),
|
|
144
144
|
setLocalColumnsConfig = (config) => {
|
|
@@ -148,6 +148,9 @@ function GridComponent(props) {
|
|
|
148
148
|
}
|
|
149
149
|
},
|
|
150
150
|
onRowClick = (item, e) => {
|
|
151
|
+
if (isInlineEditorShown) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
151
154
|
const
|
|
152
155
|
{
|
|
153
156
|
shiftKey,
|
|
@@ -223,8 +226,8 @@ function GridComponent(props) {
|
|
|
223
226
|
if (canRowsReorder) {
|
|
224
227
|
items.unshift(<IconButton
|
|
225
228
|
key="reorderBtn"
|
|
226
|
-
onPress={() =>
|
|
227
|
-
icon={<Icon as={
|
|
229
|
+
onPress={() => setIsDragMode(!isDragMode)}
|
|
230
|
+
icon={<Icon as={isDragMode ? NoReorderRows : ReorderRows} color={styles.GRID_TOOLBAR_ITEMS_COLOR} />}
|
|
228
231
|
/>);
|
|
229
232
|
}
|
|
230
233
|
return items;
|
|
@@ -233,6 +236,9 @@ function GridComponent(props) {
|
|
|
233
236
|
if (row.item.isDestroyed) {
|
|
234
237
|
return null;
|
|
235
238
|
}
|
|
239
|
+
if (row.item.id === 'inlineEditor') {
|
|
240
|
+
return inlineEditor;
|
|
241
|
+
}
|
|
236
242
|
|
|
237
243
|
let {
|
|
238
244
|
item,
|
|
@@ -248,7 +254,7 @@ function GridComponent(props) {
|
|
|
248
254
|
if (e.preventDefault && e.cancelable) {
|
|
249
255
|
e.preventDefault();
|
|
250
256
|
}
|
|
251
|
-
if (isHeaderRow ||
|
|
257
|
+
if (isHeaderRow || isDragMode) {
|
|
252
258
|
return
|
|
253
259
|
}
|
|
254
260
|
switch (e.detail) {
|
|
@@ -275,7 +281,7 @@ function GridComponent(props) {
|
|
|
275
281
|
if (e.preventDefault && e.cancelable) {
|
|
276
282
|
e.preventDefault();
|
|
277
283
|
}
|
|
278
|
-
if (isHeaderRow ||
|
|
284
|
+
if (isHeaderRow || isDragMode) {
|
|
279
285
|
return
|
|
280
286
|
}
|
|
281
287
|
|
|
@@ -309,6 +315,7 @@ function GridComponent(props) {
|
|
|
309
315
|
setSelection={setSelection}
|
|
310
316
|
gridRef={gridRef}
|
|
311
317
|
isHovered={isHovered}
|
|
318
|
+
isInlineEditorShown={isInlineEditorShown}
|
|
312
319
|
/>;
|
|
313
320
|
}
|
|
314
321
|
|
|
@@ -331,7 +338,7 @@ function GridComponent(props) {
|
|
|
331
338
|
}
|
|
332
339
|
let WhichGridRow = GridRow,
|
|
333
340
|
rowReorderProps = {};
|
|
334
|
-
if (canRowsReorder &&
|
|
341
|
+
if (canRowsReorder && isDragMode) {
|
|
335
342
|
WhichGridRow = ReorderableGridRow;
|
|
336
343
|
rowReorderProps = {
|
|
337
344
|
mode: VERTICAL,
|
|
@@ -353,6 +360,7 @@ function GridComponent(props) {
|
|
|
353
360
|
hideNavColumn={hideNavColumn}
|
|
354
361
|
bg={bg}
|
|
355
362
|
item={item}
|
|
363
|
+
isInlineEditorShown={isInlineEditorShown}
|
|
356
364
|
{...rowReorderProps}
|
|
357
365
|
/>;
|
|
358
366
|
}}
|
|
@@ -660,10 +668,10 @@ function GridComponent(props) {
|
|
|
660
668
|
...propsToPass,
|
|
661
669
|
};
|
|
662
670
|
|
|
663
|
-
if (!config.w && !config.flex) {
|
|
671
|
+
if (!(config.w || config.width) && !config.flex) {
|
|
664
672
|
// Neither is set
|
|
665
673
|
config.w = 100; // default
|
|
666
|
-
} else if (config.flex && config.width) {
|
|
674
|
+
} else if (config.flex && (config.w || config.width)) {
|
|
667
675
|
// Both are set. Width overrules flex.
|
|
668
676
|
delete config.flex;
|
|
669
677
|
}
|
|
@@ -729,7 +737,7 @@ function GridComponent(props) {
|
|
|
729
737
|
|
|
730
738
|
}, [selectorId, selectorSelected]);
|
|
731
739
|
|
|
732
|
-
const footerToolbarItemComponents = useMemo(() => getFooterToolbarItems(), [additionalToolbarButtons,
|
|
740
|
+
const footerToolbarItemComponents = useMemo(() => getFooterToolbarItems(), [additionalToolbarButtons, isDragMode]);
|
|
733
741
|
|
|
734
742
|
if (!isReady) {
|
|
735
743
|
return null;
|
|
@@ -741,6 +749,9 @@ function GridComponent(props) {
|
|
|
741
749
|
if (showHeaders) {
|
|
742
750
|
rowData.unshift({ id: 'headerRow' });
|
|
743
751
|
}
|
|
752
|
+
if (inlineEditor) {
|
|
753
|
+
rowData.push({ id: 'inlineEditor' }); // make editor the last row so it can scroll with all other rows
|
|
754
|
+
}
|
|
744
755
|
const initialNumToRender = rowData.length || 10;
|
|
745
756
|
|
|
746
757
|
// headers & footers
|
|
@@ -768,7 +779,7 @@ function GridComponent(props) {
|
|
|
768
779
|
{topToolbar}
|
|
769
780
|
|
|
770
781
|
<Column w="100%" flex={1} borderTopWidth={isLoading ? 2 : 1} borderTopColor={isLoading ? '#f00' : 'trueGray.300'} onClick={() => {
|
|
771
|
-
if (!
|
|
782
|
+
if (!isDragMode && !isInlineEditorShown) {
|
|
772
783
|
deselectAll();
|
|
773
784
|
}
|
|
774
785
|
}}>
|
|
@@ -781,8 +792,8 @@ function GridComponent(props) {
|
|
|
781
792
|
nestedScrollEnabled={true}
|
|
782
793
|
contentContainerStyle={{
|
|
783
794
|
overflow: 'auto',
|
|
784
|
-
borderWidth:
|
|
785
|
-
borderColor:
|
|
795
|
+
borderWidth: isDragMode ? styles.REORDER_BORDER_WIDTH : 0,
|
|
796
|
+
borderColor: isDragMode ? styles.REORDER_BORDER_COLOR : null,
|
|
786
797
|
borderStyle: styles.REORDER_BORDER_STYLE,
|
|
787
798
|
flex: 1,
|
|
788
799
|
}}
|
|
@@ -40,6 +40,7 @@ export default function GridHeaderRow(props) {
|
|
|
40
40
|
setSelection,
|
|
41
41
|
gridRef,
|
|
42
42
|
isHovered,
|
|
43
|
+
isInlineEditorShown,
|
|
43
44
|
} = props,
|
|
44
45
|
styles = UiGlobals.styles,
|
|
45
46
|
sortFn = Repository && Repository.getSortFn(),
|
|
@@ -316,6 +317,10 @@ export default function GridHeaderRow(props) {
|
|
|
316
317
|
}
|
|
317
318
|
}
|
|
318
319
|
|
|
320
|
+
if (isInlineEditorShown) {
|
|
321
|
+
propsToPass.minWidth = styles.INLINE_EDITOR_MIN_WIDTH;
|
|
322
|
+
}
|
|
323
|
+
|
|
319
324
|
return <Pressable
|
|
320
325
|
key={ix}
|
|
321
326
|
onPress={(e) => {
|
|
@@ -453,6 +458,7 @@ export default function GridHeaderRow(props) {
|
|
|
453
458
|
isSortDirectionAsc,
|
|
454
459
|
sortFn,
|
|
455
460
|
sortField,
|
|
461
|
+
isInlineEditorShown,
|
|
456
462
|
]);
|
|
457
463
|
}
|
|
458
464
|
|
|
@@ -23,6 +23,7 @@ export default function GridRow(props) {
|
|
|
23
23
|
hideNavColumn,
|
|
24
24
|
bg,
|
|
25
25
|
item,
|
|
26
|
+
isInlineEditorShown,
|
|
26
27
|
} = props,
|
|
27
28
|
styles = UiGlobals.styles,
|
|
28
29
|
isPhantom = item.isPhantom,
|
|
@@ -44,6 +45,10 @@ export default function GridRow(props) {
|
|
|
44
45
|
propsToPass.p = 1;
|
|
45
46
|
propsToPass.justifyContent = 'center';
|
|
46
47
|
|
|
48
|
+
if (isInlineEditorShown) {
|
|
49
|
+
propsToPass.minWidth = styles.INLINE_EDITOR_MIN_WIDTH;
|
|
50
|
+
}
|
|
51
|
+
|
|
47
52
|
let value;
|
|
48
53
|
if (_.isPlainObject(config)) {
|
|
49
54
|
if (config.renderer) {
|
|
@@ -64,6 +69,16 @@ export default function GridRow(props) {
|
|
|
64
69
|
'showDragHandles',
|
|
65
70
|
]);
|
|
66
71
|
|
|
72
|
+
if (!extraProps._web) {
|
|
73
|
+
extraProps._web = {};
|
|
74
|
+
}
|
|
75
|
+
if (!extraProps._web.style) {
|
|
76
|
+
extraProps._web.style = {};
|
|
77
|
+
}
|
|
78
|
+
extraProps._web.style = {
|
|
79
|
+
userSelect: 'none',
|
|
80
|
+
};
|
|
81
|
+
|
|
67
82
|
return <Row key={key} {...propsToPass} {...extraProps}>{config.renderer(item)}</Row>;
|
|
68
83
|
}
|
|
69
84
|
if (config.fieldName) {
|
|
@@ -98,7 +113,9 @@ export default function GridRow(props) {
|
|
|
98
113
|
overflow="hidden"
|
|
99
114
|
textOverflow="ellipsis"
|
|
100
115
|
alignSelf="center"
|
|
101
|
-
style={{
|
|
116
|
+
style={{
|
|
117
|
+
userSelect: 'none',
|
|
118
|
+
}}
|
|
102
119
|
fontSize={styles.GRID_CELL_FONTSIZE}
|
|
103
120
|
px={styles.GRID_CELL_PX}
|
|
104
121
|
py={styles.GRID_CELL_PY}
|
|
@@ -141,6 +158,7 @@ export default function GridRow(props) {
|
|
|
141
158
|
item,
|
|
142
159
|
isPhantom,
|
|
143
160
|
hash, // this is an easy way to determine if the data has changed and the item needs to be rerendered
|
|
161
|
+
isInlineEditorShown,
|
|
144
162
|
]);
|
|
145
163
|
}
|
|
146
164
|
|
|
@@ -64,6 +64,12 @@ export default function withContextMenu(WrappedComponent) {
|
|
|
64
64
|
};
|
|
65
65
|
icon = React.cloneElement(icon, {...iconProps});
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
// <div style={{
|
|
69
|
+
// userSelect: 'none',
|
|
70
|
+
// }}>
|
|
71
|
+
// </div>
|
|
72
|
+
|
|
67
73
|
return <Pressable
|
|
68
74
|
key={ix}
|
|
69
75
|
onPress={() => {
|
|
@@ -79,6 +85,10 @@ export default function withContextMenu(WrappedComponent) {
|
|
|
79
85
|
bg: '#ffc',
|
|
80
86
|
}}
|
|
81
87
|
isDisabled={isDisabled}
|
|
88
|
+
style={{
|
|
89
|
+
userSelect: 'none',
|
|
90
|
+
}}
|
|
91
|
+
userSelect="none"
|
|
82
92
|
>
|
|
83
93
|
{icon}
|
|
84
94
|
<Text
|
|
@@ -86,6 +96,10 @@ export default function withContextMenu(WrappedComponent) {
|
|
|
86
96
|
color={isDisabled ? 'disabled' : 'trueGray.800'}
|
|
87
97
|
numberOfLines={1}
|
|
88
98
|
ellipsizeMode="head"
|
|
99
|
+
style={{
|
|
100
|
+
userSelect: 'none',
|
|
101
|
+
}}
|
|
102
|
+
userSelect="none"
|
|
89
103
|
>{text}</Text>
|
|
90
104
|
</Pressable>;
|
|
91
105
|
});
|
|
@@ -96,6 +110,7 @@ export default function withContextMenu(WrappedComponent) {
|
|
|
96
110
|
flex={1}
|
|
97
111
|
py={2}
|
|
98
112
|
px={4}
|
|
113
|
+
userSelect="none"
|
|
99
114
|
>id: {selection?.[0]?.id}</Text>);
|
|
100
115
|
}
|
|
101
116
|
setContextMenuItemComponents(contextMenuItemComponents);
|
|
@@ -47,10 +47,10 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
47
47
|
hideAlert,
|
|
48
48
|
} = props,
|
|
49
49
|
listeners = useRef({}),
|
|
50
|
+
editorStateRef = useRef(),
|
|
50
51
|
[currentRecord, setCurrentRecord] = useState(null),
|
|
51
52
|
[isEditorShown, setIsEditorShown] = useState(false),
|
|
52
53
|
[isEditorViewOnly, setIsEditorViewOnly] = useState(false),
|
|
53
|
-
[isModalShown, setIsModalShown] = useState(false),
|
|
54
54
|
[lastSelection, setLastSelection] = useState(),
|
|
55
55
|
getListeners = () => {
|
|
56
56
|
return listeners.current;
|
|
@@ -115,7 +115,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
115
115
|
setEditorMode(EDITOR_MODE__EDIT);
|
|
116
116
|
setIsEditorShown(true);
|
|
117
117
|
},
|
|
118
|
-
onDelete = async () => {
|
|
118
|
+
onDelete = async (cb) => {
|
|
119
119
|
if (getListeners().onBeforeDelete) {
|
|
120
120
|
const listenerResult = await getListeners().onBeforeDelete();
|
|
121
121
|
if (listenerResult === false) {
|
|
@@ -126,7 +126,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
126
126
|
isSingle = selection.length === 1,
|
|
127
127
|
firstSelection = selection[0],
|
|
128
128
|
isTree = firstSelection?.isTree,
|
|
129
|
-
hasChildren = firstSelection?.hasChildren,
|
|
129
|
+
hasChildren = isTree ? firstSelection?.hasChildren : false,
|
|
130
130
|
isPhantom = firstSelection?.isPhantom;
|
|
131
131
|
|
|
132
132
|
if (isSingle && isTree && hasChildren) {
|
|
@@ -135,10 +135,10 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
135
135
|
message: 'The node you have selected for deletion has children. ' +
|
|
136
136
|
'Should these children be moved up to this node\'s parent, or be deleted?',
|
|
137
137
|
buttons: [
|
|
138
|
-
<Button colorScheme="danger" onPress={onMoveChildren} key="moveBtn">
|
|
138
|
+
<Button colorScheme="danger" onPress={() => onMoveChildren(cb)} key="moveBtn">
|
|
139
139
|
Move Children
|
|
140
140
|
</Button>,
|
|
141
|
-
<Button colorScheme="danger" onPress={onDeleteChildren} key="deleteBtn">
|
|
141
|
+
<Button colorScheme="danger" onPress={() => onDeleteChildren(cb)} key="deleteBtn">
|
|
142
142
|
Delete Children
|
|
143
143
|
</Button>
|
|
144
144
|
],
|
|
@@ -146,21 +146,21 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
146
146
|
});
|
|
147
147
|
} else
|
|
148
148
|
if (isSingle && isPhantom) {
|
|
149
|
-
deleteRecord();
|
|
149
|
+
deleteRecord(cb);
|
|
150
150
|
} else {
|
|
151
151
|
const identifier = getRecordIdentifier(selection);
|
|
152
|
-
confirm('Are you sure you want to delete the ' + identifier, deleteRecord);
|
|
152
|
+
confirm('Are you sure you want to delete the ' + identifier, () => deleteRecord(cb));
|
|
153
153
|
}
|
|
154
154
|
},
|
|
155
|
-
onMoveChildren = () => {
|
|
155
|
+
onMoveChildren = (cb) => {
|
|
156
156
|
hideAlert();
|
|
157
|
-
deleteRecord(true);
|
|
157
|
+
deleteRecord(true, cb);
|
|
158
158
|
},
|
|
159
|
-
onDeleteChildren = () => {
|
|
159
|
+
onDeleteChildren = (cb) => {
|
|
160
160
|
hideAlert();
|
|
161
|
-
deleteRecord();
|
|
161
|
+
deleteRecord(false, cb);
|
|
162
162
|
},
|
|
163
|
-
deleteRecord = async (moveSubtreeUp) => {
|
|
163
|
+
deleteRecord = async (moveSubtreeUp, cb) => {
|
|
164
164
|
if (getListeners().onBeforeDeleteSave) {
|
|
165
165
|
await getListeners().onBeforeDeleteSave(selection);
|
|
166
166
|
}
|
|
@@ -172,6 +172,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
172
172
|
if (getListeners().onAfterDelete) {
|
|
173
173
|
await getListeners().onAfterDelete(selection);
|
|
174
174
|
}
|
|
175
|
+
if (cb) {
|
|
176
|
+
cb();
|
|
177
|
+
}
|
|
175
178
|
},
|
|
176
179
|
viewRecord = async () => {
|
|
177
180
|
if (!userCanView) {
|
|
@@ -237,31 +240,32 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
237
240
|
await getListeners().onAfterEdit(what);
|
|
238
241
|
}
|
|
239
242
|
},
|
|
240
|
-
onEditorCancel =
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
onEditorCancel = () => {
|
|
244
|
+
async function doIt() {
|
|
245
|
+
const
|
|
246
|
+
isSingle = selection.length === 1,
|
|
247
|
+
isPhantom = selection[0] && selection[0].isPhantom;
|
|
248
|
+
if (isSingle && isPhantom) {
|
|
249
|
+
await deleteRecord();
|
|
250
|
+
}
|
|
251
|
+
setEditorMode(EDITOR_MODE__VIEW);
|
|
252
|
+
setIsEditorShown(false);
|
|
253
|
+
}
|
|
254
|
+
const formState = editorStateRef.current;
|
|
255
|
+
if (!_.isEmpty(formState.dirtyFields)) {
|
|
256
|
+
confirm('This record has unsaved changes. Are you sure you want to cancel editing? Changes will be lost.', doIt);
|
|
257
|
+
} else {
|
|
258
|
+
doIt();
|
|
246
259
|
}
|
|
247
|
-
setEditorMode(EDITOR_MODE__VIEW);
|
|
248
|
-
setIsEditorShown(false);
|
|
249
260
|
},
|
|
250
261
|
onEditorClose = () => {
|
|
251
262
|
setIsEditorShown(false);
|
|
252
263
|
},
|
|
253
264
|
onEditorDelete = async () => {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
await deleteRecord();
|
|
259
|
-
setEditorMode(EDITOR_MODE__VIEW);
|
|
260
|
-
setIsEditorShown(false);
|
|
261
|
-
|
|
262
|
-
if (getListeners().onAfterDelete) {
|
|
263
|
-
await getListeners().onAfterDelete(selection);
|
|
264
|
-
}
|
|
265
|
+
onDelete(() => {
|
|
266
|
+
setEditorMode(EDITOR_MODE__VIEW);
|
|
267
|
+
setIsEditorShown(false);
|
|
268
|
+
});
|
|
265
269
|
},
|
|
266
270
|
calculateEditorMode = () => {
|
|
267
271
|
let mode = EDITOR_MODE__VIEW;
|
|
@@ -279,6 +283,20 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
279
283
|
}
|
|
280
284
|
}
|
|
281
285
|
return mode;
|
|
286
|
+
},
|
|
287
|
+
onEditMode = () => {
|
|
288
|
+
setEditorMode(EDITOR_MODE__EDIT);
|
|
289
|
+
},
|
|
290
|
+
onViewMode = () => {
|
|
291
|
+
function doIt() {
|
|
292
|
+
setEditorMode(EDITOR_MODE__VIEW);
|
|
293
|
+
}
|
|
294
|
+
const formState = editorStateRef.current;
|
|
295
|
+
if (!_.isEmpty(formState.dirtyFields)) {
|
|
296
|
+
confirm('This record has unsaved changes. Are you sure you want to switch to "View" mode? Changes will be lost.', doIt);
|
|
297
|
+
} else {
|
|
298
|
+
doIt();
|
|
299
|
+
}
|
|
282
300
|
};
|
|
283
301
|
|
|
284
302
|
useEffect(() => {
|
|
@@ -301,7 +319,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
301
319
|
isEditorShown={isEditorShown}
|
|
302
320
|
isEditorViewOnly={isEditorViewOnly}
|
|
303
321
|
editorMode={editorMode}
|
|
304
|
-
|
|
322
|
+
onEditMode={onEditMode}
|
|
323
|
+
onViewMode={onViewMode}
|
|
324
|
+
editorStateRef={editorStateRef}
|
|
305
325
|
setIsEditorShown={setIsEditorShown}
|
|
306
326
|
onAdd={(!userCanEdit || disableAdd) ? null : onAdd}
|
|
307
327
|
onEdit={(!userCanEdit || disableEdit) ? null : onEdit}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef, } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
Box,
|
|
3
4
|
Column,
|
|
4
5
|
Modal,
|
|
5
6
|
Row,
|
|
@@ -28,6 +29,7 @@ export default function withInlineEditor(WrappedComponent) {
|
|
|
28
29
|
onEditorCancel,
|
|
29
30
|
onEditorSave,
|
|
30
31
|
onEditorClose,
|
|
32
|
+
editorStateRef,
|
|
31
33
|
|
|
32
34
|
// withSelection
|
|
33
35
|
selection,
|
|
@@ -36,6 +38,7 @@ export default function withInlineEditor(WrappedComponent) {
|
|
|
36
38
|
Repository,
|
|
37
39
|
} = props,
|
|
38
40
|
styles = UiGlobals.styles,
|
|
41
|
+
maskRef = useRef(),
|
|
39
42
|
inlineEditorRef = useRef(),
|
|
40
43
|
[localColumnsConfig, setLocalColumnsConfig] = useState([]),
|
|
41
44
|
[currentRow, setCurrentRow] = useState(),
|
|
@@ -56,35 +59,52 @@ export default function withInlineEditor(WrappedComponent) {
|
|
|
56
59
|
},
|
|
57
60
|
moveEditor = (currentRow) => {
|
|
58
61
|
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
rowBounds = currentRow.getBoundingClientRect(),
|
|
63
|
+
editor = inlineEditorRef.current,
|
|
64
|
+
editorStyle = editor.style,
|
|
65
|
+
editorBounds = editor.parentElement.getBoundingClientRect(), // reference parentElement, because this doesn't change based on last moveEditor call
|
|
66
|
+
delta = editorBounds.top - rowBounds.top;
|
|
67
|
+
|
|
68
|
+
editorStyle.top = (-1 * delta) -20 + 'px';
|
|
64
69
|
};
|
|
65
70
|
|
|
71
|
+
if (isEditorShown && selection.length < 1) {
|
|
72
|
+
throw new Error('Lost the selection!');
|
|
73
|
+
}
|
|
66
74
|
if (isEditorShown && selection.length !== 1) {
|
|
67
75
|
throw new Error('Can only edit one at a time with inline editor!');
|
|
68
76
|
}
|
|
69
77
|
if (UiGlobals.mode === UI_MODE_REACT_NATIVE) {
|
|
70
78
|
throw new Error('Not yet implemented for RN.');
|
|
71
79
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
|
|
81
|
+
let inlineEditor = null;
|
|
82
|
+
if (useEditor && Repository) {
|
|
83
|
+
inlineEditor = <>
|
|
84
|
+
{isEditorShown && <Box
|
|
85
|
+
ref={maskRef}
|
|
86
|
+
position="fixed"
|
|
87
|
+
w="100vw"
|
|
88
|
+
h="100vh"
|
|
89
|
+
top="0"
|
|
90
|
+
left="0"
|
|
91
|
+
bg="#000"
|
|
92
|
+
opacity={0.35}
|
|
93
|
+
zIndex={0}
|
|
94
|
+
onClick={(e) => {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
e.stopPropagation();
|
|
97
|
+
onEditorCancel();
|
|
98
|
+
}}
|
|
99
|
+
></Box>}
|
|
100
|
+
<Column
|
|
101
|
+
ref={inlineEditorRef}
|
|
102
|
+
position="absolute"
|
|
103
|
+
zIndex={10}
|
|
104
|
+
>
|
|
86
105
|
{isEditorShown && <Form
|
|
87
|
-
editorType={EDITOR_TYPE__INLINE}
|
|
106
|
+
editorType={EDITOR_TYPE__INLINE}
|
|
107
|
+
editorStateRef={editorStateRef}
|
|
88
108
|
record={selection[0]}
|
|
89
109
|
Repository={Repository}
|
|
90
110
|
isMultiple={selection.length > 1}
|
|
@@ -113,7 +133,15 @@ export default function withInlineEditor(WrappedComponent) {
|
|
|
113
133
|
px={0}
|
|
114
134
|
/>}
|
|
115
135
|
</Column>
|
|
116
|
-
|
|
117
|
-
|
|
136
|
+
</>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return <WrappedComponent
|
|
140
|
+
{...props}
|
|
141
|
+
onChangeColumnsConfig={onChangeColumnsConfig}
|
|
142
|
+
onEditorRowClick={onRowClick}
|
|
143
|
+
inlineEditor={inlineEditor}
|
|
144
|
+
isInlineEditorShown={isEditorShown}
|
|
145
|
+
/>;
|
|
118
146
|
});
|
|
119
147
|
}
|
|
@@ -24,6 +24,12 @@ const presetButtons = [
|
|
|
24
24
|
|
|
25
25
|
export default function withPresetButtons(WrappedComponent, isGrid = false) {
|
|
26
26
|
return (props) => {
|
|
27
|
+
|
|
28
|
+
if (props.disablePresetButtons) {
|
|
29
|
+
// bypass everything
|
|
30
|
+
return <WrappedComponent {...props} />;
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
const {
|
|
28
34
|
// extract and pass
|
|
29
35
|
contextMenuItems,
|
package/src/Components/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import BooleanCombo from './Form/Field/Combo/BooleanCombo.js';
|
|
|
13
13
|
import CheckboxGroup from './Form/Field/CheckboxGroup/CheckboxGroup.js';
|
|
14
14
|
import Color from './Form/Field/Color.js';
|
|
15
15
|
import Combo from './Form/Field/Combo/Combo.js';
|
|
16
|
-
// import ComboEditor from '../Components/Form/Field/Combo/
|
|
16
|
+
// import { ComboEditor } from '../Components/Form/Field/Combo/Combo.js';
|
|
17
17
|
import Container from './Container/Container.js';
|
|
18
18
|
import DataMgt from './Screens/DataMgt.js';
|
|
19
19
|
import Date from './Form/Field/Date.js';
|
package/src/Constants/Styles.js
CHANGED
|
@@ -67,6 +67,7 @@ const defaults = {
|
|
|
67
67
|
ICON_BUTTON_BG_DISABLED: 'trueGray.200',
|
|
68
68
|
ICON_BUTTON_BG_HOVER: '#000:alpha.20',
|
|
69
69
|
ICON_BUTTON_BG_PRESSED: '#000:alpha.30',
|
|
70
|
+
INLINE_EDITOR_MIN_WIDTH: 150,
|
|
70
71
|
PANEL_FOOTER_BG: 'primary.100', // :alpha.50
|
|
71
72
|
PANEL_HEADER_BG: 'primary.100',
|
|
72
73
|
PANEL_HEADER_BG_VERTICAL: 'primary.100',
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import withAlert from '../../../Hoc/withAlert.js';
|
|
2
|
-
import withData from '../../../Hoc/withData.js';
|
|
3
|
-
import withPresetButtons from '../../../Hoc/withPresetButtons.js';
|
|
4
|
-
import withSelection from '../../../Hoc/withSelection.js';
|
|
5
|
-
import withValue from '../../../Hoc/withValue.js';
|
|
6
|
-
import withWindowedEditor from '../../../Hoc/withWindowedEditor.js';
|
|
7
|
-
import { Combo } from './Combo.js';
|
|
8
|
-
|
|
9
|
-
export default
|
|
10
|
-
// withAlert(
|
|
11
|
-
withData(
|
|
12
|
-
withValue(
|
|
13
|
-
withSelection(
|
|
14
|
-
withWindowedEditor(
|
|
15
|
-
withPresetButtons(
|
|
16
|
-
Combo
|
|
17
|
-
)
|
|
18
|
-
)
|
|
19
|
-
)
|
|
20
|
-
)
|
|
21
|
-
);
|
|
22
|
-
//);
|