@nocobase/client-v2 2.2.0-beta.9 → 2.3.0-alpha.1
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/APIClient.d.ts +1 -3
- package/es/BaseApplication.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
- package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
- package/es/components/category-tabs/index.d.ts +9 -0
- package/es/components/form/ScanInput/useCodeScanner.d.ts +2 -1
- package/es/components/form/TypedVariableInput.d.ts +31 -5
- package/es/components/form/filter/CollectionFilter.d.ts +2 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
- package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
- package/es/components/form/filter/index.d.ts +2 -0
- package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
- package/es/components/index.d.ts +1 -0
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/actions/linkageRules.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
- package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
- package/es/flow/components/RunJSValueEditor.d.ts +1 -0
- package/es/flow/components/filter/FilterGroup.d.ts +1 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
- package/es/flow/index.d.ts +1 -0
- package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
- package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
- package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
- package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
- package/es/flow-compat/routeTypes.d.ts +1 -0
- package/es/index.d.ts +4 -0
- package/es/index.mjs +153 -118
- package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
- package/es/utils/index.d.ts +1 -0
- package/lib/index.js +162 -127
- package/lib/locale/languageCodes.js +2 -1
- package/package.json +8 -7
- package/src/APIClient.ts +1 -10
- package/src/Application.tsx +4 -0
- package/src/BaseApplication.tsx +11 -6
- package/src/__tests__/app.test.tsx +73 -0
- package/src/__tests__/browserChecker.test.ts +61 -0
- package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +28 -0
- package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
- package/src/collection-manager/field-validation.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/JsonTextArea.tsx +4 -0
- package/src/components/form/ScanInput/CodeScanner.tsx +23 -6
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +180 -3
- package/src/components/form/ScanInput/useCodeScanner.ts +170 -5
- package/src/components/form/TypedVariableInput.tsx +452 -101
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
- package/src/components/form/filter/CollectionFilter.tsx +4 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
- package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
- package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
- package/src/components/form/filter/index.ts +2 -0
- package/src/components/form/filter/useFilterActionProps.ts +37 -6
- package/src/components/index.ts +1 -0
- package/src/flow/FlowPage.tsx +9 -1
- package/src/flow/__tests__/FlowPage.test.tsx +50 -3
- package/src/flow/__tests__/FlowRoute.test.tsx +2 -2
- package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
- package/src/flow/actions/index.ts +2 -0
- package/src/flow/actions/linkageRules.tsx +86 -11
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +0 -1
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
- package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
- package/src/flow/components/FieldAssignValueInput.tsx +91 -20
- package/src/flow/components/FlowRoute.tsx +7 -3
- package/src/flow/components/RunJSValueEditor.tsx +9 -1
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
- package/src/flow/components/filter/FilterGroup.tsx +33 -5
- package/src/flow/components/filter/VariableFilterItem.tsx +158 -10
- package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +70 -1
- package/src/flow/index.ts +1 -0
- package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
- package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
- package/src/flow/models/base/PageModel/PageTabModel.tsx +184 -3
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -10
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +572 -1
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +1 -0
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +34 -0
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +2 -1
- package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
- package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
- package/src/flow-compat/fieldValidationConstants.ts +1 -1
- package/src/flow-compat/routeTypes.ts +1 -0
- package/src/index.ts +4 -0
- package/src/locale/languageCodes.ts +2 -1
- package/src/nocobase-buildin-plugin/index.tsx +12 -0
- package/src/utils/getRouteRuntimeVersion.ts +185 -0
- package/src/utils/index.tsx +1 -0
|
@@ -35,12 +35,8 @@ export const linkageRulesRefresh = defineAction({
|
|
|
35
35
|
// Prefer running on the current model; fallback to blockModel when the current model doesn't own the flow.
|
|
36
36
|
if (!hasFlow) return;
|
|
37
37
|
|
|
38
|
-
//
|
|
38
|
+
// Skip master when unmounted forks can handle the same flow.
|
|
39
39
|
// Otherwise master is likely the rendered model and still needs refresh.
|
|
40
|
-
// In design mode, always refresh master so the currently edited model state stays in sync.
|
|
41
|
-
const flowSettingsEnabled = Boolean(
|
|
42
|
-
(ctx as any)?.flowSettingsEnabled || (model as any)?.context?.flowSettingsEnabled,
|
|
43
|
-
);
|
|
44
40
|
const hasForkWithFlow =
|
|
45
41
|
!model?.isFork &&
|
|
46
42
|
!!model?.forks?.size &&
|
|
@@ -49,7 +45,7 @@ export const linkageRulesRefresh = defineAction({
|
|
|
49
45
|
return !!fork?.getFlow?.(flowKey);
|
|
50
46
|
});
|
|
51
47
|
const isMasterMounted = Boolean((model as any)?.context?.ref?.current);
|
|
52
|
-
if (hasForkWithFlow && !isMasterMounted
|
|
48
|
+
if (hasForkWithFlow && !isMasterMounted) {
|
|
53
49
|
return;
|
|
54
50
|
}
|
|
55
51
|
|
|
@@ -712,7 +712,6 @@ export const AdminLayoutComponent = observer((props: any) => {
|
|
|
712
712
|
<MobileMenuControlContext.Provider value={{ closeMobileMenu }}>
|
|
713
713
|
<DndProvider collisionDetection={menuCollisionDetection} onDragEnd={handleMenuDragEnd}>
|
|
714
714
|
<ProLayout
|
|
715
|
-
key={`admin-layout-menu-${menuRouteRefreshVersion}`}
|
|
716
715
|
{...props}
|
|
717
716
|
contentStyle={contentStyle}
|
|
718
717
|
siderWidth={customToken.siderWidth || 200}
|
|
@@ -11,23 +11,58 @@ import { FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
|
|
|
11
11
|
import { act, render, screen, waitFor } from '@testing-library/react';
|
|
12
12
|
import React, { useEffect } from 'react';
|
|
13
13
|
import { createMemoryRouter, RouterProvider, useParams } from 'react-router-dom';
|
|
14
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
14
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
15
|
+
import { NocoBaseDesktopRouteType, type NocoBaseDesktopRoute } from '../../../../flow-compat';
|
|
15
16
|
import type { LayoutDefinition } from '../../../../layout-manager/types';
|
|
16
17
|
import { AdminLayoutComponent } from '../AdminLayoutComponent';
|
|
17
18
|
import { AdminLayoutModel } from '../AdminLayoutModel';
|
|
19
|
+
import { AdminLayoutMenuItemModel } from '../AdminLayoutMenuModels';
|
|
18
20
|
import { AdminLayoutContent } from '../AdminLayoutSlotModels';
|
|
19
21
|
|
|
22
|
+
const proLayoutLifecycle = vi.hoisted(() => ({
|
|
23
|
+
events: [] as string[],
|
|
24
|
+
routes: [] as Array<{ children?: Array<{ name?: React.ReactNode }> }>,
|
|
25
|
+
}));
|
|
26
|
+
|
|
20
27
|
vi.mock('@ant-design/pro-layout', async () => {
|
|
21
28
|
const ReactModule = await import('react');
|
|
22
29
|
const RouteContext = ReactModule.createContext({ isMobile: false });
|
|
30
|
+
const ProLayoutMock = (props: {
|
|
31
|
+
children?: React.ReactNode;
|
|
32
|
+
route?: { children?: Array<{ name?: React.ReactNode }> };
|
|
33
|
+
}) => {
|
|
34
|
+
ReactModule.useEffect(() => {
|
|
35
|
+
proLayoutLifecycle.events.push('pro-layout:mount');
|
|
36
|
+
return () => {
|
|
37
|
+
proLayoutLifecycle.events.push('pro-layout:unmount');
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
proLayoutLifecycle.routes.push(props.route || {});
|
|
23
41
|
|
|
24
|
-
|
|
25
|
-
|
|
42
|
+
return ReactModule.createElement(
|
|
43
|
+
RouteContext.Provider,
|
|
44
|
+
{ value: { isMobile: false } },
|
|
26
45
|
ReactModule.createElement(
|
|
27
|
-
|
|
28
|
-
{
|
|
29
|
-
ReactModule.createElement(
|
|
46
|
+
'div',
|
|
47
|
+
{ 'data-testid': 'pro-layout' },
|
|
48
|
+
ReactModule.createElement(
|
|
49
|
+
'nav',
|
|
50
|
+
{ 'data-testid': 'pro-layout-menu' },
|
|
51
|
+
(props.route?.children || []).map((item) =>
|
|
52
|
+
ReactModule.createElement(
|
|
53
|
+
'span',
|
|
54
|
+
{ key: String(item.name), 'data-testid': 'pro-layout-menu-item' },
|
|
55
|
+
item.name,
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
props.children,
|
|
30
60
|
),
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
default: ProLayoutMock,
|
|
31
66
|
RouteContext,
|
|
32
67
|
};
|
|
33
68
|
});
|
|
@@ -53,6 +88,10 @@ vi.mock('../AppListRender', () => ({
|
|
|
53
88
|
useAppListRender: () => undefined,
|
|
54
89
|
}));
|
|
55
90
|
|
|
91
|
+
afterEach(() => {
|
|
92
|
+
delete (window as Window & { __nocobase_modern_client_prefix__?: string }).__nocobase_modern_client_prefix__;
|
|
93
|
+
});
|
|
94
|
+
|
|
56
95
|
describe('AdminLayoutComponent', () => {
|
|
57
96
|
it('keeps custom admin layout pages alive by layout route name', async () => {
|
|
58
97
|
const layout: LayoutDefinition = {
|
|
@@ -185,4 +224,212 @@ describe('AdminLayoutComponent', () => {
|
|
|
185
224
|
|
|
186
225
|
expect(deactivateLayout).toHaveBeenCalledTimes(1);
|
|
187
226
|
});
|
|
227
|
+
|
|
228
|
+
it('keeps layout content mounted when the menu route tree refreshes', async () => {
|
|
229
|
+
proLayoutLifecycle.events = [];
|
|
230
|
+
proLayoutLifecycle.routes = [];
|
|
231
|
+
const layout: LayoutDefinition = {
|
|
232
|
+
routeName: 'admin',
|
|
233
|
+
routePath: '/admin',
|
|
234
|
+
rootRouteName: 'admin',
|
|
235
|
+
uid: 'admin-layout-model',
|
|
236
|
+
layoutModelClass: 'AdminLayoutModel',
|
|
237
|
+
rootPageModelClass: 'RootPageModel',
|
|
238
|
+
childPageModelClass: 'ChildPageModel',
|
|
239
|
+
authCheck: true,
|
|
240
|
+
};
|
|
241
|
+
const events: string[] = [];
|
|
242
|
+
const engine = new FlowEngine();
|
|
243
|
+
engine.context.defineProperty('routeRepository', {
|
|
244
|
+
value: {
|
|
245
|
+
activateLayout: vi.fn(() => vi.fn()),
|
|
246
|
+
ensureAccessibleLoaded: vi.fn(async () => []),
|
|
247
|
+
listAccessible: () => [],
|
|
248
|
+
subscribe: vi.fn(),
|
|
249
|
+
unsubscribe: vi.fn(),
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
engine.context.defineProperty('t', {
|
|
253
|
+
value: (key: string) => key,
|
|
254
|
+
});
|
|
255
|
+
const model = engine.createModel<AdminLayoutModel>({
|
|
256
|
+
uid: layout.uid,
|
|
257
|
+
use: AdminLayoutModel,
|
|
258
|
+
props: {
|
|
259
|
+
layout,
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const Page = () => {
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
events.push('page:mount');
|
|
266
|
+
return () => {
|
|
267
|
+
events.push('page:unmount');
|
|
268
|
+
};
|
|
269
|
+
}, []);
|
|
270
|
+
|
|
271
|
+
return <div data-testid="custom-admin-route">Workflow tasks</div>;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const router = createMemoryRouter(
|
|
275
|
+
[
|
|
276
|
+
{
|
|
277
|
+
id: layout.routeName,
|
|
278
|
+
path: layout.routePath,
|
|
279
|
+
element: <AdminLayoutComponent model={model} />,
|
|
280
|
+
children: [
|
|
281
|
+
{
|
|
282
|
+
id: 'admin.workflow.tasks',
|
|
283
|
+
path: 'workflow/tasks/:taskType?/:status?/:popupId?',
|
|
284
|
+
element: <Page />,
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
{
|
|
290
|
+
initialEntries: ['/admin/workflow/tasks/approval-apply/pending'],
|
|
291
|
+
},
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
render(
|
|
295
|
+
<FlowEngineProvider engine={engine}>
|
|
296
|
+
<RouterProvider router={router} />
|
|
297
|
+
</FlowEngineProvider>,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
expect(await screen.findByTestId('custom-admin-route')).toBeInTheDocument();
|
|
301
|
+
expect(proLayoutLifecycle.events).toEqual(['pro-layout:mount']);
|
|
302
|
+
|
|
303
|
+
act(() => {
|
|
304
|
+
model.refreshMenuRouteTree();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
await act(async () => {
|
|
308
|
+
await router.navigate('/admin/workflow/tasks/approval-apply/pending/1');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
expect(screen.getByTestId('custom-admin-route')).toBeInTheDocument();
|
|
312
|
+
expect(proLayoutLifecycle.events).toEqual(['pro-layout:mount']);
|
|
313
|
+
expect(events).toEqual(['page:mount']);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('updates ProLayout menu routes after accessible routes change without remounting the layout', async () => {
|
|
317
|
+
proLayoutLifecycle.events = [];
|
|
318
|
+
proLayoutLifecycle.routes = [];
|
|
319
|
+
(window as Window & { __nocobase_modern_client_prefix__?: string }).__nocobase_modern_client_prefix__ = 'v2';
|
|
320
|
+
const layout: LayoutDefinition = {
|
|
321
|
+
routeName: 'admin',
|
|
322
|
+
routePath: '/admin',
|
|
323
|
+
rootRouteName: 'admin',
|
|
324
|
+
uid: 'admin-layout-model',
|
|
325
|
+
layoutModelClass: 'AdminLayoutModel',
|
|
326
|
+
rootPageModelClass: 'RootPageModel',
|
|
327
|
+
childPageModelClass: 'ChildPageModel',
|
|
328
|
+
authCheck: true,
|
|
329
|
+
};
|
|
330
|
+
let accessibleRoutes: NocoBaseDesktopRoute[] = [
|
|
331
|
+
{
|
|
332
|
+
id: 1,
|
|
333
|
+
title: 'Visible page',
|
|
334
|
+
schemaUid: 'visible-page',
|
|
335
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
336
|
+
},
|
|
337
|
+
];
|
|
338
|
+
let routeRepositorySubscriber: (() => void) | undefined;
|
|
339
|
+
const events: string[] = [];
|
|
340
|
+
const engine = new FlowEngine();
|
|
341
|
+
engine.registerModels({
|
|
342
|
+
AdminLayoutMenuItemModel,
|
|
343
|
+
});
|
|
344
|
+
engine.context.defineProperty('routeRepository', {
|
|
345
|
+
value: {
|
|
346
|
+
activateLayout: vi.fn(() => vi.fn()),
|
|
347
|
+
ensureAccessibleLoaded: vi.fn(async () => accessibleRoutes),
|
|
348
|
+
listAccessible: () => accessibleRoutes,
|
|
349
|
+
subscribe: vi.fn((subscriber: () => void) => {
|
|
350
|
+
routeRepositorySubscriber = subscriber;
|
|
351
|
+
}),
|
|
352
|
+
unsubscribe: vi.fn(),
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
engine.context.defineProperty('app', {
|
|
356
|
+
value: {
|
|
357
|
+
getPublicPath: () => '/v/',
|
|
358
|
+
router: {
|
|
359
|
+
getBasename: () => '/v',
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
engine.context.defineProperty('t', {
|
|
364
|
+
value: (key: string) => key,
|
|
365
|
+
});
|
|
366
|
+
const model = engine.createModel<AdminLayoutModel>({
|
|
367
|
+
uid: layout.uid,
|
|
368
|
+
use: AdminLayoutModel,
|
|
369
|
+
props: {
|
|
370
|
+
layout,
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const Page = () => {
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
events.push('page:mount');
|
|
377
|
+
return () => {
|
|
378
|
+
events.push('page:unmount');
|
|
379
|
+
};
|
|
380
|
+
}, []);
|
|
381
|
+
|
|
382
|
+
return <div data-testid="custom-admin-route">Workflow tasks</div>;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const router = createMemoryRouter(
|
|
386
|
+
[
|
|
387
|
+
{
|
|
388
|
+
id: layout.routeName,
|
|
389
|
+
path: layout.routePath,
|
|
390
|
+
element: <AdminLayoutComponent model={model} />,
|
|
391
|
+
children: [
|
|
392
|
+
{
|
|
393
|
+
id: 'admin.workflow.tasks',
|
|
394
|
+
path: 'workflow/tasks/:taskType?/:status?/:popupId?',
|
|
395
|
+
element: <Page />,
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
{
|
|
401
|
+
initialEntries: ['/admin/workflow/tasks/approval-apply/pending'],
|
|
402
|
+
},
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
render(
|
|
406
|
+
<FlowEngineProvider engine={engine}>
|
|
407
|
+
<RouterProvider router={router} />
|
|
408
|
+
</FlowEngineProvider>,
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
expect(await screen.findByText('Visible page')).toBeInTheDocument();
|
|
412
|
+
expect(proLayoutLifecycle.events).toEqual(['pro-layout:mount']);
|
|
413
|
+
|
|
414
|
+
act(() => {
|
|
415
|
+
accessibleRoutes = [];
|
|
416
|
+
routeRepositorySubscriber?.();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
await waitFor(() => {
|
|
420
|
+
expect(screen.queryByText('Visible page')).not.toBeInTheDocument();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
await act(async () => {
|
|
424
|
+
await router.navigate('/admin/workflow/tasks/approval-apply/pending/1');
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
expect(screen.getByTestId('custom-admin-route')).toBeInTheDocument();
|
|
428
|
+
expect(proLayoutLifecycle.events).toEqual(['pro-layout:mount']);
|
|
429
|
+
expect(events).toEqual(['page:mount']);
|
|
430
|
+
expect(
|
|
431
|
+
proLayoutLifecycle.routes.some((route) => route.children?.some((item) => item.name === 'Visible page')),
|
|
432
|
+
).toBe(true);
|
|
433
|
+
expect(proLayoutLifecycle.routes.at(-1)?.children?.some((item) => item.name === 'Visible page')).toBe(false);
|
|
434
|
+
});
|
|
188
435
|
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import React, { useMemo, useState } from 'react';
|
|
10
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
11
11
|
import { DatePicker, Select, Space } from 'antd';
|
|
12
12
|
import { inferPickerType } from '../../flow-compat';
|
|
13
13
|
import { dayjs, getDateTimeFormat, getPickerFormat } from '@nocobase/utils/client';
|
|
@@ -29,6 +29,7 @@ export interface FieldAssignExactDatePickerProps {
|
|
|
29
29
|
isRange?: boolean;
|
|
30
30
|
onChange?: (value: ExactDatePickerValue) => void;
|
|
31
31
|
style?: React.CSSProperties;
|
|
32
|
+
disabled?: boolean;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
function getRawSingleValue(value: unknown, isRange: boolean): unknown {
|
|
@@ -114,7 +115,7 @@ function parseRangeValue(value: unknown, format: string): [dayjs.Dayjs, dayjs.Da
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
export const FieldAssignExactDatePicker: React.FC<FieldAssignExactDatePickerProps> = (props) => {
|
|
117
|
-
const { picker = 'date', format, showTime, timeFormat, value, isRange = false, onChange, style } = props;
|
|
118
|
+
const { picker = 'date', format, showTime, timeFormat, value, isRange = false, onChange, style, disabled } = props;
|
|
118
119
|
const flowCtx = useFlowContext();
|
|
119
120
|
const t = flowCtx.model.translate.bind(flowCtx.model);
|
|
120
121
|
|
|
@@ -124,17 +125,17 @@ export const FieldAssignExactDatePicker: React.FC<FieldAssignExactDatePickerProp
|
|
|
124
125
|
return inferPickerFromRawValue(rawSingleValue, picker);
|
|
125
126
|
});
|
|
126
127
|
|
|
127
|
-
const getResolvedFormat = (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
() => getResolvedFormat(targetPicker),
|
|
135
|
-
[targetPicker, format, picker, showTime, timeFormat],
|
|
128
|
+
const getResolvedFormat = useCallback(
|
|
129
|
+
(nextPicker: ExactDatePickerMode) => {
|
|
130
|
+
const baseFormat = nextPicker === picker && format ? format : getPickerFormat(nextPicker);
|
|
131
|
+
const dateFormat = nextPicker === 'date' ? stripTimeFromFormat(baseFormat) : baseFormat;
|
|
132
|
+
return getDateTimeFormat(nextPicker, dateFormat, showTime, timeFormat);
|
|
133
|
+
},
|
|
134
|
+
[format, picker, showTime, timeFormat],
|
|
136
135
|
);
|
|
137
136
|
|
|
137
|
+
const resolvedFormat = useMemo(() => getResolvedFormat(targetPicker), [getResolvedFormat, targetPicker]);
|
|
138
|
+
|
|
138
139
|
const singleValue = useMemo(() => {
|
|
139
140
|
if (isRange) return null;
|
|
140
141
|
return parseDateFromRawValue(value, resolvedFormat);
|
|
@@ -156,6 +157,10 @@ export const FieldAssignExactDatePicker: React.FC<FieldAssignExactDatePickerProp
|
|
|
156
157
|
);
|
|
157
158
|
|
|
158
159
|
const handlePickerTypeChange = (nextPicker: ExactDatePickerMode) => {
|
|
160
|
+
if (disabled) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
159
164
|
setTargetPicker(nextPicker);
|
|
160
165
|
|
|
161
166
|
if (!onChange) {
|
|
@@ -182,7 +187,11 @@ export const FieldAssignExactDatePicker: React.FC<FieldAssignExactDatePickerProp
|
|
|
182
187
|
inputReadOnly: flowCtx.isMobileLayout,
|
|
183
188
|
showTime: showTime ? { defaultValue: dayjs('00:00:00', 'HH:mm:ss') } : false,
|
|
184
189
|
value: singleValue,
|
|
190
|
+
disabled,
|
|
185
191
|
onChange: (nextDate: dayjs.Dayjs | null) => {
|
|
192
|
+
if (disabled) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
186
195
|
if (nextDate && dayjs.isDayjs(nextDate)) {
|
|
187
196
|
onChange?.(nextDate);
|
|
188
197
|
return;
|
|
@@ -200,7 +209,11 @@ export const FieldAssignExactDatePicker: React.FC<FieldAssignExactDatePickerProp
|
|
|
200
209
|
inputReadOnly: flowCtx.isMobileLayout,
|
|
201
210
|
showTime: showTime ? { defaultValue: [dayjs('00:00:00', 'HH:mm:ss'), dayjs('23:59:59', 'HH:mm:ss')] } : false,
|
|
202
211
|
value: rangeValue,
|
|
212
|
+
disabled,
|
|
203
213
|
onChange: (nextDates: [dayjs.Dayjs | null, dayjs.Dayjs | null] | null) => {
|
|
214
|
+
if (disabled) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
204
217
|
if (Array.isArray(nextDates) && nextDates[0] && nextDates[1]) {
|
|
205
218
|
onChange?.([nextDates[0], nextDates[1]]);
|
|
206
219
|
return;
|
|
@@ -218,6 +231,7 @@ export const FieldAssignExactDatePicker: React.FC<FieldAssignExactDatePickerProp
|
|
|
218
231
|
value={targetPicker}
|
|
219
232
|
options={pickerOptions}
|
|
220
233
|
onChange={handlePickerTypeChange}
|
|
234
|
+
disabled={disabled}
|
|
221
235
|
/>
|
|
222
236
|
{isRange ? <DatePicker.RangePicker {...rangePickerProps} /> : <DatePicker {...singlePickerProps} />}
|
|
223
237
|
</Space.Compact>
|