@nocobase/client-v2 2.1.17 → 2.1.19
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/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
- package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
- package/es/flow/routeTransientInputArgs.d.ts +14 -0
- package/es/index.d.ts +1 -0
- package/es/index.mjs +93 -93
- package/es/utils/markdownSanitize.d.ts +11 -0
- package/lib/index.js +114 -114
- package/package.json +7 -7
- package/src/flow/FlowPage.tsx +29 -2
- package/src/flow/__tests__/FlowPage.test.tsx +152 -25
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +62 -0
- package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +80 -20
- package/src/flow/actions/dataScopeFilter.ts +10 -1
- package/src/flow/actions/openView.tsx +21 -1
- package/src/flow/admin-shell/BaseLayoutModel.tsx +111 -0
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +184 -42
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1016 -119
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +29 -2
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +359 -2
- package/src/flow/common/Markdown/Display.tsx +14 -3
- package/src/flow/common/Markdown/Edit.tsx +19 -7
- package/src/flow/internal/components/Markdown/util.ts +2 -1
- package/src/flow/models/fields/TextareaFieldModel.tsx +42 -19
- package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +32 -1
- package/src/flow/routeTransientInputArgs.ts +27 -0
- package/src/index.ts +1 -0
- package/src/utils/markdownSanitize.ts +88 -0
|
@@ -72,6 +72,7 @@ export interface LayoutRouteLike {
|
|
|
72
72
|
id?: string;
|
|
73
73
|
name?: string;
|
|
74
74
|
pathname?: string;
|
|
75
|
+
state?: unknown;
|
|
75
76
|
params?: Record<string, string | undefined>;
|
|
76
77
|
layoutRouteName?: string;
|
|
77
78
|
layoutBasePathname?: string;
|
|
@@ -111,6 +112,15 @@ const getDefaultBasePathnameFromRoutePath = (routePath?: string) => {
|
|
|
111
112
|
|
|
112
113
|
const isKnownViewParamName = (segment: string) => ['tab', 'filterbytk', 'sourceid'].includes(segment);
|
|
113
114
|
|
|
115
|
+
const isLegacyLayoutContentRouteName = (routeName: string, targetRouteName?: string) => {
|
|
116
|
+
return (
|
|
117
|
+
!!targetRouteName &&
|
|
118
|
+
['page', 'page.tabs', 'page.tabs.popups', 'page.tab', 'page.view', 'page.tab.view'].some(
|
|
119
|
+
(legacyName) => targetRouteName === `${routeName}.${legacyName}`,
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
114
124
|
const isStandardLayoutRelativePath = (relativePath: string) => {
|
|
115
125
|
if (!relativePath) {
|
|
116
126
|
return true;
|
|
@@ -170,6 +180,7 @@ export class BaseLayoutModel<
|
|
|
170
180
|
currentLayoutRoute: LayoutRouteMatch | null = null;
|
|
171
181
|
protected routeCoordinator?: BaseLayoutRouteCoordinator;
|
|
172
182
|
private activePageUid = '';
|
|
183
|
+
private currentRouteState?: unknown;
|
|
173
184
|
private layoutContentElement: HTMLElement | null = null;
|
|
174
185
|
private readonly routePageMetaMap = new Map<string, RoutePageMeta>();
|
|
175
186
|
private contextBindingsActive = false;
|
|
@@ -184,6 +195,7 @@ export class BaseLayoutModel<
|
|
|
184
195
|
|
|
185
196
|
registerRoutePage(pageUid: string, meta: RoutePageMeta) {
|
|
186
197
|
this.routePageMetaMap.set(pageUid, meta);
|
|
198
|
+
this.restoreCurrentLayoutRouteFromRouterContext(pageUid);
|
|
187
199
|
const routeModel = this.getCoordinator().registerPage(pageUid, meta);
|
|
188
200
|
this.getCoordinator().syncRoute(this.getCurrentCoordinatorRouteLike());
|
|
189
201
|
return routeModel;
|
|
@@ -345,6 +357,7 @@ export class BaseLayoutModel<
|
|
|
345
357
|
|
|
346
358
|
const layoutRoute = this.resolveLayoutRoute(routeLike);
|
|
347
359
|
this.currentLayoutRoute = layoutRoute;
|
|
360
|
+
this.currentRouteState = routeLike.state;
|
|
348
361
|
this.activePageUid = this.getPageUidFromLayoutRoute(layoutRoute);
|
|
349
362
|
this.getCoordinator().syncRoute({
|
|
350
363
|
...routeLike,
|
|
@@ -364,7 +377,12 @@ export class BaseLayoutModel<
|
|
|
364
377
|
return;
|
|
365
378
|
}
|
|
366
379
|
|
|
380
|
+
if (this.shouldIgnoreStaleLayoutRouteCleanup(routeLike)) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
367
384
|
this.currentLayoutRoute = null;
|
|
385
|
+
this.currentRouteState = undefined;
|
|
368
386
|
this.activePageUid = '';
|
|
369
387
|
this.routeCoordinator?.syncRoute({});
|
|
370
388
|
}
|
|
@@ -434,12 +452,105 @@ export class BaseLayoutModel<
|
|
|
434
452
|
layoutRouteName: this.layout.routeName,
|
|
435
453
|
pageUid: this.currentLayoutRoute.pageUid,
|
|
436
454
|
pathname: this.currentLayoutRoute.pathname,
|
|
455
|
+
state: this.currentRouteState,
|
|
437
456
|
layoutBasePathname: this.currentLayoutRoute.basePathname,
|
|
438
457
|
layoutRoute: this.currentLayoutRoute,
|
|
439
458
|
};
|
|
440
459
|
}
|
|
441
460
|
return {};
|
|
442
461
|
}
|
|
462
|
+
|
|
463
|
+
private restoreCurrentLayoutRouteFromRouterContext(pageUid: string) {
|
|
464
|
+
if (this.currentLayoutRoute?.type === 'page') {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const routeLike = this.getRouterContextRouteLike();
|
|
469
|
+
if (!routeLike) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (!this.isRouteLikeOwnedByCurrentLayout(routeLike)) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const layoutRoute = this.resolveLayoutRoute({
|
|
478
|
+
...routeLike,
|
|
479
|
+
layoutRouteName: routeLike.layoutRouteName || this.layout.routeName,
|
|
480
|
+
});
|
|
481
|
+
if (layoutRoute.type !== 'page' || layoutRoute.pageUid !== pageUid) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
this.currentLayoutRoute = layoutRoute;
|
|
486
|
+
this.activePageUid = layoutRoute.pageUid;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private shouldIgnoreStaleLayoutRouteCleanup(routeLike?: LayoutRouteLike) {
|
|
490
|
+
if (!routeLike?.pathname || this.currentLayoutRoute?.type !== 'page') {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const currentLayoutRoute = this.currentLayoutRoute;
|
|
495
|
+
if (!this.routePageMetaMap.has(currentLayoutRoute.pageUid)) {
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const routeLikePathname = normalizePathname(routeLike.pathname);
|
|
500
|
+
if (routeLikePathname !== currentLayoutRoute.pathname) {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const routerRouteLike = this.getRouterContextRouteLike();
|
|
505
|
+
if (!routerRouteLike) {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (!this.isRouteLikeOwnedByCurrentLayout(routerRouteLike)) {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const routerLayoutRoute = this.resolveLayoutRoute({
|
|
514
|
+
...routerRouteLike,
|
|
515
|
+
layoutRouteName: routerRouteLike.layoutRouteName || this.layout.routeName,
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
return (
|
|
519
|
+
routerLayoutRoute.type === 'page' &&
|
|
520
|
+
routerLayoutRoute.pageUid === currentLayoutRoute.pageUid &&
|
|
521
|
+
routerLayoutRoute.pathname === currentLayoutRoute.pathname
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private getRouterContextRouteLike(): LayoutRouteLike | null {
|
|
526
|
+
const route = this.flowEngine.context.route as LayoutRouteLike | undefined;
|
|
527
|
+
if (!route?.pathname) {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
id: route.id,
|
|
533
|
+
name: route.name || route.id,
|
|
534
|
+
pathname: route.pathname,
|
|
535
|
+
params: route.params,
|
|
536
|
+
layoutRouteName: route.layoutRouteName,
|
|
537
|
+
layoutBasePathname: route.layoutBasePathname,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
private isRouteLikeOwnedByCurrentLayout(routeLike: LayoutRouteLike) {
|
|
542
|
+
if (routeLike.layoutRouteName) {
|
|
543
|
+
return routeLike.layoutRouteName === this.layout.routeName;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const layoutBasePathname = getDefaultBasePathnameFromRoutePath(this.layout.routePath);
|
|
547
|
+
if (routeLike.layoutBasePathname && layoutBasePathname) {
|
|
548
|
+
return normalizeBasePathname(routeLike.layoutBasePathname) === normalizeBasePathname(layoutBasePathname);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const routeName = routeLike.name || routeLike.id;
|
|
552
|
+
return this.isLayoutContentRoute(routeLike) || isLegacyLayoutContentRouteName(this.layout.routeName, routeName);
|
|
553
|
+
}
|
|
443
554
|
}
|
|
444
555
|
|
|
445
556
|
/**
|
|
@@ -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
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));
|
|
@@ -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>();
|
|
@@ -392,43 +525,52 @@ export class BaseLayoutRouteCoordinator {
|
|
|
392
525
|
: openViewParams?.associationName;
|
|
393
526
|
const openViewRouteState = viewItem.params.openViewRouteState;
|
|
394
527
|
const openerUids = viewList.slice(0, viewItem.index).map((item) => item.params.viewUid);
|
|
528
|
+
const transientInputArgs = getRouteTransientInputArgs(routeState, viewItem.params.viewUid);
|
|
395
529
|
const navigation = new ViewNavigation(
|
|
396
530
|
this.flowEngine.context,
|
|
397
531
|
viewList.slice(0, viewItem.index + 1).map((item) => item.params),
|
|
398
532
|
{ basePath: this.basePathname },
|
|
399
533
|
);
|
|
400
534
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
+
}
|
|
424
565
|
|
|
425
|
-
runtime.viewState[
|
|
566
|
+
runtime.viewState[viewKey] = {
|
|
426
567
|
destroy: (_force?: boolean) => destroyRef.current?.(),
|
|
427
568
|
update: (value: any) => updateRef.current?.(value),
|
|
428
569
|
activate: (forceRefresh?: boolean) => activateRef.current?.(forceRefresh),
|
|
429
570
|
deactivate: () => deactivateRef.current?.(),
|
|
430
571
|
navigation,
|
|
431
572
|
};
|
|
573
|
+
runtime.pendingOpenViewKeys.delete(viewKey);
|
|
432
574
|
}
|
|
433
575
|
|
|
434
576
|
private ensureRouteModelContext(runtime: RoutePageRuntime) {
|