@nocobase/flow-engine 2.2.0-beta.3 → 2.2.0-beta.5
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/flowSettings.d.ts +5 -1
- package/lib/flowSettings.js +70 -0
- package/lib/types.d.ts +12 -0
- package/package.json +4 -4
- package/src/__tests__/flowSettings.test.ts +72 -0
- package/src/flowSettings.ts +85 -1
- package/src/types.ts +14 -0
package/lib/flowSettings.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import React from 'react';
|
|
10
10
|
import { FlowEngine } from '.';
|
|
11
11
|
import type { FlowModel } from './models';
|
|
12
|
-
import { StepSettingsDialogProps, ToolbarItemConfig } from './types';
|
|
12
|
+
import { DynamicFlowSource, DynamicFlowSourceProvider, StepSettingsDialogProps, ToolbarItemConfig } from './types';
|
|
13
13
|
/**
|
|
14
14
|
* 打开流程设置的参数接口
|
|
15
15
|
*/
|
|
@@ -93,6 +93,7 @@ export declare class FlowSettings {
|
|
|
93
93
|
enabled: boolean;
|
|
94
94
|
private engine;
|
|
95
95
|
toolbarItems: ToolbarItemConfig[];
|
|
96
|
+
private dynamicFlowSourceProviders;
|
|
96
97
|
constructor(engine: FlowEngine);
|
|
97
98
|
on(event: 'beforeOpen', callback: (...args: any[]) => void): void;
|
|
98
99
|
off(event: 'beforeOpen', callback: (...args: any[]) => void): void;
|
|
@@ -206,6 +207,9 @@ export declare class FlowSettings {
|
|
|
206
207
|
* @returns {ToolbarItemConfig[]} 所有项目配置
|
|
207
208
|
*/
|
|
208
209
|
getToolbarItems(): ToolbarItemConfig[];
|
|
210
|
+
registerDynamicFlowSourceProvider(provider: DynamicFlowSourceProvider): () => void;
|
|
211
|
+
hasDynamicFlowSourceProvider(model: FlowModel): boolean;
|
|
212
|
+
getDynamicFlowSources(model: FlowModel): Promise<DynamicFlowSource[]>;
|
|
209
213
|
/**
|
|
210
214
|
* 清空所有工具栏项目
|
|
211
215
|
* @example
|
package/lib/flowSettings.js
CHANGED
|
@@ -78,6 +78,7 @@ const _FlowSettings = class _FlowSettings {
|
|
|
78
78
|
__privateAdd(this, _forceEnabled, false);
|
|
79
79
|
// 强制启用状态,主要用于设计模式下的强制启用
|
|
80
80
|
__publicField(this, "toolbarItems", []);
|
|
81
|
+
__publicField(this, "dynamicFlowSourceProviders", []);
|
|
81
82
|
__privateAdd(this, _emitter, new import_emitter.Emitter());
|
|
82
83
|
this.engine = engine;
|
|
83
84
|
this.enabled = false;
|
|
@@ -369,6 +370,75 @@ const _FlowSettings = class _FlowSettings {
|
|
|
369
370
|
getToolbarItems() {
|
|
370
371
|
return [...this.toolbarItems];
|
|
371
372
|
}
|
|
373
|
+
registerDynamicFlowSourceProvider(provider) {
|
|
374
|
+
const existingIndex = this.dynamicFlowSourceProviders.findIndex((item) => item.key === provider.key);
|
|
375
|
+
if (existingIndex !== -1) {
|
|
376
|
+
console.warn(
|
|
377
|
+
`FlowSettings: Dynamic flow source provider with key '${provider.key}' already exists and will be replaced.`
|
|
378
|
+
);
|
|
379
|
+
this.dynamicFlowSourceProviders[existingIndex] = provider;
|
|
380
|
+
} else {
|
|
381
|
+
this.dynamicFlowSourceProviders.push(provider);
|
|
382
|
+
}
|
|
383
|
+
this.dynamicFlowSourceProviders.sort((a, b) => (a.sort || 0) - (b.sort || 0));
|
|
384
|
+
return () => {
|
|
385
|
+
const index = this.dynamicFlowSourceProviders.indexOf(provider);
|
|
386
|
+
if (index !== -1) {
|
|
387
|
+
this.dynamicFlowSourceProviders.splice(index, 1);
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
hasDynamicFlowSourceProvider(model) {
|
|
392
|
+
return this.dynamicFlowSourceProviders.some((provider) => {
|
|
393
|
+
try {
|
|
394
|
+
return provider.visible ? provider.visible(model) : true;
|
|
395
|
+
} catch (error) {
|
|
396
|
+
console.warn(`FlowSettings: Dynamic flow source provider '${provider.key}' visibility check failed.`, error);
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
async getDynamicFlowSources(model) {
|
|
402
|
+
const t = (0, import_utils.getT)(model);
|
|
403
|
+
const selfSource = {
|
|
404
|
+
key: "self",
|
|
405
|
+
label: t("Current block"),
|
|
406
|
+
model,
|
|
407
|
+
sort: -1e3
|
|
408
|
+
};
|
|
409
|
+
const sources = [];
|
|
410
|
+
const seenKeys = /* @__PURE__ */ new Set(["self"]);
|
|
411
|
+
const seenModelUids = /* @__PURE__ */ new Set([model.uid]);
|
|
412
|
+
for (const provider of this.dynamicFlowSourceProviders) {
|
|
413
|
+
try {
|
|
414
|
+
if (provider.visible && !provider.visible(model)) {
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const providerSources = await provider.getSources(model);
|
|
418
|
+
for (const source of providerSources || []) {
|
|
419
|
+
if (!(source == null ? void 0 : source.key) || !source.model) {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
const key = String(source.key);
|
|
423
|
+
const uid = source.model.uid;
|
|
424
|
+
if (seenKeys.has(key) || seenModelUids.has(uid)) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
seenKeys.add(key);
|
|
428
|
+
seenModelUids.add(uid);
|
|
429
|
+
sources.push({
|
|
430
|
+
...source,
|
|
431
|
+
key,
|
|
432
|
+
label: source.label || key,
|
|
433
|
+
sort: source.sort || 0
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
} catch (error) {
|
|
437
|
+
console.warn(`FlowSettings: Dynamic flow source provider '${provider.key}' failed.`, error);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return [selfSource, ...sources.sort((a, b) => (a.sort || 0) - (b.sort || 0))];
|
|
441
|
+
}
|
|
372
442
|
/**
|
|
373
443
|
* 清空所有工具栏项目
|
|
374
444
|
* @example
|
package/lib/types.d.ts
CHANGED
|
@@ -500,6 +500,18 @@ export interface ToolbarItemConfig {
|
|
|
500
500
|
/** 排序权重,数字越小越靠右(先添加的在右边) */
|
|
501
501
|
sort?: number;
|
|
502
502
|
}
|
|
503
|
+
export interface DynamicFlowSource {
|
|
504
|
+
key: string;
|
|
505
|
+
label: React.ReactNode;
|
|
506
|
+
model: FlowModel;
|
|
507
|
+
sort?: number;
|
|
508
|
+
}
|
|
509
|
+
export interface DynamicFlowSourceProvider {
|
|
510
|
+
key: string;
|
|
511
|
+
sort?: number;
|
|
512
|
+
visible?: (model: FlowModel) => boolean;
|
|
513
|
+
getSources: (model: FlowModel) => DynamicFlowSource[] | Promise<DynamicFlowSource[]>;
|
|
514
|
+
}
|
|
503
515
|
export interface ApplyFlowCacheEntry {
|
|
504
516
|
status: 'pending' | 'resolved' | 'rejected';
|
|
505
517
|
promise: Promise<any>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.2.0-beta.
|
|
3
|
+
"version": "2.2.0-beta.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@formily/antd-v5": "1.x",
|
|
10
10
|
"@formily/reactive": "2.x",
|
|
11
|
-
"@nocobase/sdk": "2.2.0-beta.
|
|
12
|
-
"@nocobase/shared": "2.2.0-beta.
|
|
11
|
+
"@nocobase/sdk": "2.2.0-beta.5",
|
|
12
|
+
"@nocobase/shared": "2.2.0-beta.5",
|
|
13
13
|
"ahooks": "^3.7.2",
|
|
14
14
|
"axios": "^1.7.0",
|
|
15
15
|
"dayjs": "^1.11.9",
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
],
|
|
38
38
|
"author": "NocoBase Team",
|
|
39
39
|
"license": "Apache-2.0",
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "81eab2cfa9d3d989e99ba0c914807b38db55f023"
|
|
41
41
|
}
|
|
@@ -557,6 +557,78 @@ describe('FlowSettings', () => {
|
|
|
557
557
|
});
|
|
558
558
|
});
|
|
559
559
|
|
|
560
|
+
describe('Dynamic Flow Source Providers', () => {
|
|
561
|
+
test('should return current model as the default dynamic flow source', async () => {
|
|
562
|
+
const model = new FlowModel({ uid: 'source-model', flowEngine: engine });
|
|
563
|
+
|
|
564
|
+
const sources = await flowSettings.getDynamicFlowSources(model);
|
|
565
|
+
|
|
566
|
+
expect(sources).toHaveLength(1);
|
|
567
|
+
expect(sources[0].key).toBe('self');
|
|
568
|
+
expect(sources[0].label).toBe('Current block');
|
|
569
|
+
expect(sources[0].model).toBe(model);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test('should register, resolve, and dispose dynamic flow source providers', async () => {
|
|
573
|
+
const model = new FlowModel({ uid: 'source-model', flowEngine: engine });
|
|
574
|
+
const target = new FlowModel({ uid: 'target-model', flowEngine: engine });
|
|
575
|
+
|
|
576
|
+
const dispose = flowSettings.registerDynamicFlowSourceProvider({
|
|
577
|
+
key: 'test-provider',
|
|
578
|
+
visible: (m) => m.uid === model.uid,
|
|
579
|
+
getSources: () => [{ key: 'target', label: 'Target model', model: target, sort: 10 }],
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
expect(flowSettings.hasDynamicFlowSourceProvider(model)).toBe(true);
|
|
583
|
+
expect(flowSettings.hasDynamicFlowSourceProvider(target)).toBe(false);
|
|
584
|
+
|
|
585
|
+
const sources = await flowSettings.getDynamicFlowSources(model);
|
|
586
|
+
expect(sources.map((source) => source.key)).toEqual(['self', 'target']);
|
|
587
|
+
|
|
588
|
+
dispose();
|
|
589
|
+
|
|
590
|
+
expect(flowSettings.hasDynamicFlowSourceProvider(model)).toBe(false);
|
|
591
|
+
await expect(flowSettings.getDynamicFlowSources(model)).resolves.toHaveLength(1);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
test('should skip duplicate dynamic flow source keys and model uids', async () => {
|
|
595
|
+
const model = new FlowModel({ uid: 'source-model', flowEngine: engine });
|
|
596
|
+
const target = new FlowModel({ uid: 'target-model', flowEngine: engine });
|
|
597
|
+
|
|
598
|
+
flowSettings.registerDynamicFlowSourceProvider({
|
|
599
|
+
key: 'test-provider',
|
|
600
|
+
getSources: () => [
|
|
601
|
+
{ key: 'target', label: 'Target model', model: target },
|
|
602
|
+
{ key: 'target', label: 'Duplicate key', model: new FlowModel({ uid: 'other-model', flowEngine: engine }) },
|
|
603
|
+
{ key: 'duplicate-uid', label: 'Duplicate uid', model: target },
|
|
604
|
+
],
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
const sources = await flowSettings.getDynamicFlowSources(model);
|
|
608
|
+
|
|
609
|
+
expect(sources.map((source) => source.key)).toEqual(['self', 'target']);
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
test('should ignore failing dynamic flow source providers', async () => {
|
|
613
|
+
const model = new FlowModel({ uid: 'source-model', flowEngine: engine });
|
|
614
|
+
|
|
615
|
+
flowSettings.registerDynamicFlowSourceProvider({
|
|
616
|
+
key: 'failing-provider',
|
|
617
|
+
getSources: () => {
|
|
618
|
+
throw new Error('provider failed');
|
|
619
|
+
},
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
const sources = await flowSettings.getDynamicFlowSources(model);
|
|
623
|
+
|
|
624
|
+
expect(sources.map((source) => source.key)).toEqual(['self']);
|
|
625
|
+
expect(consoleSpy.warn).toHaveBeenCalledWith(
|
|
626
|
+
"FlowSettings: Dynamic flow source provider 'failing-provider' failed.",
|
|
627
|
+
expect.any(Error),
|
|
628
|
+
);
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
|
|
560
632
|
describe('Step Settings Dialog', () => {
|
|
561
633
|
test('should call openStepSettingsDialog with correct parameters', async () => {
|
|
562
634
|
const { openStepSettingsDialog } = await import('../components/settings/wrappers/contextual/StepSettingsDialog');
|
package/src/flowSettings.ts
CHANGED
|
@@ -20,7 +20,13 @@ import { FlowRuntimeContext } from './flowContext';
|
|
|
20
20
|
import { FlowEngine, untracked } from '.';
|
|
21
21
|
import { FlowSettingsContextProvider, useFlowSettingsContext } from './hooks/useFlowSettingsContext';
|
|
22
22
|
import type { FlowModel } from './models';
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
DynamicFlowSource,
|
|
25
|
+
DynamicFlowSourceProvider,
|
|
26
|
+
ParamObject,
|
|
27
|
+
StepSettingsDialogProps,
|
|
28
|
+
ToolbarItemConfig,
|
|
29
|
+
} from './types';
|
|
24
30
|
import {
|
|
25
31
|
compileUiSchema,
|
|
26
32
|
FlowCancelSaveException,
|
|
@@ -128,6 +134,7 @@ export class FlowSettings {
|
|
|
128
134
|
private engine: FlowEngine;
|
|
129
135
|
#forceEnabled = false; // 强制启用状态,主要用于设计模式下的强制启用
|
|
130
136
|
public toolbarItems: ToolbarItemConfig[] = [];
|
|
137
|
+
private dynamicFlowSourceProviders: DynamicFlowSourceProvider[] = [];
|
|
131
138
|
#emitter: Emitter = new Emitter();
|
|
132
139
|
|
|
133
140
|
constructor(engine: FlowEngine) {
|
|
@@ -463,6 +470,83 @@ export class FlowSettings {
|
|
|
463
470
|
return [...this.toolbarItems];
|
|
464
471
|
}
|
|
465
472
|
|
|
473
|
+
public registerDynamicFlowSourceProvider(provider: DynamicFlowSourceProvider): () => void {
|
|
474
|
+
const existingIndex = this.dynamicFlowSourceProviders.findIndex((item) => item.key === provider.key);
|
|
475
|
+
if (existingIndex !== -1) {
|
|
476
|
+
console.warn(
|
|
477
|
+
`FlowSettings: Dynamic flow source provider with key '${provider.key}' already exists and will be replaced.`,
|
|
478
|
+
);
|
|
479
|
+
this.dynamicFlowSourceProviders[existingIndex] = provider;
|
|
480
|
+
} else {
|
|
481
|
+
this.dynamicFlowSourceProviders.push(provider);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
this.dynamicFlowSourceProviders.sort((a, b) => (a.sort || 0) - (b.sort || 0));
|
|
485
|
+
|
|
486
|
+
return () => {
|
|
487
|
+
const index = this.dynamicFlowSourceProviders.indexOf(provider);
|
|
488
|
+
if (index !== -1) {
|
|
489
|
+
this.dynamicFlowSourceProviders.splice(index, 1);
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
public hasDynamicFlowSourceProvider(model: FlowModel): boolean {
|
|
495
|
+
return this.dynamicFlowSourceProviders.some((provider) => {
|
|
496
|
+
try {
|
|
497
|
+
return provider.visible ? provider.visible(model) : true;
|
|
498
|
+
} catch (error) {
|
|
499
|
+
console.warn(`FlowSettings: Dynamic flow source provider '${provider.key}' visibility check failed.`, error);
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
public async getDynamicFlowSources(model: FlowModel): Promise<DynamicFlowSource[]> {
|
|
506
|
+
const t = getT(model);
|
|
507
|
+
const selfSource: DynamicFlowSource = {
|
|
508
|
+
key: 'self',
|
|
509
|
+
label: t('Current block'),
|
|
510
|
+
model,
|
|
511
|
+
sort: -1000,
|
|
512
|
+
};
|
|
513
|
+
const sources: DynamicFlowSource[] = [];
|
|
514
|
+
const seenKeys = new Set<string>(['self']);
|
|
515
|
+
const seenModelUids = new Set<string>([model.uid]);
|
|
516
|
+
|
|
517
|
+
for (const provider of this.dynamicFlowSourceProviders) {
|
|
518
|
+
try {
|
|
519
|
+
if (provider.visible && !provider.visible(model)) {
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const providerSources = await provider.getSources(model);
|
|
524
|
+
for (const source of providerSources || []) {
|
|
525
|
+
if (!source?.key || !source.model) {
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
const key = String(source.key);
|
|
529
|
+
const uid = source.model.uid;
|
|
530
|
+
if (seenKeys.has(key) || seenModelUids.has(uid)) {
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
seenKeys.add(key);
|
|
534
|
+
seenModelUids.add(uid);
|
|
535
|
+
sources.push({
|
|
536
|
+
...source,
|
|
537
|
+
key,
|
|
538
|
+
label: source.label || key,
|
|
539
|
+
sort: source.sort || 0,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
} catch (error) {
|
|
543
|
+
console.warn(`FlowSettings: Dynamic flow source provider '${provider.key}' failed.`, error);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return [selfSource, ...sources.sort((a, b) => (a.sort || 0) - (b.sort || 0))];
|
|
548
|
+
}
|
|
549
|
+
|
|
466
550
|
/**
|
|
467
551
|
* 清空所有工具栏项目
|
|
468
552
|
* @example
|
package/src/types.ts
CHANGED
|
@@ -612,6 +612,20 @@ export interface ToolbarItemConfig {
|
|
|
612
612
|
sort?: number;
|
|
613
613
|
}
|
|
614
614
|
|
|
615
|
+
export interface DynamicFlowSource {
|
|
616
|
+
key: string;
|
|
617
|
+
label: React.ReactNode;
|
|
618
|
+
model: FlowModel;
|
|
619
|
+
sort?: number;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
export interface DynamicFlowSourceProvider {
|
|
623
|
+
key: string;
|
|
624
|
+
sort?: number;
|
|
625
|
+
visible?: (model: FlowModel) => boolean;
|
|
626
|
+
getSources: (model: FlowModel) => DynamicFlowSource[] | Promise<DynamicFlowSource[]>;
|
|
627
|
+
}
|
|
628
|
+
|
|
615
629
|
export interface ApplyFlowCacheEntry {
|
|
616
630
|
status: 'pending' | 'resolved' | 'rejected';
|
|
617
631
|
promise: Promise<any>;
|