@nocobase/client-v2 2.2.0-beta.7 → 2.2.0-beta.9
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/BaseApplication.d.ts +2 -0
- package/es/RouteRepository.d.ts +8 -0
- package/es/RouterManager.d.ts +15 -0
- package/es/authRedirect.d.ts +1 -0
- package/es/entry-actions/EntryActionManager.d.ts +24 -0
- package/es/entry-actions/index.d.ts +9 -0
- package/es/flow/actions/afterSuccess.d.ts +8 -1
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
- package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
- package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
- package/es/flow/models/base/ActionModelCore.d.ts +2 -0
- package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +9 -0
- package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
- 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/flow/routeTransientInputArgs.d.ts +14 -0
- package/es/index.d.ts +2 -0
- package/es/index.mjs +243 -141
- package/es/utils/markdownSanitize.d.ts +11 -0
- package/lib/index.js +232 -130
- package/package.json +7 -7
- package/src/BaseApplication.tsx +2 -0
- package/src/RouteRepository.ts +25 -0
- package/src/RouterManager.tsx +142 -1
- package/src/__tests__/RouteRepository.test.ts +23 -0
- package/src/__tests__/RouterManager.test.ts +125 -0
- package/src/__tests__/authRedirect.test.ts +17 -0
- package/src/authRedirect.ts +38 -0
- 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/entry-actions/EntryActionManager.ts +76 -0
- package/src/entry-actions/index.ts +10 -0
- package/src/flow/FlowPage.tsx +29 -2
- package/src/flow/__tests__/FlowPage.test.tsx +152 -25
- package/src/flow/__tests__/FlowRoute.test.tsx +547 -2
- 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__/dataScopeFilter.test.ts +62 -0
- package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +164 -0
- package/src/flow/actions/afterSuccess.tsx +142 -3
- package/src/flow/actions/dataScopeFilter.ts +10 -1
- package/src/flow/actions/dateTimeFormat.tsx +2 -2
- package/src/flow/actions/index.ts +1 -1
- package/src/flow/actions/openView.tsx +59 -5
- package/src/flow/admin-shell/BaseLayoutModel.tsx +149 -3
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +187 -42
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1020 -110
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +42 -4
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +177 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +20 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +5 -4
- package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
- package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +518 -2
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +48 -0
- package/src/flow/admin-shell/admin-layout/index.ts +1 -0
- package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
- package/src/flow/common/Markdown/Display.tsx +14 -3
- package/src/flow/common/Markdown/Edit.tsx +19 -7
- package/src/flow/components/FlowRoute.tsx +128 -16
- package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
- package/src/flow/internal/components/Markdown/util.ts +2 -1
- package/src/flow/models/base/ActionModel.tsx +10 -8
- package/src/flow/models/base/ActionModelCore.tsx +11 -4
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
- 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/__tests__/submitHandler.test.ts +54 -1
- package/src/flow/models/blocks/form/submitHandler.ts +17 -3
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +87 -0
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +91 -0
- package/src/flow/models/blocks/js-block/JSBlock.tsx +223 -2
- package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +35 -12
- package/src/flow/models/blocks/table/TableBlockModel.tsx +25 -14
- package/src/flow/models/blocks/table/TableColumnModel.tsx +6 -2
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +57 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +51 -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/AssociationFieldModel/RecordSelectFieldModel.tsx +45 -13
- package/src/flow/models/fields/TextareaFieldModel.tsx +42 -19
- package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +32 -1
- package/src/flow/models/topbar/TopbarActionModel.tsx +78 -3
- package/src/flow/resolveViewParamsToViewList.tsx +11 -3
- package/src/flow/routeTransientInputArgs.ts +27 -0
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +42 -0
- package/src/index.ts +2 -0
- package/src/nocobase-buildin-plugin/index.tsx +7 -1
- package/src/settings-center/SystemSettingsPage.tsx +1 -1
- package/src/utils/markdownSanitize.ts +88 -0
|
@@ -8,12 +8,19 @@
|
|
|
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,
|
|
15
21
|
type RoutePageMeta,
|
|
16
22
|
} from './BaseLayoutRouteCoordinator';
|
|
23
|
+
import { NocoBaseDesktopRouteType } from '../../flow-compat';
|
|
17
24
|
import type { LayoutDefinition } from '../../layout-manager/types';
|
|
18
25
|
import { isLayoutContentRouteName } from '../../layout-manager/utils';
|
|
19
26
|
|
|
@@ -66,6 +73,7 @@ export interface LayoutRouteLike {
|
|
|
66
73
|
id?: string;
|
|
67
74
|
name?: string;
|
|
68
75
|
pathname?: string;
|
|
76
|
+
state?: unknown;
|
|
69
77
|
params?: Record<string, string | undefined>;
|
|
70
78
|
layoutRouteName?: string;
|
|
71
79
|
layoutBasePathname?: string;
|
|
@@ -105,6 +113,15 @@ const getDefaultBasePathnameFromRoutePath = (routePath?: string) => {
|
|
|
105
113
|
|
|
106
114
|
const isKnownViewParamName = (segment: string) => ['tab', 'filterbytk', 'sourceid'].includes(segment);
|
|
107
115
|
|
|
116
|
+
const isLegacyLayoutContentRouteName = (routeName: string, targetRouteName?: string) => {
|
|
117
|
+
return (
|
|
118
|
+
!!targetRouteName &&
|
|
119
|
+
['page', 'page.tabs', 'page.tabs.popups', 'page.tab', 'page.view', 'page.tab.view'].some(
|
|
120
|
+
(legacyName) => targetRouteName === `${routeName}.${legacyName}`,
|
|
121
|
+
)
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
108
125
|
const isStandardLayoutRelativePath = (relativePath: string) => {
|
|
109
126
|
if (!relativePath) {
|
|
110
127
|
return true;
|
|
@@ -116,20 +133,36 @@ const isStandardLayoutRelativePath = (relativePath: string) => {
|
|
|
116
133
|
}
|
|
117
134
|
|
|
118
135
|
let i = 1;
|
|
136
|
+
let currentViewUid = segments[0];
|
|
119
137
|
while (i < segments.length) {
|
|
120
138
|
const segment = segments[i];
|
|
139
|
+
|
|
121
140
|
if (segment === 'view') {
|
|
122
141
|
if (!segments[i + 1]) {
|
|
123
142
|
return false;
|
|
124
143
|
}
|
|
144
|
+
currentViewUid = segments[i + 1];
|
|
145
|
+
i += 2;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (segment === 'opts') {
|
|
150
|
+
if (!segments[i + 1] || !decodeOpenViewRouteState(currentViewUid, segments[i + 1])) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
125
153
|
i += 2;
|
|
126
154
|
continue;
|
|
127
155
|
}
|
|
128
156
|
|
|
129
|
-
if (
|
|
157
|
+
if (isKnownViewParamName(segment) && segments[i + 1]) {
|
|
158
|
+
i += 2;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!segments[i + 1]) {
|
|
130
163
|
return false;
|
|
131
164
|
}
|
|
132
|
-
|
|
165
|
+
return false;
|
|
133
166
|
}
|
|
134
167
|
|
|
135
168
|
return true;
|
|
@@ -148,6 +181,7 @@ export class BaseLayoutModel<
|
|
|
148
181
|
currentLayoutRoute: LayoutRouteMatch | null = null;
|
|
149
182
|
protected routeCoordinator?: BaseLayoutRouteCoordinator;
|
|
150
183
|
private activePageUid = '';
|
|
184
|
+
private currentRouteState?: unknown;
|
|
151
185
|
private layoutContentElement: HTMLElement | null = null;
|
|
152
186
|
private readonly routePageMetaMap = new Map<string, RoutePageMeta>();
|
|
153
187
|
private contextBindingsActive = false;
|
|
@@ -162,6 +196,7 @@ export class BaseLayoutModel<
|
|
|
162
196
|
|
|
163
197
|
registerRoutePage(pageUid: string, meta: RoutePageMeta) {
|
|
164
198
|
this.routePageMetaMap.set(pageUid, meta);
|
|
199
|
+
this.restoreCurrentLayoutRouteFromRouterContext(pageUid);
|
|
165
200
|
const routeModel = this.getCoordinator().registerPage(pageUid, meta);
|
|
166
201
|
this.getCoordinator().syncRoute(this.getCurrentCoordinatorRouteLike());
|
|
167
202
|
return routeModel;
|
|
@@ -300,6 +335,18 @@ export class BaseLayoutModel<
|
|
|
300
335
|
};
|
|
301
336
|
}
|
|
302
337
|
|
|
338
|
+
const routeRepository = this.flowEngine.context.routeRepository;
|
|
339
|
+
const schemaRoute = routeRepository?.getRouteBySchemaUid?.(pageUid);
|
|
340
|
+
const route = schemaRoute ? undefined : routeRepository?.getRouteById?.(pageUid);
|
|
341
|
+
if (!schemaRoute && route?.type === NocoBaseDesktopRouteType.group) {
|
|
342
|
+
return {
|
|
343
|
+
type: 'root',
|
|
344
|
+
pathname,
|
|
345
|
+
basePathname,
|
|
346
|
+
relativePath,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
303
350
|
return {
|
|
304
351
|
type: 'page',
|
|
305
352
|
pathname,
|
|
@@ -323,6 +370,7 @@ export class BaseLayoutModel<
|
|
|
323
370
|
|
|
324
371
|
const layoutRoute = this.resolveLayoutRoute(routeLike);
|
|
325
372
|
this.currentLayoutRoute = layoutRoute;
|
|
373
|
+
this.currentRouteState = routeLike.state;
|
|
326
374
|
this.activePageUid = this.getPageUidFromLayoutRoute(layoutRoute);
|
|
327
375
|
this.getCoordinator().syncRoute({
|
|
328
376
|
...routeLike,
|
|
@@ -342,7 +390,12 @@ export class BaseLayoutModel<
|
|
|
342
390
|
return;
|
|
343
391
|
}
|
|
344
392
|
|
|
393
|
+
if (this.shouldIgnoreStaleLayoutRouteCleanup(routeLike)) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
345
397
|
this.currentLayoutRoute = null;
|
|
398
|
+
this.currentRouteState = undefined;
|
|
346
399
|
this.activePageUid = '';
|
|
347
400
|
this.routeCoordinator?.syncRoute({});
|
|
348
401
|
}
|
|
@@ -412,12 +465,105 @@ export class BaseLayoutModel<
|
|
|
412
465
|
layoutRouteName: this.layout.routeName,
|
|
413
466
|
pageUid: this.currentLayoutRoute.pageUid,
|
|
414
467
|
pathname: this.currentLayoutRoute.pathname,
|
|
468
|
+
state: this.currentRouteState,
|
|
415
469
|
layoutBasePathname: this.currentLayoutRoute.basePathname,
|
|
416
470
|
layoutRoute: this.currentLayoutRoute,
|
|
417
471
|
};
|
|
418
472
|
}
|
|
419
473
|
return {};
|
|
420
474
|
}
|
|
475
|
+
|
|
476
|
+
private restoreCurrentLayoutRouteFromRouterContext(pageUid: string) {
|
|
477
|
+
if (this.currentLayoutRoute?.type === 'page') {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const routeLike = this.getRouterContextRouteLike();
|
|
482
|
+
if (!routeLike) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (!this.isRouteLikeOwnedByCurrentLayout(routeLike)) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const layoutRoute = this.resolveLayoutRoute({
|
|
491
|
+
...routeLike,
|
|
492
|
+
layoutRouteName: routeLike.layoutRouteName || this.layout.routeName,
|
|
493
|
+
});
|
|
494
|
+
if (layoutRoute.type !== 'page' || layoutRoute.pageUid !== pageUid) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
this.currentLayoutRoute = layoutRoute;
|
|
499
|
+
this.activePageUid = layoutRoute.pageUid;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
private shouldIgnoreStaleLayoutRouteCleanup(routeLike?: LayoutRouteLike) {
|
|
503
|
+
if (!routeLike?.pathname || this.currentLayoutRoute?.type !== 'page') {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const currentLayoutRoute = this.currentLayoutRoute;
|
|
508
|
+
if (!this.routePageMetaMap.has(currentLayoutRoute.pageUid)) {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const routeLikePathname = normalizePathname(routeLike.pathname);
|
|
513
|
+
if (routeLikePathname !== currentLayoutRoute.pathname) {
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const routerRouteLike = this.getRouterContextRouteLike();
|
|
518
|
+
if (!routerRouteLike) {
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (!this.isRouteLikeOwnedByCurrentLayout(routerRouteLike)) {
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const routerLayoutRoute = this.resolveLayoutRoute({
|
|
527
|
+
...routerRouteLike,
|
|
528
|
+
layoutRouteName: routerRouteLike.layoutRouteName || this.layout.routeName,
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
return (
|
|
532
|
+
routerLayoutRoute.type === 'page' &&
|
|
533
|
+
routerLayoutRoute.pageUid === currentLayoutRoute.pageUid &&
|
|
534
|
+
routerLayoutRoute.pathname === currentLayoutRoute.pathname
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
private getRouterContextRouteLike(): LayoutRouteLike | null {
|
|
539
|
+
const route = this.flowEngine.context.route as LayoutRouteLike | undefined;
|
|
540
|
+
if (!route?.pathname) {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return {
|
|
545
|
+
id: route.id,
|
|
546
|
+
name: route.name || route.id,
|
|
547
|
+
pathname: route.pathname,
|
|
548
|
+
params: route.params,
|
|
549
|
+
layoutRouteName: route.layoutRouteName,
|
|
550
|
+
layoutBasePathname: route.layoutBasePathname,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private isRouteLikeOwnedByCurrentLayout(routeLike: LayoutRouteLike) {
|
|
555
|
+
if (routeLike.layoutRouteName) {
|
|
556
|
+
return routeLike.layoutRouteName === this.layout.routeName;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const layoutBasePathname = getDefaultBasePathnameFromRoutePath(this.layout.routePath);
|
|
560
|
+
if (routeLike.layoutBasePathname && layoutBasePathname) {
|
|
561
|
+
return normalizeBasePathname(routeLike.layoutBasePathname) === normalizeBasePathname(layoutBasePathname);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const routeName = routeLike.name || routeLike.id;
|
|
565
|
+
return this.isLayoutContentRoute(routeLike) || isLegacyLayoutContentRouteName(this.layout.routeName, routeName);
|
|
566
|
+
}
|
|
421
567
|
}
|
|
422
568
|
|
|
423
569
|
/**
|
|
@@ -22,6 +22,7 @@ import { getOpenViewStepParams } from '../flows/openViewFlow';
|
|
|
22
22
|
import { RouteModel } from '../models/base/RouteModel';
|
|
23
23
|
import { resolveViewParamsToViewList, updateViewListHidden, type ViewItem } from '../resolveViewParamsToViewList';
|
|
24
24
|
import type { LayoutDefinition } from '../../layout-manager/types';
|
|
25
|
+
import { getRouteTransientInputArgs } from '../routeTransientInputArgs';
|
|
25
26
|
|
|
26
27
|
export interface RoutePageMeta {
|
|
27
28
|
active: boolean;
|
|
@@ -49,14 +50,19 @@ interface RoutePageRuntime {
|
|
|
49
50
|
meta: RoutePageMeta;
|
|
50
51
|
viewState: Record<string, ViewRuntimeState>;
|
|
51
52
|
prevViewList: ViewItem[];
|
|
53
|
+
pendingOpenViewKeys: Set<string>;
|
|
52
54
|
hasStepNavigated: boolean;
|
|
55
|
+
currentPathname?: string;
|
|
56
|
+
currentRouteState?: unknown;
|
|
53
57
|
forceStop: boolean;
|
|
58
|
+
viewGeneration: number;
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
interface RouteLike {
|
|
57
62
|
layoutRouteName?: string;
|
|
58
63
|
params?: { name?: string };
|
|
59
64
|
pathname?: string;
|
|
65
|
+
state?: unknown;
|
|
60
66
|
pageUid?: string;
|
|
61
67
|
layoutBasePathname?: string;
|
|
62
68
|
layoutRoute?: {
|
|
@@ -66,6 +72,11 @@ interface RouteLike {
|
|
|
66
72
|
} | null;
|
|
67
73
|
}
|
|
68
74
|
|
|
75
|
+
interface SyncRuntimeOptions {
|
|
76
|
+
skipInitialStepNavigation?: boolean;
|
|
77
|
+
routeState?: unknown;
|
|
78
|
+
}
|
|
79
|
+
|
|
69
80
|
const hasUsableSourceId = (sourceId: unknown) => sourceId !== undefined && sourceId !== null && String(sourceId) !== '';
|
|
70
81
|
|
|
71
82
|
const normalizeBasePathname = (basePathname?: string) => {
|
|
@@ -92,6 +103,7 @@ export class BaseLayoutRouteCoordinator {
|
|
|
92
103
|
private basePathname: string;
|
|
93
104
|
private readonly runtimes = new Map<string, RoutePageRuntime>();
|
|
94
105
|
private layoutContentElement: HTMLElement | null = null;
|
|
106
|
+
private lastNonNullLayoutContentElement: HTMLElement | null = null;
|
|
95
107
|
|
|
96
108
|
constructor(flowEngine: FlowEngine, options: BaseLayoutRouteCoordinatorOptions = {}) {
|
|
97
109
|
this.flowEngine = flowEngine;
|
|
@@ -104,7 +116,19 @@ export class BaseLayoutRouteCoordinator {
|
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
setLayoutContentElement(element: HTMLElement | null) {
|
|
119
|
+
const previousTarget = this.layoutContentElement || this.lastNonNullLayoutContentElement;
|
|
107
120
|
this.layoutContentElement = element;
|
|
121
|
+
|
|
122
|
+
if (!element) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const shouldReplayActiveViews = !!previousTarget && previousTarget !== element;
|
|
127
|
+
this.lastNonNullLayoutContentElement = element;
|
|
128
|
+
|
|
129
|
+
if (shouldReplayActiveViews) {
|
|
130
|
+
this.replayActiveRuntimeViewsAfterLayoutContentElementChange();
|
|
131
|
+
}
|
|
108
132
|
}
|
|
109
133
|
|
|
110
134
|
registerPage(pageUid: string, meta: RoutePageMeta) {
|
|
@@ -119,8 +143,10 @@ export class BaseLayoutRouteCoordinator {
|
|
|
119
143
|
},
|
|
120
144
|
viewState: {},
|
|
121
145
|
prevViewList: [],
|
|
146
|
+
pendingOpenViewKeys: new Set(),
|
|
122
147
|
hasStepNavigated: false,
|
|
123
148
|
forceStop: false,
|
|
149
|
+
viewGeneration: 0,
|
|
124
150
|
};
|
|
125
151
|
|
|
126
152
|
this.ensureRouteModelContext(runtime);
|
|
@@ -159,6 +185,7 @@ export class BaseLayoutRouteCoordinator {
|
|
|
159
185
|
this.runtimes.forEach((runtime) => {
|
|
160
186
|
this.setRuntimeActive(runtime, false);
|
|
161
187
|
});
|
|
188
|
+
this.invalidatePendingRuntimeViewOpens();
|
|
162
189
|
return;
|
|
163
190
|
}
|
|
164
191
|
|
|
@@ -175,11 +202,16 @@ export class BaseLayoutRouteCoordinator {
|
|
|
175
202
|
});
|
|
176
203
|
|
|
177
204
|
if (!activePageUid || !pathname) {
|
|
205
|
+
this.invalidatePendingRuntimeViewOpens();
|
|
178
206
|
return;
|
|
179
207
|
}
|
|
180
208
|
|
|
181
209
|
this.runtimes.forEach((runtime) => {
|
|
182
210
|
if (runtime.pageUid !== activePageUid) {
|
|
211
|
+
if (!runtime.forceStop) {
|
|
212
|
+
runtime.viewGeneration += 1;
|
|
213
|
+
runtime.pendingOpenViewKeys.clear();
|
|
214
|
+
}
|
|
183
215
|
runtime.forceStop = true;
|
|
184
216
|
}
|
|
185
217
|
});
|
|
@@ -190,7 +222,13 @@ export class BaseLayoutRouteCoordinator {
|
|
|
190
222
|
}
|
|
191
223
|
|
|
192
224
|
runtime.forceStop = false;
|
|
193
|
-
|
|
225
|
+
if (runtime.currentPathname && runtime.currentPathname !== pathname) {
|
|
226
|
+
runtime.viewGeneration += 1;
|
|
227
|
+
runtime.pendingOpenViewKeys.clear();
|
|
228
|
+
}
|
|
229
|
+
runtime.currentPathname = pathname;
|
|
230
|
+
runtime.currentRouteState = routeLike.state;
|
|
231
|
+
this.syncRuntimeWithPathname(runtime, pathname, { routeState: routeLike.state });
|
|
194
232
|
}
|
|
195
233
|
|
|
196
234
|
cleanupPage(pageUid: string) {
|
|
@@ -200,6 +238,8 @@ export class BaseLayoutRouteCoordinator {
|
|
|
200
238
|
}
|
|
201
239
|
|
|
202
240
|
runtime.forceStop = true;
|
|
241
|
+
runtime.viewGeneration += 1;
|
|
242
|
+
runtime.pendingOpenViewKeys.clear();
|
|
203
243
|
runtime.prevViewList.forEach((viewItem) => {
|
|
204
244
|
this.flowEngine.removeModelWithSubModels(viewItem.params.viewUid);
|
|
205
245
|
runtime.viewState[getKey(viewItem)]?.destroy?.();
|
|
@@ -207,6 +247,8 @@ export class BaseLayoutRouteCoordinator {
|
|
|
207
247
|
});
|
|
208
248
|
runtime.prevViewList = [];
|
|
209
249
|
runtime.hasStepNavigated = false;
|
|
250
|
+
runtime.currentPathname = undefined;
|
|
251
|
+
runtime.currentRouteState = undefined;
|
|
210
252
|
}
|
|
211
253
|
|
|
212
254
|
destroy() {
|
|
@@ -249,7 +291,7 @@ export class BaseLayoutRouteCoordinator {
|
|
|
249
291
|
viewState?.deactivate?.();
|
|
250
292
|
}
|
|
251
293
|
|
|
252
|
-
private syncRuntimeWithPathname(runtime: RoutePageRuntime, pathname: string) {
|
|
294
|
+
private syncRuntimeWithPathname(runtime: RoutePageRuntime, pathname: string, options: SyncRuntimeOptions = {}) {
|
|
253
295
|
try {
|
|
254
296
|
if (!this.basePathname) {
|
|
255
297
|
return;
|
|
@@ -257,29 +299,42 @@ export class BaseLayoutRouteCoordinator {
|
|
|
257
299
|
|
|
258
300
|
const viewStack = parsePathnameToViewParams(pathname, { basePath: this.basePathname });
|
|
259
301
|
const viewList = resolveViewParamsToViewList(this.flowEngine, viewStack, runtime.routeModel);
|
|
302
|
+
const routeState = options.routeState;
|
|
260
303
|
|
|
261
|
-
if (this.shouldStepNavigate(runtime, viewList)) {
|
|
262
|
-
this.stepNavigate(viewList, 0);
|
|
304
|
+
if (!options.skipInitialStepNavigation && this.shouldStepNavigate(runtime, viewList)) {
|
|
305
|
+
this.stepNavigate(viewList, 0, routeState);
|
|
263
306
|
runtime.hasStepNavigated = true;
|
|
264
|
-
this.scheduleInitialDeepLinkReplay(runtime, pathname);
|
|
307
|
+
this.scheduleInitialDeepLinkReplay(runtime, pathname, routeState);
|
|
265
308
|
return;
|
|
266
309
|
}
|
|
267
310
|
|
|
268
311
|
const { viewsToClose, viewsToOpen } = getViewDiffAndUpdateHidden(runtime.prevViewList, viewList);
|
|
312
|
+
const viewsToOpenKeys = new Set(viewsToOpen.map((viewItem) => getKey(viewItem)));
|
|
313
|
+
const nextViewsToOpen = viewList.filter((viewItem) => {
|
|
314
|
+
const key = getKey(viewItem);
|
|
315
|
+
if (runtime.pendingOpenViewKeys.has(key)) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
return viewsToOpenKeys.has(key) || !runtime.viewState[key];
|
|
319
|
+
});
|
|
269
320
|
|
|
270
321
|
if (viewsToClose.length) {
|
|
271
322
|
viewsToClose.forEach((viewItem) => {
|
|
323
|
+
runtime.pendingOpenViewKeys.delete(getKey(viewItem));
|
|
272
324
|
runtime.viewState[getKey(viewItem)]?.destroy?.(true);
|
|
273
325
|
delete runtime.viewState[getKey(viewItem)];
|
|
274
326
|
});
|
|
275
|
-
updateViewListHidden(viewList);
|
|
327
|
+
updateViewListHidden(viewList, !!this.layoutContext?.isMobileLayout);
|
|
276
328
|
}
|
|
277
329
|
|
|
278
|
-
if (
|
|
279
|
-
|
|
330
|
+
if (nextViewsToOpen.length) {
|
|
331
|
+
this.markPendingOpenViews(runtime, nextViewsToOpen);
|
|
332
|
+
this.handleOpenViews(runtime, viewList, nextViewsToOpen, runtime.viewGeneration, routeState).catch((error) => {
|
|
333
|
+
console.error(`[NocoBase] Failed to open route-managed views:`, error);
|
|
334
|
+
});
|
|
280
335
|
}
|
|
281
336
|
|
|
282
|
-
if (viewsToClose.length === 0 &&
|
|
337
|
+
if (viewsToClose.length === 0 && nextViewsToOpen.length === 0) {
|
|
283
338
|
const currentViewItem = viewList.at(-1);
|
|
284
339
|
if (currentViewItem) {
|
|
285
340
|
runtime.viewState[getKey(currentViewItem)]?.navigation.setViewStack(viewList.map((item) => item.params));
|
|
@@ -299,7 +354,7 @@ export class BaseLayoutRouteCoordinator {
|
|
|
299
354
|
}
|
|
300
355
|
|
|
301
356
|
if (runtime.meta.active) {
|
|
302
|
-
updateViewListHidden(viewList);
|
|
357
|
+
updateViewListHidden(viewList, !!this.layoutContext?.isMobileLayout);
|
|
303
358
|
return;
|
|
304
359
|
}
|
|
305
360
|
|
|
@@ -312,30 +367,38 @@ export class BaseLayoutRouteCoordinator {
|
|
|
312
367
|
return runtime.prevViewList.length === 0 && viewList.length > 1 && !runtime.hasStepNavigated;
|
|
313
368
|
}
|
|
314
369
|
|
|
315
|
-
private scheduleInitialDeepLinkReplay(runtime: RoutePageRuntime, pathname: string) {
|
|
370
|
+
private scheduleInitialDeepLinkReplay(runtime: RoutePageRuntime, pathname: string, routeState?: unknown) {
|
|
371
|
+
const viewGeneration = runtime.viewGeneration;
|
|
316
372
|
Promise.resolve()
|
|
317
373
|
.then(() => {
|
|
318
|
-
if (
|
|
374
|
+
if (
|
|
375
|
+
runtime.forceStop ||
|
|
376
|
+
runtime.viewGeneration !== viewGeneration ||
|
|
377
|
+
runtime.currentPathname !== pathname ||
|
|
378
|
+
runtime.prevViewList.length > 0
|
|
379
|
+
) {
|
|
319
380
|
return;
|
|
320
381
|
}
|
|
321
382
|
|
|
322
|
-
this.syncRuntimeWithPathname(runtime, pathname);
|
|
383
|
+
this.syncRuntimeWithPathname(runtime, pathname, { routeState });
|
|
323
384
|
})
|
|
324
385
|
.catch(() => {
|
|
325
386
|
// ignore
|
|
326
387
|
});
|
|
327
388
|
}
|
|
328
389
|
|
|
329
|
-
private stepNavigate(viewList: ViewItem[], index: number) {
|
|
390
|
+
private stepNavigate(viewList: ViewItem[], index: number, routeState?: unknown) {
|
|
330
391
|
if (!viewList[index]) {
|
|
331
392
|
return;
|
|
332
393
|
}
|
|
333
394
|
|
|
395
|
+
const navigationOptions = routeState ? { state: routeState } : undefined;
|
|
334
396
|
if (index === 0) {
|
|
335
397
|
new ViewNavigation(this.flowEngine.context, [], { basePath: this.basePathname }).navigateTo(
|
|
336
398
|
viewList[index].params,
|
|
337
399
|
{
|
|
338
400
|
replace: true,
|
|
401
|
+
...navigationOptions,
|
|
339
402
|
},
|
|
340
403
|
);
|
|
341
404
|
} else {
|
|
@@ -343,13 +406,70 @@ export class BaseLayoutRouteCoordinator {
|
|
|
343
406
|
this.flowEngine.context,
|
|
344
407
|
viewList.slice(0, index).map((item) => item.params),
|
|
345
408
|
{ basePath: this.basePathname },
|
|
346
|
-
).navigateTo(viewList[index].params);
|
|
409
|
+
).navigateTo(viewList[index].params, navigationOptions);
|
|
347
410
|
}
|
|
348
411
|
|
|
349
|
-
this.stepNavigate(viewList, index + 1);
|
|
412
|
+
this.stepNavigate(viewList, index + 1, routeState);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private replayActiveRuntimeViewsAfterLayoutContentElementChange() {
|
|
416
|
+
this.runtimes.forEach((runtime) => {
|
|
417
|
+
if (!runtime.currentPathname || runtime.prevViewList.length === 0) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
this.resetRuntimeViewStateForLayoutContentElementChange(runtime);
|
|
422
|
+
if (runtime.meta.active) {
|
|
423
|
+
this.syncRuntimeWithPathname(runtime, runtime.currentPathname, {
|
|
424
|
+
skipInitialStepNavigation: true,
|
|
425
|
+
routeState: runtime.currentRouteState,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private invalidatePendingRuntimeViewOpens() {
|
|
432
|
+
this.runtimes.forEach((runtime) => {
|
|
433
|
+
runtime.forceStop = true;
|
|
434
|
+
runtime.viewGeneration += 1;
|
|
435
|
+
runtime.pendingOpenViewKeys.clear();
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private resetRuntimeViewStateForLayoutContentElementChange(runtime: RoutePageRuntime) {
|
|
440
|
+
runtime.viewGeneration += 1;
|
|
441
|
+
runtime.pendingOpenViewKeys.clear();
|
|
442
|
+
runtime.prevViewList.forEach((viewItem, index) => {
|
|
443
|
+
runtime.viewState[getKey(viewItem)]?.destroy?.(true);
|
|
444
|
+
delete runtime.viewState[getKey(viewItem)];
|
|
445
|
+
|
|
446
|
+
if (index > 0) {
|
|
447
|
+
this.flowEngine.removeModelWithSubModels(viewItem.params.viewUid);
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
runtime.prevViewList = [];
|
|
451
|
+
runtime.hasStepNavigated = false;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private markPendingOpenViews(runtime: RoutePageRuntime, viewItems: ViewItem[]) {
|
|
455
|
+
viewItems.forEach((viewItem) => {
|
|
456
|
+
runtime.pendingOpenViewKeys.add(getKey(viewItem));
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private clearPendingOpenViews(runtime: RoutePageRuntime, viewItems: ViewItem[]) {
|
|
461
|
+
viewItems.forEach((viewItem) => {
|
|
462
|
+
runtime.pendingOpenViewKeys.delete(getKey(viewItem));
|
|
463
|
+
});
|
|
350
464
|
}
|
|
351
465
|
|
|
352
|
-
private async handleOpenViews(
|
|
466
|
+
private async handleOpenViews(
|
|
467
|
+
runtime: RoutePageRuntime,
|
|
468
|
+
viewList: ViewItem[],
|
|
469
|
+
viewsToOpen: ViewItem[],
|
|
470
|
+
viewGeneration: number,
|
|
471
|
+
routeState?: unknown,
|
|
472
|
+
) {
|
|
353
473
|
const missingModels = viewsToOpen.filter((v) => !v.model);
|
|
354
474
|
if (missingModels.length > 0) {
|
|
355
475
|
await Promise.all(
|
|
@@ -363,24 +483,37 @@ export class BaseLayoutRouteCoordinator {
|
|
|
363
483
|
);
|
|
364
484
|
}
|
|
365
485
|
|
|
366
|
-
if (runtime.forceStop) {
|
|
486
|
+
if (runtime.forceStop || runtime.viewGeneration !== viewGeneration) {
|
|
367
487
|
return;
|
|
368
488
|
}
|
|
369
489
|
|
|
370
490
|
this.syncViewListVisibility(runtime, viewList);
|
|
371
|
-
this.openViews(runtime, viewList, viewsToOpen, 0);
|
|
491
|
+
this.openViews(runtime, viewList, viewsToOpen, 0, viewGeneration, routeState);
|
|
372
492
|
}
|
|
373
493
|
|
|
374
|
-
private openViews(
|
|
494
|
+
private openViews(
|
|
495
|
+
runtime: RoutePageRuntime,
|
|
496
|
+
viewList: ViewItem[],
|
|
497
|
+
viewsToOpen: ViewItem[],
|
|
498
|
+
index: number,
|
|
499
|
+
viewGeneration: number,
|
|
500
|
+
routeState?: unknown,
|
|
501
|
+
) {
|
|
502
|
+
if (runtime.forceStop || runtime.viewGeneration !== viewGeneration) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
375
506
|
if (!viewsToOpen[index]) {
|
|
376
507
|
return;
|
|
377
508
|
}
|
|
378
509
|
|
|
379
510
|
const viewItem = viewsToOpen[index];
|
|
380
511
|
if (!viewItem.model) {
|
|
512
|
+
this.clearPendingOpenViews(runtime, viewsToOpen.slice(index));
|
|
381
513
|
return;
|
|
382
514
|
}
|
|
383
515
|
|
|
516
|
+
const viewKey = getKey(viewItem);
|
|
384
517
|
const destroyRef = React.createRef<(result?: any, force?: boolean) => void>();
|
|
385
518
|
const updateRef = React.createRef<(value: any) => void>();
|
|
386
519
|
const activateRef = React.createRef<(forceRefresh?: boolean) => void>();
|
|
@@ -390,42 +523,54 @@ export class BaseLayoutRouteCoordinator {
|
|
|
390
523
|
openViewParams?.associationName && !hasUsableSourceId(viewItem.params.sourceId)
|
|
391
524
|
? null
|
|
392
525
|
: openViewParams?.associationName;
|
|
526
|
+
const openViewRouteState = viewItem.params.openViewRouteState;
|
|
393
527
|
const openerUids = viewList.slice(0, viewItem.index).map((item) => item.params.viewUid);
|
|
528
|
+
const transientInputArgs = getRouteTransientInputArgs(routeState, viewItem.params.viewUid);
|
|
394
529
|
const navigation = new ViewNavigation(
|
|
395
530
|
this.flowEngine.context,
|
|
396
531
|
viewList.slice(0, viewItem.index + 1).map((item) => item.params),
|
|
397
532
|
{ basePath: this.basePathname },
|
|
398
533
|
);
|
|
399
534
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
535
|
+
try {
|
|
536
|
+
viewItem.model.dispatchEvent('click', {
|
|
537
|
+
target: this.layoutContentElement || runtime.meta.layoutContentElement,
|
|
538
|
+
collectionName: openViewParams?.collectionName,
|
|
539
|
+
associationName,
|
|
540
|
+
dataSourceKey: openViewParams?.dataSourceKey,
|
|
541
|
+
destroyRef,
|
|
542
|
+
updateRef,
|
|
543
|
+
activateRef,
|
|
544
|
+
deactivateRef,
|
|
545
|
+
openerUids,
|
|
546
|
+
...viewItem.params,
|
|
547
|
+
...transientInputArgs,
|
|
548
|
+
...(openViewRouteState?.mode ? { mode: openViewRouteState.mode } : {}),
|
|
549
|
+
...(openViewRouteState?.size ? { size: openViewRouteState.size } : {}),
|
|
550
|
+
pageActive: runtime.meta.active,
|
|
551
|
+
activationControlledByLayout: true,
|
|
552
|
+
navigation,
|
|
553
|
+
onOpen: () => {
|
|
554
|
+
this.openViews(runtime, viewList, viewsToOpen, index + 1, viewGeneration, routeState);
|
|
555
|
+
},
|
|
556
|
+
hidden: viewItem.hidden,
|
|
557
|
+
isMobileLayout: !!this.layoutContext?.isMobileLayout,
|
|
558
|
+
triggerByRouter: true,
|
|
559
|
+
});
|
|
560
|
+
} catch (error) {
|
|
561
|
+
this.clearPendingOpenViews(runtime, viewsToOpen.slice(index));
|
|
562
|
+
console.error(`[NocoBase] Failed to dispatch route-managed view open:`, error);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
421
565
|
|
|
422
|
-
runtime.viewState[
|
|
566
|
+
runtime.viewState[viewKey] = {
|
|
423
567
|
destroy: (_force?: boolean) => destroyRef.current?.(),
|
|
424
568
|
update: (value: any) => updateRef.current?.(value),
|
|
425
569
|
activate: (forceRefresh?: boolean) => activateRef.current?.(forceRefresh),
|
|
426
570
|
deactivate: () => deactivateRef.current?.(),
|
|
427
571
|
navigation,
|
|
428
572
|
};
|
|
573
|
+
runtime.pendingOpenViewKeys.delete(viewKey);
|
|
429
574
|
}
|
|
430
575
|
|
|
431
576
|
private ensureRouteModelContext(runtime: RoutePageRuntime) {
|