@nocobase/flow-engine 2.1.0-beta.8 → 2.1.0
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/FlowContextProvider.d.ts +5 -1
- package/lib/FlowContextProvider.js +9 -2
- package/lib/components/FieldModelRenderer.js +2 -2
- package/lib/components/FlowModelRenderer.d.ts +3 -1
- package/lib/components/FlowModelRenderer.js +12 -6
- package/lib/components/FormItem.d.ts +6 -0
- package/lib/components/FormItem.js +11 -3
- package/lib/components/MobilePopup.js +6 -5
- package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
- package/lib/components/dnd/gridDragPlanner.js +607 -19
- package/lib/components/dnd/index.d.ts +31 -2
- package/lib/components/dnd/index.js +244 -23
- package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
- package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +152 -42
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
- package/lib/components/subModel/AddSubModelButton.js +12 -1
- package/lib/components/subModel/LazyDropdown.js +301 -52
- package/lib/components/subModel/index.d.ts +1 -0
- package/lib/components/subModel/index.js +19 -0
- package/lib/components/subModel/utils.d.ts +2 -1
- package/lib/components/subModel/utils.js +15 -5
- package/lib/components/variables/VariableHybridInput.d.ts +27 -0
- package/lib/components/variables/VariableHybridInput.js +499 -0
- package/lib/components/variables/index.d.ts +2 -0
- package/lib/components/variables/index.js +3 -0
- package/lib/data-source/index.d.ts +84 -0
- package/lib/data-source/index.js +269 -7
- package/lib/executor/FlowExecutor.js +6 -3
- package/lib/flow-registry/DetachedFlowRegistry.d.ts +21 -0
- package/lib/flow-registry/DetachedFlowRegistry.js +80 -0
- package/lib/flow-registry/index.d.ts +1 -0
- package/lib/flow-registry/index.js +3 -1
- package/lib/flowContext.d.ts +9 -1
- package/lib/flowContext.js +77 -6
- package/lib/flowEngine.d.ts +136 -4
- package/lib/flowEngine.js +429 -51
- package/lib/flowI18n.js +2 -1
- package/lib/flowSettings.d.ts +14 -6
- package/lib/flowSettings.js +34 -6
- package/lib/index.d.ts +2 -0
- package/lib/index.js +7 -0
- package/lib/lazy-helper.d.ts +14 -0
- package/lib/lazy-helper.js +71 -0
- package/lib/locale/en-US.json +1 -0
- package/lib/locale/index.d.ts +2 -0
- package/lib/locale/zh-CN.json +1 -0
- 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 +13 -10
- package/lib/models/flowModel.js +126 -34
- package/lib/provider.js +38 -23
- package/lib/reactive/observer.js +46 -16
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/JSBlockRunJSContext.js +4 -15
- package/lib/runjs-context/contexts/JSColumnRunJSContext.js +5 -2
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +5 -8
- package/lib/runjs-context/contexts/JSFieldRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/JSItemRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/base.js +464 -29
- package/lib/runjs-context/contexts/elementDoc.d.ts +11 -0
- package/lib/runjs-context/contexts/elementDoc.js +152 -0
- package/lib/runjs-context/setup.js +1 -0
- package/lib/runjs-context/snippets/index.js +13 -2
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
- package/lib/types.d.ts +50 -2
- package/lib/types.js +1 -0
- package/lib/utils/createCollectionContextMeta.js +6 -2
- package/lib/utils/index.d.ts +3 -2
- package/lib/utils/index.js +7 -0
- package/lib/utils/loadedPageCache.d.ts +24 -0
- package/lib/utils/loadedPageCache.js +139 -0
- package/lib/utils/parsePathnameToViewParams.d.ts +5 -1
- package/lib/utils/parsePathnameToViewParams.js +28 -4
- package/lib/utils/randomId.d.ts +39 -0
- package/lib/utils/randomId.js +45 -0
- package/lib/utils/runjsTemplateCompat.js +1 -1
- package/lib/utils/runjsValue.js +41 -11
- package/lib/utils/schema-utils.d.ts +7 -1
- package/lib/utils/schema-utils.js +19 -0
- package/lib/views/FlowView.d.ts +7 -1
- package/lib/views/FlowView.js +11 -1
- package/lib/views/PageComponent.js +8 -6
- package/lib/views/ViewNavigation.d.ts +12 -2
- package/lib/views/ViewNavigation.js +28 -9
- package/lib/views/createViewMeta.js +114 -50
- package/lib/views/inheritLayoutContext.d.ts +10 -0
- package/lib/views/inheritLayoutContext.js +50 -0
- package/lib/views/runViewBeforeClose.d.ts +10 -0
- package/lib/views/runViewBeforeClose.js +45 -0
- package/lib/views/useDialog.d.ts +2 -1
- package/lib/views/useDialog.js +12 -3
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +12 -3
- package/lib/views/usePage.d.ts +5 -11
- package/lib/views/usePage.js +304 -144
- package/package.json +5 -4
- package/src/FlowContextProvider.tsx +9 -1
- package/src/__tests__/createViewMeta.popup.test.ts +115 -1
- package/src/__tests__/flow-engine.test.ts +166 -0
- package/src/__tests__/flowContext.test.ts +105 -1
- package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
- package/src/__tests__/flowEngine.moveModel.test.ts +81 -1
- package/src/__tests__/flowEngine.removeModel.test.ts +47 -3
- package/src/__tests__/flowSettings.test.ts +94 -15
- package/src/__tests__/objectVariable.test.ts +24 -0
- package/src/__tests__/provider.test.tsx +24 -2
- package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
- package/src/__tests__/runjsContext.test.ts +21 -0
- package/src/__tests__/runjsContextImplementations.test.ts +9 -2
- package/src/__tests__/runjsContextRuntime.test.ts +2 -0
- package/src/__tests__/runjsLocales.test.ts +6 -5
- package/src/__tests__/runjsSnippets.test.ts +21 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +136 -3
- package/src/components/FieldModelRenderer.tsx +2 -1
- package/src/components/FlowModelRenderer.tsx +18 -6
- package/src/components/FormItem.tsx +7 -1
- package/src/components/MobilePopup.tsx +4 -2
- package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
- package/src/components/__tests__/FormItem.test.tsx +25 -0
- package/src/components/__tests__/dnd.test.ts +44 -0
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
- package/src/components/__tests__/gridDragPlanner.test.ts +472 -5
- package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
- package/src/components/dnd/gridDragPlanner.ts +750 -17
- package/src/components/dnd/index.tsx +305 -28
- package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +178 -48
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +344 -8
- package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
- package/src/components/subModel/AddSubModelButton.tsx +16 -2
- package/src/components/subModel/LazyDropdown.tsx +341 -56
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +524 -38
- package/src/components/subModel/__tests__/utils.test.ts +24 -0
- package/src/components/subModel/index.ts +1 -0
- package/src/components/subModel/utils.ts +13 -2
- package/src/components/variables/VariableHybridInput.tsx +531 -0
- package/src/components/variables/index.ts +2 -0
- package/src/data-source/__tests__/collection.test.ts +41 -2
- package/src/data-source/__tests__/index.test.ts +69 -2
- package/src/data-source/index.ts +332 -8
- package/src/executor/FlowExecutor.ts +6 -3
- package/src/executor/__tests__/flowExecutor.test.ts +57 -0
- package/src/flow-registry/DetachedFlowRegistry.ts +46 -0
- package/src/flow-registry/__tests__/detachedFlowRegistry.test.ts +47 -0
- package/src/flow-registry/index.ts +1 -0
- package/src/flowContext.ts +85 -6
- package/src/flowEngine.ts +484 -45
- package/src/flowI18n.ts +2 -1
- package/src/flowSettings.ts +40 -6
- package/src/index.ts +2 -0
- package/src/lazy-helper.tsx +57 -0
- package/src/locale/en-US.json +1 -0
- package/src/locale/zh-CN.json +1 -0
- package/src/models/DisplayItemModel.tsx +1 -1
- package/src/models/EditableItemModel.tsx +1 -1
- package/src/models/FilterableItemModel.tsx +1 -1
- package/src/models/__tests__/flowEngine.resolveUse.test.ts +0 -15
- package/src/models/__tests__/flowModel.test.ts +65 -37
- package/src/models/flowModel.tsx +184 -65
- package/src/provider.tsx +41 -25
- package/src/reactive/__tests__/observer.test.tsx +82 -0
- package/src/reactive/observer.tsx +87 -25
- package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/JSBlockRunJSContext.ts +4 -15
- package/src/runjs-context/contexts/JSColumnRunJSContext.ts +4 -2
- package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +5 -9
- package/src/runjs-context/contexts/JSFieldRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/JSItemRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/base.ts +467 -31
- package/src/runjs-context/contexts/elementDoc.ts +130 -0
- package/src/runjs-context/setup.ts +1 -0
- package/src/runjs-context/snippets/index.ts +12 -1
- package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
- package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
- package/src/types.ts +62 -0
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +21 -0
- package/src/utils/__tests__/runjsValue.test.ts +11 -0
- package/src/utils/__tests__/utils.test.ts +62 -0
- package/src/utils/createCollectionContextMeta.ts +6 -2
- package/src/utils/index.ts +5 -1
- package/src/utils/loadedPageCache.ts +147 -0
- package/src/utils/parsePathnameToViewParams.ts +45 -5
- package/src/utils/randomId.ts +48 -0
- package/src/utils/runjsTemplateCompat.ts +1 -1
- package/src/utils/runjsValue.ts +50 -11
- package/src/utils/schema-utils.ts +30 -1
- package/src/views/FlowView.tsx +22 -2
- package/src/views/PageComponent.tsx +7 -4
- package/src/views/ViewNavigation.ts +46 -9
- package/src/views/__tests__/FlowView.usePage.test.tsx +243 -3
- package/src/views/__tests__/ViewNavigation.test.ts +52 -0
- package/src/views/__tests__/inheritLayoutContext.test.ts +53 -0
- package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +12 -12
- package/src/views/createViewMeta.ts +106 -34
- package/src/views/inheritLayoutContext.ts +26 -0
- package/src/views/runViewBeforeClose.ts +19 -0
- package/src/views/useDialog.tsx +13 -3
- package/src/views/useDrawer.tsx +13 -3
- package/src/views/usePage.tsx +367 -180
package/src/views/usePage.tsx
CHANGED
|
@@ -19,6 +19,8 @@ import { VIEW_ACTIVATED_EVENT, bumpViewActivatedVersion, resolveOpenerEngine } f
|
|
|
19
19
|
import { FlowEngineProvider } from '../provider';
|
|
20
20
|
import { createViewScopedEngine } from '../ViewScopedFlowEngine';
|
|
21
21
|
import { createViewRecordResolveOnServer, getViewRecordFromParent } from '../utils/variablesParams';
|
|
22
|
+
import { runViewBeforeClose } from './runViewBeforeClose';
|
|
23
|
+
import { inheritLayoutContextForDetachedView } from './inheritLayoutContext';
|
|
22
24
|
|
|
23
25
|
let uuid = 0;
|
|
24
26
|
|
|
@@ -27,6 +29,122 @@ export const GLOBAL_EMBED_CONTAINER_ID = 'nocobase-embed-container';
|
|
|
27
29
|
/** Dataset key used to signal embed replacement in progress (skip style reset on close) */
|
|
28
30
|
export const EMBED_REPLACING_DATA_KEY = 'nocobaseEmbedReplacing';
|
|
29
31
|
|
|
32
|
+
type GlobalEmbedActiveView = {
|
|
33
|
+
close: (result?: any, force?: boolean) => Promise<boolean | void> | boolean | void;
|
|
34
|
+
destroy: (result?: any) => void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type PendingGlobalEmbedActions = {
|
|
38
|
+
beforeClose?: any;
|
|
39
|
+
destroyed?: { result?: any };
|
|
40
|
+
update?: any;
|
|
41
|
+
footer?: React.ReactNode;
|
|
42
|
+
header?: { title?: React.ReactNode; extra?: React.ReactNode };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function isPromiseLike<T = any>(value: unknown): value is PromiseLike<T> {
|
|
46
|
+
return !!value && typeof (value as PromiseLike<T>).then === 'function';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function closeReplacingGlobalEmbed(target: HTMLElement, activeView: GlobalEmbedActiveView) {
|
|
50
|
+
target.dataset[EMBED_REPLACING_DATA_KEY] = '1';
|
|
51
|
+
try {
|
|
52
|
+
const closeResult = activeView.close();
|
|
53
|
+
if (isPromiseLike(closeResult)) {
|
|
54
|
+
return Promise.resolve(closeResult).finally(() => {
|
|
55
|
+
delete target.dataset[EMBED_REPLACING_DATA_KEY];
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
delete target.dataset[EMBED_REPLACING_DATA_KEY];
|
|
59
|
+
return closeResult;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
delete target.dataset[EMBED_REPLACING_DATA_KEY];
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createPendingGlobalEmbedView(
|
|
67
|
+
openedPromise: Promise<{ page: any | null }>,
|
|
68
|
+
inputArgs: any,
|
|
69
|
+
preventClose: boolean,
|
|
70
|
+
) {
|
|
71
|
+
let openedPage: any;
|
|
72
|
+
const pendingActions: PendingGlobalEmbedActions = {};
|
|
73
|
+
|
|
74
|
+
const readyPromise = openedPromise.then(({ page }) => {
|
|
75
|
+
openedPage = page;
|
|
76
|
+
if (openedPage) {
|
|
77
|
+
if ('beforeClose' in pendingActions) {
|
|
78
|
+
openedPage.beforeClose = pendingActions.beforeClose;
|
|
79
|
+
}
|
|
80
|
+
if ('update' in pendingActions) {
|
|
81
|
+
openedPage.update(pendingActions.update);
|
|
82
|
+
}
|
|
83
|
+
if ('footer' in pendingActions) {
|
|
84
|
+
openedPage.setFooter(pendingActions.footer);
|
|
85
|
+
}
|
|
86
|
+
if ('header' in pendingActions) {
|
|
87
|
+
openedPage.setHeader(pendingActions.header);
|
|
88
|
+
}
|
|
89
|
+
if (pendingActions.destroyed) {
|
|
90
|
+
openedPage.destroy(pendingActions.destroyed.result);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { page };
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return Object.assign(
|
|
97
|
+
readyPromise.then(({ page }) => (page ? page : false)),
|
|
98
|
+
{
|
|
99
|
+
type: 'embed' as const,
|
|
100
|
+
inputArgs,
|
|
101
|
+
preventClose,
|
|
102
|
+
Header: null,
|
|
103
|
+
Footer: null,
|
|
104
|
+
get beforeClose() {
|
|
105
|
+
return openedPage?.beforeClose ?? pendingActions.beforeClose;
|
|
106
|
+
},
|
|
107
|
+
set beforeClose(value) {
|
|
108
|
+
if (openedPage) {
|
|
109
|
+
openedPage.beforeClose = value;
|
|
110
|
+
} else {
|
|
111
|
+
pendingActions.beforeClose = value;
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
close: (result?: any, force?: boolean) =>
|
|
115
|
+
readyPromise.then(({ page }) => (page ? page.close(result, force) : false)),
|
|
116
|
+
destroy: (result?: any) => {
|
|
117
|
+
if (openedPage) {
|
|
118
|
+
openedPage.destroy(result);
|
|
119
|
+
} else {
|
|
120
|
+
pendingActions.destroyed = { result };
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
update: (newConfig: any) => {
|
|
124
|
+
if (openedPage) {
|
|
125
|
+
openedPage.update(newConfig);
|
|
126
|
+
} else {
|
|
127
|
+
pendingActions.update = { ...pendingActions.update, ...newConfig };
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
setFooter: (footer: React.ReactNode) => {
|
|
131
|
+
if (openedPage) {
|
|
132
|
+
openedPage.setFooter(footer);
|
|
133
|
+
} else {
|
|
134
|
+
pendingActions.footer = footer;
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
setHeader: (header: { title?: React.ReactNode; extra?: React.ReactNode }) => {
|
|
138
|
+
if (openedPage) {
|
|
139
|
+
openedPage.setHeader(header);
|
|
140
|
+
} else {
|
|
141
|
+
pendingActions.header = header;
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
30
148
|
// 稳定的 Holder 组件,避免在父组件重渲染时更换组件类型导致卸载, 否则切换主题时会丢失所有页面内容
|
|
31
149
|
const PageElementsHolder = React.memo(
|
|
32
150
|
React.forwardRef((props: any, ref: any) => {
|
|
@@ -38,211 +156,280 @@ const PageElementsHolder = React.memo(
|
|
|
38
156
|
|
|
39
157
|
export function usePage() {
|
|
40
158
|
const holderRef = React.useRef(null);
|
|
41
|
-
const globalEmbedActiveRef = React.useRef<null |
|
|
159
|
+
const globalEmbedActiveRef = React.useRef<null | GlobalEmbedActiveView>(null);
|
|
160
|
+
const globalEmbedReplacementTokenRef = React.useRef(0);
|
|
42
161
|
|
|
43
162
|
const open = (config, flowContext) => {
|
|
44
|
-
const parentEngine = flowContext?.engine;
|
|
45
|
-
uuid += 1;
|
|
46
|
-
const pageRef = React.createRef<{
|
|
47
|
-
destroy: () => void;
|
|
48
|
-
update: (config: any) => void;
|
|
49
|
-
setFooter: (footer: React.ReactNode) => void;
|
|
50
|
-
setHeader: (header: { title?: React.ReactNode; extra?: React.ReactNode }) => void;
|
|
51
|
-
}>();
|
|
52
|
-
|
|
53
|
-
let closeFunc: (() => void) | undefined;
|
|
54
|
-
let resolvePromise: (value?: any) => void;
|
|
55
|
-
const promise = new Promise((resolve) => {
|
|
56
|
-
resolvePromise = resolve;
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Footer 组件实现
|
|
60
|
-
const FooterComponent: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
|
61
|
-
React.useEffect(() => {
|
|
62
|
-
pageRef.current?.setFooter(children);
|
|
63
|
-
|
|
64
|
-
return () => {
|
|
65
|
-
pageRef.current?.setFooter(null);
|
|
66
|
-
};
|
|
67
|
-
}, [children]);
|
|
68
|
-
|
|
69
|
-
return null; // Footer 组件本身不渲染内容
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// Header 组件实现
|
|
73
|
-
const HeaderComponent: React.FC<{ title?: React.ReactNode; extra?: React.ReactNode }> = (props) => {
|
|
74
|
-
React.useEffect(() => {
|
|
75
|
-
pageRef.current?.setHeader(props);
|
|
76
|
-
|
|
77
|
-
return () => {
|
|
78
|
-
pageRef.current?.setHeader(null);
|
|
79
|
-
};
|
|
80
|
-
}, [props]);
|
|
81
|
-
|
|
82
|
-
return null; // Header 组件本身不渲染内容
|
|
83
|
-
};
|
|
84
|
-
|
|
85
163
|
const {
|
|
86
164
|
target,
|
|
87
165
|
content,
|
|
88
166
|
preventClose,
|
|
89
167
|
inheritContext = true,
|
|
90
168
|
inputArgs: viewInputArgs = {},
|
|
169
|
+
onOpenCancelled,
|
|
91
170
|
...restConfig
|
|
92
171
|
} = config;
|
|
93
172
|
const isGlobalEmbedContainer = target instanceof HTMLElement && target.id === GLOBAL_EMBED_CONTAINER_ID;
|
|
94
173
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
174
|
+
const openCurrentPage = () => {
|
|
175
|
+
const parentEngine = flowContext?.engine;
|
|
176
|
+
uuid += 1;
|
|
177
|
+
const pageRef = React.createRef<{
|
|
178
|
+
destroy: () => void;
|
|
179
|
+
update: (config: any) => void;
|
|
180
|
+
setFooter: (footer: React.ReactNode) => void;
|
|
181
|
+
setHeader: (header: { title?: React.ReactNode; extra?: React.ReactNode }) => void;
|
|
182
|
+
}>();
|
|
183
|
+
|
|
184
|
+
let closeFunc: (() => void) | undefined;
|
|
185
|
+
let resolvePromise: (value?: any) => void;
|
|
186
|
+
const promise = new Promise((resolve) => {
|
|
187
|
+
resolvePromise = resolve;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Footer 组件实现
|
|
191
|
+
const FooterComponent: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
|
192
|
+
React.useEffect(() => {
|
|
193
|
+
pageRef.current?.setFooter(children);
|
|
106
194
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
ctx.defineProperty('engine', { value: scopedEngine });
|
|
113
|
-
ctx.addDelegate(scopedEngine.context);
|
|
114
|
-
if (inheritContext) {
|
|
115
|
-
ctx.addDelegate(flowContext);
|
|
116
|
-
} else {
|
|
117
|
-
ctx.addDelegate(flowContext.engine.context);
|
|
118
|
-
}
|
|
195
|
+
return () => {
|
|
196
|
+
pageRef.current?.setFooter(null);
|
|
197
|
+
};
|
|
198
|
+
}, [children]);
|
|
119
199
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
type: 'embed' as const,
|
|
123
|
-
inputArgs: viewInputArgs,
|
|
124
|
-
preventClose: !!config.preventClose,
|
|
125
|
-
destroy: (result?: any) => {
|
|
126
|
-
config.onClose?.();
|
|
127
|
-
resolvePromise?.(result);
|
|
128
|
-
pageRef.current?.destroy();
|
|
129
|
-
closeFunc?.();
|
|
200
|
+
return null; // Footer 组件本身不渲染内容
|
|
201
|
+
};
|
|
130
202
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
203
|
+
// Header 组件实现
|
|
204
|
+
const HeaderComponent: React.FC<{ title?: React.ReactNode; extra?: React.ReactNode }> = (props) => {
|
|
205
|
+
React.useEffect(() => {
|
|
206
|
+
pageRef.current?.setHeader(props);
|
|
207
|
+
|
|
208
|
+
return () => {
|
|
209
|
+
pageRef.current?.setHeader(null);
|
|
210
|
+
};
|
|
211
|
+
}, [props]);
|
|
212
|
+
|
|
213
|
+
return null; // Header 组件本身不渲染内容
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const ctx = new FlowContext();
|
|
217
|
+
// 为当前视图创建作用域引擎(隔离实例与缓存)
|
|
218
|
+
const scopedEngine = createViewScopedEngine(flowContext.engine);
|
|
219
|
+
const openerEngine = resolveOpenerEngine(parentEngine, scopedEngine);
|
|
220
|
+
|
|
221
|
+
ctx.defineProperty('engine', { value: scopedEngine });
|
|
222
|
+
ctx.addDelegate(scopedEngine.context);
|
|
223
|
+
if (inheritContext) {
|
|
224
|
+
ctx.addDelegate(flowContext);
|
|
225
|
+
} else {
|
|
226
|
+
ctx.addDelegate(flowContext.engine.context);
|
|
227
|
+
inheritLayoutContextForDetachedView(ctx, flowContext);
|
|
228
|
+
}
|
|
143
229
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
230
|
+
// 构造 currentPage 实例
|
|
231
|
+
let destroyed = false;
|
|
232
|
+
let closingPromise: Promise<boolean | void> | undefined;
|
|
233
|
+
const currentPage = {
|
|
234
|
+
type: 'embed' as const,
|
|
235
|
+
inputArgs: viewInputArgs,
|
|
236
|
+
preventClose: !!config.preventClose,
|
|
237
|
+
beforeClose: undefined,
|
|
238
|
+
destroy: (result?: any) => {
|
|
239
|
+
if (destroyed) return;
|
|
240
|
+
destroyed = true;
|
|
241
|
+
config.onClose?.();
|
|
242
|
+
resolvePromise?.(result);
|
|
243
|
+
pageRef.current?.destroy();
|
|
244
|
+
closeFunc?.();
|
|
245
|
+
|
|
246
|
+
if (isGlobalEmbedContainer && globalEmbedActiveRef.current === currentPage) {
|
|
247
|
+
globalEmbedActiveRef.current = null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Notify opener view that it becomes active again.
|
|
251
|
+
const isReplacing =
|
|
252
|
+
isGlobalEmbedContainer &&
|
|
253
|
+
target instanceof HTMLElement &&
|
|
254
|
+
target.dataset?.[EMBED_REPLACING_DATA_KEY] === '1';
|
|
255
|
+
if (!isReplacing) {
|
|
256
|
+
const openerEmitter = openerEngine?.emitter;
|
|
257
|
+
bumpViewActivatedVersion(openerEmitter);
|
|
258
|
+
openerEmitter?.emit?.(VIEW_ACTIVATED_EVENT, { type: 'embed', viewUid: currentPage?.inputArgs?.viewUid });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 关闭时修正 previous/next 指针
|
|
262
|
+
scopedEngine.unlinkFromStack();
|
|
263
|
+
},
|
|
264
|
+
update: (newConfig) => pageRef.current?.update(newConfig),
|
|
265
|
+
close: (result?: any, force?: boolean) => {
|
|
266
|
+
if (destroyed) {
|
|
267
|
+
return Promise.resolve(true);
|
|
268
|
+
}
|
|
269
|
+
if (closingPromise) {
|
|
270
|
+
return closingPromise;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
closingPromise = (async () => {
|
|
274
|
+
try {
|
|
275
|
+
if (preventClose && !force) {
|
|
276
|
+
closingPromise = undefined;
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const shouldClose = await runViewBeforeClose(currentPage, { result, force });
|
|
281
|
+
if (destroyed) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
if (!shouldClose) {
|
|
285
|
+
closingPromise = undefined;
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (config.triggerByRouter && config.inputArgs?.navigation?.back) {
|
|
290
|
+
// 交由路由系统来销毁当前视图
|
|
291
|
+
config.inputArgs.navigation.back();
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
currentPage.destroy(result);
|
|
296
|
+
return true;
|
|
297
|
+
} catch (error) {
|
|
298
|
+
if (!destroyed) {
|
|
299
|
+
closingPromise = undefined;
|
|
300
|
+
}
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
})();
|
|
304
|
+
|
|
305
|
+
return closingPromise;
|
|
306
|
+
},
|
|
307
|
+
Header: HeaderComponent,
|
|
308
|
+
Footer: FooterComponent,
|
|
309
|
+
setFooter: (footer: React.ReactNode) => {
|
|
310
|
+
pageRef.current?.setFooter(footer);
|
|
311
|
+
},
|
|
312
|
+
setHeader: (header: { title?: React.ReactNode; extra?: React.ReactNode }) => {
|
|
313
|
+
pageRef.current?.setHeader(header);
|
|
314
|
+
},
|
|
315
|
+
navigation: config.inputArgs?.navigation,
|
|
316
|
+
get record() {
|
|
317
|
+
// 当视图正在查看与父 record 同一条记录时,复用父记录深拷贝;否则走服务端解析
|
|
318
|
+
return getViewRecordFromParent(flowContext, ctx);
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
ctx.defineProperty('view', {
|
|
323
|
+
get: () => currentPage,
|
|
324
|
+
// 仅当访问关联字段或前端无本地记录数据时,才交给服务端解析
|
|
325
|
+
resolveOnServer: createViewRecordResolveOnServer(ctx, () => getViewRecordFromParent(flowContext, ctx)),
|
|
326
|
+
});
|
|
327
|
+
// embed 视图不注册 destroyView,afterSuccess 关闭弹窗时自然跳过
|
|
328
|
+
// 顶层 popup 变量:弹窗记录/数据源/上级弹窗链(去重封装)
|
|
329
|
+
registerPopupVariable(ctx, currentPage);
|
|
330
|
+
|
|
331
|
+
const PageWithContext = observer(
|
|
332
|
+
() => {
|
|
333
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
334
|
+
const mountedRef = React.useRef(false);
|
|
335
|
+
// 支持 content 为函数,传递 currentPage
|
|
336
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
337
|
+
const pageContent = React.useMemo(
|
|
338
|
+
() => (typeof content === 'function' ? content(currentPage, ctx) : content),
|
|
339
|
+
[],
|
|
340
|
+
);
|
|
341
|
+
// 响应themeToken的响应式更新
|
|
342
|
+
void ctx.themeToken;
|
|
343
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
344
|
+
React.useEffect(() => {
|
|
345
|
+
config.onOpen?.(currentPage, ctx);
|
|
346
|
+
}, []);
|
|
347
|
+
|
|
348
|
+
if (config.inputArgs?.hidden?.value && !mountedRef.current) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
mountedRef.current = true;
|
|
353
|
+
|
|
354
|
+
return (
|
|
355
|
+
<PageComponent
|
|
356
|
+
ref={pageRef}
|
|
357
|
+
hidden={config.inputArgs?.hidden?.value}
|
|
358
|
+
{...restConfig}
|
|
359
|
+
onClose={() => {
|
|
360
|
+
return currentPage.close(config.result);
|
|
361
|
+
}}
|
|
362
|
+
>
|
|
363
|
+
{pageContent}
|
|
364
|
+
</PageComponent>
|
|
365
|
+
);
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
displayName: 'PageWithContext',
|
|
369
|
+
},
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
const key = viewInputArgs?.viewUid || `page-${uuid}`;
|
|
373
|
+
const page = (
|
|
374
|
+
<FlowEngineProvider key={key} engine={scopedEngine}>
|
|
375
|
+
<FlowViewContextProvider context={ctx}>
|
|
376
|
+
<PageWithContext />
|
|
377
|
+
</FlowViewContextProvider>
|
|
378
|
+
</FlowEngineProvider>
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
if (target && target instanceof HTMLElement) {
|
|
382
|
+
closeFunc = holderRef.current?.patchElement(ReactDOM.createPortal(page, target, key));
|
|
383
|
+
} else {
|
|
384
|
+
closeFunc = holderRef.current?.patchElement(page);
|
|
385
|
+
}
|
|
152
386
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
387
|
+
if (isGlobalEmbedContainer) {
|
|
388
|
+
globalEmbedActiveRef.current = currentPage;
|
|
389
|
+
}
|
|
158
390
|
|
|
159
|
-
|
|
160
|
-
},
|
|
161
|
-
Header: HeaderComponent,
|
|
162
|
-
Footer: FooterComponent,
|
|
163
|
-
setFooter: (footer: React.ReactNode) => {
|
|
164
|
-
pageRef.current?.setFooter(footer);
|
|
165
|
-
},
|
|
166
|
-
setHeader: (header: { title?: React.ReactNode; extra?: React.ReactNode }) => {
|
|
167
|
-
pageRef.current?.setHeader(header);
|
|
168
|
-
},
|
|
169
|
-
navigation: config.inputArgs?.navigation,
|
|
170
|
-
get record() {
|
|
171
|
-
// 当视图正在查看与父 record 同一条记录时,复用父记录深拷贝;否则走服务端解析
|
|
172
|
-
return getViewRecordFromParent(flowContext, ctx);
|
|
173
|
-
},
|
|
391
|
+
return Object.assign(promise, currentPage);
|
|
174
392
|
};
|
|
175
393
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
188
|
-
const mountedRef = React.useRef(false);
|
|
189
|
-
// 支持 content 为函数,传递 currentPage
|
|
190
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
191
|
-
const pageContent = React.useMemo(
|
|
192
|
-
() => (typeof content === 'function' ? content(currentPage, ctx) : content),
|
|
193
|
-
[],
|
|
194
|
-
);
|
|
195
|
-
// 响应themeToken的响应式更新
|
|
196
|
-
void ctx.themeToken;
|
|
197
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
198
|
-
React.useEffect(() => {
|
|
199
|
-
config.onOpen?.(currentPage, ctx);
|
|
200
|
-
}, []);
|
|
201
|
-
|
|
202
|
-
if (config.inputArgs?.hidden?.value && !mountedRef.current) {
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
394
|
+
// Global embed container uses "replace" behavior. The previous view still owns beforeClose.
|
|
395
|
+
if (isGlobalEmbedContainer && globalEmbedActiveRef.current) {
|
|
396
|
+
const replacementToken = (globalEmbedReplacementTokenRef.current += 1);
|
|
397
|
+
const cancelOpen = () => onOpenCancelled?.();
|
|
398
|
+
let closeResult: Promise<boolean | void> | boolean | void;
|
|
399
|
+
try {
|
|
400
|
+
closeResult = closeReplacingGlobalEmbed(target, globalEmbedActiveRef.current);
|
|
401
|
+
} catch (error) {
|
|
402
|
+
cancelOpen();
|
|
403
|
+
throw error;
|
|
404
|
+
}
|
|
205
405
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
406
|
+
if (isPromiseLike(closeResult)) {
|
|
407
|
+
return createPendingGlobalEmbedView(
|
|
408
|
+
Promise.resolve(closeResult).then(
|
|
409
|
+
(closed) => {
|
|
410
|
+
if (closed === false || replacementToken !== globalEmbedReplacementTokenRef.current) {
|
|
411
|
+
cancelOpen();
|
|
412
|
+
return { page: null };
|
|
413
|
+
}
|
|
414
|
+
return { page: openCurrentPage() };
|
|
415
|
+
},
|
|
416
|
+
(error) => {
|
|
417
|
+
cancelOpen();
|
|
418
|
+
throw error;
|
|
419
|
+
},
|
|
420
|
+
),
|
|
421
|
+
viewInputArgs,
|
|
422
|
+
!!config.preventClose,
|
|
219
423
|
);
|
|
220
|
-
}
|
|
221
|
-
{
|
|
222
|
-
displayName: 'PageWithContext',
|
|
223
|
-
},
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
const key = viewInputArgs?.viewUid || `page-${uuid}`;
|
|
227
|
-
const page = (
|
|
228
|
-
<FlowEngineProvider key={key} engine={scopedEngine}>
|
|
229
|
-
<FlowViewContextProvider context={ctx}>
|
|
230
|
-
<PageWithContext />
|
|
231
|
-
</FlowViewContextProvider>
|
|
232
|
-
</FlowEngineProvider>
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
if (target && target instanceof HTMLElement) {
|
|
236
|
-
closeFunc = holderRef.current?.patchElement(ReactDOM.createPortal(page, target, key));
|
|
237
|
-
} else {
|
|
238
|
-
closeFunc = holderRef.current?.patchElement(page);
|
|
239
|
-
}
|
|
424
|
+
}
|
|
240
425
|
|
|
241
|
-
|
|
242
|
-
|
|
426
|
+
if (closeResult === false) {
|
|
427
|
+
cancelOpen();
|
|
428
|
+
return createPendingGlobalEmbedView(Promise.resolve({ page: null }), viewInputArgs, !!config.preventClose);
|
|
429
|
+
}
|
|
243
430
|
}
|
|
244
431
|
|
|
245
|
-
return
|
|
432
|
+
return openCurrentPage();
|
|
246
433
|
};
|
|
247
434
|
|
|
248
435
|
const api = React.useMemo(() => ({ open }), []);
|