@qoretechnologies/reqraft 0.10.51 → 0.10.52
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/dist/components/form/expressions/ExpressionField.d.ts +4 -2
- package/dist/components/form/expressions/ExpressionField.d.ts.map +1 -1
- package/dist/components/form/expressions/ExpressionField.js +2 -2
- package/dist/components/form/expressions/ExpressionField.js.map +1 -1
- package/dist/components/form/expressions/builder/argumentWrapper.d.ts +13 -2
- package/dist/components/form/expressions/builder/argumentWrapper.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/argumentWrapper.js +61 -3
- package/dist/components/form/expressions/builder/argumentWrapper.js.map +1 -1
- package/dist/components/form/expressions/builder/index.d.ts +4 -2
- package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/index.js +96 -15
- package/dist/components/form/expressions/builder/index.js.map +1 -1
- package/dist/components/form/expressions/builder/moveToPositionStrip.d.ts +19 -0
- package/dist/components/form/expressions/builder/moveToPositionStrip.d.ts.map +1 -0
- package/dist/components/form/expressions/builder/moveToPositionStrip.js +62 -0
- package/dist/components/form/expressions/builder/moveToPositionStrip.js.map +1 -0
- package/dist/components/form/expressions/mockExpressions.d.ts.map +1 -1
- package/dist/components/form/expressions/mockExpressions.js +18 -0
- package/dist/components/form/expressions/mockExpressions.js.map +1 -1
- package/dist/components/form/expressions/types.d.ts +8 -0
- package/dist/components/form/expressions/types.d.ts.map +1 -1
- package/dist/components/form/fields/schema-definition/CatalogNodeEditor.js +3 -2
- package/dist/components/form/fields/schema-definition/CatalogNodeEditor.js.map +1 -1
- package/dist/components/form/fields/schema-definition/MigrationsTab.js +3 -3
- package/dist/components/form/fields/schema-definition/MigrationsTab.js.map +1 -1
- package/dist/components/form/fields/schema-definition/helpers.d.ts +0 -2
- package/dist/components/form/fields/schema-definition/helpers.d.ts.map +1 -1
- package/dist/components/form/fields/schema-definition/helpers.js +1 -20
- package/dist/components/form/fields/schema-definition/helpers.js.map +1 -1
- package/dist/components/form/fields/template/TemplateField.d.ts +33 -4
- package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
- package/dist/components/form/fields/template/TemplateField.js +57 -7
- package/dist/components/form/fields/template/TemplateField.js.map +1 -1
- package/dist/helpers/common.d.ts +4 -0
- package/dist/helpers/common.d.ts.map +1 -1
- package/dist/helpers/common.js +30 -1
- package/dist/helpers/common.js.map +1 -1
- package/package.json +1 -1
- package/src/components/form/expressions/ExpressionField.tsx +5 -1
- package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +386 -0
- package/src/components/form/expressions/builder/argumentWrapper.tsx +137 -11
- package/src/components/form/expressions/builder/index.tsx +153 -5
- package/src/components/form/expressions/builder/moveToPositionStrip.tsx +156 -0
- package/src/components/form/expressions/mockExpressions.ts +18 -0
- package/src/components/form/expressions/types.ts +10 -0
- package/src/components/form/fields/schema-definition/CatalogNodeEditor.tsx +1 -1
- package/src/components/form/fields/schema-definition/MigrationsTab.tsx +1 -1
- package/src/components/form/fields/schema-definition/helpers.ts +0 -9
- package/src/components/form/fields/template/TemplateField.tsx +112 -4
- package/src/helpers/common.ts +31 -0
|
@@ -15,12 +15,14 @@ import {
|
|
|
15
15
|
TReqoreHexColor,
|
|
16
16
|
} from '@qoretechnologies/reqore/dist/components/Effect';
|
|
17
17
|
import { IReqorePanelAction } from '@qoretechnologies/reqore/dist/components/Panel';
|
|
18
|
+
import { IReqoreIconName } from '@qoretechnologies/reqore/dist/types/icons';
|
|
18
19
|
import { IReqoreFormTemplates } from '@qoretechnologies/reqore/dist/components/Textarea';
|
|
19
20
|
import { TQorusType } from '@qoretechnologies/ts-toolkit';
|
|
20
21
|
import { clone, cloneDeep, get, isArray, set, size, unset } from 'lodash';
|
|
21
22
|
import { darken, rgba } from 'polished';
|
|
22
|
-
import React, { useCallback, useMemo, useState } from 'react';
|
|
23
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
23
24
|
import styled from 'styled-components';
|
|
25
|
+
import { moveItem } from '../../../../helpers/common';
|
|
24
26
|
import { areQorusTypesCompatible, getArgumentType } from '../../../../helpers/expressions';
|
|
25
27
|
import { findTemplate } from '../../../../helpers/templates';
|
|
26
28
|
import { validateField, validateFieldWithResult } from '../../../../helpers/validations';
|
|
@@ -29,12 +31,18 @@ import { useReqraftStorage } from '../../../../hooks/useStorage/useStorage';
|
|
|
29
31
|
import { useTemplates } from '../../../../hooks/useTemplates';
|
|
30
32
|
import { AutoFormField as auto } from '../../fields/auto/AutoFormField';
|
|
31
33
|
import { SelectFormField as Select } from '../../fields/select/Select';
|
|
32
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
ITemplateMenuActions,
|
|
36
|
+
TCustomTemplateItems,
|
|
37
|
+
TemplateField,
|
|
38
|
+
} from '../../fields/template/TemplateField';
|
|
33
39
|
import {
|
|
34
40
|
ExpressionDefaultValue,
|
|
35
41
|
IExpression,
|
|
36
42
|
IExpressionSchema,
|
|
37
43
|
IExpressionSchemaArg,
|
|
44
|
+
TExpressionReorder,
|
|
45
|
+
TExpressionReorderSurface,
|
|
38
46
|
} from '../types';
|
|
39
47
|
import { useExpressions } from '../useExpressions';
|
|
40
48
|
import { ExpressionArgumentDetail } from './argumentDetail';
|
|
@@ -42,6 +50,7 @@ import { ExpressionBuilderArgumentWrapper } from './argumentWrapper';
|
|
|
42
50
|
import { ConfirmMismatchedTypesModal } from './confirmMismatchedTypesModal';
|
|
43
51
|
import { ConfirmUnsupportedTypeModal } from './confirmUnsupportedTypeModal';
|
|
44
52
|
import { ExpressionItem } from './item';
|
|
53
|
+
import { ExpressionMoveToPositionStrip } from './moveToPositionStrip';
|
|
45
54
|
import { ExpressionRenderTemplate } from './renderTemplate';
|
|
46
55
|
|
|
47
56
|
const noopUseRegisterHintView = (...args: unknown[]): void => {
|
|
@@ -60,6 +69,8 @@ export interface IExpressionBuilderProps {
|
|
|
60
69
|
group?: '&&' | '||';
|
|
61
70
|
onChange?: (value: IExpression, remove?: boolean) => void;
|
|
62
71
|
onValueChange?: (value: IExpression, path: string, remove?: boolean) => void;
|
|
72
|
+
/** Operand reordering for varargs expressions — see `TExpressionReorder`. Default `true`. */
|
|
73
|
+
reorder?: TExpressionReorder;
|
|
63
74
|
readOnly?: boolean;
|
|
64
75
|
id?: string;
|
|
65
76
|
expressions?: IExpressionSchema[];
|
|
@@ -416,6 +427,63 @@ export const Expression = ({
|
|
|
416
427
|
[onValueChange, JSON.stringify(value), path]
|
|
417
428
|
);
|
|
418
429
|
|
|
430
|
+
const reorderSurfaces = useMemo((): TExpressionReorderSurface[] => {
|
|
431
|
+
const reorder = props.reorder ?? true;
|
|
432
|
+
|
|
433
|
+
if (reorder === false) {
|
|
434
|
+
return [];
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return reorder === true ? ['dragHandle', 'overflowMenu'] : reorder;
|
|
438
|
+
}, [props.reorder]);
|
|
439
|
+
// Bumped on every move and folded into the operand keys: the operands are
|
|
440
|
+
// keyed by slot, and a slot's TemplateField keeps template / text-mode
|
|
441
|
+
// state that only resyncs one way — remounting hands every slot a field
|
|
442
|
+
// seeded from the operand that now lives there.
|
|
443
|
+
const [argsGeneration, setArgsGeneration] = useState(0);
|
|
444
|
+
const dragArg = useRef<number | null>(null);
|
|
445
|
+
const [dragOverArg, setDragOverArg] = useState<number | null>(null);
|
|
446
|
+
|
|
447
|
+
const moveVarArg = useCallback(
|
|
448
|
+
(from: number, to: number) => {
|
|
449
|
+
setArgsGeneration((generation) => generation + 1);
|
|
450
|
+
onValueChange(
|
|
451
|
+
{
|
|
452
|
+
...value,
|
|
453
|
+
value: {
|
|
454
|
+
...value.value,
|
|
455
|
+
args: moveItem(value.value.args, from, to),
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
path
|
|
459
|
+
);
|
|
460
|
+
},
|
|
461
|
+
[onValueChange, JSON.stringify(value), path]
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
const handleArgDragStart = useCallback((index: number) => {
|
|
465
|
+
dragArg.current = index;
|
|
466
|
+
}, []);
|
|
467
|
+
const handleArgDragOver = useCallback((index: number) => {
|
|
468
|
+
setDragOverArg((current) => (current === index ? current : index));
|
|
469
|
+
}, []);
|
|
470
|
+
const handleArgDragEnd = useCallback(() => {
|
|
471
|
+
dragArg.current = null;
|
|
472
|
+
setDragOverArg(null);
|
|
473
|
+
}, []);
|
|
474
|
+
const handleArgDrop = useCallback(
|
|
475
|
+
(index: number) => {
|
|
476
|
+
const from = dragArg.current;
|
|
477
|
+
|
|
478
|
+
if (from !== null && from !== index) {
|
|
479
|
+
moveVarArg(from, index);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
handleArgDragEnd();
|
|
483
|
+
},
|
|
484
|
+
[moveVarArg, handleArgDragEnd]
|
|
485
|
+
);
|
|
486
|
+
|
|
419
487
|
const updateMultipleArgs = useCallback(
|
|
420
488
|
(args: IExpression[]) => {
|
|
421
489
|
onValueChange(
|
|
@@ -478,6 +546,59 @@ export const Expression = ({
|
|
|
478
546
|
? firstArgSchema?.ui_type
|
|
479
547
|
: firstArgument?.type || type || 'context';
|
|
480
548
|
let restOfArgs = selectedExpression?.args.slice(1);
|
|
549
|
+
const argCount = size(value.value?.args);
|
|
550
|
+
const canReorderArgs =
|
|
551
|
+
!!selectedExpression?.varargs && argCount > 1 && !readOnly && size(reorderSurfaces) > 0;
|
|
552
|
+
const argReorderSurfaces = canReorderArgs ? reorderSurfaces : undefined;
|
|
553
|
+
const isDragOverArg = (index: number) =>
|
|
554
|
+
dragOverArg === index && dragArg.current !== null && dragArg.current !== index;
|
|
555
|
+
|
|
556
|
+
// The `⋮` menu's "Move Argument" section, one per operand. The wording is
|
|
557
|
+
// axis-free on purpose — the operands wrap into a column on narrow screens.
|
|
558
|
+
const argMenuActions = useMemo((): ITemplateMenuActions[] | undefined => {
|
|
559
|
+
if (!canReorderArgs || !reorderSurfaces.includes('overflowMenu')) {
|
|
560
|
+
return undefined;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const moves: [string, string, IReqoreIconName, (index: number) => number][] = [
|
|
564
|
+
['before', 'Move before', 'ArrowGoBackLine', (index) => index - 1],
|
|
565
|
+
['after', 'Move after', 'ArrowGoForwardLine', (index) => index + 1],
|
|
566
|
+
['start', 'Move to start', 'SkipBackLine', () => 0],
|
|
567
|
+
['end', 'Move to end', 'SkipForwardLine', () => argCount - 1],
|
|
568
|
+
];
|
|
569
|
+
|
|
570
|
+
return Array.from({ length: argCount }, (_, index) => ({
|
|
571
|
+
label: 'Move Argument',
|
|
572
|
+
icon: 'ArrowLeftRightLine',
|
|
573
|
+
items: [
|
|
574
|
+
...moves.map(([key, label, icon, target]) => {
|
|
575
|
+
const to = target(index);
|
|
576
|
+
|
|
577
|
+
return {
|
|
578
|
+
label,
|
|
579
|
+
icon,
|
|
580
|
+
className: `expression-arg-move-${key}`,
|
|
581
|
+
disabled: to === index || to < 0 || to >= argCount,
|
|
582
|
+
onClick: () => moveVarArg(index, to),
|
|
583
|
+
};
|
|
584
|
+
}),
|
|
585
|
+
// Any position in one pick — what a ten-part concat needs.
|
|
586
|
+
{
|
|
587
|
+
isCustom: true as const,
|
|
588
|
+
content: (closePopover) => (
|
|
589
|
+
<ExpressionMoveToPositionStrip
|
|
590
|
+
index={index}
|
|
591
|
+
count={argCount}
|
|
592
|
+
onMoveTo={(to) => {
|
|
593
|
+
moveVarArg(index, to);
|
|
594
|
+
closePopover?.();
|
|
595
|
+
}}
|
|
596
|
+
/>
|
|
597
|
+
),
|
|
598
|
+
},
|
|
599
|
+
],
|
|
600
|
+
}));
|
|
601
|
+
}, [canReorderArgs, reorderSurfaces, argCount, moveVarArg]);
|
|
481
602
|
|
|
482
603
|
if (selectedExpression?.varargs) {
|
|
483
604
|
restOfArgs = [
|
|
@@ -880,6 +1001,15 @@ export const Expression = ({
|
|
|
880
1001
|
schema={firstArgSchema}
|
|
881
1002
|
onTypeChange={handleUpdateTypeChange}
|
|
882
1003
|
readOnly={readOnly}
|
|
1004
|
+
reorder={argReorderSurfaces}
|
|
1005
|
+
argIndex={0}
|
|
1006
|
+
argCount={argCount}
|
|
1007
|
+
dragOver={isDragOverArg(0)}
|
|
1008
|
+
onMoveArg={moveVarArg}
|
|
1009
|
+
onArgDragStart={handleArgDragStart}
|
|
1010
|
+
onArgDragOver={handleArgDragOver}
|
|
1011
|
+
onArgDrop={handleArgDrop}
|
|
1012
|
+
onArgDragEnd={handleArgDragEnd}
|
|
883
1013
|
>
|
|
884
1014
|
<TemplateField
|
|
885
1015
|
component={auto}
|
|
@@ -905,7 +1035,7 @@ export const Expression = ({
|
|
|
905
1035
|
!firstArgSchema?.element_allowed_values
|
|
906
1036
|
}
|
|
907
1037
|
level={level + 1}
|
|
908
|
-
key={`${firstParamType}${selectedExpression?.name}`}
|
|
1038
|
+
key={`${argsGeneration}-${firstParamType}${selectedExpression?.name}`}
|
|
909
1039
|
type={firstParamType}
|
|
910
1040
|
defaultType={
|
|
911
1041
|
firstArgument?.allowed_values || firstArgument?.element_allowed_values
|
|
@@ -955,6 +1085,8 @@ export const Expression = ({
|
|
|
955
1085
|
);
|
|
956
1086
|
})
|
|
957
1087
|
}
|
|
1088
|
+
menuActions={argMenuActions?.[0]}
|
|
1089
|
+
reorder={props.reorder}
|
|
958
1090
|
expressions={props.expressions as any}
|
|
959
1091
|
expressions_url={expressionsUrl}
|
|
960
1092
|
/>
|
|
@@ -964,7 +1096,9 @@ export const Expression = ({
|
|
|
964
1096
|
)}
|
|
965
1097
|
{selectedExpression
|
|
966
1098
|
? restOfArgs?.map((arg, index) => (
|
|
967
|
-
<React.Fragment
|
|
1099
|
+
<React.Fragment
|
|
1100
|
+
key={`${argsGeneration}-${index}-${arg.ui_type}-${arg.display_name}`}
|
|
1101
|
+
>
|
|
968
1102
|
{arg?.label_before && <ExpressionArgumentDetail label={arg.label_before} />}
|
|
969
1103
|
<ExpressionBuilderArgumentWrapper
|
|
970
1104
|
readOnly={readOnly}
|
|
@@ -985,6 +1119,15 @@ export const Expression = ({
|
|
|
985
1119
|
removeVarArg(index + 1);
|
|
986
1120
|
}}
|
|
987
1121
|
hasMultipleArgs={selectedExpression.varargs && size(rest) > 1}
|
|
1122
|
+
reorder={argReorderSurfaces}
|
|
1123
|
+
argIndex={index + 1}
|
|
1124
|
+
argCount={argCount}
|
|
1125
|
+
dragOver={isDragOverArg(index + 1)}
|
|
1126
|
+
onMoveArg={moveVarArg}
|
|
1127
|
+
onArgDragStart={handleArgDragStart}
|
|
1128
|
+
onArgDragOver={handleArgDragOver}
|
|
1129
|
+
onArgDrop={handleArgDrop}
|
|
1130
|
+
onArgDragEnd={handleArgDragEnd}
|
|
988
1131
|
>
|
|
989
1132
|
<TemplateField
|
|
990
1133
|
minimal
|
|
@@ -995,7 +1138,7 @@ export const Expression = ({
|
|
|
995
1138
|
level={level + 1}
|
|
996
1139
|
allowFunctions={!arg?.allowed_values && !arg?.element_allowed_values}
|
|
997
1140
|
isFunction={rest[index]?.is_expression}
|
|
998
|
-
key={`${index}-${arg.ui_type}-${arg.display_name}-${level + 1}`}
|
|
1141
|
+
key={`${argsGeneration}-${index}-${arg.ui_type}-${arg.display_name}-${level + 1}`}
|
|
999
1142
|
// An empty argument has no value to take a type from; fall
|
|
1000
1143
|
// back to the type the catalogue declares for it, as the
|
|
1001
1144
|
// first-argument mount already does. Without this an
|
|
@@ -1043,6 +1186,8 @@ export const Expression = ({
|
|
|
1043
1186
|
type.name === 'context' ? undefined : (type.name as string)
|
|
1044
1187
|
);
|
|
1045
1188
|
})}
|
|
1189
|
+
menuActions={argMenuActions?.[index + 1]}
|
|
1190
|
+
reorder={props.reorder}
|
|
1046
1191
|
expressions={props.expressions as any}
|
|
1047
1192
|
expressions_url={expressionsUrl}
|
|
1048
1193
|
allowed_values={arg?.allowed_values}
|
|
@@ -1103,6 +1248,7 @@ export const ExpressionBuilder = ({
|
|
|
1103
1248
|
expressionsUrl,
|
|
1104
1249
|
serverHandled,
|
|
1105
1250
|
extraActions,
|
|
1251
|
+
reorder,
|
|
1106
1252
|
}: IExpressionBuilderProps) => {
|
|
1107
1253
|
const templates = useTemplates(!isChild, localTemplates);
|
|
1108
1254
|
const theme = useReqoreTheme();
|
|
@@ -1248,6 +1394,7 @@ export const ExpressionBuilder = ({
|
|
|
1248
1394
|
expressionsUrl={expressionsUrl}
|
|
1249
1395
|
serverHandled={serverHandled}
|
|
1250
1396
|
extraActions={extraActions}
|
|
1397
|
+
reorder={reorder}
|
|
1251
1398
|
/>
|
|
1252
1399
|
{index < size(value.value.args) - 1 && (
|
|
1253
1400
|
<ExpressionArgumentDetail
|
|
@@ -1285,6 +1432,7 @@ export const ExpressionBuilder = ({
|
|
|
1285
1432
|
id={level === 0 && index === 0 ? 'expression-builder' : undefined}
|
|
1286
1433
|
serverHandled={serverHandled}
|
|
1287
1434
|
extraActions={extraActions}
|
|
1435
|
+
reorder={reorder}
|
|
1288
1436
|
/>
|
|
1289
1437
|
</ReqoreErrorBoundary>
|
|
1290
1438
|
);
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ReqoreButton,
|
|
3
|
+
ReqoreControlGroup,
|
|
4
|
+
ReqoreDropdown,
|
|
5
|
+
ReqoreInput,
|
|
6
|
+
ReqoreMenuItem,
|
|
7
|
+
} from '@qoretechnologies/reqore';
|
|
8
|
+
import { memo, useState } from 'react';
|
|
9
|
+
import { ordinal } from '../../../../helpers/common';
|
|
10
|
+
|
|
11
|
+
/** Positions offered as one-click segments before the typed field takes over. */
|
|
12
|
+
export const QUICK_POSITIONS = 4;
|
|
13
|
+
/** Up to this many operands, a single position dropdown replaces the strip. */
|
|
14
|
+
export const PICKER_POSITIONS = 3;
|
|
15
|
+
|
|
16
|
+
export interface IExpressionMoveToPositionStripProps {
|
|
17
|
+
index: number;
|
|
18
|
+
count: number;
|
|
19
|
+
onMoveTo: (to: number) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const positionItems = (index: number, from: number, count: number) =>
|
|
23
|
+
Array.from({ length: count - from }, (_, i) => {
|
|
24
|
+
const to = from + i;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
label: ordinal(to + 1),
|
|
28
|
+
className: `expression-arg-move-to-${to + 1}`,
|
|
29
|
+
selected: to === index,
|
|
30
|
+
disabled: to === index,
|
|
31
|
+
value: to,
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The "Move to position" row of the ⋮ menu's "Move Argument" section: an
|
|
37
|
+
* icon and label in the shape of the rows above it. Up to three operands the
|
|
38
|
+
* position sits beside the label as a small dropdown; past that the strip
|
|
39
|
+
* follows under the label — the first four positions as segments, a number
|
|
40
|
+
* field with an arrow, and a "More" list of the rest.
|
|
41
|
+
*/
|
|
42
|
+
export const ExpressionMoveToPositionStrip = memo(
|
|
43
|
+
({ index, count, onMoveTo }: IExpressionMoveToPositionStripProps) => {
|
|
44
|
+
const [draft, setDraft] = useState('');
|
|
45
|
+
const target = Number(draft);
|
|
46
|
+
const canGo =
|
|
47
|
+
draft !== '' &&
|
|
48
|
+
Number.isInteger(target) &&
|
|
49
|
+
target >= 1 &&
|
|
50
|
+
target <= count &&
|
|
51
|
+
target - 1 !== index;
|
|
52
|
+
const go = () => canGo && onMoveTo(target - 1);
|
|
53
|
+
const quick = Math.min(QUICK_POSITIONS, count);
|
|
54
|
+
// The label row: a menu item like its siblings, but not a control itself.
|
|
55
|
+
const label = (
|
|
56
|
+
<ReqoreMenuItem
|
|
57
|
+
icon='ListOrdered'
|
|
58
|
+
label='Move to position'
|
|
59
|
+
readOnly
|
|
60
|
+
fluid={false}
|
|
61
|
+
style={{ cursor: 'default' }}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (count <= PICKER_POSITIONS) {
|
|
66
|
+
return (
|
|
67
|
+
<ReqoreControlGroup
|
|
68
|
+
size='small'
|
|
69
|
+
gapSize='small'
|
|
70
|
+
verticalAlign='center'
|
|
71
|
+
className='expression-arg-move-to'
|
|
72
|
+
>
|
|
73
|
+
{label}
|
|
74
|
+
<ReqoreDropdown
|
|
75
|
+
compact
|
|
76
|
+
className='expression-arg-move-to-picker'
|
|
77
|
+
label={ordinal(index + 1)}
|
|
78
|
+
caretPosition='right'
|
|
79
|
+
items={positionItems(index, 0, count).map((item) => ({
|
|
80
|
+
...item,
|
|
81
|
+
onClick: () => onMoveTo(item.value),
|
|
82
|
+
}))}
|
|
83
|
+
/>
|
|
84
|
+
</ReqoreControlGroup>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<ReqoreControlGroup vertical gapSize='small' className='expression-arg-move-to'>
|
|
90
|
+
{label}
|
|
91
|
+
<ReqoreControlGroup
|
|
92
|
+
size='small'
|
|
93
|
+
gapSize='small'
|
|
94
|
+
fluid={false}
|
|
95
|
+
verticalAlign='center'
|
|
96
|
+
style={{ padding: '0 10px 6px 32px' }}
|
|
97
|
+
>
|
|
98
|
+
<ReqoreControlGroup stack size='small' fluid={false}>
|
|
99
|
+
{Array.from({ length: quick }, (_, to) => (
|
|
100
|
+
<ReqoreButton
|
|
101
|
+
key={to}
|
|
102
|
+
compact
|
|
103
|
+
textAlign='center'
|
|
104
|
+
className={`expression-arg-move-to-${to + 1}`}
|
|
105
|
+
active={to === index}
|
|
106
|
+
disabled={to === index}
|
|
107
|
+
label={String(to + 1)}
|
|
108
|
+
tooltip={to === index ? 'Current position' : `Move to ${ordinal(to + 1)}`}
|
|
109
|
+
onClick={() => onMoveTo(to)}
|
|
110
|
+
/>
|
|
111
|
+
))}
|
|
112
|
+
</ReqoreControlGroup>
|
|
113
|
+
{count > QUICK_POSITIONS && (
|
|
114
|
+
<>
|
|
115
|
+
<ReqoreControlGroup stack size='small' fluid={false}>
|
|
116
|
+
<ReqoreInput
|
|
117
|
+
type='number'
|
|
118
|
+
size='small'
|
|
119
|
+
className='expression-arg-move-to-input'
|
|
120
|
+
placeholder={`${QUICK_POSITIONS + 1}–${count}`}
|
|
121
|
+
value={draft}
|
|
122
|
+
min={1}
|
|
123
|
+
max={count}
|
|
124
|
+
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
|
125
|
+
setDraft(event.target.value)
|
|
126
|
+
}
|
|
127
|
+
onKeyDown={(event: React.KeyboardEvent) => event.key === 'Enter' && go()}
|
|
128
|
+
style={{ width: 64 }}
|
|
129
|
+
/>
|
|
130
|
+
<ReqoreButton
|
|
131
|
+
compact
|
|
132
|
+
className='expression-arg-move-to-go'
|
|
133
|
+
icon='ArrowRightLine'
|
|
134
|
+
tooltip='Move to that position'
|
|
135
|
+
disabled={!canGo}
|
|
136
|
+
onClick={go}
|
|
137
|
+
/>
|
|
138
|
+
</ReqoreControlGroup>
|
|
139
|
+
<ReqoreDropdown
|
|
140
|
+
compact
|
|
141
|
+
className='expression-arg-move-to-more'
|
|
142
|
+
label='More'
|
|
143
|
+
caretPosition='right'
|
|
144
|
+
filterable={count - QUICK_POSITIONS > 8}
|
|
145
|
+
items={positionItems(index, QUICK_POSITIONS, count).map((item) => ({
|
|
146
|
+
...item,
|
|
147
|
+
onClick: () => onMoveTo(item.value),
|
|
148
|
+
}))}
|
|
149
|
+
/>
|
|
150
|
+
</>
|
|
151
|
+
)}
|
|
152
|
+
</ReqoreControlGroup>
|
|
153
|
+
</ReqoreControlGroup>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
);
|
|
@@ -169,6 +169,24 @@ export const mockExpressions: IExpressionSchema[] = [
|
|
|
169
169
|
groups: ['Math'],
|
|
170
170
|
args: [{ name: 'int', display_name: 'Value', ui_type: 'int', required: true }],
|
|
171
171
|
},
|
|
172
|
+
// --- Varargs (string) ----------------------------------------------------
|
|
173
|
+
// String twin of `+` — the reorder stories seed this one because "first",
|
|
174
|
+
// "second", "third" read as an order where int operands don't.
|
|
175
|
+
{
|
|
176
|
+
name: 'concat',
|
|
177
|
+
display_name: 'Concatenate Strings',
|
|
178
|
+
short_desc: 'Returns all arguments joined into one string',
|
|
179
|
+
desc: 'Returns all arguments joined into one string, in argument order. Accepts a variable number of string operands.',
|
|
180
|
+
symbol: 'concat',
|
|
181
|
+
type: 2,
|
|
182
|
+
subtype: 1,
|
|
183
|
+
return_type: 'string',
|
|
184
|
+
ui_return_type: 'string',
|
|
185
|
+
varargs: true,
|
|
186
|
+
min_args: 1,
|
|
187
|
+
groups: ['String'],
|
|
188
|
+
args: [{ name: 'softstring', display_name: 'Value', ui_type: 'string', required: true }],
|
|
189
|
+
},
|
|
172
190
|
// --- Function expressions ------------------------------------------------
|
|
173
191
|
// Single-arg type-conversion function. Used as the wrap/unwrap base: a
|
|
174
192
|
// boolean-returning op with one operand ("Value To Convert").
|
|
@@ -83,3 +83,13 @@ export interface IExpressionSchema {
|
|
|
83
83
|
return_type_first_arg?: boolean;
|
|
84
84
|
return_type_arg_priority?: string[];
|
|
85
85
|
}
|
|
86
|
+
|
|
87
|
+
/** Where a varargs expression offers operand reordering. */
|
|
88
|
+
export type TExpressionReorderSurface = 'overflowMenu' | 'dragHandle' | 'positionPicker';
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* `true` (the default) renders the drag grip together with the "Move Argument"
|
|
92
|
+
* section in the field's `⋮` menu; `false` turns reordering off; a list
|
|
93
|
+
* renders exactly those surfaces.
|
|
94
|
+
*/
|
|
95
|
+
export type TExpressionReorder = boolean | TExpressionReorderSurface[];
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import { memo, useCallback } from 'react';
|
|
19
19
|
import { Hint } from './Hint';
|
|
20
20
|
import { CatalogGroupBody } from './CatalogNodeEditor';
|
|
21
|
-
import { moveItem } from '
|
|
21
|
+
import { moveItem } from '../../../../helpers/common';
|
|
22
22
|
import { ICatalogList } from './types';
|
|
23
23
|
|
|
24
24
|
export interface IMigrationsTabProps {
|
|
@@ -38,15 +38,6 @@ export const renameKey = <T>(
|
|
|
38
38
|
return next;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
/** Immutably moves a list element from `from` to `to`. */
|
|
42
|
-
export const moveItem = <T>(list: T[], from: number, to: number): T[] => {
|
|
43
|
-
if (from === to || from < 0 || to < 0 || from >= list.length) return list;
|
|
44
|
-
const next = [...list];
|
|
45
|
-
const [item] = next.splice(from, 1);
|
|
46
|
-
next.splice(Math.min(to, next.length), 0, item);
|
|
47
|
-
return next;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
41
|
/**
|
|
51
42
|
* Picks a unique key for a new map entry — `base`, then `base_2`,
|
|
52
43
|
* `base_3`, … — so "Add" never silently overwrites an existing entry.
|