@nocobase/client-v2 2.1.0-beta.37 → 2.1.0-beta.38
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/Application.d.ts +1 -0
- package/es/BaseApplication.d.ts +3 -0
- package/es/RouterManager.d.ts +1 -0
- package/es/components/KeepAlive.d.ts +22 -0
- package/es/components/RouterBridge.d.ts +9 -0
- package/es/data-source/ExtendCollectionsProvider.d.ts +28 -2
- package/es/flow/FlowPage.d.ts +2 -1
- package/es/flow/admin-shell/AdminLayoutRouteCoordinator.d.ts +8 -40
- package/es/flow/admin-shell/BaseLayoutModel.d.ts +89 -0
- package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +74 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +12 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -92
- package/es/flow/admin-shell/admin-layout/index.d.ts +2 -0
- package/es/flow/admin-shell/useAdminLayoutRoutePage.d.ts +2 -2
- package/es/flow/admin-shell/useLayoutRoutePage.d.ts +23 -0
- package/es/flow/components/FlowRoute.d.ts +10 -1
- package/es/flow/index.d.ts +4 -0
- package/es/flow/models/base/PageModel/PageModel.d.ts +3 -1
- package/es/flow/models/blocks/form/FormActionGroupModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +10 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.d.ts +1 -1
- package/es/index.d.ts +1 -0
- package/es/index.mjs +484 -437
- package/es/layout-manager/LayoutContentRoute.d.ts +14 -0
- package/es/layout-manager/LayoutManager.d.ts +22 -0
- package/es/layout-manager/LayoutRoute.d.ts +14 -0
- package/es/layout-manager/index.d.ts +13 -0
- package/es/layout-manager/types.d.ts +20 -0
- package/es/layout-manager/utils.d.ts +14 -0
- package/es/settings-center/index.d.ts +1 -1
- package/es/settings-center/plugin-manager/BulkEnableButton.d.ts +15 -0
- package/es/settings-center/plugin-manager/PluginCard.d.ts +15 -0
- package/es/settings-center/plugin-manager/PluginDetail.d.ts +16 -0
- package/es/settings-center/{PluginManagerPage.d.ts → plugin-manager/index.d.ts} +1 -7
- package/es/settings-center/plugin-manager/types.d.ts +34 -0
- package/lib/index.js +484 -437
- package/package.json +8 -7
- package/src/Application.tsx +27 -12
- package/src/BaseApplication.tsx +6 -0
- package/src/PluginSettingsManager.ts +1 -1
- package/src/RouterManager.tsx +17 -1
- package/src/__tests__/PluginSettingsManager.test.ts +41 -2
- package/src/__tests__/app.test.tsx +8 -1
- package/src/__tests__/globalDeps.test.ts +1 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +45 -2
- package/src/__tests__/plugin-manager.test.tsx +177 -0
- package/src/__tests__/settings-center.test.tsx +24 -2
- package/src/components/KeepAlive.tsx +131 -0
- package/src/components/RouterBridge.tsx +28 -4
- package/src/components/__tests__/KeepAlive.test.tsx +63 -0
- package/src/components/__tests__/RouterBridge.test.tsx +27 -0
- package/src/data-source/ExtendCollectionsProvider.tsx +94 -20
- package/src/data-source/__tests__/ExtendCollectionsProvider.test.tsx +264 -0
- package/src/flow/FlowPage.tsx +35 -7
- package/src/flow/__tests__/FlowPage.test.tsx +79 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +529 -2
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +191 -0
- package/src/flow/actions/__tests__/openView.subModelKey.test.tsx +33 -0
- package/src/flow/actions/aclCheck.tsx +4 -0
- package/src/flow/actions/aclCheckRefresh.tsx +4 -0
- package/src/flow/actions/dateTimeFormat.tsx +12 -8
- package/src/flow/actions/linkageRules.tsx +122 -0
- package/src/flow/actions/openView.tsx +28 -4
- package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +11 -329
- package/src/flow/admin-shell/BaseLayoutModel.tsx +455 -0
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +502 -0
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +547 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +4 -4
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +160 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +0 -12
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +28 -201
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +11 -2
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +1 -26
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +149 -27
- package/src/flow/admin-shell/admin-layout/index.ts +2 -0
- package/src/flow/admin-shell/useAdminLayoutRoutePage.ts +10 -26
- package/src/flow/admin-shell/useLayoutRoutePage.ts +61 -0
- package/src/flow/components/AdminLayout.tsx +4 -154
- package/src/flow/components/FlowRoute.tsx +105 -15
- package/src/flow/index.ts +4 -0
- package/src/flow/models/base/ActionModel.tsx +8 -1
- package/src/flow/models/base/PageModel/PageModel.tsx +51 -18
- package/src/flow/models/base/PageModel/RootPageModel.tsx +6 -13
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +102 -1
- package/src/flow/models/base/RouteModel.tsx +1 -1
- package/src/flow/models/blocks/form/FormActionGroupModel.tsx +14 -0
- package/src/flow/models/blocks/form/FormItemModel.tsx +8 -1
- package/src/flow/models/blocks/form/__tests__/FormActionGroupModel.test.ts +46 -0
- package/src/flow/models/blocks/form/submitValues.ts +4 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +118 -16
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +114 -0
- package/src/flow/models/fields/AssociationFieldModel/SubFormFieldModel.tsx +7 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -5
- package/src/flow/models/fields/ClickableFieldModel.tsx +9 -1
- package/src/flow/models/fields/DisplayTimeFieldModel.tsx +1 -1
- package/src/flow/models/fields/TimeFieldModel.tsx +1 -1
- package/src/flow/models/fields/__tests__/TimeFieldModel.test.tsx +61 -0
- package/src/flow/models/fields/mobile-components/MobileDatePicker.tsx +19 -3
- package/src/flow/models/fields/mobile-components/__tests__/MobileDatePicker.test.tsx +94 -0
- package/src/flow/models/topbar/TopbarActionModel.tsx +1 -1
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +91 -0
- package/src/index.ts +1 -0
- package/src/layout-manager/LayoutContentRoute.tsx +90 -0
- package/src/layout-manager/LayoutManager.tsx +185 -0
- package/src/layout-manager/LayoutRoute.tsx +138 -0
- package/src/layout-manager/__tests__/LayoutManager.test.tsx +335 -0
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +473 -0
- package/src/layout-manager/index.ts +14 -0
- package/src/layout-manager/types.ts +22 -0
- package/src/layout-manager/utils.ts +37 -0
- package/src/nocobase-buildin-plugin/index.tsx +56 -48
- package/src/settings-center/index.ts +1 -1
- package/src/settings-center/plugin-manager/BulkEnableButton.tsx +111 -0
- package/src/settings-center/plugin-manager/PluginCard.tsx +270 -0
- package/src/settings-center/plugin-manager/PluginDetail.tsx +195 -0
- package/src/settings-center/plugin-manager/index.tsx +254 -0
- package/src/settings-center/plugin-manager/types.ts +35 -0
- package/src/settings-center/utils.tsx +8 -1
- package/src/theme/__tests__/globalStyles.test.ts +24 -0
- package/src/theme/globalStyles.ts +10 -0
- package/src/utils/globalDeps.ts +2 -0
- package/src/settings-center/PluginManagerPage.tsx +0 -162
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { FlowEngine } from '@nocobase/flow-engine';
|
|
10
|
+
import { FlowContext, FlowEngine } from '@nocobase/flow-engine';
|
|
11
11
|
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
12
12
|
import { getViewDiffAndUpdateHidden } from '../../getViewDiffAndUpdateHidden';
|
|
13
13
|
import { getOpenViewStepParams } from '../../flows/openViewFlow';
|
|
14
|
-
import { resolveViewParamsToViewList } from '../../resolveViewParamsToViewList';
|
|
14
|
+
import { resolveViewParamsToViewList, updateViewListHidden } from '../../resolveViewParamsToViewList';
|
|
15
15
|
import { AdminLayoutRouteCoordinator } from '../AdminLayoutRouteCoordinator';
|
|
16
|
+
import { BaseLayoutRouteCoordinator, toViewStack } from '../BaseLayoutRouteCoordinator';
|
|
16
17
|
import { RouteModel } from '../../models/base/RouteModel';
|
|
17
18
|
|
|
18
19
|
vi.mock('../../resolveViewParamsToViewList', () => ({
|
|
@@ -36,6 +37,7 @@ vi.mock('../../flows/openViewFlow', async (importOriginal) => {
|
|
|
36
37
|
const mockResolveViewParamsToViewList = vi.mocked(resolveViewParamsToViewList);
|
|
37
38
|
const mockGetViewDiffAndUpdateHidden = vi.mocked(getViewDiffAndUpdateHidden);
|
|
38
39
|
const mockGetOpenViewStepParams = vi.mocked(getOpenViewStepParams);
|
|
40
|
+
const mockUpdateViewListHidden = vi.mocked(updateViewListHidden);
|
|
39
41
|
|
|
40
42
|
function setupRouteReplay(viewParams: Record<string, any>) {
|
|
41
43
|
const engine = new FlowEngine();
|
|
@@ -52,7 +54,7 @@ function setupRouteReplay(viewParams: Record<string, any>) {
|
|
|
52
54
|
},
|
|
53
55
|
});
|
|
54
56
|
|
|
55
|
-
const dispatchEvent = vi.fn(() => Promise.resolve());
|
|
57
|
+
const dispatchEvent = vi.fn((_eventName: string, _payload: any) => Promise.resolve());
|
|
56
58
|
const viewItem = {
|
|
57
59
|
params: {
|
|
58
60
|
viewUid: 'popup',
|
|
@@ -81,6 +83,10 @@ function setupRouteReplay(viewParams: Record<string, any>) {
|
|
|
81
83
|
active: true,
|
|
82
84
|
layoutContentElement: document.createElement('div'),
|
|
83
85
|
});
|
|
86
|
+
coordinator.syncRoute({
|
|
87
|
+
params: { name: 'test-route' },
|
|
88
|
+
pathname: '/admin/popup/filterbytk/member',
|
|
89
|
+
});
|
|
84
90
|
|
|
85
91
|
return { dispatchEvent };
|
|
86
92
|
}
|
|
@@ -114,4 +120,542 @@ describe('AdminLayoutRouteCoordinator', () => {
|
|
|
114
120
|
triggerByRouter: true,
|
|
115
121
|
});
|
|
116
122
|
});
|
|
123
|
+
|
|
124
|
+
it('uses layout content element before route page placeholder as view target', () => {
|
|
125
|
+
const engine = new FlowEngine();
|
|
126
|
+
engine.registerModels({ RouteModel });
|
|
127
|
+
engine.context.defineProperty('route', {
|
|
128
|
+
value: {},
|
|
129
|
+
});
|
|
130
|
+
engine.context.defineProperty('routeRepository', {
|
|
131
|
+
value: {
|
|
132
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const layoutContentElement = document.createElement('div');
|
|
137
|
+
const routePageElement = document.createElement('div');
|
|
138
|
+
const dispatchEvent = vi.fn((_eventName: string, _payload: any) => Promise.resolve());
|
|
139
|
+
const viewItem = {
|
|
140
|
+
params: { viewUid: 'test-route' },
|
|
141
|
+
modelUid: 'test-route',
|
|
142
|
+
model: { uid: 'test-route', dispatchEvent } as any,
|
|
143
|
+
hidden: { value: false },
|
|
144
|
+
index: 0,
|
|
145
|
+
};
|
|
146
|
+
mockResolveViewParamsToViewList.mockReturnValue([viewItem]);
|
|
147
|
+
mockGetViewDiffAndUpdateHidden.mockReturnValue({
|
|
148
|
+
viewsToClose: [],
|
|
149
|
+
viewsToOpen: [viewItem],
|
|
150
|
+
});
|
|
151
|
+
mockGetOpenViewStepParams.mockReturnValue({} as any);
|
|
152
|
+
|
|
153
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
154
|
+
coordinator.setLayoutContentElement(layoutContentElement);
|
|
155
|
+
coordinator.registerPage('test-route', {
|
|
156
|
+
active: true,
|
|
157
|
+
layoutContentElement: routePageElement,
|
|
158
|
+
});
|
|
159
|
+
coordinator.syncRoute({
|
|
160
|
+
pageUid: 'test-route',
|
|
161
|
+
pathname: '/admin/test-route',
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(dispatchEvent.mock.calls[0][1].target).toBe(layoutContentElement);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('replaces stale non-route model before registering route page', () => {
|
|
168
|
+
const engine = new FlowEngine();
|
|
169
|
+
engine.registerModels({ RouteModel });
|
|
170
|
+
const staleModel = engine.createModel({
|
|
171
|
+
uid: 'test-route',
|
|
172
|
+
use: 'FlowModel',
|
|
173
|
+
});
|
|
174
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
175
|
+
|
|
176
|
+
const routeModel = coordinator.registerPage('test-route', {
|
|
177
|
+
active: true,
|
|
178
|
+
layoutContentElement: document.createElement('div'),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(routeModel).toBeInstanceOf(RouteModel);
|
|
182
|
+
expect(engine.getModel('test-route')).toBe(routeModel);
|
|
183
|
+
expect(engine.getModel('test-route')).not.toBe(staleModel);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('parses view stack with custom layout prefix', () => {
|
|
187
|
+
const engine = new FlowEngine();
|
|
188
|
+
engine.registerModels({ RouteModel });
|
|
189
|
+
engine.context.defineProperty('route', {
|
|
190
|
+
value: {
|
|
191
|
+
params: { name: 'test-route' },
|
|
192
|
+
pathname: '/embed/test-route/view/popup/filterbytk/member',
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
engine.context.defineProperty('routeRepository', {
|
|
196
|
+
value: {
|
|
197
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
mockResolveViewParamsToViewList.mockReturnValue([]);
|
|
202
|
+
mockGetViewDiffAndUpdateHidden.mockReturnValue({
|
|
203
|
+
viewsToClose: [],
|
|
204
|
+
viewsToOpen: [],
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/embed' });
|
|
208
|
+
coordinator.registerPage('test-route', {
|
|
209
|
+
active: true,
|
|
210
|
+
layoutContentElement: document.createElement('div'),
|
|
211
|
+
});
|
|
212
|
+
coordinator.syncRoute({
|
|
213
|
+
params: { name: 'test-route' },
|
|
214
|
+
pathname: '/embed/test-route/view/popup/filterbytk/member',
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
expect(mockResolveViewParamsToViewList).toHaveBeenCalledWith(
|
|
218
|
+
engine,
|
|
219
|
+
[{ viewUid: 'test-route' }, { viewUid: 'popup', filterByTk: 'member' }],
|
|
220
|
+
expect.anything(),
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('syncs view stack from layout route pageUid', () => {
|
|
225
|
+
const engine = new FlowEngine();
|
|
226
|
+
engine.registerModels({ RouteModel });
|
|
227
|
+
engine.context.defineProperty('route', {
|
|
228
|
+
value: {},
|
|
229
|
+
});
|
|
230
|
+
engine.context.defineProperty('routeRepository', {
|
|
231
|
+
value: {
|
|
232
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
mockResolveViewParamsToViewList.mockReturnValue([]);
|
|
237
|
+
mockGetViewDiffAndUpdateHidden.mockReturnValue({
|
|
238
|
+
viewsToClose: [],
|
|
239
|
+
viewsToOpen: [],
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/embed' });
|
|
243
|
+
coordinator.registerPage('test-route', {
|
|
244
|
+
active: false,
|
|
245
|
+
layoutContentElement: document.createElement('div'),
|
|
246
|
+
});
|
|
247
|
+
coordinator.syncRoute({
|
|
248
|
+
pageUid: 'test-route',
|
|
249
|
+
pathname: '/embed/test-route/view/popup/filterbytk/member',
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
expect(mockResolveViewParamsToViewList).toHaveBeenLastCalledWith(
|
|
253
|
+
engine,
|
|
254
|
+
[{ viewUid: 'test-route' }, { viewUid: 'popup', filterByTk: 'member' }],
|
|
255
|
+
expect.anything(),
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('notifies cached route page activation when switching pages', () => {
|
|
260
|
+
const engine = new FlowEngine();
|
|
261
|
+
engine.registerModels({ RouteModel });
|
|
262
|
+
engine.context.defineProperty('route', {
|
|
263
|
+
value: {},
|
|
264
|
+
});
|
|
265
|
+
engine.context.defineProperty('routeRepository', {
|
|
266
|
+
value: {
|
|
267
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const dispatchEvent = vi.fn((_eventName: string, payload: any) => {
|
|
272
|
+
payload.activateRef.current = vi.fn();
|
|
273
|
+
payload.deactivateRef.current = vi.fn();
|
|
274
|
+
return Promise.resolve();
|
|
275
|
+
});
|
|
276
|
+
const viewItem = {
|
|
277
|
+
params: { viewUid: 'test-route' },
|
|
278
|
+
modelUid: 'test-route',
|
|
279
|
+
model: { uid: 'test-route', dispatchEvent } as any,
|
|
280
|
+
hidden: { value: false },
|
|
281
|
+
index: 0,
|
|
282
|
+
};
|
|
283
|
+
mockResolveViewParamsToViewList.mockReturnValue([viewItem]);
|
|
284
|
+
mockGetViewDiffAndUpdateHidden.mockReturnValueOnce({
|
|
285
|
+
viewsToClose: [],
|
|
286
|
+
viewsToOpen: [viewItem],
|
|
287
|
+
});
|
|
288
|
+
mockGetViewDiffAndUpdateHidden.mockReturnValue({
|
|
289
|
+
viewsToClose: [],
|
|
290
|
+
viewsToOpen: [],
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
294
|
+
coordinator.registerPage('test-route', {
|
|
295
|
+
active: false,
|
|
296
|
+
layoutContentElement: document.createElement('div'),
|
|
297
|
+
});
|
|
298
|
+
coordinator.syncRoute({
|
|
299
|
+
pageUid: 'test-route',
|
|
300
|
+
pathname: '/admin/test-route',
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const payload = dispatchEvent.mock.calls[0][1];
|
|
304
|
+
expect(payload.pageActive).toBe(true);
|
|
305
|
+
expect(payload.activationControlledByLayout).toBe(true);
|
|
306
|
+
|
|
307
|
+
coordinator.syncRoute({
|
|
308
|
+
pageUid: 'other-route',
|
|
309
|
+
pathname: '/admin/other-route',
|
|
310
|
+
});
|
|
311
|
+
coordinator.syncRoute({
|
|
312
|
+
pageUid: 'test-route',
|
|
313
|
+
pathname: '/admin/test-route',
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
expect(payload.deactivateRef.current).toHaveBeenCalledTimes(1);
|
|
317
|
+
expect(payload.activateRef.current).toHaveBeenCalledWith(true);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('syncs cached embed page visibility when switching pages', () => {
|
|
321
|
+
const engine = new FlowEngine();
|
|
322
|
+
engine.registerModels({ RouteModel });
|
|
323
|
+
engine.context.defineProperty('route', {
|
|
324
|
+
value: {},
|
|
325
|
+
});
|
|
326
|
+
engine.context.defineProperty('routeRepository', {
|
|
327
|
+
value: {
|
|
328
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const viewItemsByPageUid = new Map<string, any>();
|
|
333
|
+
const createViewItem = (pageUid: string) => {
|
|
334
|
+
const dispatchEvent = vi.fn((_eventName: string, payload: any) => {
|
|
335
|
+
payload.activateRef.current = vi.fn();
|
|
336
|
+
payload.deactivateRef.current = vi.fn();
|
|
337
|
+
return Promise.resolve();
|
|
338
|
+
});
|
|
339
|
+
return {
|
|
340
|
+
params: { viewUid: pageUid },
|
|
341
|
+
modelUid: pageUid,
|
|
342
|
+
model: { uid: pageUid, dispatchEvent } as any,
|
|
343
|
+
hidden: { value: false },
|
|
344
|
+
index: 0,
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
mockResolveViewParamsToViewList.mockImplementation((_engine, viewParams) => {
|
|
349
|
+
const pageUid = viewParams[0].viewUid;
|
|
350
|
+
if (!viewItemsByPageUid.has(pageUid)) {
|
|
351
|
+
viewItemsByPageUid.set(pageUid, createViewItem(pageUid));
|
|
352
|
+
}
|
|
353
|
+
return [viewItemsByPageUid.get(pageUid)];
|
|
354
|
+
});
|
|
355
|
+
mockGetViewDiffAndUpdateHidden.mockImplementation((prevViewList, currentViewList) => ({
|
|
356
|
+
viewsToClose: prevViewList.filter((prevViewItem) => !currentViewList.includes(prevViewItem)),
|
|
357
|
+
viewsToOpen: currentViewList.filter((currentViewItem) => !prevViewList.includes(currentViewItem)),
|
|
358
|
+
}));
|
|
359
|
+
mockUpdateViewListHidden.mockImplementation((viewItems) => {
|
|
360
|
+
viewItems.forEach((viewItem) => {
|
|
361
|
+
viewItem.hidden.value = false;
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
366
|
+
coordinator.registerPage('page-1', {
|
|
367
|
+
active: false,
|
|
368
|
+
layoutContentElement: document.createElement('div'),
|
|
369
|
+
});
|
|
370
|
+
coordinator.registerPage('page-3', {
|
|
371
|
+
active: false,
|
|
372
|
+
layoutContentElement: document.createElement('div'),
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
coordinator.syncRoute({
|
|
376
|
+
pageUid: 'page-3',
|
|
377
|
+
pathname: '/admin/page-3',
|
|
378
|
+
});
|
|
379
|
+
const page3ViewItem = viewItemsByPageUid.get('page-3');
|
|
380
|
+
expect(page3ViewItem.hidden.value).toBe(false);
|
|
381
|
+
|
|
382
|
+
coordinator.syncRoute({
|
|
383
|
+
pageUid: 'page-1',
|
|
384
|
+
pathname: '/admin/page-1',
|
|
385
|
+
});
|
|
386
|
+
const page1ViewItem = viewItemsByPageUid.get('page-1');
|
|
387
|
+
expect(page1ViewItem.hidden.value).toBe(false);
|
|
388
|
+
expect(page3ViewItem.hidden.value).toBe(true);
|
|
389
|
+
|
|
390
|
+
coordinator.syncRoute({
|
|
391
|
+
pageUid: 'page-3',
|
|
392
|
+
pathname: '/admin/page-3',
|
|
393
|
+
});
|
|
394
|
+
expect(page3ViewItem.hidden.value).toBe(false);
|
|
395
|
+
expect(page1ViewItem.hidden.value).toBe(true);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('notifies cached route page activation when page meta active changes', () => {
|
|
399
|
+
const engine = new FlowEngine();
|
|
400
|
+
engine.registerModels({ RouteModel });
|
|
401
|
+
engine.context.defineProperty('route', {
|
|
402
|
+
value: {},
|
|
403
|
+
});
|
|
404
|
+
engine.context.defineProperty('routeRepository', {
|
|
405
|
+
value: {
|
|
406
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const dispatchEvent = vi.fn((_eventName: string, payload: any) => {
|
|
411
|
+
payload.activateRef.current = vi.fn();
|
|
412
|
+
payload.deactivateRef.current = vi.fn();
|
|
413
|
+
return Promise.resolve();
|
|
414
|
+
});
|
|
415
|
+
const viewItem = {
|
|
416
|
+
params: { viewUid: 'test-route' },
|
|
417
|
+
modelUid: 'test-route',
|
|
418
|
+
model: { uid: 'test-route', dispatchEvent } as any,
|
|
419
|
+
hidden: { value: false },
|
|
420
|
+
index: 0,
|
|
421
|
+
};
|
|
422
|
+
mockResolveViewParamsToViewList.mockReturnValue([viewItem]);
|
|
423
|
+
mockGetViewDiffAndUpdateHidden.mockReturnValueOnce({
|
|
424
|
+
viewsToClose: [],
|
|
425
|
+
viewsToOpen: [viewItem],
|
|
426
|
+
});
|
|
427
|
+
mockGetViewDiffAndUpdateHidden.mockReturnValue({
|
|
428
|
+
viewsToClose: [],
|
|
429
|
+
viewsToOpen: [],
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
433
|
+
coordinator.registerPage('test-route', {
|
|
434
|
+
active: true,
|
|
435
|
+
layoutContentElement: document.createElement('div'),
|
|
436
|
+
});
|
|
437
|
+
coordinator.syncRoute({
|
|
438
|
+
pageUid: 'test-route',
|
|
439
|
+
pathname: '/admin/test-route',
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const payload = dispatchEvent.mock.calls[0][1];
|
|
443
|
+
|
|
444
|
+
coordinator.syncPageMeta('test-route', { active: false });
|
|
445
|
+
coordinator.syncPageMeta('test-route', { active: true });
|
|
446
|
+
|
|
447
|
+
expect(payload.deactivateRef.current).toHaveBeenCalledTimes(1);
|
|
448
|
+
expect(payload.activateRef.current).toHaveBeenCalledWith(true);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('replays an initial deep link when step navigation does not trigger another route sync', async () => {
|
|
452
|
+
const engine = new FlowEngine();
|
|
453
|
+
engine.registerModels({ RouteModel });
|
|
454
|
+
const navigate = vi.fn();
|
|
455
|
+
engine.context.defineProperty('router', {
|
|
456
|
+
value: {
|
|
457
|
+
navigate,
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
engine.context.defineProperty('route', {
|
|
461
|
+
value: {},
|
|
462
|
+
});
|
|
463
|
+
engine.context.defineProperty('routeRepository', {
|
|
464
|
+
value: {
|
|
465
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
466
|
+
},
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const popupViewItem = {
|
|
470
|
+
params: { viewUid: 'popup', filterByTk: '1' },
|
|
471
|
+
modelUid: 'popup',
|
|
472
|
+
model: { uid: 'popup', dispatchEvent: vi.fn() } as any,
|
|
473
|
+
hidden: { value: false },
|
|
474
|
+
index: 1,
|
|
475
|
+
};
|
|
476
|
+
mockResolveViewParamsToViewList.mockImplementation((_engine, viewParams, routeModel) => [
|
|
477
|
+
{
|
|
478
|
+
params: viewParams[0],
|
|
479
|
+
modelUid: routeModel.uid,
|
|
480
|
+
model: routeModel,
|
|
481
|
+
hidden: { value: false },
|
|
482
|
+
index: 0,
|
|
483
|
+
},
|
|
484
|
+
popupViewItem,
|
|
485
|
+
]);
|
|
486
|
+
mockGetViewDiffAndUpdateHidden.mockReturnValue({
|
|
487
|
+
viewsToClose: [],
|
|
488
|
+
viewsToOpen: [],
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
492
|
+
coordinator.registerPage('test-route', {
|
|
493
|
+
active: false,
|
|
494
|
+
layoutContentElement: document.createElement('div'),
|
|
495
|
+
});
|
|
496
|
+
coordinator.syncRoute({
|
|
497
|
+
pageUid: 'test-route',
|
|
498
|
+
pathname: '/admin/test-route/view/popup/filterbytk/1',
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
expect(navigate).toHaveBeenCalledWith('/admin/test-route', { replace: true });
|
|
502
|
+
expect(mockGetViewDiffAndUpdateHidden).not.toHaveBeenCalled();
|
|
503
|
+
|
|
504
|
+
await Promise.resolve();
|
|
505
|
+
|
|
506
|
+
expect(mockResolveViewParamsToViewList).toHaveBeenCalledTimes(2);
|
|
507
|
+
expect(mockGetViewDiffAndUpdateHidden).toHaveBeenCalledTimes(1);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('does not replay global route when a route page registers', () => {
|
|
511
|
+
const engine = new FlowEngine();
|
|
512
|
+
engine.registerModels({ RouteModel });
|
|
513
|
+
engine.context.defineProperty('route', {
|
|
514
|
+
value: {
|
|
515
|
+
params: { name: 'test-route' },
|
|
516
|
+
pathname: '/embed/test-route/view/popup',
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
engine.context.defineProperty('routeRepository', {
|
|
520
|
+
value: {
|
|
521
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
mockResolveViewParamsToViewList.mockReturnValue([]);
|
|
526
|
+
mockGetViewDiffAndUpdateHidden.mockReturnValue({
|
|
527
|
+
viewsToClose: [],
|
|
528
|
+
viewsToOpen: [],
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/embed' });
|
|
532
|
+
coordinator.registerPage('test-route', {
|
|
533
|
+
active: false,
|
|
534
|
+
layoutContentElement: document.createElement('div'),
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
expect(mockResolveViewParamsToViewList).not.toHaveBeenCalled();
|
|
538
|
+
expect(engine.getModel('test-route')?.context.pageActive.value).toBe(false);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('parses view stack with nested basePath', () => {
|
|
542
|
+
expect(
|
|
543
|
+
toViewStack('/admin/settings/public-forms/form-1/view/popup', {
|
|
544
|
+
basePathname: '/admin/settings/public-forms',
|
|
545
|
+
}),
|
|
546
|
+
).toEqual([{ viewUid: 'form-1' }, { viewUid: 'popup' }]);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('parses view stack for empty nested routePath from runtime basePathname', () => {
|
|
550
|
+
const engine = new FlowEngine();
|
|
551
|
+
engine.registerModels({ RouteModel });
|
|
552
|
+
engine.context.defineProperty('route', {
|
|
553
|
+
value: {
|
|
554
|
+
params: { name: 'form-1' },
|
|
555
|
+
pathname: '/admin/settings/public-forms/form-1/view/popup/filterbytk/member',
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
engine.context.defineProperty('routeRepository', {
|
|
559
|
+
value: {
|
|
560
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
mockResolveViewParamsToViewList.mockReturnValue([]);
|
|
565
|
+
mockGetViewDiffAndUpdateHidden.mockReturnValue({
|
|
566
|
+
viewsToClose: [],
|
|
567
|
+
viewsToOpen: [],
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, {
|
|
571
|
+
layout: {
|
|
572
|
+
routeName: 'admin.settings.publicForms.layout',
|
|
573
|
+
routePath: '',
|
|
574
|
+
rootRouteName: 'admin',
|
|
575
|
+
uid: 'public-form-layout-model',
|
|
576
|
+
layoutModelClass: 'PublicFormLayoutModel',
|
|
577
|
+
rootPageModelClass: 'RootPageModel',
|
|
578
|
+
childPageModelClass: 'ChildPageModel',
|
|
579
|
+
authCheck: true,
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
coordinator.registerPage('form-1', {
|
|
583
|
+
active: true,
|
|
584
|
+
layoutContentElement: document.createElement('div'),
|
|
585
|
+
});
|
|
586
|
+
coordinator.syncRoute({
|
|
587
|
+
layoutRouteName: 'admin.settings.publicForms.layout',
|
|
588
|
+
params: { name: 'form-1' },
|
|
589
|
+
pathname: '/admin/settings/public-forms/form-1/view/popup/filterbytk/member',
|
|
590
|
+
layoutBasePathname: '/admin/settings/public-forms',
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
expect(mockResolveViewParamsToViewList).toHaveBeenCalledWith(
|
|
594
|
+
engine,
|
|
595
|
+
[{ viewUid: 'form-1' }, { viewUid: 'popup', filterByTk: 'member' }],
|
|
596
|
+
expect.anything(),
|
|
597
|
+
);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it('does not parse relative layout routePath without runtime basePathname', () => {
|
|
601
|
+
expect(
|
|
602
|
+
toViewStack('/admin/settings/public-forms/form-1/view/popup', {
|
|
603
|
+
layout: {
|
|
604
|
+
routeName: 'admin.settings.publicForms',
|
|
605
|
+
routePath: 'public-forms',
|
|
606
|
+
rootRouteName: 'admin',
|
|
607
|
+
uid: 'public-form-layout-model',
|
|
608
|
+
layoutModelClass: 'PublicFormLayoutModel',
|
|
609
|
+
rootPageModelClass: 'RootPageModel',
|
|
610
|
+
childPageModelClass: 'ChildPageModel',
|
|
611
|
+
authCheck: true,
|
|
612
|
+
},
|
|
613
|
+
}),
|
|
614
|
+
).toEqual([]);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it('exposes current layout on route model context', () => {
|
|
618
|
+
const engine = new FlowEngine();
|
|
619
|
+
engine.registerModels({ RouteModel });
|
|
620
|
+
engine.context.defineProperty('route', {
|
|
621
|
+
value: {
|
|
622
|
+
params: { name: 'test-route' },
|
|
623
|
+
pathname: '/embed/test-route',
|
|
624
|
+
},
|
|
625
|
+
});
|
|
626
|
+
engine.context.defineProperty('routeRepository', {
|
|
627
|
+
value: {
|
|
628
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
mockResolveViewParamsToViewList.mockReturnValue([]);
|
|
633
|
+
mockGetViewDiffAndUpdateHidden.mockReturnValue({
|
|
634
|
+
viewsToClose: [],
|
|
635
|
+
viewsToOpen: [],
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
const layout = {
|
|
639
|
+
routeName: 'embed',
|
|
640
|
+
routePath: '/embed',
|
|
641
|
+
rootRouteName: 'embed',
|
|
642
|
+
uid: 'embed-layout-model',
|
|
643
|
+
layoutModelClass: 'EmbedLayoutModelV2',
|
|
644
|
+
rootPageModelClass: 'RootPageModel',
|
|
645
|
+
childPageModelClass: 'ChildPageModel',
|
|
646
|
+
authCheck: true,
|
|
647
|
+
};
|
|
648
|
+
const layoutContext = new FlowContext();
|
|
649
|
+
layoutContext.defineProperty('layoutMarker', { value: 'layout-context' });
|
|
650
|
+
|
|
651
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { layout, layoutContext });
|
|
652
|
+
coordinator.registerPage('test-route', {
|
|
653
|
+
active: true,
|
|
654
|
+
layoutContentElement: document.createElement('div'),
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
expect(engine.getModel('test-route')?.context.layout).toBe(layout);
|
|
658
|
+
expect(engine.getModel('test-route')?.context.layoutContext).toBe(layoutContext);
|
|
659
|
+
expect(engine.getModel('test-route')?.context.layoutMarker).toBe('layout-context');
|
|
660
|
+
});
|
|
117
661
|
});
|
|
@@ -279,9 +279,9 @@ const MobileActions: FC = () => {
|
|
|
279
279
|
* @param props
|
|
280
280
|
* @returns
|
|
281
281
|
*/
|
|
282
|
-
function SetIsMobileLayout(props: { isMobile: boolean; children: any }) {
|
|
282
|
+
function SetIsMobileLayout(props: { isMobile: boolean; children: any; model?: AdminLayoutModel }) {
|
|
283
283
|
const flowEngine = useFlowEngine();
|
|
284
|
-
const adminLayoutModel = flowEngine.getModel<AdminLayoutModel>(ADMIN_LAYOUT_MODEL_UID);
|
|
284
|
+
const adminLayoutModel = props.model || flowEngine.getModel<AdminLayoutModel>(ADMIN_LAYOUT_MODEL_UID);
|
|
285
285
|
|
|
286
286
|
useEffect(() => {
|
|
287
287
|
adminLayoutModel?.setIsMobileLayout(props.isMobile);
|
|
@@ -367,7 +367,7 @@ const renderMenuNodeWithModel = (
|
|
|
367
367
|
|
|
368
368
|
export const AdminLayoutComponent = observer((props: any) => {
|
|
369
369
|
const flowEngine = useFlowEngine();
|
|
370
|
-
const adminLayoutModel = flowEngine.getModel<AdminLayoutModel>(ADMIN_LAYOUT_MODEL_UID);
|
|
370
|
+
const adminLayoutModel = props.model || flowEngine.getModel<AdminLayoutModel>(ADMIN_LAYOUT_MODEL_UID);
|
|
371
371
|
const [allAccessRoutes, setAllAccessRoutes] = useState<NocoBaseDesktopRoute[]>(
|
|
372
372
|
() => flowEngine.context.routeRepository?.listAccessible?.() || [],
|
|
373
373
|
);
|
|
@@ -666,7 +666,7 @@ export const AdminLayoutComponent = observer((props: any) => {
|
|
|
666
666
|
const { isMobile } = value;
|
|
667
667
|
|
|
668
668
|
return (
|
|
669
|
-
<SetIsMobileLayout isMobile={isMobile}>
|
|
669
|
+
<SetIsMobileLayout isMobile={isMobile} model={adminLayoutModel}>
|
|
670
670
|
<ConfigProvider theme={isMobile ? mobileTheme : theme}>
|
|
671
671
|
<GlobalStyle />
|
|
672
672
|
<AdminLayoutContent onContentElementChange={handleLayoutContentElementChange} />
|