@nocobase/flow-engine 2.0.0-beta.2 → 2.0.0-beta.20
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/BlockScopedFlowEngine.js +0 -1
- package/lib/JSRunner.d.ts +6 -0
- package/lib/JSRunner.js +2 -1
- package/lib/ViewScopedFlowEngine.js +3 -0
- package/lib/acl/Acl.js +13 -3
- package/lib/components/dnd/gridDragPlanner.d.ts +1 -0
- package/lib/components/dnd/gridDragPlanner.js +53 -1
- package/lib/components/settings/wrappers/component/SwitchWithTitle.js +2 -1
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +11 -3
- package/lib/components/variables/VariableInput.js +8 -2
- package/lib/data-source/index.js +6 -0
- package/lib/executor/FlowExecutor.d.ts +2 -1
- package/lib/executor/FlowExecutor.js +156 -22
- package/lib/flowContext.d.ts +4 -1
- package/lib/flowContext.js +176 -107
- package/lib/flowEngine.d.ts +21 -0
- package/lib/flowEngine.js +38 -0
- package/lib/flowSettings.js +12 -10
- package/lib/index.d.ts +3 -0
- package/lib/index.js +16 -0
- package/lib/models/CollectionFieldModel.d.ts +1 -0
- package/lib/models/CollectionFieldModel.js +3 -2
- package/lib/models/flowModel.d.ts +7 -0
- package/lib/models/flowModel.js +66 -1
- package/lib/provider.js +7 -6
- package/lib/resources/baseRecordResource.d.ts +5 -0
- package/lib/resources/baseRecordResource.js +24 -0
- package/lib/resources/multiRecordResource.d.ts +1 -0
- package/lib/resources/multiRecordResource.js +11 -4
- package/lib/resources/singleRecordResource.js +2 -0
- package/lib/resources/sqlResource.d.ts +1 -0
- package/lib/resources/sqlResource.js +8 -3
- package/lib/runjs-context/contexts/base.js +10 -4
- package/lib/runjsLibs.d.ts +28 -0
- package/lib/runjsLibs.js +532 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +2 -0
- package/lib/scheduler/ModelOperationScheduler.js +21 -21
- package/lib/types.d.ts +15 -0
- package/lib/utils/createCollectionContextMeta.js +1 -0
- package/lib/utils/index.d.ts +2 -0
- package/lib/utils/index.js +10 -0
- package/lib/utils/params-resolvers.js +16 -9
- package/lib/utils/resolveModuleUrl.d.ts +58 -0
- package/lib/utils/resolveModuleUrl.js +65 -0
- package/lib/utils/runjsModuleLoader.d.ts +58 -0
- package/lib/utils/runjsModuleLoader.js +422 -0
- package/lib/utils/runjsTemplateCompat.d.ts +35 -0
- package/lib/utils/runjsTemplateCompat.js +743 -0
- package/lib/utils/safeGlobals.d.ts +5 -9
- package/lib/utils/safeGlobals.js +129 -17
- package/lib/views/createViewMeta.d.ts +0 -7
- package/lib/views/createViewMeta.js +19 -70
- package/lib/views/index.d.ts +1 -2
- package/lib/views/index.js +4 -3
- package/lib/views/useDialog.js +8 -3
- package/lib/views/useDrawer.js +7 -2
- package/lib/views/usePage.d.ts +4 -0
- package/lib/views/usePage.js +43 -6
- package/lib/views/usePopover.js +4 -1
- package/lib/views/viewEvents.d.ts +17 -0
- package/lib/views/viewEvents.js +90 -0
- package/package.json +4 -4
- package/src/BlockScopedFlowEngine.ts +2 -5
- package/src/JSRunner.ts +8 -1
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/createViewMeta.popup.test.ts +62 -1
- package/src/__tests__/flowEngine.dataSourceDirty.test.ts +63 -0
- package/src/__tests__/flowSettings.open.test.tsx +69 -15
- package/src/__tests__/provider.test.tsx +0 -5
- package/src/__tests__/runjsExternalLibs.test.ts +242 -0
- package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
- package/src/__tests__/runjsPreprocessDefault.test.ts +49 -0
- package/src/acl/Acl.tsx +3 -3
- package/src/components/__tests__/gridDragPlanner.test.ts +141 -1
- package/src/components/dnd/gridDragPlanner.ts +60 -0
- package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +2 -1
- package/src/components/settings/wrappers/component/__tests__/InlineControls.test.tsx +74 -0
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +11 -3
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +63 -4
- package/src/components/variables/VariableInput.tsx +8 -2
- package/src/data-source/index.ts +6 -0
- package/src/executor/FlowExecutor.ts +193 -23
- package/src/executor/__tests__/flowExecutor.test.ts +66 -0
- package/src/flowContext.ts +234 -118
- package/src/flowEngine.ts +41 -0
- package/src/flowSettings.ts +12 -11
- package/src/index.ts +10 -0
- package/src/models/CollectionFieldModel.tsx +3 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +356 -0
- package/src/models/__tests__/flowModel.clone.test.ts +416 -0
- package/src/models/__tests__/flowModel.test.ts +16 -0
- package/src/models/flowModel.tsx +94 -1
- package/src/provider.tsx +9 -7
- package/src/resources/__tests__/multiRecordResource.test.ts +44 -0
- package/src/resources/__tests__/sqlResource.test.ts +60 -0
- package/src/resources/baseRecordResource.ts +31 -0
- package/src/resources/multiRecordResource.ts +11 -4
- package/src/resources/singleRecordResource.ts +3 -0
- package/src/resources/sqlResource.ts +8 -3
- package/src/runjs-context/contexts/base.ts +9 -2
- package/src/runjsLibs.ts +622 -0
- package/src/scheduler/ModelOperationScheduler.ts +23 -21
- package/src/types.ts +26 -1
- package/src/utils/__tests__/params-resolvers.test.ts +40 -0
- package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +38 -0
- package/src/utils/__tests__/runjsTemplateCompat.test.ts +159 -0
- package/src/utils/__tests__/safeGlobals.test.ts +49 -2
- package/src/utils/createCollectionContextMeta.ts +1 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/params-resolvers.ts +23 -9
- package/src/utils/resolveModuleUrl.ts +91 -0
- package/src/utils/runjsModuleLoader.ts +553 -0
- package/src/utils/runjsTemplateCompat.ts +828 -0
- package/src/utils/safeGlobals.ts +133 -16
- package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +35 -8
- package/src/views/__tests__/viewEvents.resolveOpenerEngine.test.ts +28 -0
- package/src/views/createViewMeta.ts +22 -75
- package/src/views/index.tsx +1 -2
- package/src/views/useDialog.tsx +9 -2
- package/src/views/useDrawer.tsx +8 -1
- package/src/views/usePage.tsx +51 -5
- package/src/views/usePopover.tsx +4 -1
- package/src/views/viewEvents.ts +55 -0
package/src/views/usePage.tsx
CHANGED
|
@@ -15,26 +15,33 @@ import { FlowViewContextProvider } from '../FlowContextProvider';
|
|
|
15
15
|
import { registerPopupVariable } from './createViewMeta';
|
|
16
16
|
import { PageComponent } from './PageComponent';
|
|
17
17
|
import usePatchElement from './usePatchElement';
|
|
18
|
+
import { VIEW_ACTIVATED_EVENT, bumpViewActivatedVersion, resolveOpenerEngine } from './viewEvents';
|
|
18
19
|
import { FlowEngineProvider } from '../provider';
|
|
19
20
|
import { createViewScopedEngine } from '../ViewScopedFlowEngine';
|
|
20
21
|
import { createViewRecordResolveOnServer, getViewRecordFromParent } from '../utils/variablesParams';
|
|
21
22
|
|
|
22
23
|
let uuid = 0;
|
|
23
24
|
|
|
25
|
+
/** Global embed container element ID */
|
|
26
|
+
export const GLOBAL_EMBED_CONTAINER_ID = 'nocobase-embed-container';
|
|
27
|
+
/** Dataset key used to signal embed replacement in progress (skip style reset on close) */
|
|
28
|
+
export const EMBED_REPLACING_DATA_KEY = 'nocobaseEmbedReplacing';
|
|
29
|
+
|
|
24
30
|
// 稳定的 Holder 组件,避免在父组件重渲染时更换组件类型导致卸载, 否则切换主题时会丢失所有页面内容
|
|
25
31
|
const PageElementsHolder = React.memo(
|
|
26
32
|
React.forwardRef((props: any, ref: any) => {
|
|
27
33
|
const [elements, patchElement] = usePatchElement();
|
|
28
34
|
React.useImperativeHandle(ref, () => ({ patchElement }), [patchElement]);
|
|
29
|
-
console.log('[NocoBase] Rendering PageElementsHolder with elements count:', elements.length);
|
|
30
35
|
return <>{elements}</>;
|
|
31
36
|
}),
|
|
32
37
|
);
|
|
33
38
|
|
|
34
39
|
export function usePage() {
|
|
35
40
|
const holderRef = React.useRef(null);
|
|
41
|
+
const globalEmbedActiveRef = React.useRef<null | { destroy: () => void }>(null);
|
|
36
42
|
|
|
37
43
|
const open = (config, flowContext) => {
|
|
44
|
+
const parentEngine = flowContext?.engine;
|
|
38
45
|
uuid += 1;
|
|
39
46
|
const pageRef = React.createRef<{
|
|
40
47
|
destroy: () => void;
|
|
@@ -75,11 +82,33 @@ export function usePage() {
|
|
|
75
82
|
return null; // Header 组件本身不渲染内容
|
|
76
83
|
};
|
|
77
84
|
|
|
78
|
-
const {
|
|
85
|
+
const {
|
|
86
|
+
target,
|
|
87
|
+
content,
|
|
88
|
+
preventClose,
|
|
89
|
+
inheritContext = true,
|
|
90
|
+
inputArgs: viewInputArgs = {},
|
|
91
|
+
...restConfig
|
|
92
|
+
} = config;
|
|
93
|
+
const isGlobalEmbedContainer = target instanceof HTMLElement && target.id === GLOBAL_EMBED_CONTAINER_ID;
|
|
94
|
+
|
|
95
|
+
// Global embed container uses "replace" behavior: opening a new view destroys the previous one.
|
|
96
|
+
if (isGlobalEmbedContainer && globalEmbedActiveRef.current) {
|
|
97
|
+
try {
|
|
98
|
+
// Avoid style "reset flicker" when replacing: tell embed wrappers to skip resetting container styles.
|
|
99
|
+
target.dataset[EMBED_REPLACING_DATA_KEY] = '1';
|
|
100
|
+
globalEmbedActiveRef.current.destroy();
|
|
101
|
+
} finally {
|
|
102
|
+
delete target.dataset[EMBED_REPLACING_DATA_KEY];
|
|
103
|
+
globalEmbedActiveRef.current = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
79
106
|
|
|
80
107
|
const ctx = new FlowContext();
|
|
81
108
|
// 为当前视图创建作用域引擎(隔离实例与缓存)
|
|
82
109
|
const scopedEngine = createViewScopedEngine(flowContext.engine);
|
|
110
|
+
const openerEngine = resolveOpenerEngine(parentEngine, scopedEngine);
|
|
111
|
+
|
|
83
112
|
ctx.defineProperty('engine', { value: scopedEngine });
|
|
84
113
|
ctx.addDelegate(scopedEngine.context);
|
|
85
114
|
if (inheritContext) {
|
|
@@ -91,13 +120,27 @@ export function usePage() {
|
|
|
91
120
|
// 构造 currentPage 实例
|
|
92
121
|
const currentPage = {
|
|
93
122
|
type: 'embed' as const,
|
|
94
|
-
inputArgs:
|
|
123
|
+
inputArgs: viewInputArgs,
|
|
95
124
|
preventClose: !!config.preventClose,
|
|
96
125
|
destroy: (result?: any) => {
|
|
97
126
|
config.onClose?.();
|
|
98
127
|
resolvePromise?.(result);
|
|
99
128
|
pageRef.current?.destroy();
|
|
100
129
|
closeFunc?.();
|
|
130
|
+
|
|
131
|
+
if (isGlobalEmbedContainer) {
|
|
132
|
+
globalEmbedActiveRef.current = null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Notify opener view that it becomes active again.
|
|
136
|
+
const isReplacing =
|
|
137
|
+
isGlobalEmbedContainer && target instanceof HTMLElement && target.dataset?.[EMBED_REPLACING_DATA_KEY] === '1';
|
|
138
|
+
if (!isReplacing) {
|
|
139
|
+
const openerEmitter = openerEngine?.emitter;
|
|
140
|
+
bumpViewActivatedVersion(openerEmitter);
|
|
141
|
+
openerEmitter?.emit?.(VIEW_ACTIVATED_EVENT, { type: 'embed', viewUid: currentPage?.inputArgs?.viewUid });
|
|
142
|
+
}
|
|
143
|
+
|
|
101
144
|
// 关闭时修正 previous/next 指针
|
|
102
145
|
scopedEngine.unlinkFromStack();
|
|
103
146
|
},
|
|
@@ -132,7 +175,6 @@ export function usePage() {
|
|
|
132
175
|
|
|
133
176
|
ctx.defineProperty('view', {
|
|
134
177
|
get: () => currentPage,
|
|
135
|
-
// meta: createViewMeta(ctx),
|
|
136
178
|
// 仅当访问关联字段或前端无本地记录数据时,才交给服务端解析
|
|
137
179
|
resolveOnServer: createViewRecordResolveOnServer(ctx, () => getViewRecordFromParent(flowContext, ctx)),
|
|
138
180
|
});
|
|
@@ -180,7 +222,7 @@ export function usePage() {
|
|
|
180
222
|
},
|
|
181
223
|
);
|
|
182
224
|
|
|
183
|
-
const key =
|
|
225
|
+
const key = viewInputArgs?.viewUid || `page-${uuid}`;
|
|
184
226
|
const page = (
|
|
185
227
|
<FlowEngineProvider key={key} engine={scopedEngine}>
|
|
186
228
|
<FlowViewContextProvider context={ctx}>
|
|
@@ -195,6 +237,10 @@ export function usePage() {
|
|
|
195
237
|
closeFunc = holderRef.current?.patchElement(page);
|
|
196
238
|
}
|
|
197
239
|
|
|
240
|
+
if (isGlobalEmbedContainer) {
|
|
241
|
+
globalEmbedActiveRef.current = { destroy: currentPage.destroy };
|
|
242
|
+
}
|
|
243
|
+
|
|
198
244
|
return Object.assign(promise, currentPage);
|
|
199
245
|
};
|
|
200
246
|
|
package/src/views/usePopover.tsx
CHANGED
|
@@ -40,8 +40,11 @@ const PopoverComponent = React.forwardRef<any, any>(({ afterClose, content, plac
|
|
|
40
40
|
destroyTooltipOnHide
|
|
41
41
|
content={config.content}
|
|
42
42
|
placement={config.placement}
|
|
43
|
-
getPopupContainer={() => document.body}
|
|
43
|
+
getPopupContainer={() => document.querySelector('#nocobase-app-container') || document.body}
|
|
44
44
|
onOpenChange={(nextOpen) => {
|
|
45
|
+
if (!nextOpen && config.preventClose) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
45
48
|
setVisible(nextOpen);
|
|
46
49
|
if (!nextOpen) {
|
|
47
50
|
afterClose?.();
|
|
@@ -0,0 +1,55 @@
|
|
|
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 { FlowEngine } from '../flowEngine';
|
|
11
|
+
|
|
12
|
+
export const VIEW_ACTIVATED_VERSION = Symbol.for('__NOCOBASE_VIEW_ACTIVATED_VERSION__');
|
|
13
|
+
|
|
14
|
+
export const VIEW_ACTIVATED_EVENT = 'view:activated' as const;
|
|
15
|
+
export const DATA_SOURCE_DIRTY_EVENT = 'dataSource:dirty' as const;
|
|
16
|
+
|
|
17
|
+
export const ENGINE_SCOPE_KEY = '__NOCOBASE_ENGINE_SCOPE__' as const;
|
|
18
|
+
export const VIEW_ENGINE_SCOPE = 'view' as const;
|
|
19
|
+
|
|
20
|
+
export function getEmitterViewActivatedVersion(emitter): number {
|
|
21
|
+
const raw = Reflect.get(emitter, VIEW_ACTIVATED_VERSION);
|
|
22
|
+
const num = typeof raw === 'number' ? raw : Number(raw);
|
|
23
|
+
return Number.isFinite(num) && num > 0 ? num : 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function bumpViewActivatedVersion(emitter): number {
|
|
27
|
+
const current = getEmitterViewActivatedVersion(emitter);
|
|
28
|
+
if (!Object.isExtensible(emitter)) return current;
|
|
29
|
+
const next = current + 1;
|
|
30
|
+
Reflect.set(emitter, VIEW_ACTIVATED_VERSION, next);
|
|
31
|
+
return next;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isViewEngine(engine: FlowEngine): boolean {
|
|
35
|
+
return Reflect.get(engine, ENGINE_SCOPE_KEY) === VIEW_ENGINE_SCOPE;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function findNearestViewEngine(engine: FlowEngine | undefined): FlowEngine | undefined {
|
|
39
|
+
let cur: FlowEngine | undefined = engine;
|
|
40
|
+
let guard = 0;
|
|
41
|
+
while (cur && guard++ < 50) {
|
|
42
|
+
if (isViewEngine(cur)) return cur;
|
|
43
|
+
cur = (cur as { previousEngine?: FlowEngine | undefined }).previousEngine;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function resolveOpenerEngine(parentEngine: FlowEngine, scopedEngine: FlowEngine): FlowEngine | undefined {
|
|
48
|
+
if (!parentEngine) return undefined;
|
|
49
|
+
const parentViewEngine = findNearestViewEngine(parentEngine);
|
|
50
|
+
if (parentViewEngine) return parentViewEngine;
|
|
51
|
+
|
|
52
|
+
// Fallback: resolve from previous engine in the stack (historical behavior).
|
|
53
|
+
const previousEngine = scopedEngine?.previousEngine;
|
|
54
|
+
return findNearestViewEngine(previousEngine) || parentEngine;
|
|
55
|
+
}
|