@nocobase/client-v2 2.1.0-beta.26 → 2.1.0-beta.27
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/es/flow/components/code-editor/types.d.ts +1 -0
- package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +15 -6
- package/es/flow/models/blocks/shared/filterOperators.d.ts +9 -0
- package/es/flow-compat/data.d.ts +9 -2
- package/es/flow-compat/index.d.ts +1 -1
- package/es/index.d.ts +1 -1
- package/es/index.mjs +90 -90
- package/lib/index.js +83 -83
- package/package.json +5 -5
- package/src/BaseApplication.tsx +1 -1
- package/src/__tests__/app.test.tsx +23 -6
- package/src/flow/actions/titleField.tsx +8 -3
- package/src/flow/components/FieldAssignValueInput.tsx +1 -0
- package/src/flow/components/code-editor/__tests__/linter.test.ts +18 -0
- package/src/flow/components/code-editor/__tests__/runjsDiagnostics.test.ts +23 -0
- package/src/flow/components/code-editor/index.tsx +18 -17
- package/src/flow/components/code-editor/linter.ts +222 -158
- package/src/flow/components/code-editor/runjsDiagnostics.ts +161 -97
- package/src/flow/components/code-editor/types.ts +1 -0
- package/src/flow/components/filter/LinkageFilterItem.tsx +6 -5
- package/src/flow/components/filter/VariableFilterItem.tsx +14 -13
- package/src/flow/components/filter/__tests__/LinkageFilterItem.test.tsx +33 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +48 -5
- package/src/flow/internal/utils/__tests__/titleFieldQuickSync.test.ts +1 -0
- package/src/flow/internal/utils/titleFieldQuickSync.ts +2 -2
- package/src/flow/models/actions/FilterActionModel.tsx +17 -9
- package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +200 -36
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.toggleFormFieldsCollapse.test.ts +270 -1
- package/src/flow/models/blocks/filter-form/__tests__/customFieldOperators.test.tsx +23 -0
- package/src/flow/models/blocks/filter-form/customFieldOperators.ts +12 -1
- package/src/flow/models/blocks/filter-form/fields/FieldComponentProps.tsx +22 -8
- package/src/flow/models/blocks/filter-form/fields/__tests__/FilterFormCustomFieldModel.recordSelect.test.tsx +18 -0
- package/src/flow/models/blocks/filter-manager/FilterManager.ts +51 -1
- package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +75 -0
- package/src/flow/models/blocks/form/FormItemModel.tsx +48 -28
- package/src/flow/models/blocks/shared/filterOperators.ts +14 -0
- package/src/flow/models/blocks/table/TableBlockModel.tsx +19 -3
- package/src/flow/models/fields/DividerItemModel.tsx +30 -15
- package/src/flow-compat/data.ts +25 -3
- package/src/flow-compat/index.ts +7 -1
- package/src/index.ts +1 -1
|
@@ -14,7 +14,11 @@ import { Input, Radio, Checkbox, Space, Button, Select } from 'antd';
|
|
|
14
14
|
import { PlusOutlined, CloseOutlined } from '@ant-design/icons';
|
|
15
15
|
import { useTranslation } from 'react-i18next';
|
|
16
16
|
import { FilterableItemModel, useFlowContext, useFlowEngine } from '@nocobase/flow-engine';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
getFlowFieldInterfaceOptions,
|
|
19
|
+
hasFlowFieldInterfaceLookup,
|
|
20
|
+
isTitleFieldInterface,
|
|
21
|
+
} from '../../../../../flow-compat';
|
|
18
22
|
|
|
19
23
|
const RECORD_SELECT_DATA_SOURCE_KEY = 'recordSelectDataSourceKey';
|
|
20
24
|
const RECORD_SELECT_COLLECTION_KEY = 'recordSelectTargetCollection';
|
|
@@ -36,7 +40,12 @@ export const FieldComponentProps: React.FC<{ fieldModel: string; source: string[
|
|
|
36
40
|
const resolvedFieldModel = fieldModel || propsValue?.fieldModel;
|
|
37
41
|
const flowEngine = useFlowEngine();
|
|
38
42
|
const ctx = useFlowContext();
|
|
39
|
-
const
|
|
43
|
+
const dataSourceManager = ctx?.dataSourceManager || flowEngine?.dataSourceManager || ctx?.app?.dataSourceManager;
|
|
44
|
+
const hasFieldInterfaceLookup = hasFlowFieldInterfaceLookup(dataSourceManager);
|
|
45
|
+
const getFieldInterface = useCallback(
|
|
46
|
+
(interfaceName: string | undefined) => getFlowFieldInterfaceOptions(interfaceName, dataSourceManager),
|
|
47
|
+
[dataSourceManager],
|
|
48
|
+
);
|
|
40
49
|
|
|
41
50
|
const getCurrentValue = () => field.value || {};
|
|
42
51
|
const updateProps = (key: string, value: any) => {
|
|
@@ -165,15 +174,20 @@ export const FieldComponentProps: React.FC<{ fieldModel: string; source: string[
|
|
|
165
174
|
}, [activeDataSource, translateLabel]);
|
|
166
175
|
const titleFieldOptions = useMemo(() => {
|
|
167
176
|
if (!activeCollection?.getFields) return [];
|
|
168
|
-
const shouldKeep = (fieldItem: any) =>
|
|
169
|
-
|
|
177
|
+
const shouldKeep = (fieldItem: any) => {
|
|
178
|
+
if (!hasFieldInterfaceLookup) {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
const fieldOptions = fieldItem?.options || fieldItem;
|
|
182
|
+
return isTitleFieldInterface(getFieldInterface(fieldOptions?.interface));
|
|
183
|
+
};
|
|
170
184
|
return (activeCollection.getFields() || [])
|
|
171
185
|
.filter((fieldItem: any) => shouldKeep(fieldItem))
|
|
172
186
|
.map((fieldItem: any) => ({
|
|
173
187
|
label: translateLabel(fieldItem.options?.uiSchema?.title) || translateLabel(fieldItem.title) || fieldItem.name,
|
|
174
188
|
value: fieldItem.name,
|
|
175
189
|
}));
|
|
176
|
-
}, [activeCollection,
|
|
190
|
+
}, [activeCollection, getFieldInterface, hasFieldInterfaceLookup, translateLabel]);
|
|
177
191
|
const valueFieldOptions = useMemo(() => {
|
|
178
192
|
if (!activeCollection?.getFields) return [];
|
|
179
193
|
const shouldKeep = (fieldItem: any) => {
|
|
@@ -182,8 +196,8 @@ export const FieldComponentProps: React.FC<{ fieldModel: string; source: string[
|
|
|
182
196
|
if (fieldOptions.filterable === false || !interfaceName) {
|
|
183
197
|
return false;
|
|
184
198
|
}
|
|
185
|
-
if (
|
|
186
|
-
const fieldInterface =
|
|
199
|
+
if (hasFieldInterfaceLookup) {
|
|
200
|
+
const fieldInterface = getFieldInterface(interfaceName);
|
|
187
201
|
if (!fieldInterface?.filterable) {
|
|
188
202
|
return false;
|
|
189
203
|
}
|
|
@@ -197,7 +211,7 @@ export const FieldComponentProps: React.FC<{ fieldModel: string; source: string[
|
|
|
197
211
|
label: translateLabel(fieldItem.options?.uiSchema?.title) || translateLabel(fieldItem.title) || fieldItem.name,
|
|
198
212
|
value: fieldItem.name,
|
|
199
213
|
}));
|
|
200
|
-
}, [activeCollection,
|
|
214
|
+
}, [activeCollection, getFieldInterface, hasFieldInterfaceLookup, translateLabel]);
|
|
201
215
|
|
|
202
216
|
useEffect(() => {
|
|
203
217
|
if (resolvedFieldModel !== 'FilterFormCustomRecordSelectFieldModel') return;
|
|
@@ -222,6 +222,17 @@ describe('FilterForm custom field record select', () => {
|
|
|
222
222
|
it('hides association fields from value field options', async () => {
|
|
223
223
|
const engine = new FlowEngine();
|
|
224
224
|
engine.registerModels({ HostModel, FilterFormCustomRecordSelectFieldModel });
|
|
225
|
+
engine.dataSourceManager.setCollectionFieldInterfaceManager({
|
|
226
|
+
getFieldInterface: vi.fn((name: string) => {
|
|
227
|
+
if (['input', 'number'].includes(name)) {
|
|
228
|
+
return { titleUsable: true, filterable: { operators: [] } };
|
|
229
|
+
}
|
|
230
|
+
if (name === 'm2m') {
|
|
231
|
+
return { filterable: { operators: [] } };
|
|
232
|
+
}
|
|
233
|
+
return undefined;
|
|
234
|
+
}),
|
|
235
|
+
});
|
|
225
236
|
|
|
226
237
|
const ds = engine.dataSourceManager.getDataSource('main');
|
|
227
238
|
ds?.addCollection({
|
|
@@ -242,6 +253,12 @@ describe('FilterForm custom field record select', () => {
|
|
|
242
253
|
title: 'Hidden text',
|
|
243
254
|
filterable: false,
|
|
244
255
|
},
|
|
256
|
+
{
|
|
257
|
+
name: 'jsonPayload',
|
|
258
|
+
type: 'json',
|
|
259
|
+
interface: 'json',
|
|
260
|
+
title: 'JSON payload',
|
|
261
|
+
},
|
|
245
262
|
{
|
|
246
263
|
name: 'roles',
|
|
247
264
|
type: 'belongsToMany',
|
|
@@ -298,6 +315,7 @@ describe('FilterForm custom field record select', () => {
|
|
|
298
315
|
);
|
|
299
316
|
expect(optionTexts).toContain('nickname');
|
|
300
317
|
expect(optionTexts).not.toContain('Hidden text');
|
|
318
|
+
expect(optionTexts).not.toContain('JSON payload');
|
|
301
319
|
expect(optionTexts).not.toContain('Roles relation');
|
|
302
320
|
});
|
|
303
321
|
});
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { FilterGroup, FilterItem, FlowModel } from '@nocobase/flow-engine';
|
|
11
11
|
import _ from 'lodash';
|
|
12
12
|
import { CollectionBlockModel } from '../../base/CollectionBlockModel';
|
|
13
|
+
import { isArrayLikeField } from '../shared/filterOperators';
|
|
13
14
|
import { getDefaultOperator, isFilterValueEmpty } from './utils';
|
|
14
15
|
|
|
15
16
|
type FilterConfig = {
|
|
@@ -23,6 +24,13 @@ type FilterConfig = {
|
|
|
23
24
|
operator?: string;
|
|
24
25
|
};
|
|
25
26
|
|
|
27
|
+
const ARRAY_FIELD_OPERATORS_TO_SCALAR_OPERATORS: Record<string, string> = {
|
|
28
|
+
$match: '$in',
|
|
29
|
+
$anyOf: '$in',
|
|
30
|
+
$notMatch: '$notIn',
|
|
31
|
+
$noneOf: '$notIn',
|
|
32
|
+
};
|
|
33
|
+
|
|
26
34
|
export type ConnectFieldsConfig = {
|
|
27
35
|
targets: {
|
|
28
36
|
/** 数据区块或者图表区块的 model uid */
|
|
@@ -32,6 +40,44 @@ export type ConnectFieldsConfig = {
|
|
|
32
40
|
}[];
|
|
33
41
|
};
|
|
34
42
|
|
|
43
|
+
function getTargetField(targetModel: any, fieldPath: string) {
|
|
44
|
+
const dataSourceManager = targetModel?.context?.dataSourceManager;
|
|
45
|
+
const collection = targetModel?.collection;
|
|
46
|
+
if (!dataSourceManager || !collection?.dataSourceKey || !collection?.name || !fieldPath) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let collectionName = collection.name;
|
|
51
|
+
const fieldNames = fieldPath.split('.').filter(Boolean);
|
|
52
|
+
for (let index = 0; index < fieldNames.length; index += 1) {
|
|
53
|
+
const field = dataSourceManager.getCollectionField?.(
|
|
54
|
+
`${collection.dataSourceKey}.${collectionName}.${fieldNames[index]}`,
|
|
55
|
+
);
|
|
56
|
+
if (!field || index === fieldNames.length - 1) {
|
|
57
|
+
return field;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
collectionName = field.target || field.targetCollection?.name;
|
|
61
|
+
if (!collectionName) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeOperatorForTargetField(operator: string, targetModel: any, fieldPath: string) {
|
|
68
|
+
const scalarOperator = ARRAY_FIELD_OPERATORS_TO_SCALAR_OPERATORS[operator];
|
|
69
|
+
if (!scalarOperator) {
|
|
70
|
+
return operator;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const targetField = getTargetField(targetModel, fieldPath);
|
|
74
|
+
if (!targetField || isArrayLikeField(targetField)) {
|
|
75
|
+
return operator;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return scalarOperator;
|
|
79
|
+
}
|
|
80
|
+
|
|
35
81
|
export class FilterManager {
|
|
36
82
|
private filterConfigs: FilterConfig[];
|
|
37
83
|
private readonly gridModel: FlowModel;
|
|
@@ -300,7 +346,11 @@ export class FilterManager {
|
|
|
300
346
|
// 构建筛选条件
|
|
301
347
|
const filterConditions = config.filterPaths.map((fieldPath) => ({
|
|
302
348
|
path: fieldPath,
|
|
303
|
-
operator:
|
|
349
|
+
operator: normalizeOperatorForTargetField(
|
|
350
|
+
config.operator || getDefaultOperator(filterModel),
|
|
351
|
+
targetModel,
|
|
352
|
+
fieldPath,
|
|
353
|
+
),
|
|
304
354
|
value: filterValue,
|
|
305
355
|
}));
|
|
306
356
|
|
|
@@ -496,6 +496,81 @@ describe('FilterManager', () => {
|
|
|
496
496
|
expect(mockTargetModel2.resource.refresh).toHaveBeenCalledTimes(1);
|
|
497
497
|
});
|
|
498
498
|
|
|
499
|
+
it('should normalize array operators when the target field is scalar', async () => {
|
|
500
|
+
const filterConfigs = [
|
|
501
|
+
{
|
|
502
|
+
filterId: 'filter-1',
|
|
503
|
+
targetId: 'target-scalar',
|
|
504
|
+
filterPaths: ['status'],
|
|
505
|
+
operator: '$match',
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
filterId: 'filter-1',
|
|
509
|
+
targetId: 'target-array',
|
|
510
|
+
filterPaths: ['tags'],
|
|
511
|
+
operator: '$match',
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
filterId: 'filter-1',
|
|
515
|
+
targetId: 'target-deep-array',
|
|
516
|
+
filterPaths: ['org.company.tags'],
|
|
517
|
+
operator: '$match',
|
|
518
|
+
},
|
|
519
|
+
];
|
|
520
|
+
|
|
521
|
+
(filterManager as any).filterConfigs = filterConfigs;
|
|
522
|
+
|
|
523
|
+
const createTargetModel = (getCollectionField: any) => ({
|
|
524
|
+
collection: {
|
|
525
|
+
dataSourceKey: 'main',
|
|
526
|
+
name: 'users',
|
|
527
|
+
},
|
|
528
|
+
context: {
|
|
529
|
+
dataSourceManager: {
|
|
530
|
+
getCollectionField,
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
resource: {
|
|
534
|
+
addFilterGroup: vi.fn(),
|
|
535
|
+
removeFilterGroup: vi.fn(),
|
|
536
|
+
refresh: vi.fn().mockResolvedValue(undefined),
|
|
537
|
+
},
|
|
538
|
+
setFilterActive: vi.fn(),
|
|
539
|
+
getDataLoadingMode: vi.fn().mockReturnValue('auto'),
|
|
540
|
+
});
|
|
541
|
+
const mockScalarTargetModel = createTargetModel(vi.fn().mockReturnValue({ interface: 'select', type: 'string' }));
|
|
542
|
+
const mockArrayTargetModel = createTargetModel(
|
|
543
|
+
vi.fn().mockReturnValue({ interface: 'multipleSelect', type: 'array' }),
|
|
544
|
+
);
|
|
545
|
+
const mockDeepArrayTargetModel = createTargetModel(
|
|
546
|
+
vi.fn((key: string) => {
|
|
547
|
+
const fields = {
|
|
548
|
+
'main.users.org': { target: 'orgs' },
|
|
549
|
+
'main.orgs.company': { target: 'companies' },
|
|
550
|
+
'main.companies.tags': { interface: 'multipleSelect', type: 'array' },
|
|
551
|
+
};
|
|
552
|
+
return fields[key];
|
|
553
|
+
}),
|
|
554
|
+
);
|
|
555
|
+
const mockFilterModel = {
|
|
556
|
+
getFilterValue: vi.fn().mockReturnValue(['a1']),
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
(mockFlowModel.flowEngine.getModel as any).mockImplementation((uid: string) => {
|
|
560
|
+
if (uid === 'target-scalar') return mockScalarTargetModel;
|
|
561
|
+
if (uid === 'target-array') return mockArrayTargetModel;
|
|
562
|
+
if (uid === 'target-deep-array') return mockDeepArrayTargetModel;
|
|
563
|
+
if (uid === 'filter-1') return mockFilterModel;
|
|
564
|
+
return null;
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
await filterManager.refreshTargetsByFilter('filter-1');
|
|
568
|
+
|
|
569
|
+
expect(mockScalarTargetModel.resource.addFilterGroup.mock.calls[0][1].options.operator).toBe('$in');
|
|
570
|
+
expect(mockArrayTargetModel.resource.addFilterGroup.mock.calls[0][1].options.operator).toBe('$match');
|
|
571
|
+
expect(mockDeepArrayTargetModel.resource.addFilterGroup.mock.calls[0][1].options.operator).toBe('$match');
|
|
572
|
+
});
|
|
573
|
+
|
|
499
574
|
it('should process multiple filterIds successfully', async () => {
|
|
500
575
|
// Setup filter configs
|
|
501
576
|
const filterConfigs = [
|
|
@@ -22,6 +22,7 @@ import { FieldModel } from '../../base/FieldModel';
|
|
|
22
22
|
import { DetailsItemModel } from '../details/DetailsItemModel';
|
|
23
23
|
import { EditFormModel } from './EditFormModel';
|
|
24
24
|
import _ from 'lodash';
|
|
25
|
+
import { Tooltip } from 'antd';
|
|
25
26
|
import { coerceForToOneField } from '../../../internal/utils/associationValueCoercion';
|
|
26
27
|
import { buildDynamicNamePath } from './dynamicNamePath';
|
|
27
28
|
|
|
@@ -99,54 +100,63 @@ export class FormItemModel<T extends DefaultStructure = DefaultStructure> extend
|
|
|
99
100
|
|
|
100
101
|
renderItem() {
|
|
101
102
|
const fieldModel = this.subModels.field as FieldModel;
|
|
102
|
-
// 行索引(来自数组子表单)
|
|
103
103
|
const idx = this.context.fieldIndex;
|
|
104
104
|
const fieldKey = this.context.fieldKey;
|
|
105
105
|
const parentFieldPathArray = this.parent?.context.fieldPathArray || [];
|
|
106
|
+
const isHiddenReservedValuePreview =
|
|
107
|
+
!!this.context.flowSettingsEnabled && !!this.props.hidden && !this.hidden && !this.forbidden;
|
|
106
108
|
|
|
107
|
-
// 嵌套场景下继续传透,为字段子模型创建 fork
|
|
108
109
|
const modelForRender =
|
|
109
|
-
idx != null
|
|
110
|
+
idx != null || isHiddenReservedValuePreview
|
|
110
111
|
? (() => {
|
|
111
|
-
const
|
|
112
|
-
fork.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
get: () => fieldKey,
|
|
117
|
-
});
|
|
118
|
-
if (this.context.currentObject) {
|
|
119
|
-
fork.context.defineProperty('currentObject', {
|
|
120
|
-
get: () => this.context.currentObject,
|
|
112
|
+
const forkKey = isHiddenReservedValuePreview ? `${this.uid}:config-visible` : `${fieldKey}`;
|
|
113
|
+
const fork = fieldModel.createFork({}, forkKey);
|
|
114
|
+
if (idx != null) {
|
|
115
|
+
fork.context.defineProperty('fieldIndex', {
|
|
116
|
+
get: () => idx,
|
|
121
117
|
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (this.context.item) {
|
|
125
|
-
const { value: _value, ...rest } = (itemOptions || {}) as any;
|
|
126
|
-
fork.context.defineProperty('item', {
|
|
127
|
-
...rest,
|
|
128
|
-
get: () => this.context.item,
|
|
129
|
-
cache: false,
|
|
118
|
+
fork.context.defineProperty('fieldKey', {
|
|
119
|
+
get: () => fieldKey,
|
|
130
120
|
});
|
|
121
|
+
if (this.context.currentObject) {
|
|
122
|
+
fork.context.defineProperty('currentObject', {
|
|
123
|
+
get: () => this.context.currentObject,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const itemOptions = this.context.getPropertyOptions('item');
|
|
127
|
+
if (this.context.item) {
|
|
128
|
+
const { value: _value, ...rest } = (itemOptions || {}) as any;
|
|
129
|
+
fork.context.defineProperty('item', {
|
|
130
|
+
...rest,
|
|
131
|
+
get: () => this.context.item,
|
|
132
|
+
cache: false,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (this.context.pattern) {
|
|
136
|
+
fork.context.defineProperty('pattern', {
|
|
137
|
+
get: () => this.context.pattern,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
131
140
|
}
|
|
132
|
-
if (
|
|
133
|
-
fork.
|
|
134
|
-
get: () => this.context.pattern,
|
|
135
|
-
});
|
|
141
|
+
if (isHiddenReservedValuePreview) {
|
|
142
|
+
fork.setProps({ hidden: false });
|
|
136
143
|
}
|
|
137
144
|
return fork;
|
|
138
145
|
})()
|
|
139
146
|
: fieldModel;
|
|
140
147
|
const mergedProps = this.context.pattern ? { ...this.props, pattern: this.context.pattern } : this.props;
|
|
141
|
-
const { initialValue, ...mergedPropsWithoutInitial } = mergedProps as any;
|
|
148
|
+
const { initialValue, hidden, ...mergedPropsWithoutInitial } = mergedProps as any;
|
|
149
|
+
const formItemProps = isHiddenReservedValuePreview
|
|
150
|
+
? mergedPropsWithoutInitial
|
|
151
|
+
: { hidden, ...mergedPropsWithoutInitial };
|
|
142
152
|
const fieldPath = buildDynamicNamePath(this.props.name, idx);
|
|
143
153
|
this.context.defineProperty('fieldPathArray', {
|
|
144
154
|
value: [...parentFieldPathArray, ..._.castArray(fieldPath)],
|
|
145
155
|
});
|
|
146
156
|
const record = this.context.item?.value || this.context.record;
|
|
147
|
-
|
|
157
|
+
const content = (
|
|
148
158
|
<FormItem
|
|
149
|
-
{...
|
|
159
|
+
{...formItemProps}
|
|
150
160
|
name={fieldPath}
|
|
151
161
|
validateFirst={true}
|
|
152
162
|
disabled={
|
|
@@ -158,6 +168,16 @@ export class FormItemModel<T extends DefaultStructure = DefaultStructure> extend
|
|
|
158
168
|
<FieldModelRenderer model={modelForRender} name={fieldPath} />
|
|
159
169
|
</FormItem>
|
|
160
170
|
);
|
|
171
|
+
|
|
172
|
+
if (isHiddenReservedValuePreview) {
|
|
173
|
+
return (
|
|
174
|
+
<Tooltip title={this.context.t('The field is hidden and only visible when the UI Editor is active')}>
|
|
175
|
+
<div style={{ opacity: 0.3 }}>{content}</div>
|
|
176
|
+
</Tooltip>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return content;
|
|
161
181
|
}
|
|
162
182
|
}
|
|
163
183
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export function isArrayLikeField(field: any) {
|
|
11
|
+
return (
|
|
12
|
+
['multipleSelect', 'checkboxGroup'].includes(field?.interface) || ['array', 'json', 'jsonb'].includes(field?.type)
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -563,6 +563,9 @@ export class TableBlockModel extends CollectionBlockModel<TableBlockModelStructu
|
|
|
563
563
|
columns={this.columns.value}
|
|
564
564
|
pagination={this.pagination()}
|
|
565
565
|
highlightedRowKey={highlightedRowKey}
|
|
566
|
+
selectedRowKeysFromResource={this.resource
|
|
567
|
+
.getSelectedRows()
|
|
568
|
+
.map((row) => getRowKey(row, this.collection.filterTargetKey))}
|
|
566
569
|
defaultExpandAllRows={this.props.defaultExpandAllRows}
|
|
567
570
|
expandedRowKeys={this.props.expandedRowKeys}
|
|
568
571
|
heightMode={heightMode}
|
|
@@ -581,6 +584,7 @@ const TableBlockContent = (props: {
|
|
|
581
584
|
columns: any;
|
|
582
585
|
pagination: any;
|
|
583
586
|
highlightedRowKey: string;
|
|
587
|
+
selectedRowKeysFromResource: string[];
|
|
584
588
|
defaultExpandAllRows?: boolean;
|
|
585
589
|
expandedRowKeys?: any[];
|
|
586
590
|
heightMode?: string;
|
|
@@ -594,6 +598,7 @@ const TableBlockContent = (props: {
|
|
|
594
598
|
columns,
|
|
595
599
|
pagination,
|
|
596
600
|
highlightedRowKey,
|
|
601
|
+
selectedRowKeysFromResource,
|
|
597
602
|
defaultExpandAllRows,
|
|
598
603
|
expandedRowKeys,
|
|
599
604
|
heightMode,
|
|
@@ -620,6 +625,7 @@ const TableBlockContent = (props: {
|
|
|
620
625
|
columns={columns}
|
|
621
626
|
pagination={pagination}
|
|
622
627
|
highlightedRowKey={highlightedRowKey}
|
|
628
|
+
selectedRowKeysFromResource={selectedRowKeysFromResource}
|
|
623
629
|
defaultExpandAllRows={defaultExpandAllRows}
|
|
624
630
|
expandedRowKeys={expandedRowKeys}
|
|
625
631
|
tableScroll={tableScroll}
|
|
@@ -829,6 +835,7 @@ const HighPerformanceTable = React.memo(
|
|
|
829
835
|
columns: any;
|
|
830
836
|
pagination: any;
|
|
831
837
|
highlightedRowKey: string;
|
|
838
|
+
selectedRowKeysFromResource: string[];
|
|
832
839
|
defaultExpandAllRows?: boolean;
|
|
833
840
|
expandedRowKeys?: any[];
|
|
834
841
|
tableScroll;
|
|
@@ -841,6 +848,7 @@ const HighPerformanceTable = React.memo(
|
|
|
841
848
|
columns,
|
|
842
849
|
pagination: _pagination,
|
|
843
850
|
highlightedRowKey,
|
|
851
|
+
selectedRowKeysFromResource,
|
|
844
852
|
defaultExpandAllRows,
|
|
845
853
|
expandedRowKeys,
|
|
846
854
|
tableScroll,
|
|
@@ -848,9 +856,17 @@ const HighPerformanceTable = React.memo(
|
|
|
848
856
|
const dataSourceRef = useRef(dataSource);
|
|
849
857
|
dataSourceRef.current = dataSource;
|
|
850
858
|
|
|
851
|
-
const [selectedRowKeys, setSelectedRowKeys] = React.useState<string[]>(
|
|
852
|
-
|
|
853
|
-
)
|
|
859
|
+
const [selectedRowKeys, setSelectedRowKeys] = React.useState<string[]>(selectedRowKeysFromResource || []);
|
|
860
|
+
|
|
861
|
+
useEffect(() => {
|
|
862
|
+
const nextSelectedRowKeys = selectedRowKeysFromResource || [];
|
|
863
|
+
setSelectedRowKeys((prev) => {
|
|
864
|
+
if (_.isEqual(prev, nextSelectedRowKeys)) {
|
|
865
|
+
return prev;
|
|
866
|
+
}
|
|
867
|
+
return nextSelectedRowKeys;
|
|
868
|
+
});
|
|
869
|
+
}, [selectedRowKeysFromResource]);
|
|
854
870
|
|
|
855
871
|
const getRowKeyFunc = useCallback(
|
|
856
872
|
(record) => {
|
|
@@ -8,25 +8,40 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { FormItem } from '@nocobase/flow-engine';
|
|
11
|
-
import { Divider } from 'antd';
|
|
11
|
+
import { Divider, theme } from 'antd';
|
|
12
12
|
import React from 'react';
|
|
13
13
|
import { CommonItemModel } from '../base/CommonItemModel';
|
|
14
14
|
import { NBColorPicker } from './ColorFieldModel';
|
|
15
15
|
|
|
16
|
+
const resolveThemeColor = (value: string | undefined, fallback: string) => {
|
|
17
|
+
return value ? value : fallback;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const DividerItem = (props: any) => {
|
|
21
|
+
const { token } = theme.useToken();
|
|
22
|
+
const { color, borderColor, label, orientation, dashed } = props;
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Divider
|
|
26
|
+
type="horizontal"
|
|
27
|
+
style={{
|
|
28
|
+
color: resolveThemeColor(color, token.colorText),
|
|
29
|
+
borderColor: resolveThemeColor(borderColor, token.colorSplit),
|
|
30
|
+
}}
|
|
31
|
+
orientationMargin="0"
|
|
32
|
+
orientation={orientation}
|
|
33
|
+
dashed={dashed}
|
|
34
|
+
>
|
|
35
|
+
{label}
|
|
36
|
+
</Divider>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
16
40
|
export class DividerItemModel extends CommonItemModel {
|
|
17
41
|
render() {
|
|
18
|
-
const { color, borderColor, label, orientation, dashed } = this.props;
|
|
19
42
|
return (
|
|
20
43
|
<FormItem shouldUpdate showLabel={false}>
|
|
21
|
-
<
|
|
22
|
-
type="horizontal"
|
|
23
|
-
style={{ color, borderColor }}
|
|
24
|
-
orientationMargin="0"
|
|
25
|
-
orientation={orientation}
|
|
26
|
-
dashed={dashed}
|
|
27
|
-
>
|
|
28
|
-
{label}
|
|
29
|
-
</Divider>
|
|
44
|
+
<DividerItem {...this.props} />
|
|
30
45
|
</FormItem>
|
|
31
46
|
);
|
|
32
47
|
}
|
|
@@ -38,12 +53,12 @@ DividerItemModel.registerFlow({
|
|
|
38
53
|
steps: {
|
|
39
54
|
title: {
|
|
40
55
|
title: '{{t("Edit divider")}}',
|
|
41
|
-
defaultParams: {
|
|
56
|
+
defaultParams: (ctx) => ({
|
|
42
57
|
label: '{{t("Text")}}',
|
|
43
58
|
orientation: 'left',
|
|
44
|
-
color:
|
|
45
|
-
borderColor:
|
|
46
|
-
},
|
|
59
|
+
color: ctx.themeToken?.colorText,
|
|
60
|
+
borderColor: ctx.themeToken?.colorSplit,
|
|
61
|
+
}),
|
|
47
62
|
uiSchema(ctx) {
|
|
48
63
|
return {
|
|
49
64
|
label: {
|
package/src/flow-compat/data.ts
CHANGED
|
@@ -7,15 +7,37 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { getCollectionFieldInterface } from '@nocobase/flow-engine';
|
|
11
|
+
import type { CollectionFieldInterfaceDataSourceManager } from '@nocobase/flow-engine';
|
|
11
12
|
|
|
12
13
|
export interface CollectionFieldOptions {
|
|
13
14
|
interface?: string;
|
|
14
15
|
[key: string]: any;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
type FieldInterfaceOptions = { titleUsable?: boolean } | null | undefined;
|
|
19
|
+
|
|
17
20
|
export const DEFAULT_DATA_SOURCE_KEY = 'main';
|
|
18
21
|
|
|
19
|
-
export const
|
|
20
|
-
|
|
22
|
+
export const getFlowFieldInterfaceOptions = (
|
|
23
|
+
interfaceName: string | undefined,
|
|
24
|
+
...dataSourceManagers: Array<CollectionFieldInterfaceDataSourceManager | null | undefined>
|
|
25
|
+
) => getCollectionFieldInterface(interfaceName, ...dataSourceManagers);
|
|
26
|
+
|
|
27
|
+
export const hasFlowFieldInterfaceLookup = (
|
|
28
|
+
...dataSourceManagers: Array<CollectionFieldInterfaceDataSourceManager | null | undefined>
|
|
29
|
+
) =>
|
|
30
|
+
dataSourceManagers.some(
|
|
31
|
+
(dataSourceManager) => typeof dataSourceManager?.collectionFieldInterfaceManager?.getFieldInterface === 'function',
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export const isTitleFieldInterface = (fieldInterfaceOptions: FieldInterfaceOptions) => {
|
|
35
|
+
return fieldInterfaceOptions?.titleUsable;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const isTitleField = (
|
|
39
|
+
dataSourceManager: CollectionFieldInterfaceDataSourceManager | null | undefined,
|
|
40
|
+
field: CollectionFieldOptions | null | undefined,
|
|
41
|
+
) => {
|
|
42
|
+
return isTitleFieldInterface(getFlowFieldInterfaceOptions(field?.interface, dataSourceManager));
|
|
21
43
|
};
|
package/src/flow-compat/index.ts
CHANGED
|
@@ -11,7 +11,13 @@ export { Plugin } from '../Plugin';
|
|
|
11
11
|
export { useApp } from '../hooks/useApp';
|
|
12
12
|
export { usePlugin } from '../hooks/usePlugin';
|
|
13
13
|
export { ColorPicker } from './ColorPicker';
|
|
14
|
-
export {
|
|
14
|
+
export {
|
|
15
|
+
DEFAULT_DATA_SOURCE_KEY,
|
|
16
|
+
getFlowFieldInterfaceOptions,
|
|
17
|
+
hasFlowFieldInterfaceLookup,
|
|
18
|
+
isTitleField,
|
|
19
|
+
isTitleFieldInterface,
|
|
20
|
+
} from './data';
|
|
15
21
|
export { FieldValidation } from './FieldValidation';
|
|
16
22
|
export { HighPerformanceSpin } from './HighPerformanceSpin';
|
|
17
23
|
export { Icon, hasIcon, icons, registerIcon, registerIcons } from './Icon';
|
package/src/index.ts
CHANGED
|
@@ -32,5 +32,5 @@ export * from './collection-field-interface/CollectionFieldInterface';
|
|
|
32
32
|
export * from './collection-field-interface/CollectionFieldInterfaceManager';
|
|
33
33
|
export * from './collection-manager/interfaces';
|
|
34
34
|
export * from './flow';
|
|
35
|
-
export { DEFAULT_DATA_SOURCE_KEY, isTitleField } from './flow-compat';
|
|
35
|
+
export { DEFAULT_DATA_SOURCE_KEY, isTitleField, isTitleFieldInterface } from './flow-compat';
|
|
36
36
|
export { default as AntdAppProvider } from './theme/AntdAppProvider';
|