@nocobase/flow-engine 2.1.0-beta.22 → 2.1.0-beta.24
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/lib/components/FieldModelRenderer.js +2 -2
- package/lib/components/FlowModelRenderer.d.ts +2 -0
- package/lib/components/FlowModelRenderer.js +2 -0
- package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
- package/lib/components/dnd/gridDragPlanner.js +595 -19
- package/lib/components/dnd/index.d.ts +19 -1
- package/lib/components/dnd/index.js +243 -23
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +20 -1
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +4 -0
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +21 -8
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +2 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +100 -32
- package/lib/components/subModel/index.d.ts +1 -0
- package/lib/components/subModel/index.js +19 -0
- package/lib/components/subModel/utils.d.ts +1 -1
- package/lib/data-source/index.d.ts +73 -0
- package/lib/data-source/index.js +205 -1
- package/lib/flowContext.d.ts +2 -0
- package/lib/flowI18n.js +2 -1
- package/lib/models/DisplayItemModel.d.ts +1 -1
- package/lib/models/EditableItemModel.d.ts +1 -1
- package/lib/models/FilterableItemModel.d.ts +1 -1
- package/lib/models/flowModel.d.ts +11 -9
- package/lib/models/flowModel.js +48 -9
- package/lib/provider.js +38 -23
- package/package.json +4 -4
- package/src/__tests__/provider.test.tsx +24 -2
- package/src/components/FieldModelRenderer.tsx +2 -1
- package/src/components/FlowModelRenderer.tsx +6 -0
- package/src/components/__tests__/dnd.test.ts +44 -0
- package/src/components/__tests__/gridDragPlanner.test.ts +426 -5
- package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
- package/src/components/dnd/gridDragPlanner.ts +735 -17
- package/src/components/dnd/index.tsx +291 -27
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +25 -1
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +24 -5
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +94 -3
- package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +171 -2
- package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +2 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +112 -32
- package/src/components/subModel/index.ts +1 -0
- package/src/data-source/__tests__/index.test.ts +34 -1
- package/src/data-source/index.ts +252 -2
- package/src/flowContext.ts +2 -0
- package/src/flowI18n.ts +2 -1
- package/src/models/DisplayItemModel.tsx +1 -1
- package/src/models/EditableItemModel.tsx +1 -1
- package/src/models/FilterableItemModel.tsx +1 -1
- package/src/models/flowModel.tsx +85 -23
- package/src/provider.tsx +41 -25
package/src/data-source/index.ts
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { observable } from '@formily/reactive';
|
|
11
|
-
import { CascaderProps } from 'antd';
|
|
12
11
|
import _ from 'lodash';
|
|
13
12
|
import { FlowEngine } from '../flowEngine';
|
|
14
13
|
import { jioToJoiSchema } from './jioToJoiSchema';
|
|
@@ -20,9 +19,31 @@ export interface DataSourceOptions extends Record<string, any> {
|
|
|
20
19
|
[key: string]: any;
|
|
21
20
|
}
|
|
22
21
|
|
|
22
|
+
export type DataSourceRequester = (options: Record<string, any>) => Promise<any>;
|
|
23
|
+
|
|
24
|
+
export interface DataSourceLoadResult {
|
|
25
|
+
collections?: CollectionOptions[];
|
|
26
|
+
dataSources?: Array<DataSourceOptions & { collections?: CollectionOptions[] }>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type DataSourceLoader = (context: { key: string; manager: DataSourceManager }) => Promise<DataSourceLoadResult>;
|
|
30
|
+
|
|
23
31
|
export class DataSourceManager {
|
|
24
32
|
dataSources: Map<string, DataSource>;
|
|
25
33
|
flowEngine: FlowEngine;
|
|
34
|
+
requester?: DataSourceRequester;
|
|
35
|
+
collectionFieldInterfaceManager?: {
|
|
36
|
+
addFieldInterfaces?: (fieldInterfaceClasses?: any[]) => void;
|
|
37
|
+
addFieldInterfaceGroups?: (groups: Record<string, { label: string; order?: number }>) => void;
|
|
38
|
+
addFieldInterfaceComponentOption?: (name: string, option: any) => void;
|
|
39
|
+
addFieldInterfaceOperator?: (name: string, operator: any) => void;
|
|
40
|
+
getFieldInterface?: (name: string) => any;
|
|
41
|
+
};
|
|
42
|
+
loaders = new Map<string, DataSourceLoader>();
|
|
43
|
+
loadedKeys = new Set<string>();
|
|
44
|
+
loadingKeys = new Set<string>();
|
|
45
|
+
loadErrors = new Map<string, Error | null>();
|
|
46
|
+
loadingPromise: Promise<void> | null = null;
|
|
26
47
|
|
|
27
48
|
constructor() {
|
|
28
49
|
this.dataSources = observable.shallow<Map<string, DataSource>>(new Map());
|
|
@@ -32,6 +53,38 @@ export class DataSourceManager {
|
|
|
32
53
|
this.flowEngine = flowEngine;
|
|
33
54
|
}
|
|
34
55
|
|
|
56
|
+
setRequester(requester?: DataSourceRequester) {
|
|
57
|
+
this.requester = requester;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setCollectionFieldInterfaceManager(manager: DataSourceManager['collectionFieldInterfaceManager']) {
|
|
61
|
+
this.collectionFieldInterfaceManager = manager;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
addFieldInterfaces(fieldInterfaceClasses: any[] = []) {
|
|
65
|
+
this.collectionFieldInterfaceManager?.addFieldInterfaces?.(fieldInterfaceClasses);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
addFieldInterfaceGroups(groups: Record<string, { label: string; order?: number }>) {
|
|
69
|
+
this.collectionFieldInterfaceManager?.addFieldInterfaceGroups?.(groups);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
addFieldInterfaceComponentOption(name: string, option: any) {
|
|
73
|
+
this.collectionFieldInterfaceManager?.addFieldInterfaceComponentOption?.(name, option);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
addFieldInterfaceOperator(name: string, operator: any) {
|
|
77
|
+
this.collectionFieldInterfaceManager?.addFieldInterfaceOperator?.(name, operator);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
registerLoader(key: string, loader: DataSourceLoader) {
|
|
81
|
+
this.loaders.set(key, loader);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
removeLoader(key: string) {
|
|
85
|
+
this.loaders.delete(key);
|
|
86
|
+
}
|
|
87
|
+
|
|
35
88
|
addDataSource(ds: DataSource | DataSourceOptions) {
|
|
36
89
|
if (this.dataSources.has(ds.key)) {
|
|
37
90
|
throw new Error(`DataSource with name ${ds.key} already exists`);
|
|
@@ -48,7 +101,7 @@ export class DataSourceManager {
|
|
|
48
101
|
|
|
49
102
|
upsertDataSource(ds: DataSource | DataSourceOptions) {
|
|
50
103
|
if (this.dataSources.has(ds.key)) {
|
|
51
|
-
this.dataSources.get(ds.key)?.
|
|
104
|
+
this.dataSources.get(ds.key)?.patchOptions(ds);
|
|
52
105
|
} else {
|
|
53
106
|
this.addDataSource(ds);
|
|
54
107
|
}
|
|
@@ -82,6 +135,164 @@ export class DataSourceManager {
|
|
|
82
135
|
if (!ds) return undefined;
|
|
83
136
|
return ds.getCollectionField(otherKeys.join('.'));
|
|
84
137
|
}
|
|
138
|
+
|
|
139
|
+
async ensureLoaded(options: { force?: boolean; keys?: string[] } = {}) {
|
|
140
|
+
const { force = false } = options;
|
|
141
|
+
const keys = this.resolveLoadKeys(options.keys);
|
|
142
|
+
const pendingKeys = force ? keys : keys.filter((key) => !this.loadedKeys.has(key));
|
|
143
|
+
if (!pendingKeys.length) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (this.loadingPromise) {
|
|
147
|
+
return this.loadingPromise;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.loadingPromise = (async () => {
|
|
151
|
+
try {
|
|
152
|
+
for (const key of pendingKeys) {
|
|
153
|
+
await this.loadKey(key, { initial: !this.loadedKeys.has(key), force });
|
|
154
|
+
}
|
|
155
|
+
} finally {
|
|
156
|
+
this.loadingPromise = null;
|
|
157
|
+
}
|
|
158
|
+
})();
|
|
159
|
+
|
|
160
|
+
return this.loadingPromise;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async reload(options: { keys?: string[] } = {}) {
|
|
164
|
+
const keys = this.resolveLoadKeys(options.keys);
|
|
165
|
+
if (!keys.length) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (this.loadingPromise) {
|
|
169
|
+
return this.loadingPromise;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this.loadingPromise = (async () => {
|
|
173
|
+
try {
|
|
174
|
+
for (const key of keys) {
|
|
175
|
+
await this.loadKey(key, { initial: false, force: true });
|
|
176
|
+
}
|
|
177
|
+
} finally {
|
|
178
|
+
this.loadingPromise = null;
|
|
179
|
+
}
|
|
180
|
+
})();
|
|
181
|
+
|
|
182
|
+
return this.loadingPromise;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async reloadDataSource(key: string) {
|
|
186
|
+
if (this.loadingKeys.has(key) && this.loadingPromise) {
|
|
187
|
+
return this.loadingPromise;
|
|
188
|
+
}
|
|
189
|
+
if (!this.loaders.has(key) && this.loaders.has('*')) {
|
|
190
|
+
return this.reload({ keys: ['*'] });
|
|
191
|
+
}
|
|
192
|
+
return this.reload({ keys: [key] });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
protected resolveLoadKeys(requestedKeys?: string[]) {
|
|
196
|
+
const normalizedKeys = requestedKeys?.length ? requestedKeys : ['main'];
|
|
197
|
+
const explicitKeys = normalizedKeys.filter((key) => this.loaders.has(key));
|
|
198
|
+
if (this.loaders.has('*')) {
|
|
199
|
+
return _.uniq(['*', ...explicitKeys]);
|
|
200
|
+
}
|
|
201
|
+
return explicitKeys.length ? explicitKeys : normalizedKeys;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
protected getApp() {
|
|
205
|
+
return this.flowEngine?.context?.app as { eventBus?: EventTarget } | undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
protected dispatchDataSourceEvent(
|
|
209
|
+
type: 'dataSource:loaded' | 'dataSource:loadFailed',
|
|
210
|
+
detail: { dataSourceKey: string; initial: boolean; error?: Error },
|
|
211
|
+
) {
|
|
212
|
+
this.getApp()?.eventBus?.dispatchEvent(new CustomEvent(type, { detail }));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
protected setDataSourceState(
|
|
216
|
+
key: string,
|
|
217
|
+
options: Partial<Pick<DataSourceOptions, 'status' | 'errorMessage'>> & Record<string, any>,
|
|
218
|
+
) {
|
|
219
|
+
const dataSource = this.getDataSource(key);
|
|
220
|
+
if (!dataSource) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
dataSource.patchOptions(options);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
protected applyDataSourceLoadResult(key: string, result: DataSourceLoadResult) {
|
|
227
|
+
if (key === '*') {
|
|
228
|
+
const dataSources = result?.dataSources || [];
|
|
229
|
+
dataSources.forEach((dataSourceOptions) => {
|
|
230
|
+
const { collections, ...dataSource } = dataSourceOptions;
|
|
231
|
+
this.upsertDataSource(dataSource);
|
|
232
|
+
if (collections) {
|
|
233
|
+
this.getDataSource(dataSource.key)?.setCollections(collections, { clearFields: true });
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const dataSource = this.getDataSource(key);
|
|
240
|
+
if (!dataSource) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
dataSource.setCollections(result?.collections || [], { clearFields: true });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
protected async loadKey(key: string, options: { initial: boolean; force: boolean }) {
|
|
247
|
+
const loader = this.loaders.get(key);
|
|
248
|
+
if (!loader) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!this.getDataSource(key) && key !== '*') {
|
|
253
|
+
this.addDataSource({ key });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const { initial } = options;
|
|
257
|
+
this.loadingKeys.add(key);
|
|
258
|
+
if (key !== '*') {
|
|
259
|
+
this.setDataSourceState(key, {
|
|
260
|
+
status: initial ? 'loading' : 'reloading',
|
|
261
|
+
errorMessage: undefined,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
this.loadErrors.set(key, null);
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const result = (await loader({ key, manager: this })) || {};
|
|
268
|
+
this.applyDataSourceLoadResult(key, result);
|
|
269
|
+
this.loadedKeys.add(key);
|
|
270
|
+
if (key !== '*') {
|
|
271
|
+
this.setDataSourceState(key, {
|
|
272
|
+
status: 'loaded',
|
|
273
|
+
errorMessage: undefined,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
this.dispatchDataSourceEvent('dataSource:loaded', { dataSourceKey: key, initial });
|
|
277
|
+
} catch (error) {
|
|
278
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
279
|
+
this.loadErrors.set(key, normalizedError);
|
|
280
|
+
if (key !== '*') {
|
|
281
|
+
this.setDataSourceState(key, {
|
|
282
|
+
status: initial ? 'loading-failed' : 'reloading-failed',
|
|
283
|
+
errorMessage: normalizedError.message,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
this.dispatchDataSourceEvent('dataSource:loadFailed', {
|
|
287
|
+
dataSourceKey: key,
|
|
288
|
+
initial,
|
|
289
|
+
error: normalizedError,
|
|
290
|
+
});
|
|
291
|
+
throw normalizedError;
|
|
292
|
+
} finally {
|
|
293
|
+
this.loadingKeys.delete(key);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
85
296
|
}
|
|
86
297
|
|
|
87
298
|
export class DataSource {
|
|
@@ -110,6 +321,14 @@ export class DataSource {
|
|
|
110
321
|
return this.options.key;
|
|
111
322
|
}
|
|
112
323
|
|
|
324
|
+
get status() {
|
|
325
|
+
return this.options.status;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
get errorMessage() {
|
|
329
|
+
return this.options.errorMessage;
|
|
330
|
+
}
|
|
331
|
+
|
|
113
332
|
setDataSourceManager(dataSourceManager: DataSourceManager) {
|
|
114
333
|
this.dataSourceManager = dataSourceManager;
|
|
115
334
|
}
|
|
@@ -149,6 +368,10 @@ export class DataSource {
|
|
|
149
368
|
return this.collectionManager.upsertCollections(collections, options);
|
|
150
369
|
}
|
|
151
370
|
|
|
371
|
+
setCollections(collections: CollectionOptions[], options: { clearFields?: boolean } = {}) {
|
|
372
|
+
return this.collectionManager.setCollections(collections, options);
|
|
373
|
+
}
|
|
374
|
+
|
|
152
375
|
removeCollection(name: string) {
|
|
153
376
|
return this.collectionManager.removeCollection(name);
|
|
154
377
|
}
|
|
@@ -162,6 +385,14 @@ export class DataSource {
|
|
|
162
385
|
Object.assign(this.options, newOptions);
|
|
163
386
|
}
|
|
164
387
|
|
|
388
|
+
patchOptions(newOptions: any = {}) {
|
|
389
|
+
Object.assign(this.options, newOptions);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
reload() {
|
|
393
|
+
return this.dataSourceManager.reloadDataSource(this.key);
|
|
394
|
+
}
|
|
395
|
+
|
|
165
396
|
getCollectionField(fieldPath: string) {
|
|
166
397
|
const [collectionName, ...otherKeys] = fieldPath.split('.');
|
|
167
398
|
const fieldName = otherKeys.join('.');
|
|
@@ -194,6 +425,11 @@ export class CollectionManager {
|
|
|
194
425
|
this.collections = observable.shallow<Map<string, Collection>>(new Map());
|
|
195
426
|
}
|
|
196
427
|
|
|
428
|
+
protected resetCaches() {
|
|
429
|
+
this.childrenCollectionsName = {};
|
|
430
|
+
this.allCollectionsInheritChain = undefined;
|
|
431
|
+
}
|
|
432
|
+
|
|
197
433
|
get flowEngine() {
|
|
198
434
|
return this.dataSource.flowEngine;
|
|
199
435
|
}
|
|
@@ -208,10 +444,12 @@ export class CollectionManager {
|
|
|
208
444
|
col.setDataSource(this.dataSource);
|
|
209
445
|
col.initInherits();
|
|
210
446
|
this.collections.set(col.name, col);
|
|
447
|
+
this.resetCaches();
|
|
211
448
|
}
|
|
212
449
|
|
|
213
450
|
removeCollection(name: string) {
|
|
214
451
|
this.collections.delete(name);
|
|
452
|
+
this.resetCaches();
|
|
215
453
|
}
|
|
216
454
|
|
|
217
455
|
updateCollection(newOptions: CollectionOptions, options: { clearFields?: boolean } = {}) {
|
|
@@ -220,6 +458,7 @@ export class CollectionManager {
|
|
|
220
458
|
throw new Error(`Collection ${newOptions.name} not found`);
|
|
221
459
|
}
|
|
222
460
|
collection.setOptions(newOptions, options);
|
|
461
|
+
this.resetCaches();
|
|
223
462
|
}
|
|
224
463
|
|
|
225
464
|
upsertCollection(options: CollectionOptions) {
|
|
@@ -239,6 +478,12 @@ export class CollectionManager {
|
|
|
239
478
|
this.addCollection(collection);
|
|
240
479
|
}
|
|
241
480
|
}
|
|
481
|
+
this.resetCaches();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
setCollections(collections: CollectionOptions[], options: { clearFields?: boolean } = {}) {
|
|
485
|
+
this.clearCollections();
|
|
486
|
+
this.upsertCollections(collections, options);
|
|
242
487
|
}
|
|
243
488
|
|
|
244
489
|
sortCollectionsByInherits(collections: CollectionOptions[]): CollectionOptions[] {
|
|
@@ -315,6 +560,7 @@ export class CollectionManager {
|
|
|
315
560
|
|
|
316
561
|
clearCollections() {
|
|
317
562
|
this.collections.clear();
|
|
563
|
+
this.resetCaches();
|
|
318
564
|
}
|
|
319
565
|
|
|
320
566
|
getAssociation(associationName: string): CollectionField | undefined {
|
|
@@ -537,6 +783,10 @@ export class Collection {
|
|
|
537
783
|
this.upsertFields(this.options.fields || []);
|
|
538
784
|
}
|
|
539
785
|
|
|
786
|
+
setOption(key: string, value: any) {
|
|
787
|
+
this.options[key] = value;
|
|
788
|
+
}
|
|
789
|
+
|
|
540
790
|
getFields(): CollectionField[] {
|
|
541
791
|
// 合并自身 fields 和所有 inherits 的 fields,后者优先被覆盖
|
|
542
792
|
const fieldMap = new Map<string, CollectionField>();
|
package/src/flowContext.ts
CHANGED
|
@@ -3006,8 +3006,10 @@ export class FlowContext {
|
|
|
3006
3006
|
}
|
|
3007
3007
|
|
|
3008
3008
|
class BaseFlowEngineContext extends FlowContext {
|
|
3009
|
+
declare t: (key: any, options?: any) => string;
|
|
3009
3010
|
declare router: Router;
|
|
3010
3011
|
declare dataSourceManager: DataSourceManager;
|
|
3012
|
+
declare isDarkTheme: boolean;
|
|
3011
3013
|
declare requireAsync: (url: string) => Promise<any>;
|
|
3012
3014
|
declare importAsync: (url: string) => Promise<any>;
|
|
3013
3015
|
declare createJSRunner: (options?: JSRunnerOptions) => Promise<JSRunner>;
|
package/src/flowI18n.ts
CHANGED
|
@@ -52,7 +52,8 @@ export class FlowI18n {
|
|
|
52
52
|
*/
|
|
53
53
|
private translateKey(key: string, options?: any): string {
|
|
54
54
|
if (this.context?.i18n?.t) {
|
|
55
|
-
|
|
55
|
+
const translated = this.context.i18n.t(key, options);
|
|
56
|
+
return translated == null || translated === '' ? key : translated;
|
|
56
57
|
}
|
|
57
58
|
// 如果没有翻译函数,返回原始键值
|
|
58
59
|
return key;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { DefaultStructure } from '
|
|
10
|
+
import type { DefaultStructure } from '../types';
|
|
11
11
|
import { CollectionFieldModel } from './CollectionFieldModel';
|
|
12
12
|
|
|
13
13
|
export class DisplayItemModel<T extends DefaultStructure = DefaultStructure> extends CollectionFieldModel<T> {}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { DefaultStructure } from '
|
|
10
|
+
import type { DefaultStructure } from '../types';
|
|
11
11
|
import { CollectionFieldModel } from './CollectionFieldModel';
|
|
12
12
|
|
|
13
13
|
export class EditableItemModel<T extends DefaultStructure = DefaultStructure> extends CollectionFieldModel<T> {}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { DefaultStructure } from '
|
|
10
|
+
import type { DefaultStructure } from '../types';
|
|
11
11
|
import { CollectionFieldModel } from './CollectionFieldModel';
|
|
12
12
|
|
|
13
13
|
export class FilterableItemModel<T extends DefaultStructure = DefaultStructure> extends CollectionFieldModel<T> {}
|
package/src/models/flowModel.tsx
CHANGED
|
@@ -60,16 +60,22 @@ const modelMetas = new WeakMap<typeof FlowModel, FlowModelMeta>();
|
|
|
60
60
|
const modelGlobalRegistries = new WeakMap<typeof FlowModel, GlobalFlowRegistry>();
|
|
61
61
|
|
|
62
62
|
type BaseMenuItem = NonNullable<MenuProps['items']>[number];
|
|
63
|
-
type
|
|
63
|
+
type MenuBaseItem = Omit<Exclude<BaseMenuItem, null>, 'key' | 'children'>;
|
|
64
64
|
|
|
65
|
-
export type FlowModelExtraMenuItem =
|
|
65
|
+
export type FlowModelExtraMenuItem = MenuBaseItem & {
|
|
66
66
|
key: React.Key;
|
|
67
67
|
group?: string;
|
|
68
68
|
sort?: number;
|
|
69
|
+
label?: React.ReactNode;
|
|
70
|
+
disabled?: boolean;
|
|
69
71
|
onClick?: () => void;
|
|
72
|
+
children?: FlowModelExtraMenuItem[];
|
|
70
73
|
};
|
|
71
74
|
|
|
72
|
-
type FlowModelExtraMenuItemInput = Omit<FlowModelExtraMenuItem, 'key'> & {
|
|
75
|
+
type FlowModelExtraMenuItemInput = Omit<FlowModelExtraMenuItem, 'key' | 'children'> & {
|
|
76
|
+
key?: React.Key;
|
|
77
|
+
children?: FlowModelExtraMenuItemInput[];
|
|
78
|
+
};
|
|
73
79
|
|
|
74
80
|
type ExtraMenuItemEntry = {
|
|
75
81
|
group?: string;
|
|
@@ -86,6 +92,56 @@ type ExtraMenuItemEntry = {
|
|
|
86
92
|
|
|
87
93
|
const classMenuExtensions = new WeakMap<typeof FlowModel, Set<ExtraMenuItemEntry>>();
|
|
88
94
|
|
|
95
|
+
const sortExtraMenuItems = (items: FlowModelExtraMenuItem[]) => {
|
|
96
|
+
return [...items].sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0));
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const isFlowModelExtraMenuItem = (item: FlowModelExtraMenuItem | null): item is FlowModelExtraMenuItem => {
|
|
100
|
+
return item !== null;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const normalizeExtraMenuItem = (
|
|
104
|
+
item: FlowModelExtraMenuItemInput,
|
|
105
|
+
{
|
|
106
|
+
group,
|
|
107
|
+
sort,
|
|
108
|
+
prefix,
|
|
109
|
+
path,
|
|
110
|
+
}: {
|
|
111
|
+
group: string;
|
|
112
|
+
sort: number;
|
|
113
|
+
prefix: string;
|
|
114
|
+
path: string;
|
|
115
|
+
},
|
|
116
|
+
): FlowModelExtraMenuItem | null => {
|
|
117
|
+
if (!item) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const normalizedGroup = item.group || group;
|
|
122
|
+
const normalizedSort = typeof item.sort === 'number' ? item.sort : sort;
|
|
123
|
+
const normalizedChildren = sortExtraMenuItems(
|
|
124
|
+
(item.children || [])
|
|
125
|
+
.map((child, index) =>
|
|
126
|
+
normalizeExtraMenuItem(child, {
|
|
127
|
+
group: normalizedGroup,
|
|
128
|
+
sort: normalizedSort,
|
|
129
|
+
prefix,
|
|
130
|
+
path: `${path}-${index}`,
|
|
131
|
+
}),
|
|
132
|
+
)
|
|
133
|
+
.filter(isFlowModelExtraMenuItem),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
...item,
|
|
138
|
+
key: item.key ?? `${prefix}-${normalizedGroup}-${path}`,
|
|
139
|
+
group: normalizedGroup,
|
|
140
|
+
sort: normalizedSort,
|
|
141
|
+
children: normalizedChildren.length ? normalizedChildren : undefined,
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
89
145
|
async function loadOpenStepSettingsDialog() {
|
|
90
146
|
const mod = await import('../components/settings/wrappers/contextual/StepSettingsDialog');
|
|
91
147
|
return mod.openStepSettingsDialog;
|
|
@@ -1194,17 +1250,17 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1194
1250
|
return model;
|
|
1195
1251
|
}
|
|
1196
1252
|
|
|
1197
|
-
filterSubModels<K extends keyof Structure['subModels']
|
|
1253
|
+
filterSubModels<K extends keyof NonNullable<Structure['subModels']>, R>(
|
|
1198
1254
|
subKey: K,
|
|
1199
|
-
callback: (model: ArrayElementType<Structure['subModels'][K]>, index: number) => boolean,
|
|
1200
|
-
): ArrayElementType<Structure['subModels'][K]>[] {
|
|
1255
|
+
callback: (model: ArrayElementType<NonNullable<Structure['subModels']>[K]>, index: number) => boolean,
|
|
1256
|
+
): ArrayElementType<NonNullable<Structure['subModels']>[K]>[] {
|
|
1201
1257
|
const model = (this.subModels as any)[subKey as string];
|
|
1202
1258
|
|
|
1203
1259
|
if (!model) {
|
|
1204
1260
|
return [];
|
|
1205
1261
|
}
|
|
1206
1262
|
|
|
1207
|
-
const results: ArrayElementType<Structure['subModels'][K]>[] = [];
|
|
1263
|
+
const results: ArrayElementType<NonNullable<Structure['subModels']>[K]>[] = [];
|
|
1208
1264
|
|
|
1209
1265
|
_.castArray(model)
|
|
1210
1266
|
.sort((a, b) => (a.sortIndex || 0) - (b.sortIndex || 0))
|
|
@@ -1218,9 +1274,9 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1218
1274
|
return results;
|
|
1219
1275
|
}
|
|
1220
1276
|
|
|
1221
|
-
mapSubModels<K extends keyof Structure['subModels']
|
|
1277
|
+
mapSubModels<K extends keyof NonNullable<Structure['subModels']>, R>(
|
|
1222
1278
|
subKey: K,
|
|
1223
|
-
callback: (model: ArrayElementType<Structure['subModels'][K]>, index: number) => R,
|
|
1279
|
+
callback: (model: ArrayElementType<NonNullable<Structure['subModels']>[K]>, index: number) => R,
|
|
1224
1280
|
): R[] {
|
|
1225
1281
|
const model = (this.subModels as any)[subKey as string];
|
|
1226
1282
|
|
|
@@ -1240,7 +1296,7 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1240
1296
|
return results;
|
|
1241
1297
|
}
|
|
1242
1298
|
|
|
1243
|
-
hasSubModel<K extends keyof Structure['subModels']
|
|
1299
|
+
hasSubModel<K extends keyof NonNullable<Structure['subModels']>>(subKey: K) {
|
|
1244
1300
|
const subModel = (this.subModels as any)[subKey as string];
|
|
1245
1301
|
if (!subModel) {
|
|
1246
1302
|
return false;
|
|
@@ -1248,10 +1304,10 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1248
1304
|
return _.castArray(subModel).length > 0;
|
|
1249
1305
|
}
|
|
1250
1306
|
|
|
1251
|
-
findSubModel<K extends keyof Structure['subModels']
|
|
1307
|
+
findSubModel<K extends keyof NonNullable<Structure['subModels']>, R>(
|
|
1252
1308
|
subKey: K,
|
|
1253
|
-
callback: (model: ArrayElementType<Structure['subModels'][K]>) => R,
|
|
1254
|
-
): ArrayElementType<Structure['subModels'][K]> | null {
|
|
1309
|
+
callback: (model: ArrayElementType<NonNullable<Structure['subModels']>[K]>) => R,
|
|
1310
|
+
): ArrayElementType<NonNullable<Structure['subModels']>[K]> | null {
|
|
1255
1311
|
const model = (this.subModels as any)[subKey as string];
|
|
1256
1312
|
|
|
1257
1313
|
if (!model) {
|
|
@@ -1261,7 +1317,7 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1261
1317
|
return (
|
|
1262
1318
|
(_.castArray(model).find((item) => {
|
|
1263
1319
|
return (callback as (model: any) => R)(item);
|
|
1264
|
-
}) as ArrayElementType<Structure['subModels'][K]> | undefined) || null
|
|
1320
|
+
}) as ArrayElementType<NonNullable<Structure['subModels']>[K]> | undefined) || null
|
|
1265
1321
|
);
|
|
1266
1322
|
}
|
|
1267
1323
|
|
|
@@ -1607,6 +1663,7 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1607
1663
|
seen.add(Cls);
|
|
1608
1664
|
const reg = classMenuExtensions.get(Cls);
|
|
1609
1665
|
if (reg) {
|
|
1666
|
+
let entryIndex = 0;
|
|
1610
1667
|
for (const entry of reg) {
|
|
1611
1668
|
if (entry.matcher && !entry.matcher(model)) continue;
|
|
1612
1669
|
const items =
|
|
@@ -1614,16 +1671,21 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1614
1671
|
const group = entry.group || 'common-actions';
|
|
1615
1672
|
const sort = entry.sort ?? 0;
|
|
1616
1673
|
const prefix = entry.keyPrefix || Cls.name || 'extra';
|
|
1617
|
-
(
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1674
|
+
sortExtraMenuItems(
|
|
1675
|
+
(items || [])
|
|
1676
|
+
.map((it, idx: number) =>
|
|
1677
|
+
normalizeExtraMenuItem(it, {
|
|
1678
|
+
group,
|
|
1679
|
+
sort,
|
|
1680
|
+
prefix,
|
|
1681
|
+
path: `${entryIndex}-${idx}`,
|
|
1682
|
+
}),
|
|
1683
|
+
)
|
|
1684
|
+
.filter(isFlowModelExtraMenuItem),
|
|
1685
|
+
).forEach((it) => {
|
|
1686
|
+
collected.push(it);
|
|
1626
1687
|
});
|
|
1688
|
+
entryIndex += 1;
|
|
1627
1689
|
}
|
|
1628
1690
|
}
|
|
1629
1691
|
const ParentClass = Object.getPrototypeOf(Cls) as typeof FlowModel;
|
package/src/provider.tsx
CHANGED
|
@@ -45,34 +45,50 @@ export const FlowEngineGlobalsContextProvider: React.FC<{ children: React.ReactN
|
|
|
45
45
|
const engine = useFlowEngine();
|
|
46
46
|
const config = useContext(ConfigProvider.ConfigContext);
|
|
47
47
|
const { token } = theme.useToken();
|
|
48
|
+
const isDarkTheme = React.useMemo(() => {
|
|
49
|
+
const algorithm = config?.theme?.algorithm;
|
|
50
|
+
if (Array.isArray(algorithm)) {
|
|
51
|
+
return algorithm.includes(theme.darkAlgorithm);
|
|
52
|
+
}
|
|
53
|
+
return algorithm === theme.darkAlgorithm;
|
|
54
|
+
}, [config]);
|
|
48
55
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (value) {
|
|
64
|
-
engine.context.defineProperty(key, { value });
|
|
65
|
-
}
|
|
56
|
+
// 这些全局能力需要在 children 首次渲染前就可读,不能等到 effect 后再挂到上下文。
|
|
57
|
+
engine.context.defineProperty('viewer', {
|
|
58
|
+
cache: false,
|
|
59
|
+
get: (ctx) => new FlowViewer(ctx, { drawer, embed, popover, dialog }),
|
|
60
|
+
});
|
|
61
|
+
for (const item of Object.entries({
|
|
62
|
+
antdConfig: config,
|
|
63
|
+
modal,
|
|
64
|
+
message,
|
|
65
|
+
notification,
|
|
66
|
+
})) {
|
|
67
|
+
const [key, value] = item;
|
|
68
|
+
if (value) {
|
|
69
|
+
engine.context.defineProperty(key, { value });
|
|
66
70
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
}
|
|
72
|
+
// 将 themeToken 定义为 observable, 使组件能够响应主题的变更。
|
|
73
|
+
engine.context.defineProperty('themeToken', {
|
|
74
|
+
get: () => token,
|
|
75
|
+
observable: true,
|
|
76
|
+
cache: true,
|
|
77
|
+
});
|
|
78
|
+
// 统一把暗色模式暴露到 Flow 上下文,避免 flow 侧继续依赖 global-theme。
|
|
79
|
+
engine.context.defineProperty('isDarkTheme', {
|
|
80
|
+
get: () => isDarkTheme,
|
|
81
|
+
observable: true,
|
|
82
|
+
cache: true,
|
|
83
|
+
info: {
|
|
84
|
+
description: 'Whether current theme algorithm is dark mode.',
|
|
85
|
+
detail: 'boolean',
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
74
90
|
engine.reactView.refresh();
|
|
75
|
-
}, [engine, drawer, modal, message, notification, config, popover, token, dialog, embed]);
|
|
91
|
+
}, [engine, drawer, modal, message, notification, config, popover, token, dialog, embed, isDarkTheme]);
|
|
76
92
|
|
|
77
93
|
return (
|
|
78
94
|
<ConfigProvider {...config} locale={engine.context.locales?.antd} popupMatchSelectWidth={false}>
|