@nocobase/flow-engine 2.0.0-alpha.3 → 2.0.0-alpha.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.
@@ -0,0 +1,125 @@
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
+ import { describe, expect, it } from 'vitest';
11
+ import { sortCollectionsByInherits } from '../sortCollectionsByInherits';
12
+
13
+ describe('sortCollectionsByInherits', () => {
14
+ it('should sort collections by their inherits in correct order', () => {
15
+ const collections = [
16
+ { name: 'posts', inherits: [] },
17
+ { name: 'comments', inherits: ['posts'] },
18
+ { name: 'users', inherits: ['comments'] },
19
+ ];
20
+ const sorted = sortCollectionsByInherits(collections);
21
+ expect(sorted.map((c) => c.name)).toEqual(['posts', 'comments', 'users']);
22
+ });
23
+
24
+ it('should handle collections with no inherits', () => {
25
+ const collections = [
26
+ { name: 'users', inherits: [] },
27
+ { name: 'posts', inherits: [] },
28
+ ];
29
+ const sorted = sortCollectionsByInherits(collections);
30
+ expect(sorted.map((c) => c.name)).toEqual(['users', 'posts']);
31
+ });
32
+
33
+ it('should handle collections with undefined inherits', () => {
34
+ const collections = [{ name: 'users' }, { name: 'posts' }];
35
+ const sorted = sortCollectionsByInherits(collections);
36
+ expect(sorted.map((c) => c.name)).toEqual(['users', 'posts']);
37
+ });
38
+
39
+ it('should handle complex inheritance chains', () => {
40
+ const collections = [
41
+ { name: 'base', inherits: [] },
42
+ { name: 'middle', inherits: ['base'] },
43
+ { name: 'top', inherits: ['middle'] },
44
+ { name: 'independent', inherits: [] },
45
+ ];
46
+ const sorted = sortCollectionsByInherits(collections);
47
+ expect(sorted.map((c) => c.name)).toEqual(['base', 'middle', 'top', 'independent']);
48
+ });
49
+
50
+ it('should handle multiple inheritance', () => {
51
+ const collections = [
52
+ { name: 'user', inherits: [] },
53
+ { name: 'post', inherits: [] },
54
+ { name: 'comment', inherits: ['user', 'post'] },
55
+ ];
56
+ const sorted = sortCollectionsByInherits(collections);
57
+ expect(sorted.map((c) => c.name)).toEqual(['user', 'post', 'comment']);
58
+ });
59
+
60
+ it('should throw error for circular dependencies', () => {
61
+ const collections = [
62
+ { name: 'a', inherits: ['b'] },
63
+ { name: 'b', inherits: ['a'] },
64
+ ];
65
+ expect(() => sortCollectionsByInherits(collections)).toThrow('Circular dependency detected');
66
+ });
67
+
68
+ it('should gracefully handle missing inherit collections', () => {
69
+ const collections = [
70
+ { name: 'posts', inherits: ['nonexistent'] },
71
+ { name: 'users', inherits: [] },
72
+ ];
73
+ const sorted = sortCollectionsByInherits(collections);
74
+ expect(sorted.map((c) => c.name)).toEqual(['posts', 'users']);
75
+ });
76
+
77
+ it('should handle self-inheritance circular dependency', () => {
78
+ const collections = [{ name: 'posts', inherits: ['posts'] }];
79
+ expect(() => sortCollectionsByInherits(collections)).toThrow('Circular dependency detected');
80
+ });
81
+
82
+ it('should maintain order for collections without dependencies', () => {
83
+ const collections = [
84
+ { name: 'z', inherits: [] },
85
+ { name: 'a', inherits: [] },
86
+ { name: 'm', inherits: [] },
87
+ ];
88
+ const sorted = sortCollectionsByInherits(collections);
89
+ expect(sorted.map((c) => c.name)).toEqual(['z', 'a', 'm']);
90
+ });
91
+
92
+ it('should handle empty collections array', () => {
93
+ const sorted = sortCollectionsByInherits([]);
94
+ expect(sorted).toEqual([]);
95
+ });
96
+
97
+ it('should handle collections with multiple missing inherits', () => {
98
+ const collections = [
99
+ { name: 'posts', inherits: ['missing1', 'missing2'] },
100
+ { name: 'users', inherits: ['missing3'] },
101
+ { name: 'comments', inherits: ['posts', 'missing4'] },
102
+ ];
103
+ const sorted = sortCollectionsByInherits(collections);
104
+ expect(sorted.map((c) => c.name)).toEqual(['posts', 'users', 'comments']);
105
+ });
106
+
107
+ it('should handle mixed existing and missing inherits', () => {
108
+ const collections = [
109
+ { name: 'base', inherits: [] },
110
+ { name: 'middle', inherits: ['base', 'missing'] },
111
+ { name: 'top', inherits: ['middle'] },
112
+ ];
113
+ const sorted = sortCollectionsByInherits(collections);
114
+ expect(sorted.map((c) => c.name)).toEqual(['base', 'middle', 'top']);
115
+ });
116
+
117
+ it('should handle all inherits missing', () => {
118
+ const collections = [
119
+ { name: 'posts', inherits: ['missing1', 'missing2'] },
120
+ { name: 'users', inherits: ['missing3'] },
121
+ ];
122
+ const sorted = sortCollectionsByInherits(collections);
123
+ expect(sorted.map((c) => c.name)).toEqual(['posts', 'users']);
124
+ });
125
+ });
@@ -11,6 +11,7 @@ import { observable } from '@formily/reactive';
11
11
  import _ from 'lodash';
