@nocobase/client-v2 2.1.11 → 2.2.0-alpha.2
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/es/PluginSettingsManager.d.ts +33 -0
- package/es/RouteRepository.d.ts +21 -6
- package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
- package/es/components/category-tabs/index.d.ts +9 -0
- package/es/components/form/TypedVariableInput.d.ts +22 -4
- package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
- package/es/components/form/filter/index.d.ts +2 -0
- package/es/components/index.d.ts +2 -0
- package/es/flow/actions/afterSuccess.d.ts +8 -1
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
- package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
- package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +2 -0
- package/es/flow/index.d.ts +1 -0
- package/es/flow/models/base/GridModel.d.ts +1 -1
- package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
- package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
- package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
- package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
- package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
- package/es/index.d.ts +2 -1
- package/es/index.mjs +181 -131
- package/lib/index.js +186 -136
- package/package.json +7 -7
- package/src/PluginSettingsManager.ts +53 -0
- package/src/RouteRepository.ts +126 -24
- package/src/__tests__/PluginSettingsManager.test.ts +13 -0
- package/src/__tests__/RouteRepository.test.ts +216 -0
- package/src/__tests__/browserChecker.test.ts +61 -0
- package/src/__tests__/exports.test.ts +16 -0
- package/src/__tests__/plugin-manager.test.tsx +44 -2
- package/src/__tests__/settings-center.test.tsx +40 -1
- package/src/collection-manager/field-configure.ts +1 -1
- package/src/collection-manager/interfaces/id.ts +1 -1
- package/src/collection-manager/interfaces/m2m.tsx +2 -2
- package/src/collection-manager/interfaces/m2o.tsx +2 -2
- package/src/collection-manager/interfaces/nanoid.ts +1 -1
- package/src/collection-manager/interfaces/o2m.tsx +2 -2
- package/src/collection-manager/interfaces/obo.tsx +2 -2
- package/src/collection-manager/interfaces/oho.tsx +2 -2
- package/src/collection-manager/interfaces/properties/index.ts +2 -2
- package/src/collection-manager/interfaces/uuid.ts +1 -1
- package/src/components/README.md +7 -1
- package/src/components/README.zh-CN.md +6 -1
- package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
- package/src/components/category-tabs/index.ts +10 -0
- package/src/components/form/TypedVariableInput.tsx +416 -93
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +266 -9
- package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
- package/src/components/form/filter/index.ts +2 -0
- package/src/components/index.ts +2 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +443 -5
- package/src/flow/__tests__/getKey.test.ts +7 -0
- package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
- package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
- package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +104 -0
- package/src/flow/actions/afterSuccess.tsx +142 -3
- package/src/flow/actions/customVariable.tsx +1 -2
- package/src/flow/actions/index.ts +1 -1
- package/src/flow/actions/linkageRules.tsx +2 -7
- package/src/flow/actions/openView.tsx +38 -4
- package/src/flow/actions/runjs.tsx +2 -14
- package/src/flow/admin-shell/BaseLayoutModel.tsx +25 -3
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +5 -2
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +13 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +34 -10
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +310 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +44 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +82 -0
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +166 -2
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
- package/src/flow/components/DefaultValue.tsx +1 -2
- package/src/flow/components/FieldAssignValueInput.tsx +10 -5
- package/src/flow/components/FlowRoute.tsx +56 -11
- package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
- package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
- package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
- package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
- package/src/flow/index.ts +1 -0
- package/src/flow/models/actions/JSActionModel.tsx +2 -7
- package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
- package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
- package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
- package/src/flow/models/base/ActionModel.tsx +12 -1
- package/src/flow/models/base/GridModel.tsx +38 -7
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +0 -4
- package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
- package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
- package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +1 -2
- package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
- package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
- package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
- package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
- package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
- package/src/flow/models/blocks/form/submitHandler.ts +17 -3
- package/src/flow/models/blocks/form/value-runtime/rules.ts +2 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +2 -7
- package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +36 -13
- package/src/flow/models/blocks/table/TableBlockModel.tsx +38 -17
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
- package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
- package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
- package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
- package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
- package/src/flow/models/fields/JSFieldModel.tsx +2 -7
- package/src/flow/models/fields/JSItemModel.tsx +2 -14
- package/src/flow/models/topbar/TopbarActionModel.tsx +93 -10
- package/src/flow/resolveViewParamsToViewList.tsx +11 -3
- package/src/flow-compat/Popover.tsx +43 -4
- package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
- package/src/index.ts +8 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
- package/src/nocobase-buildin-plugin/index.tsx +0 -2
- package/src/settings-center/AdminSettingsLayout.tsx +10 -2
- package/src/settings-center/SystemSettingsPage.tsx +1 -2
- package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
- package/src/settings-center/utils.tsx +0 -6
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { describe, it, expect, vi } from 'vitest';
|
|
12
|
+
import { RUNJS_OPEN_VIEW_ROUTE_STATE } from '@nocobase/flow-engine';
|
|
12
13
|
import { openView } from '../openView';
|
|
13
14
|
import { FlowPage } from '../../FlowPage';
|
|
14
15
|
|
|
@@ -50,6 +51,27 @@ describe('openView action - route mode defineProperties/defineMethods', () => {
|
|
|
50
51
|
return { ctx, engine };
|
|
51
52
|
};
|
|
52
53
|
|
|
54
|
+
const createFirstStageCtx = (inputArgs: Record<PropertyKey, unknown>) => {
|
|
55
|
+
const navigateTo = vi.fn();
|
|
56
|
+
const ctx: any = {
|
|
57
|
+
inputArgs,
|
|
58
|
+
engine: { context: { themeToken: { colorBgLayout: '#fff' } } },
|
|
59
|
+
model: {
|
|
60
|
+
uid: 'popup-uid',
|
|
61
|
+
context: {
|
|
62
|
+
inputArgs: {},
|
|
63
|
+
defineProperty: vi.fn(),
|
|
64
|
+
},
|
|
65
|
+
flowEngine: { context: { themeToken: { colorBgLayout: '#fff' } } },
|
|
66
|
+
},
|
|
67
|
+
view: {
|
|
68
|
+
navigation: { navigateTo },
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return { ctx, navigateTo };
|
|
73
|
+
};
|
|
74
|
+
|
|
53
75
|
it('injects defineProperties/defineMethods on onModelLoaded even when not present in current inputArgs', async () => {
|
|
54
76
|
const { ctx } = createRouteManagedCtx();
|
|
55
77
|
|
|
@@ -149,4 +171,86 @@ describe('openView action - route mode defineProperties/defineMethods', () => {
|
|
|
149
171
|
const defineMethodCalls = pageModelContext.defineMethod.mock.calls.map((c: any[]) => c[0]);
|
|
150
172
|
expect(defineMethodCalls).toContain('pong');
|
|
151
173
|
});
|
|
174
|
+
|
|
175
|
+
it('adds route state to first-stage navigation only for RunJS ctx.openView calls', async () => {
|
|
176
|
+
const { ctx, navigateTo } = createFirstStageCtx({
|
|
177
|
+
mode: 'dialog',
|
|
178
|
+
size: 'large',
|
|
179
|
+
[RUNJS_OPEN_VIEW_ROUTE_STATE]: { mode: 'dialog', size: 'large' },
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await openView.handler(ctx, { mode: 'drawer', size: 'medium', navigation: true });
|
|
183
|
+
|
|
184
|
+
expect(navigateTo).toHaveBeenCalledWith({
|
|
185
|
+
viewUid: 'popup-uid',
|
|
186
|
+
filterByTk: undefined,
|
|
187
|
+
sourceId: undefined,
|
|
188
|
+
tabUid: undefined,
|
|
189
|
+
openViewRouteState: { mode: 'dialog', size: 'large' },
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('normalizes first-stage RunJS route state mode on mobile layout', async () => {
|
|
194
|
+
const { ctx, navigateTo } = createFirstStageCtx({
|
|
195
|
+
isMobileLayout: true,
|
|
196
|
+
mode: 'dialog',
|
|
197
|
+
size: 'large',
|
|
198
|
+
[RUNJS_OPEN_VIEW_ROUTE_STATE]: { mode: 'dialog', size: 'large' },
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
await openView.handler(ctx, { mode: 'drawer', size: 'medium', navigation: true });
|
|
202
|
+
|
|
203
|
+
expect(navigateTo).toHaveBeenCalledWith({
|
|
204
|
+
viewUid: 'popup-uid',
|
|
205
|
+
filterByTk: undefined,
|
|
206
|
+
sourceId: undefined,
|
|
207
|
+
tabUid: undefined,
|
|
208
|
+
openViewRouteState: { mode: 'embed', size: 'large' },
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('uses route replay mode and size before persisted openView defaults', async () => {
|
|
213
|
+
const { ctx } = createRouteManagedCtx();
|
|
214
|
+
ctx.inputArgs.mode = 'dialog';
|
|
215
|
+
ctx.inputArgs.size = 'large';
|
|
216
|
+
|
|
217
|
+
await openView.handler(ctx, { mode: 'drawer', size: 'medium', navigation: true });
|
|
218
|
+
|
|
219
|
+
expect(ctx.viewer.open).toHaveBeenCalledWith(
|
|
220
|
+
expect.objectContaining({
|
|
221
|
+
type: 'dialog',
|
|
222
|
+
width: '80%',
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('uses decoded openView route state during route replay', async () => {
|
|
228
|
+
const { ctx } = createRouteManagedCtx();
|
|
229
|
+
ctx.inputArgs.openViewRouteState = { mode: 'dialog', size: 'large' };
|
|
230
|
+
|
|
231
|
+
await openView.handler(ctx, { mode: 'drawer', size: 'medium', navigation: true });
|
|
232
|
+
|
|
233
|
+
expect(ctx.viewer.open).toHaveBeenCalledWith(
|
|
234
|
+
expect.objectContaining({
|
|
235
|
+
type: 'dialog',
|
|
236
|
+
width: '80%',
|
|
237
|
+
}),
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('keeps first-stage navigation unchanged for non-RunJS openView calls', async () => {
|
|
242
|
+
const { ctx, navigateTo } = createFirstStageCtx({
|
|
243
|
+
mode: 'dialog',
|
|
244
|
+
size: 'large',
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
await openView.handler(ctx, { mode: 'drawer', size: 'medium', navigation: true });
|
|
248
|
+
|
|
249
|
+
expect(navigateTo).toHaveBeenCalledWith({
|
|
250
|
+
viewUid: 'popup-uid',
|
|
251
|
+
filterByTk: undefined,
|
|
252
|
+
sourceId: undefined,
|
|
253
|
+
tabUid: undefined,
|
|
254
|
+
});
|
|
255
|
+
});
|
|
152
256
|
});
|
|
@@ -7,13 +7,152 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
FlowContext,
|
|
12
|
+
createRecordMetaFactory,
|
|
13
|
+
createRecordResolveOnServerWithLocal,
|
|
14
|
+
defineAction,
|
|
15
|
+
tExpr,
|
|
16
|
+
useFlowSettingsContext,
|
|
17
|
+
type Collection,
|
|
18
|
+
type FlowRuntimeContext,
|
|
19
|
+
type MetaTreeNode,
|
|
20
|
+
type PropertyMetaFactory,
|
|
21
|
+
} from '@nocobase/flow-engine';
|
|
11
22
|
import { isURL } from '@nocobase/utils/client';
|
|
12
|
-
import {
|
|
23
|
+
import React, { useMemo } from 'react';
|
|
24
|
+
import {
|
|
25
|
+
TextAreaWithContextSelector,
|
|
26
|
+
type TextAreaWithContextSelectorProps,
|
|
27
|
+
} from '../components/TextAreaWithContextSelector';
|
|
28
|
+
|
|
29
|
+
type ResponseRecordPlainStepContext = {
|
|
30
|
+
steps?: FlowRuntimeContext['steps'];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type ResponseRecordFlowDefinitionContext = {
|
|
34
|
+
flowKey?: string;
|
|
35
|
+
model?: {
|
|
36
|
+
getFlow?: (flowKey: string) => { steps?: Record<string, unknown> } | undefined;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type ResponseRecordContext = FlowContext &
|
|
41
|
+
ResponseRecordPlainStepContext & {
|
|
42
|
+
blockModel?: { collection?: Collection };
|
|
43
|
+
collection?: Collection;
|
|
44
|
+
model?: FlowRuntimeContext['model'] & { collection?: Collection };
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function getResponseRecordSteps(ctx: FlowContext | ResponseRecordPlainStepContext): FlowRuntimeContext['steps'] {
|
|
48
|
+
return 'steps' in ctx ? ctx.steps || {} : {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getAfterSuccessResponseRecord(ctx: FlowContext | ResponseRecordPlainStepContext) {
|
|
52
|
+
const steps = getResponseRecordSteps(ctx);
|
|
53
|
+
const preferredStepKeys = ['saveResource', 'submit', 'request', 'apply', 'save'];
|
|
54
|
+
|
|
55
|
+
for (const stepKey of preferredStepKeys) {
|
|
56
|
+
if (Object.prototype.hasOwnProperty.call(steps, stepKey) && steps[stepKey]?.result != null) {
|
|
57
|
+
return steps[stepKey].result;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const results = Object.entries(steps)
|
|
62
|
+
.filter(([stepKey, step]) => stepKey !== 'afterSuccess' && step?.result != null)
|
|
63
|
+
.map(([, step]) => step.result);
|
|
64
|
+
return results[results.length - 1];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getResponseRecordMeta(ctx: ResponseRecordContext): PropertyMetaFactory | undefined {
|
|
68
|
+
if (!hasResponseRecordSource(ctx)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const collectionAccessor = getResponseRecordCollectionAccessor(ctx);
|
|
72
|
+
if (!collectionAccessor()) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
return createRecordMetaFactory(collectionAccessor, ctx.t('Response record'), () => {
|
|
76
|
+
const collection = collectionAccessor();
|
|
77
|
+
const record = getAfterSuccessResponseRecord(ctx);
|
|
78
|
+
if (!collection || !record) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const filterByTk = collection.getFilterByTK(record);
|
|
83
|
+
if (filterByTk == null) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
collection: collection.name,
|
|
88
|
+
dataSourceKey: collection.dataSourceKey || 'main',
|
|
89
|
+
filterByTk,
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function hasResponseRecordSource(ctx: FlowContext | ResponseRecordPlainStepContext) {
|
|
98
|
+
if (Object.prototype.hasOwnProperty.call(getResponseRecordSteps(ctx), 'saveResource')) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { model, flowKey } = ctx as ResponseRecordFlowDefinitionContext;
|
|
103
|
+
if (!model || typeof model.getFlow !== 'function' || !flowKey) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return Object.prototype.hasOwnProperty.call(model.getFlow(flowKey)?.steps || {}, 'saveResource');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getResponseRecordCollectionAccessor(ctx: ResponseRecordContext) {
|
|
111
|
+
return () => ctx.blockModel?.collection || ctx.collection || ctx.model?.collection || null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function getMetaTreeWithResponseRecord(ctx: FlowRuntimeContext): MetaTreeNode[] {
|
|
115
|
+
const responseRecordMeta = getResponseRecordMeta(ctx);
|
|
116
|
+
if (!responseRecordMeta) {
|
|
117
|
+
return ctx.getPropertyMetaTree?.() || [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const scoped = new FlowContext();
|
|
121
|
+
scoped.addDelegate(ctx);
|
|
122
|
+
scoped.defineProperty('responseRecord', {
|
|
123
|
+
get: () => getAfterSuccessResponseRecord(ctx),
|
|
124
|
+
cache: false,
|
|
125
|
+
resolveOnServer: createRecordResolveOnServerWithLocal(getResponseRecordCollectionAccessor(ctx), () =>
|
|
126
|
+
getAfterSuccessResponseRecord(ctx),
|
|
127
|
+
),
|
|
128
|
+
meta: responseRecordMeta,
|
|
129
|
+
});
|
|
130
|
+
return scoped.getPropertyMetaTree();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function AfterSuccessRedirectTextArea(props: TextAreaWithContextSelectorProps) {
|
|
134
|
+
const flowCtx = useFlowSettingsContext();
|
|
135
|
+
const metaTree = useMemo(() => () => getMetaTreeWithResponseRecord(flowCtx), [flowCtx]);
|
|
136
|
+
|
|
137
|
+
return <TextAreaWithContextSelector {...props} metaTree={metaTree} />;
|
|
138
|
+
}
|
|
13
139
|
|
|
14
140
|
export const afterSuccess = defineAction({
|
|
15
141
|
name: 'afterSuccess',
|
|
16
142
|
title: tExpr('After successful submission'),
|
|
143
|
+
defineProperties(ctx) {
|
|
144
|
+
const responseRecordMeta = getResponseRecordMeta(ctx);
|
|
145
|
+
return {
|
|
146
|
+
responseRecord: {
|
|
147
|
+
get: () => getAfterSuccessResponseRecord(ctx),
|
|
148
|
+
cache: false,
|
|
149
|
+
resolveOnServer: createRecordResolveOnServerWithLocal(getResponseRecordCollectionAccessor(ctx), () =>
|
|
150
|
+
getAfterSuccessResponseRecord(ctx),
|
|
151
|
+
),
|
|
152
|
+
...(responseRecordMeta ? { meta: responseRecordMeta } : {}),
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
},
|
|
17
156
|
uiSchema: {
|
|
18
157
|
successMessage: {
|
|
19
158
|
type: 'string',
|
|
@@ -46,7 +185,7 @@ export const afterSuccess = defineAction({
|
|
|
46
185
|
type: 'string',
|
|
47
186
|
title: tExpr('Link'),
|
|
48
187
|
'x-decorator': 'FormItem',
|
|
49
|
-
'x-component':
|
|
188
|
+
'x-component': AfterSuccessRedirectTextArea,
|
|
50
189
|
'x-reactions': {
|
|
51
190
|
dependencies: ['actionAfterSuccess'],
|
|
52
191
|
fulfill: {
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
defineAction,
|
|
13
13
|
isRunJSValue,
|
|
14
14
|
normalizeRunJSValue,
|
|
15
|
-
runjsWithSafeGlobals,
|
|
16
15
|
tExpr,
|
|
17
16
|
type RunJSValue,
|
|
18
17
|
useFlowContext,
|
|
@@ -43,7 +42,7 @@ export const customVariable = defineAction({
|
|
|
43
42
|
if (variable.type === 'runjs') {
|
|
44
43
|
const getFunction = async () => {
|
|
45
44
|
const { code, version } = normalizeRunJSValue(variable.runjs);
|
|
46
|
-
return
|
|
45
|
+
return ctx.runjs(code, undefined, { version });
|
|
47
46
|
};
|
|
48
47
|
const metaFunction = () => ({
|
|
49
48
|
title: variable.title,
|
|
@@ -16,13 +16,9 @@ import {
|
|
|
16
16
|
FlowRuntimeContext,
|
|
17
17
|
useFlowContext,
|
|
18
18
|
useFlowEngine,
|
|
19
|
-
createSafeWindow,
|
|
20
|
-
createSafeDocument,
|
|
21
|
-
createSafeNavigator,
|
|
22
19
|
observer,
|
|
23
20
|
isRunJSValue,
|
|
24
21
|
normalizeRunJSValue,
|
|
25
|
-
runjsWithSafeGlobals,
|
|
26
22
|
} from '@nocobase/flow-engine';
|
|
27
23
|
import { evaluateConditions, FilterGroupType, removeInvalidFilterItems } from '@nocobase/utils/client';
|
|
28
24
|
import React from 'react';
|
|
@@ -492,7 +488,7 @@ async function resolveLinkageAssignRuntimeValue(ctx: FlowContext, rawValue: any)
|
|
|
492
488
|
|
|
493
489
|
try {
|
|
494
490
|
const { code, version } = normalizeRunJSValue(rawValue);
|
|
495
|
-
const ret = await
|
|
491
|
+
const ret = await ctx.runjs(code, undefined, { version });
|
|
496
492
|
if (!ret?.success) {
|
|
497
493
|
return SKIP_RUNJS_ASSIGN_VALUE;
|
|
498
494
|
}
|
|
@@ -1397,8 +1393,7 @@ export const linkageRunjs = defineAction({
|
|
|
1397
1393
|
}
|
|
1398
1394
|
|
|
1399
1395
|
try {
|
|
1400
|
-
|
|
1401
|
-
await ctx.runjs(script, { window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator });
|
|
1396
|
+
await ctx.runjs(script);
|
|
1402
1397
|
} catch (error) {
|
|
1403
1398
|
console.error('Script execution error:', error);
|
|
1404
1399
|
// 可以选择显示错误信息给用户
|
|
@@ -7,7 +7,15 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
defineAction,
|
|
12
|
+
tExpr,
|
|
13
|
+
FlowModelContext,
|
|
14
|
+
FlowModel,
|
|
15
|
+
FlowExitAllException,
|
|
16
|
+
createOpenViewRouteState,
|
|
17
|
+
RUNJS_OPEN_VIEW_ROUTE_STATE,
|
|
18
|
+
} from '@nocobase/flow-engine';
|
|
11
19
|
import React from 'react';
|
|
12
20
|
import { FlowPage } from '../FlowPage';
|
|
13
21
|
import { PageModel, RootPageModel } from '../models';
|
|
@@ -291,8 +299,32 @@ export const openView = defineAction({
|
|
|
291
299
|
? (inputArgs as any).associationName
|
|
292
300
|
: (params as any)?.associationName;
|
|
293
301
|
const mergedTabUid = typeof inputArgs.tabUid !== 'undefined' ? inputArgs.tabUid : params.tabUid;
|
|
294
|
-
|
|
295
|
-
const
|
|
302
|
+
const hasRunJSOpenViewRouteState = Object.prototype.hasOwnProperty.call(inputArgs, RUNJS_OPEN_VIEW_ROUTE_STATE);
|
|
303
|
+
const runJSOpenViewRouteStateInput = (inputArgs as Record<PropertyKey, unknown>)[RUNJS_OPEN_VIEW_ROUTE_STATE];
|
|
304
|
+
const runJSOpenViewRouteState = hasRunJSOpenViewRouteState
|
|
305
|
+
? createOpenViewRouteState(
|
|
306
|
+
runJSOpenViewRouteStateInput && typeof runJSOpenViewRouteStateInput === 'object'
|
|
307
|
+
? (runJSOpenViewRouteStateInput as { mode?: unknown; size?: unknown })
|
|
308
|
+
: {
|
|
309
|
+
mode: inputArgs.mode,
|
|
310
|
+
size: inputArgs.size,
|
|
311
|
+
},
|
|
312
|
+
)
|
|
313
|
+
: undefined;
|
|
314
|
+
const replayOpenViewRouteStateInput = (inputArgs as { openViewRouteState?: unknown }).openViewRouteState;
|
|
315
|
+
const replayOpenViewRouteState =
|
|
316
|
+
replayOpenViewRouteStateInput && typeof replayOpenViewRouteStateInput === 'object'
|
|
317
|
+
? createOpenViewRouteState(replayOpenViewRouteStateInput as { mode?: unknown; size?: unknown })
|
|
318
|
+
: undefined;
|
|
319
|
+
const runtimeOpenViewRouteState = runJSOpenViewRouteState || replayOpenViewRouteState;
|
|
320
|
+
// 移动端中只需要显示子页面。本次 dispatch 参数优先于持久化默认值。
|
|
321
|
+
const openMode = ctx.inputArgs?.isMobileLayout
|
|
322
|
+
? 'embed'
|
|
323
|
+
: runtimeOpenViewRouteState?.mode || ctx.inputArgs?.mode || params.mode || 'drawer';
|
|
324
|
+
const effectiveRunJSOpenViewRouteState =
|
|
325
|
+
runJSOpenViewRouteState && ctx.inputArgs?.isMobileLayout
|
|
326
|
+
? createOpenViewRouteState({ ...runJSOpenViewRouteState, mode: openMode })
|
|
327
|
+
: runJSOpenViewRouteState;
|
|
296
328
|
let navigation = typeof inputArgs.navigation !== 'undefined' ? inputArgs.navigation : params.navigation;
|
|
297
329
|
|
|
298
330
|
// 传递了上下文就必须禁用路由,否则下次路由打开会缺少上下文
|
|
@@ -313,6 +345,7 @@ export const openView = defineAction({
|
|
|
313
345
|
sourceId: mergedSourceId,
|
|
314
346
|
tabUid: mergedTabUid,
|
|
315
347
|
viewUid: ctx.model.context?.inputArgs?.viewUid || ctx.model.uid,
|
|
348
|
+
...(effectiveRunJSOpenViewRouteState ? { openViewRouteState: effectiveRunJSOpenViewRouteState } : {}),
|
|
316
349
|
} as Record<string, unknown>;
|
|
317
350
|
const pendingView = {
|
|
318
351
|
type: pendingType,
|
|
@@ -328,6 +361,7 @@ export const openView = defineAction({
|
|
|
328
361
|
filterByTk: mergedFilterByTk,
|
|
329
362
|
sourceId: mergedSourceId,
|
|
330
363
|
tabUid: mergedTabUid,
|
|
364
|
+
...(effectiveRunJSOpenViewRouteState ? { openViewRouteState: effectiveRunJSOpenViewRouteState } : {}),
|
|
331
365
|
};
|
|
332
366
|
ctx.view.navigation.navigateTo(nextView);
|
|
333
367
|
return;
|
|
@@ -373,7 +407,7 @@ export const openView = defineAction({
|
|
|
373
407
|
|
|
374
408
|
const pageModelClass =
|
|
375
409
|
ctx.inputArgs.pageModelClass || params.pageModelClass || ctx.layout?.childPageModelClass || 'ChildPageModel';
|
|
376
|
-
const size = ctx.inputArgs.size || params.size || 'medium';
|
|
410
|
+
const size = runtimeOpenViewRouteState?.size || ctx.inputArgs.size || params.size || 'medium';
|
|
377
411
|
let pageModelUid: string | null = null;
|
|
378
412
|
let pageModelRef: FlowModel | null = null;
|
|
379
413
|
|
|
@@ -7,14 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
ActionScene,
|
|
12
|
-
defineAction,
|
|
13
|
-
tExpr,
|
|
14
|
-
createSafeWindow,
|
|
15
|
-
createSafeDocument,
|
|
16
|
-
createSafeNavigator,
|
|
17
|
-
} from '@nocobase/flow-engine';
|
|
10
|
+
import { ActionScene, defineAction, tExpr } from '@nocobase/flow-engine';
|
|
18
11
|
import { CodeEditor } from '../components/code-editor';
|
|
19
12
|
|
|
20
13
|
export const runjs = defineAction({
|
|
@@ -38,11 +31,6 @@ export const runjs = defineAction({
|
|
|
38
31
|
// 如果是 URL 触发的,则不执行代码
|
|
39
32
|
if (ctx.inputArgs?.navigation) return;
|
|
40
33
|
|
|
41
|
-
|
|
42
|
-
return ctx.runjs(params.code, {
|
|
43
|
-
window: createSafeWindow({ navigator }),
|
|
44
|
-
document: createSafeDocument(),
|
|
45
|
-
navigator,
|
|
46
|
-
});
|
|
34
|
+
return ctx.runjs(params.code);
|
|
47
35
|
},
|
|
48
36
|
});
|
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { define, observable } from '@formily/reactive';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
decodeOpenViewRouteState,
|
|
13
|
+
parsePathnameToViewParams,
|
|
14
|
+
type FlowEngine,
|
|
15
|
+
FlowModel,
|
|
16
|
+
type ViewParam,
|
|
17
|
+
} from '@nocobase/flow-engine';
|
|
12
18
|
import {
|
|
13
19
|
BaseLayoutRouteCoordinator,
|
|
14
20
|
type BaseLayoutRouteCoordinatorOptions,
|
|
@@ -116,20 +122,36 @@ const isStandardLayoutRelativePath = (relativePath: string) => {
|
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
let i = 1;
|
|
125
|
+
let currentViewUid = segments[0];
|
|
119
126
|
while (i < segments.length) {
|
|
120
127
|
const segment = segments[i];
|
|
128
|
+
|
|
121
129
|
if (segment === 'view') {
|
|
122
130
|
if (!segments[i + 1]) {
|
|
123
131
|
return false;
|
|
124
132
|
}
|
|
133
|
+
currentViewUid = segments[i + 1];
|
|
134
|
+
i += 2;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (segment === 'opts') {
|
|
139
|
+
if (!segments[i + 1] || !decodeOpenViewRouteState(currentViewUid, segments[i + 1])) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
i += 2;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (isKnownViewParamName(segment) && segments[i + 1]) {
|
|
125
147
|
i += 2;
|
|
126
148
|
continue;
|
|
127
149
|
}
|
|
128
150
|
|
|
129
|
-
if (!
|
|
151
|
+
if (!segments[i + 1]) {
|
|
130
152
|
return false;
|
|
131
153
|
}
|
|
132
|
-
|
|
154
|
+
return false;
|
|
133
155
|
}
|
|
134
156
|
|
|
135
157
|
return true;
|
|
@@ -272,7 +272,7 @@ export class BaseLayoutRouteCoordinator {
|
|
|
272
272
|
runtime.viewState[getKey(viewItem)]?.destroy?.(true);
|
|
273
273
|
delete runtime.viewState[getKey(viewItem)];
|
|
274
274
|
});
|
|
275
|
-
updateViewListHidden(viewList);
|
|
275
|
+
updateViewListHidden(viewList, !!this.layoutContext?.isMobileLayout);
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
if (viewsToOpen.length) {
|
|
@@ -299,7 +299,7 @@ export class BaseLayoutRouteCoordinator {
|
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
if (runtime.meta.active) {
|
|
302
|
-
updateViewListHidden(viewList);
|
|
302
|
+
updateViewListHidden(viewList, !!this.layoutContext?.isMobileLayout);
|
|
303
303
|
return;
|
|
304
304
|
}
|
|
305
305
|
|
|
@@ -390,6 +390,7 @@ export class BaseLayoutRouteCoordinator {
|
|
|
390
390
|
openViewParams?.associationName && !hasUsableSourceId(viewItem.params.sourceId)
|
|
391
391
|
? null
|
|
392
392
|
: openViewParams?.associationName;
|
|
393
|
+
const openViewRouteState = viewItem.params.openViewRouteState;
|
|
393
394
|
const openerUids = viewList.slice(0, viewItem.index).map((item) => item.params.viewUid);
|
|
394
395
|
const navigation = new ViewNavigation(
|
|
395
396
|
this.flowEngine.context,
|
|
@@ -408,6 +409,8 @@ export class BaseLayoutRouteCoordinator {
|
|
|
408
409
|
deactivateRef,
|
|
409
410
|
openerUids,
|
|
410
411
|
...viewItem.params,
|
|
412
|
+
...(openViewRouteState?.mode ? { mode: openViewRouteState.mode } : {}),
|
|
413
|
+
...(openViewRouteState?.size ? { size: openViewRouteState.size } : {}),
|
|
411
414
|
pageActive: runtime.meta.active,
|
|
412
415
|
activationControlledByLayout: true,
|
|
413
416
|
navigation,
|
|
@@ -121,6 +121,19 @@ describe('AdminLayoutRouteCoordinator', () => {
|
|
|
121
121
|
});
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
+
it('passes RunJS openView route state as runtime mode and size during route replay', () => {
|
|
125
|
+
const { dispatchEvent } = setupRouteReplay({
|
|
126
|
+
openViewRouteState: { mode: 'dialog', size: 'large' },
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(dispatchEvent.mock.calls[0][1]).toMatchObject({
|
|
130
|
+
mode: 'dialog',
|
|
131
|
+
size: 'large',
|
|
132
|
+
openViewRouteState: { mode: 'dialog', size: 'large' },
|
|
133
|
+
triggerByRouter: true,
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
124
137
|
it('uses layout content element before route page placeholder as view target', () => {
|
|
125
138
|
const engine = new FlowEngine();
|
|
126
139
|
engine.registerModels({ RouteModel });
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
FLOW_SETTINGS_PREFERENCE_STORAGE_KEY,
|
|
43
43
|
readFlowSettingsPreference,
|
|
44
44
|
} from './flowSettingsPreference';
|
|
45
|
+
import { joinAdminLayoutRoutePath, type AdminLayoutRoutePathLike } from './resolveAdminRouteRuntimeTarget';
|
|
45
46
|
import { useAppListRender } from './AppListRender';
|
|
46
47
|
|
|
47
48
|
const className1 = css`
|
|
@@ -314,25 +315,37 @@ const DesignerButtonMenuItem: FC<{ item: AdminLayoutMenuNode; fallbackParentRout
|
|
|
314
315
|
);
|
|
315
316
|
};
|
|
316
317
|
|
|
317
|
-
const matchesRoutePath = (
|
|
318
|
+
const matchesRoutePath = (
|
|
319
|
+
route: NocoBaseDesktopRoute | undefined,
|
|
320
|
+
pathname: string,
|
|
321
|
+
layout?: AdminLayoutRoutePathLike | null,
|
|
322
|
+
): boolean => {
|
|
318
323
|
if (!route) {
|
|
319
324
|
return false;
|
|
320
325
|
}
|
|
321
326
|
|
|
322
327
|
const candidates = [
|
|
323
|
-
route.id != null ?
|
|
324
|
-
route.schemaUid ?
|
|
328
|
+
route.id != null ? joinAdminLayoutRoutePath(layout, route.id) : null,
|
|
329
|
+
route.schemaUid ? joinAdminLayoutRoutePath(layout, route.schemaUid) : null,
|
|
325
330
|
].filter(Boolean) as string[];
|
|
326
331
|
|
|
327
332
|
if (candidates.some((candidate) => pathname === candidate || pathname.startsWith(`${candidate}/`))) {
|
|
328
333
|
return true;
|
|
329
334
|
}
|
|
330
335
|
|
|
331
|
-
return Array.isArray(route.children)
|
|
336
|
+
return Array.isArray(route.children)
|
|
337
|
+
? route.children.some((child) => matchesRoutePath(child, pathname, layout))
|
|
338
|
+
: false;
|
|
332
339
|
};
|
|
333
340
|
|
|
334
|
-
const findSelectedTopGroupRoute = (
|
|
335
|
-
|
|
341
|
+
const findSelectedTopGroupRoute = (
|
|
342
|
+
routes: NocoBaseDesktopRoute[],
|
|
343
|
+
pathname: string,
|
|
344
|
+
layout?: AdminLayoutRoutePathLike | null,
|
|
345
|
+
) => {
|
|
346
|
+
return routes.find(
|
|
347
|
+
(route) => route.type === NocoBaseDesktopRouteType.group && matchesRoutePath(route, pathname, layout),
|
|
348
|
+
);
|
|
336
349
|
};
|
|
337
350
|
|
|
338
351
|
const renderMenuNodeWithModel = (
|
|
@@ -376,6 +389,11 @@ export const AdminLayoutComponent = observer((props: any) => {
|
|
|
376
389
|
const { token } = antdTheme.useToken();
|
|
377
390
|
const customToken = token as CustomToken;
|
|
378
391
|
const isMobileLayout = !!adminLayoutModel?.isMobileLayout;
|
|
392
|
+
const adminLayoutRoutePath = adminLayoutModel?.layout?.routePath;
|
|
393
|
+
const adminLayoutRoutePathLike = useMemo<AdminLayoutRoutePathLike | undefined>(
|
|
394
|
+
() => (adminLayoutRoutePath ? { routePath: adminLayoutRoutePath } : undefined),
|
|
395
|
+
[adminLayoutRoutePath],
|
|
396
|
+
);
|
|
379
397
|
const menuRouteRefreshVersion = adminLayoutModel?.menuRouteRefreshVersion || 0;
|
|
380
398
|
const isMobileSider = isMobileLayout;
|
|
381
399
|
const [collapsed, setCollapsed] = useState(isMobileSider);
|
|
@@ -403,8 +421,8 @@ export const AdminLayoutComponent = observer((props: any) => {
|
|
|
403
421
|
[adminLayoutModel],
|
|
404
422
|
);
|
|
405
423
|
const selectedTopGroupRoute = useMemo(
|
|
406
|
-
() => findSelectedTopGroupRoute(allAccessRoutes, location.pathname),
|
|
407
|
-
[allAccessRoutes, location.pathname],
|
|
424
|
+
() => findSelectedTopGroupRoute(allAccessRoutes, location.pathname, adminLayoutRoutePathLike),
|
|
425
|
+
[adminLayoutRoutePathLike, allAccessRoutes, location.pathname],
|
|
408
426
|
);
|
|
409
427
|
|
|
410
428
|
const handleMenuDragEnd = useCallback(
|
|
@@ -458,6 +476,7 @@ export const AdminLayoutComponent = observer((props: any) => {
|
|
|
458
476
|
|
|
459
477
|
useEffect(() => {
|
|
460
478
|
const routeRepository = flowEngine.context.routeRepository;
|
|
479
|
+
const deactivateLayout = routeRepository?.activateLayout?.(adminLayoutModel?.layout);
|
|
461
480
|
const subscriber = () => {
|
|
462
481
|
const updatedRoutes = routeRepository?.listAccessible() || [];
|
|
463
482
|
setAllAccessRoutes(updatedRoutes);
|
|
@@ -470,8 +489,9 @@ export const AdminLayoutComponent = observer((props: any) => {
|
|
|
470
489
|
|
|
471
490
|
return () => {
|
|
472
491
|
routeRepository?.unsubscribe(subscriber);
|
|
492
|
+
deactivateLayout?.();
|
|
473
493
|
};
|
|
474
|
-
}, [flowEngine]);
|
|
494
|
+
}, [adminLayoutModel, adminLayoutModel?.layout?.uid, flowEngine]);
|
|
475
495
|
|
|
476
496
|
useEffect(() => {
|
|
477
497
|
if (typeof window === 'undefined') {
|
|
@@ -697,7 +717,11 @@ export const AdminLayoutComponent = observer((props: any) => {
|
|
|
697
717
|
<SetIsMobileLayout isMobile={isMobile} model={adminLayoutModel}>
|
|
698
718
|
<ConfigProvider theme={isMobile ? mobileTheme : theme}>
|
|
699
719
|
<GlobalStyle />
|
|
700
|
-
<AdminLayoutContent
|
|
720
|
+
<AdminLayoutContent
|
|
721
|
+
designable={designable}
|
|
722
|
+
layout={adminLayoutModel?.layout}
|
|
723
|
+
onContentElementChange={handleLayoutContentElementChange}
|
|
724
|
+
/>
|
|
701
725
|
</ConfigProvider>
|
|
702
726
|
</SetIsMobileLayout>
|
|
703
727
|
);
|