12
12
  import { FlowEngine } from '../flowEngine';
13
13
  import { jioToJoiSchema } from './jioToJoiSchema';
14
+ import { sortCollectionsByInherits } from './sortCollectionsByInherits';
14
15
  export interface DataSourceOptions extends Record<string, any> {
15
16
  key: string;
16
17
  displayName?: string;
@@ -227,7 +228,7 @@ export class CollectionManager {
227
228
  }
228
229
 
229
230
  upsertCollections(collections: CollectionOptions[]) {
230
- for (const collection of this.sortCollectionsByInherits(collections)) {
231
+ for (const collection of sortCollectionsByInherits(collections)) {
231
232
  if (this.collections.has(collection.name)) {
232
233
  this.updateCollection(collection);
233
234
  } else {
@@ -400,7 +401,8 @@ export class Collection {
400
401
  for (const inherit of this.options.inherits || []) {
401
402
  const collection = this.collectionManager.getCollection(inherit);
402
403
  if (!collection) {
403
- throw new Error(`Collection ${inherit} not found`);
404
+ console.warn(`Warning: Collection ${inherit} not found for collection ${this.name}`);
405
+ continue;
404
406
  }
405
407
  this.inherits.set(inherit, collection);
406
408
  }
@@ -0,0 +1,61 @@
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
+ import { CollectionOptions } from '.';
11
+
12
+ export function sortCollectionsByInherits(collections: CollectionOptions[]): CollectionOptions[] {
13
+ const sorted: CollectionOptions[] = [];
14
+ const visited = new Set<string>();
15
+ const visiting = new Set<string>();
16
+ const map = new Map<string, CollectionOptions>();
17
+
18
+ // Create a map for O(1) lookup
19
+ collections.forEach((col) => {
20
+ map.set(col.name, col);
21
+ });
22
+
23
+ const addToSorted = (col: CollectionOptions): void => {
24
+ // Check for circular dependency
25
+ if (visiting.has(col.name)) {
26
+ throw new Error(`Circular dependency detected: ${col.name} inherits from itself through a chain`);
27
+ }
28
+
29
+ // Skip if already processed
30
+ if (visited.has(col.name)) {
31
+ return;
32
+ }
33
+
34
+ // Mark as currently being processed
35
+ visiting.add(col.name);
36
+
37
+ // Process inherits first (dependencies)
38
+ const inherits = col.inherits || [];
39
+ for (const inheritName of inherits) {
40
+ const inheritCol = map.get(inheritName);
41
+ if (!inheritCol) {
42
+ console.warn(`Warning: Collection ${inheritName}, inherited by ${col.name}, not found.`);
43
+ continue;
44
+ }
45
+ addToSorted(inheritCol);
46
+ // Silently skip missing inherit collections
47
+ }
48
+
49
+ // Mark as processed and add to sorted array
50
+ visiting.delete(col.name);
51
+ visited.add(col.name);
52
+ sorted.push(col);
53
+ };
54
+
55
+ // Process all collections
56
+ for (const col of collections) {
57
+ addToSorted(col);
58
+ }
59
+
60
+ return sorted;
61
+ }
@@ -1392,10 +1392,12 @@ export class FlowModelContext extends BaseFlowModelContext {
1392
1392
  this.defineProperty('model', {
1393
1393
  value: model,
1394
1394
  });
1395
+ // 提供稳定的 ref 实例,确保渲染端与运行时上下文使用同一对象
1396
+ const stableRef = createRef<HTMLDivElement>();
1395
1397
  this.defineProperty('ref', {
1396
1398
  get: () => {
1397
1399
  this.model['_refCreated'] = true;
1398
- return createRef<HTMLDivElement>();
1400
+ return stableRef;
1399
1401
  },
1400
1402
  });
1401
1403
  this.defineMethod('openView', async function (uid: string, options) {
@@ -1516,10 +1518,12 @@ export class FlowForkModelContext extends BaseFlowModelContext {
1516
1518
  this.defineProperty('model', {
1517
1519
  get: () => this.fork,
1518
1520
  });
1521
+ // 提供稳定的 ref 实例,确保渲染端与运行时上下文使用同一对象
1522
+ const stableRef = createRef<HTMLDivElement>();
1519
1523
  this.defineProperty('ref', {
1520
1524
  get: () => {
1521
1525
  this.fork['_refCreated'] = true;
1522
- return createRef<HTMLDivElement>();
1526
+ return stableRef;
1523
1527
  },
1524
1528
  });
1525
1529
  this.defineMethod('runjs', async (code, variables, options?: { version?: string }) => {
@@ -1,50 +0,0 @@
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
- export declare enum ElementPosition {
10
- TOP = "top",
11
- BOTTOM = "bottom",
12
- LEFT = "left",
13
- RIGHT = "right",
14
- TOP_EDGE = "top-edge",
15
- BOTTOM_EDGE = "bottom-edge",
16
- LEFT_EDGE = "left-edge",
17
- RIGHT_EDGE = "right-edge"
18
- }
19
- interface MousePosition {
20
- x: number;
21
- y: number;
22
- }
23
- interface ElementBounds {
24
- x: number;
25
- y: number;
26
- width: number;
27
- height: number;
28
- }
29
- interface GetMousePositionOptions {
30
- initialMousePos: MousePosition;
31
- mouseOffset: MousePosition;
32
- elementBounds: ElementBounds;
33
- edgeThreshold?: number;
34
- topBottomHeightRatio?: number;
35
- }
36
- /**
37
- * 计算鼠标指针在元素上的位置
38
- *
39
- * 区域划分逻辑:
40
- * 1. 上区域:宽度=元素宽度,高度=元素高度*topBottomHeightRatio,位于元素顶部
41
- * 2. 下区域:宽度=元素宽度,高度=元素高度*topBottomHeightRatio,位于元素底部
42
- * 3. 左区域:剩余中间部分的左侧
43
- * 4. 右区域:剩余中间部分的右侧
44
- * 5. 边缘区域:在各个区域的边缘处,厚度为edgeThreshold
45
- *
46
- * @param options 配置选项
47
- * @returns 鼠标在元素上的位置
48
- */
49
- export declare const getMousePositionOnElement: ({ initialMousePos, mouseOffset, elementBounds, edgeThreshold, topBottomHeightRatio, }: GetMousePositionOptions) => ElementPosition;
50
- export {};
@@ -1,95 +0,0 @@
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
- var __defProp = Object.defineProperty;
11
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
- var __getOwnPropNames = Object.getOwnPropertyNames;
13
- var __hasOwnProp = Object.prototype.hasOwnProperty;
14
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
15
- var __export = (target, all) => {
16
- for (var name in all)
17
- __defProp(target, name, { get: all[name], enumerable: true });
18
- };
19
- var __copyProps = (to, from, except, desc) => {
20
- if (from && typeof from === "object" || typeof from === "function") {
21
- for (let key of __getOwnPropNames(from))
22
- if (!__hasOwnProp.call(to, key) && key !== except)
23
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
- }
25
- return to;
26
- };
27
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
- var getMousePositionOnElement_exports = {};
29
- __export(getMousePositionOnElement_exports, {
30
- ElementPosition: () => ElementPosition,
31
- getMousePositionOnElement: () => getMousePositionOnElement
32
- });
33
- module.exports = __toCommonJS(getMousePositionOnElement_exports);
34
- var ElementPosition = /* @__PURE__ */ ((ElementPosition2) => {
35
- ElementPosition2["TOP"] = "top";
36
- ElementPosition2["BOTTOM"] = "bottom";
37
- ElementPosition2["LEFT"] = "left";
38
- ElementPosition2["RIGHT"] = "right";
39
- ElementPosition2["TOP_EDGE"] = "top-edge";
40
- ElementPosition2["BOTTOM_EDGE"] = "bottom-edge";
41
- ElementPosition2["LEFT_EDGE"] = "left-edge";
42
- ElementPosition2["RIGHT_EDGE"] = "right-edge";
43
- return ElementPosition2;
44
- })(ElementPosition || {});
45
- const getMousePositionOnElement = /* @__PURE__ */ __name(({
46
- initialMousePos,
47
- mouseOffset,
48
- elementBounds,
49
- edgeThreshold = 5,
50
- topBottomHeightRatio = 0.25
51
- }) => {
52
- const currentMouseX = initialMousePos.x + mouseOffset.x;
53
- const currentMouseY = initialMousePos.y + mouseOffset.y;
54
- const relativeX = currentMouseX - elementBounds.x;
55
- const relativeY = currentMouseY - elementBounds.y;
56
- if (relativeX < 0 || relativeX > elementBounds.width || relativeY < 0 || relativeY > elementBounds.height) {
57
- if (relativeY < 0) return "top" /* TOP */;
58
- if (relativeY > elementBounds.height) return "bottom" /* BOTTOM */;
59
- if (relativeX < 0) return "left" /* LEFT */;
60
- return "right" /* RIGHT */;
61
- }
62
- const topBottomHeight = elementBounds.height * topBottomHeightRatio;
63
- const middleAreaTop = topBottomHeight;
64
- const middleAreaBottom = elementBounds.height - topBottomHeight;
65
- const middleAreaHeight = middleAreaBottom - middleAreaTop;
66
- if (relativeY <= topBottomHeight) {
67
- if (relativeY <= edgeThreshold) {
68
- return "top-edge" /* TOP_EDGE */;
69
- }
70
- return "top" /* TOP */;
71
- } else if (relativeY >= middleAreaBottom) {
72
- if (relativeY >= elementBounds.height - edgeThreshold) {
73
- return "bottom-edge" /* BOTTOM_EDGE */;
74
- }
75
- return "bottom" /* BOTTOM */;
76
- } else {
77
- const middleAreaCenterX = elementBounds.width / 2;
78
- if (relativeX <= middleAreaCenterX) {
79
- if (relativeX <= edgeThreshold) {
80
- return "left-edge" /* LEFT_EDGE */;
81
- }
82
- return "left" /* LEFT */;
83
- } else {
84
- if (relativeX >= elementBounds.width - edgeThreshold) {
85
- return "right-edge" /* RIGHT_EDGE */;
86
- }
87
- return "right" /* RIGHT */;
88
- }
89
- }
90
- }, "getMousePositionOnElement");
91
- // Annotate the CommonJS export names for ESM import in node:
92
- 0 && (module.exports = {
93
- ElementPosition,
94
- getMousePositionOnElement
95
- });
@@ -1,33 +0,0 @@
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
- import { ElementPosition } from './getMousePositionOnElement';
10
- type MoveDirection = 'insert-row-above' | 'insert-row-below' | 'insert-same-column-above' | 'insert-same-column-below' | 'insert-column-left' | 'insert-column-right';
11
- interface GridLayoutData {
12
- rows: Record<string, string[][]>;
13
- sizes: Record<string, number[]>;
14
- }
15
- export declare const findModelUidPosition: (uid: string, rows: Record<string, string[][]>) => {
16
- rowId: string;
17
- columnIndex: number;
18
- itemIndex: number;
19
- };
20
- export declare const moveBlock: ({ sourceUid, targetUid, direction, layoutData, }: {
21
- sourceUid: string;
22
- targetUid: string;
23
- direction: MoveDirection;
24
- layoutData: GridLayoutData;
25
- }) => GridLayoutData;
26
- /**
27
- * 将鼠标在元素上的位置转换为区块移动方向
28
- *
29
- * @param position 鼠标在元素上的位置
30
- * @returns 对应的移动方向
31
- */
32
- export declare const positionToDirection: (position: ElementPosition) => MoveDirection;
33
- export {};