@ankhorage/studio 0.14.0 → 0.16.0
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/README.md +2 -2
- package/dist/canvasDragModel.d.ts +24 -0
- package/dist/canvasDragModel.d.ts.map +1 -1
- package/dist/canvasDragModel.js +58 -0
- package/dist/canvasDragModel.js.map +1 -1
- package/dist/canvasDropZones.d.ts +2 -2
- package/dist/canvasDropZones.d.ts.map +1 -1
- package/dist/canvasDropZones.js +20 -4
- package/dist/canvasDropZones.js.map +1 -1
- package/dist/contractGuards.d.ts.map +1 -1
- package/dist/contractGuards.js +4 -2
- package/dist/contractGuards.js.map +1 -1
- package/dist/core/StudioProvider.d.ts +3 -2
- package/dist/core/StudioProvider.d.ts.map +1 -1
- package/dist/core/StudioProvider.js +150 -17
- package/dist/core/StudioProvider.js.map +1 -1
- package/dist/host/layout/auth/resolveAuthLayoutPlan.js +2 -2
- package/dist/host/layout/auth/resolveAuthLayoutPlan.js.map +1 -1
- package/dist/host/layout/layoutGenerator.d.ts.map +1 -1
- package/dist/host/layout/layoutGenerator.js +5 -3
- package/dist/host/layout/layoutGenerator.js.map +1 -1
- package/dist/host/layout/templates/navigation.js +4 -10
- package/dist/host/layout/templates/navigation.js.map +1 -1
- package/dist/host/layout/templates/rootLayout.d.ts.map +1 -1
- package/dist/host/layout/templates/rootLayout.js +25 -7
- package/dist/host/layout/templates/rootLayout.js.map +1 -1
- package/dist/host/orchestrator/templates.d.ts.map +1 -1
- package/dist/host/orchestrator/templates.js +1 -11
- package/dist/host/orchestrator/templates.js.map +1 -1
- package/dist/host/templateRegistry/index.d.ts.map +1 -1
- package/dist/host/templateRegistry/index.js +2 -17
- package/dist/host/templateRegistry/index.js.map +1 -1
- package/dist/index.d.ts +9 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +83 -59
- package/dist/index.js.map +1 -1
- package/dist/manifestNavigatorPreviewModel.js +1 -1
- package/dist/manifestNavigatorPreviewModel.js.map +1 -1
- package/dist/manifestState.d.ts +59 -3
- package/dist/manifestState.d.ts.map +1 -1
- package/dist/manifestState.js +364 -229
- package/dist/manifestState.js.map +1 -1
- package/dist/routeUtils.d.ts +1 -2
- package/dist/routeUtils.d.ts.map +1 -1
- package/dist/routeUtils.js +6 -44
- package/dist/routeUtils.js.map +1 -1
- package/dist/runtime/runtimeNodeMeasurement.d.ts +6 -3
- package/dist/runtime/runtimeNodeMeasurement.d.ts.map +1 -1
- package/dist/runtime/runtimeNodeMeasurement.js +26 -11
- package/dist/runtime/runtimeNodeMeasurement.js.map +1 -1
- package/dist/runtime/stationarySelection.d.ts +10 -0
- package/dist/runtime/stationarySelection.d.ts.map +1 -1
- package/dist/runtime/stationarySelection.js +69 -20
- package/dist/runtime/stationarySelection.js.map +1 -1
- package/dist/studioAdminRouteModel.d.ts +7 -1
- package/dist/studioAdminRouteModel.d.ts.map +1 -1
- package/dist/studioAdminRouteModel.js +54 -0
- package/dist/studioAdminRouteModel.js.map +1 -1
- package/dist/ui/StudioDeleteDialog.d.ts +9 -0
- package/dist/ui/StudioDeleteDialog.d.ts.map +1 -0
- package/dist/ui/StudioDeleteDialog.js +7 -0
- package/dist/ui/StudioDeleteDialog.js.map +1 -0
- package/dist/ui/StudioInsertDialog.d.ts +14 -0
- package/dist/ui/StudioInsertDialog.d.ts.map +1 -0
- package/dist/ui/StudioInsertDialog.js +28 -0
- package/dist/ui/StudioInsertDialog.js.map +1 -0
- package/dist/ui/admin/AnkhAdminPage.d.ts.map +1 -1
- package/dist/ui/admin/AnkhAdminPage.js +5 -1
- package/dist/ui/admin/AnkhAdminPage.js.map +1 -1
- package/dist/ui/admin/AnkhAdminShell.d.ts.map +1 -1
- package/dist/ui/admin/AnkhAdminShell.js +5 -2
- package/dist/ui/admin/AnkhAdminShell.js.map +1 -1
- package/dist/ui/admin/pages/ScreenDetailAdminPage.d.ts +6 -0
- package/dist/ui/admin/pages/ScreenDetailAdminPage.d.ts.map +1 -0
- package/dist/ui/admin/pages/ScreenDetailAdminPage.js +86 -0
- package/dist/ui/admin/pages/ScreenDetailAdminPage.js.map +1 -0
- package/dist/ui/admin/pages/ScreensAdminPage.d.ts +3 -0
- package/dist/ui/admin/pages/ScreensAdminPage.d.ts.map +1 -0
- package/dist/ui/admin/pages/ScreensAdminPage.js +99 -0
- package/dist/ui/admin/pages/ScreensAdminPage.js.map +1 -0
- package/dist/ui/admin/pages/screens/screensAdminActions.d.ts +28 -0
- package/dist/ui/admin/pages/screens/screensAdminActions.d.ts.map +1 -0
- package/dist/ui/admin/pages/screens/screensAdminActions.js +22 -0
- package/dist/ui/admin/pages/screens/screensAdminActions.js.map +1 -0
- package/dist/ui/canvas/StudioCanvasDndOverlay.d.ts +15 -0
- package/dist/ui/canvas/StudioCanvasDndOverlay.d.ts.map +1 -0
- package/dist/ui/canvas/StudioCanvasDndOverlay.js +128 -0
- package/dist/ui/canvas/StudioCanvasDndOverlay.js.map +1 -0
- package/dist/ui/canvas/canvasDndInteraction.d.ts +20 -0
- package/dist/ui/canvas/canvasDndInteraction.d.ts.map +1 -0
- package/dist/ui/canvas/canvasDndInteraction.js +32 -0
- package/dist/ui/canvas/canvasDndInteraction.js.map +1 -0
- package/dist/ui/studioAppBarModel.d.ts +4 -1
- package/dist/ui/studioAppBarModel.d.ts.map +1 -1
- package/dist/ui/studioAppBarModel.js +6 -0
- package/dist/ui/studioAppBarModel.js.map +1 -1
- package/dist/ui/useStudioAppBarAugmentation.d.ts +1 -0
- package/dist/ui/useStudioAppBarAugmentation.d.ts.map +1 -1
- package/dist/ui/useStudioAppBarAugmentation.js +86 -15
- package/dist/ui/useStudioAppBarAugmentation.js.map +1 -1
- package/dist/utils/treeUtils.d.ts +2 -2
- package/dist/utils/treeUtils.d.ts.map +1 -1
- package/dist/utils/treeUtils.js +2 -2
- package/dist/utils/treeUtils.js.map +1 -1
- package/package.json +3 -15
- package/dist/dnd/index.d.ts +0 -3
- package/dist/dnd/index.d.ts.map +0 -1
- package/dist/dnd/index.js +0 -3
- package/dist/dnd/index.js.map +0 -1
- package/dist/dnd/primitives.d.ts +0 -2
- package/dist/dnd/primitives.d.ts.map +0 -1
- package/dist/dnd/primitives.js +0 -2
- package/dist/dnd/primitives.js.map +0 -1
- package/dist/dnd/state.d.ts +0 -6
- package/dist/dnd/state.d.ts.map +0 -1
- package/dist/dnd/state.js +0 -5
- package/dist/dnd/state.js.map +0 -1
package/dist/manifestState.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { NAVIGATOR_TYPES } from '@ankhorage/contracts';
|
|
2
|
+
import { insertNodeAtPlacement, moveNodeToPlacement } from './index';
|
|
1
3
|
export const DEFAULT_STUDIO_SCREEN_TEMPLATE = {
|
|
2
4
|
id: 'tpl-screen-empty',
|
|
3
5
|
type: 'Screen',
|
|
@@ -103,6 +105,215 @@ export function groupScreenRouteEntries(entries) {
|
|
|
103
105
|
}
|
|
104
106
|
return Array.from(groups.values());
|
|
105
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Verifies the Studio manifest invariant that route registry keys and stable
|
|
110
|
+
* ScreenSpec identities are the same unique value.
|
|
111
|
+
*/
|
|
112
|
+
export function hasCanonicalStudioScreenRegistryIdentity(screens) {
|
|
113
|
+
const screenIds = new Set();
|
|
114
|
+
for (const [registryKey, screen] of Object.entries(screens)) {
|
|
115
|
+
if (registryKey !== screen.id || screenIds.has(screen.id))
|
|
116
|
+
return false;
|
|
117
|
+
screenIds.add(screen.id);
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
function resolveCanonicalStudioScreen(manifest, screenId) {
|
|
122
|
+
if (!hasCanonicalStudioScreenRegistryIdentity(manifest.screens))
|
|
123
|
+
return undefined;
|
|
124
|
+
return manifest.screens[screenId];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Derives the complete package-neutral screen/navigation authoring model from
|
|
128
|
+
* the manifest without flattening its navigator tree.
|
|
129
|
+
*/
|
|
130
|
+
export function deriveStudioScreenNavigationModel(manifest) {
|
|
131
|
+
const primaryNavigatorPath = getPrimaryNavigatorPath(manifest.navigator.routes);
|
|
132
|
+
const primaryNavigator = findNavigatorAtPath(manifest.navigator, primaryNavigatorPath) ?? manifest.navigator;
|
|
133
|
+
const diagnostics = [];
|
|
134
|
+
const referencesByRegistryKey = new Map();
|
|
135
|
+
collectNavigationModelReferences({
|
|
136
|
+
navigator: manifest.navigator,
|
|
137
|
+
screens: manifest.screens,
|
|
138
|
+
primaryNavigatorPath,
|
|
139
|
+
parentPath: [],
|
|
140
|
+
routePathPrefix: [],
|
|
141
|
+
runtimePathPrefix: [],
|
|
142
|
+
diagnostics,
|
|
143
|
+
referencesByRegistryKey,
|
|
144
|
+
});
|
|
145
|
+
const screenIdCounts = new Map();
|
|
146
|
+
for (const screen of Object.values(manifest.screens)) {
|
|
147
|
+
screenIdCounts.set(screen.id, (screenIdCounts.get(screen.id) ?? 0) + 1);
|
|
148
|
+
}
|
|
149
|
+
const reportedDuplicateScreenIds = new Set();
|
|
150
|
+
const screens = Object.entries(manifest.screens).map(([registryKey, screen]) => {
|
|
151
|
+
const screenId = screen.id;
|
|
152
|
+
const routeReferences = referencesByRegistryKey.get(registryKey) ?? [];
|
|
153
|
+
if (registryKey !== screenId) {
|
|
154
|
+
diagnostics.push({
|
|
155
|
+
code: 'screen-registry-key-mismatch',
|
|
156
|
+
message: `Screen registry key "${registryKey}" does not match stable ScreenSpec.id "${screenId}".`,
|
|
157
|
+
parentPath: [],
|
|
158
|
+
screenId,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if ((screenIdCounts.get(screenId) ?? 0) > 1 && !reportedDuplicateScreenIds.has(screenId)) {
|
|
162
|
+
reportedDuplicateScreenIds.add(screenId);
|
|
163
|
+
diagnostics.push({
|
|
164
|
+
code: 'duplicate-screen-id',
|
|
165
|
+
message: `Stable ScreenSpec.id "${screenId}" is used by multiple screen registry entries.`,
|
|
166
|
+
parentPath: [],
|
|
167
|
+
screenId,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (routeReferences.length > 1) {
|
|
171
|
+
diagnostics.push({
|
|
172
|
+
code: 'ambiguous-screen-route-reference',
|
|
173
|
+
message: `Screen "${screenId}" is referenced by ${routeReferences.length} routes.`,
|
|
174
|
+
parentPath: [],
|
|
175
|
+
screenId,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return { screenId, screen, routeReferences };
|
|
179
|
+
});
|
|
180
|
+
return {
|
|
181
|
+
primaryNavigatorPath,
|
|
182
|
+
primaryNavigator,
|
|
183
|
+
screens,
|
|
184
|
+
diagnostics,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Returns a concrete canonical app pathname only when a screen has one route
|
|
189
|
+
* reference and that route does not require runtime parameters.
|
|
190
|
+
*/
|
|
191
|
+
export function resolveStudioScreenAppPath(model, screenId) {
|
|
192
|
+
const matchingEntries = model.screens.filter((candidate) => candidate.screenId === screenId);
|
|
193
|
+
if (matchingEntries.length !== 1)
|
|
194
|
+
return null;
|
|
195
|
+
const [entry] = matchingEntries;
|
|
196
|
+
if (!entry)
|
|
197
|
+
return null;
|
|
198
|
+
if (entry.routeReferences.length !== 1)
|
|
199
|
+
return null;
|
|
200
|
+
const [reference] = entry.routeReferences;
|
|
201
|
+
if (!reference)
|
|
202
|
+
return null;
|
|
203
|
+
const hasDynamicSegment = reference.pathnamePattern
|
|
204
|
+
.split('/')
|
|
205
|
+
.some((segment) => segment.startsWith(':') || /^\[.*\]$/u.test(segment));
|
|
206
|
+
if (hasDynamicSegment)
|
|
207
|
+
return null;
|
|
208
|
+
const matchingReferences = model.screens.flatMap((candidate) => candidate.routeReferences.filter((candidateReference) => candidateReference.pathnamePattern === reference.pathnamePattern));
|
|
209
|
+
return matchingReferences.length === 1 ? reference.pathnamePattern : null;
|
|
210
|
+
}
|
|
211
|
+
function collectNavigationModelReferences(args) {
|
|
212
|
+
const { navigator, screens, primaryNavigatorPath, parentPath, routePathPrefix, runtimePathPrefix, diagnostics, referencesByRegistryKey, } = args;
|
|
213
|
+
const siblingNameCounts = new Map();
|
|
214
|
+
for (const route of navigator.routes) {
|
|
215
|
+
siblingNameCounts.set(route.name, (siblingNameCounts.get(route.name) ?? 0) + 1);
|
|
216
|
+
}
|
|
217
|
+
if (navigator.routes.length === 0) {
|
|
218
|
+
diagnostics.push({
|
|
219
|
+
code: 'empty-navigator',
|
|
220
|
+
message: `Navigator at "${formatParentPath(parentPath)}" has no routes.`,
|
|
221
|
+
parentPath,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
if (navigator.initialRouteName !== undefined &&
|
|
225
|
+
siblingNameCounts.get(navigator.initialRouteName) !== 1) {
|
|
226
|
+
diagnostics.push({
|
|
227
|
+
code: 'invalid-initial-route',
|
|
228
|
+
message: `Initial route "${navigator.initialRouteName}" is not one unambiguous direct route of "${formatParentPath(parentPath)}".`,
|
|
229
|
+
parentPath,
|
|
230
|
+
routeName: navigator.initialRouteName,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
for (const [siblingIndex, route] of navigator.routes.entries()) {
|
|
234
|
+
const routePath = [...routePathPrefix, route.name];
|
|
235
|
+
const runtimePath = appendRuntimeRoutePath(runtimePathPrefix, route);
|
|
236
|
+
const isPrimaryNavigatorMember = pathsEqual(parentPath, primaryNavigatorPath);
|
|
237
|
+
if ((siblingNameCounts.get(route.name) ?? 0) > 1 &&
|
|
238
|
+
navigator.routes.findIndex((candidate) => candidate.name === route.name) === siblingIndex) {
|
|
239
|
+
diagnostics.push({
|
|
240
|
+
code: 'duplicate-sibling-route-name',
|
|
241
|
+
message: `Route name "${route.name}" is duplicated under "${formatParentPath(parentPath)}".`,
|
|
242
|
+
parentPath,
|
|
243
|
+
routeName: route.name,
|
|
244
|
+
screenId: route.screenId,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (!route.screenId && !route.navigator) {
|
|
248
|
+
diagnostics.push({
|
|
249
|
+
code: 'missing-route-target',
|
|
250
|
+
message: `Route "${route.name}" has neither a screen nor a nested navigator.`,
|
|
251
|
+
parentPath,
|
|
252
|
+
routeName: route.name,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
else if (route.screenId && route.navigator) {
|
|
256
|
+
diagnostics.push({
|
|
257
|
+
code: 'ambiguous-route-target',
|
|
258
|
+
message: `Route "${route.name}" targets both a screen and a nested navigator.`,
|
|
259
|
+
parentPath,
|
|
260
|
+
routeName: route.name,
|
|
261
|
+
screenId: route.screenId,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
if (route.screenId) {
|
|
265
|
+
if (!screens[route.screenId]) {
|
|
266
|
+
diagnostics.push({
|
|
267
|
+
code: 'missing-screen-reference',
|
|
268
|
+
message: `Route "${route.name}" references missing screen "${route.screenId}".`,
|
|
269
|
+
parentPath,
|
|
270
|
+
routeName: route.name,
|
|
271
|
+
screenId: route.screenId,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
const reference = {
|
|
276
|
+
route,
|
|
277
|
+
parentPath,
|
|
278
|
+
routePath,
|
|
279
|
+
pathnamePattern: toCanonicalRoutePattern(runtimePath),
|
|
280
|
+
siblingIndex,
|
|
281
|
+
navigatorType: navigator.type,
|
|
282
|
+
isPrimaryNavigatorMember,
|
|
283
|
+
showInPrimaryNavigation: route.showInPrimaryNavigation !== false,
|
|
284
|
+
isPrimaryInitialRoute: isPrimaryNavigatorMember && navigator.initialRouteName === route.name,
|
|
285
|
+
};
|
|
286
|
+
const currentReferences = referencesByRegistryKey.get(route.screenId) ?? [];
|
|
287
|
+
currentReferences.push(reference);
|
|
288
|
+
referencesByRegistryKey.set(route.screenId, currentReferences);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (route.navigator) {
|
|
292
|
+
collectNavigationModelReferences({
|
|
293
|
+
navigator: route.navigator,
|
|
294
|
+
screens,
|
|
295
|
+
primaryNavigatorPath,
|
|
296
|
+
parentPath: routePath,
|
|
297
|
+
routePathPrefix: routePath,
|
|
298
|
+
runtimePathPrefix: runtimePath,
|
|
299
|
+
diagnostics,
|
|
300
|
+
referencesByRegistryKey,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function appendRuntimeRoutePath(runtimePathPrefix, route) {
|
|
306
|
+
const explicitPath = route.path?.trim();
|
|
307
|
+
const segmentPath = explicitPath ?? route.name;
|
|
308
|
+
const segments = segmentPath.split('/').filter(Boolean);
|
|
309
|
+
return explicitPath?.startsWith('/') ? segments : [...runtimePathPrefix, ...segments];
|
|
310
|
+
}
|
|
311
|
+
function pathsEqual(first, second) {
|
|
312
|
+
return first.length === second.length && first.every((value, index) => value === second[index]);
|
|
313
|
+
}
|
|
314
|
+
function formatParentPath(parentPath) {
|
|
315
|
+
return parentPath.length === 0 ? '<root>' : parentPath.join('/');
|
|
316
|
+
}
|
|
106
317
|
export function listScreenIdsInRouteOrder(routes) {
|
|
107
318
|
return collectScreenRouteEntries(routes).map((entry) => entry.screenId);
|
|
108
319
|
}
|
|
@@ -112,6 +323,8 @@ export function listScreenIdsInRouteOrder(routes) {
|
|
|
112
323
|
* back to the first route that reaches an available screen.
|
|
113
324
|
*/
|
|
114
325
|
export function resolveInitialScreenId(navigator, screens) {
|
|
326
|
+
if (screens && !hasCanonicalStudioScreenRegistryIdentity(screens))
|
|
327
|
+
return null;
|
|
115
328
|
const initialRoute = navigator.initialRouteName
|
|
116
329
|
? navigator.routes.find((route) => route.name === navigator.initialRouteName)
|
|
117
330
|
: undefined;
|
|
@@ -125,22 +338,22 @@ export function resolveInitialScreenId(navigator, screens) {
|
|
|
125
338
|
return nestedScreenId;
|
|
126
339
|
}
|
|
127
340
|
if (route.screenId && (!screens || screens[route.screenId])) {
|
|
128
|
-
return route.screenId;
|
|
341
|
+
return screens ? (screens[route.screenId]?.id ?? null) : route.screenId;
|
|
129
342
|
}
|
|
130
343
|
}
|
|
131
344
|
return null;
|
|
132
345
|
}
|
|
133
346
|
export function resolveInitialActiveScreenId(manifest) {
|
|
134
|
-
if (!manifest)
|
|
347
|
+
if (!manifest || !hasCanonicalStudioScreenRegistryIdentity(manifest.screens))
|
|
135
348
|
return null;
|
|
136
349
|
const firstRoutedScreenId = resolveInitialScreenId(manifest.navigator, manifest.screens);
|
|
137
|
-
const [firstScreenId] = Object.
|
|
350
|
+
const [firstScreenId] = Object.values(manifest.screens).map((screen) => screen.id);
|
|
138
351
|
return firstRoutedScreenId ?? firstScreenId ?? null;
|
|
139
352
|
}
|
|
140
353
|
export function resolveActiveRootNode(manifest, activeScreenId) {
|
|
141
354
|
if (!manifest || !activeScreenId)
|
|
142
355
|
return null;
|
|
143
|
-
return manifest
|
|
356
|
+
return resolveCanonicalStudioScreen(manifest, activeScreenId)?.root ?? null;
|
|
144
357
|
}
|
|
145
358
|
export function findNodeInManifest(root, id) {
|
|
146
359
|
if (root.id === id)
|
|
@@ -158,9 +371,11 @@ export function resolveSafeSelectedNodeId(rootNode, selectedNodeId) {
|
|
|
158
371
|
return findNodeInManifest(rootNode, selectedNodeId) ? selectedNodeId : null;
|
|
159
372
|
}
|
|
160
373
|
export function findScreenIdForNode(manifest, nodeId) {
|
|
161
|
-
|
|
374
|
+
if (!hasCanonicalStudioScreenRegistryIdentity(manifest.screens))
|
|
375
|
+
return null;
|
|
376
|
+
for (const screen of Object.values(manifest.screens)) {
|
|
162
377
|
if (findNodeInManifest(screen.root, nodeId)) {
|
|
163
|
-
return
|
|
378
|
+
return screen.id;
|
|
164
379
|
}
|
|
165
380
|
}
|
|
166
381
|
return null;
|
|
@@ -168,7 +383,7 @@ export function findScreenIdForNode(manifest, nodeId) {
|
|
|
168
383
|
export function updateStudioManifestNode(manifest, activeScreenId, nodeId, newProps) {
|
|
169
384
|
if (!activeScreenId)
|
|
170
385
|
return manifest;
|
|
171
|
-
const screen = manifest
|
|
386
|
+
const screen = resolveCanonicalStudioScreen(manifest, activeScreenId);
|
|
172
387
|
if (!screen)
|
|
173
388
|
return manifest;
|
|
174
389
|
const newRoot = updateNodeInManifestTree(screen.root, nodeId, newProps);
|
|
@@ -188,13 +403,17 @@ export function updateStudioManifestNode(manifest, activeScreenId, nodeId, newPr
|
|
|
188
403
|
export function deleteStudioManifestNode(manifest, activeScreenId, nodeId) {
|
|
189
404
|
if (!activeScreenId)
|
|
190
405
|
return manifest;
|
|
191
|
-
const screen = manifest
|
|
406
|
+
const screen = resolveCanonicalStudioScreen(manifest, activeScreenId);
|
|
192
407
|
if (!screen || screen.root.id === nodeId)
|
|
193
408
|
return manifest;
|
|
409
|
+
const deletedNode = findNodeInManifest(screen.root, nodeId);
|
|
410
|
+
if (!deletedNode)
|
|
411
|
+
return manifest;
|
|
194
412
|
const newRoot = removeNodeFromManifestTree(screen.root, nodeId);
|
|
195
413
|
if (!newRoot || newRoot === screen.root)
|
|
196
414
|
return manifest;
|
|
197
|
-
const
|
|
415
|
+
const deletedNodeIds = collectNodeIds(deletedNode);
|
|
416
|
+
const nextDataBindings = Object.fromEntries(Object.entries(manifest.dataBindings ?? {}).filter(([componentId, binding]) => !deletedNodeIds.has(componentId) && !deletedNodeIds.has(binding.componentId)));
|
|
198
417
|
return {
|
|
199
418
|
...manifest,
|
|
200
419
|
dataBindings: nextDataBindings,
|
|
@@ -207,42 +426,20 @@ export function deleteStudioManifestNode(manifest, activeScreenId, nodeId) {
|
|
|
207
426
|
},
|
|
208
427
|
};
|
|
209
428
|
}
|
|
210
|
-
export function moveStudioManifestNode(manifest, activeScreenId, nodeId, direction) {
|
|
211
|
-
if (!activeScreenId)
|
|
212
|
-
return manifest;
|
|
213
|
-
const screen = manifest.screens[activeScreenId];
|
|
214
|
-
if (!screen)
|
|
215
|
-
return manifest;
|
|
216
|
-
const newRoot = moveNodeInManifestTree(screen.root, nodeId, direction);
|
|
217
|
-
if (newRoot === screen.root)
|
|
218
|
-
return manifest;
|
|
219
|
-
return {
|
|
220
|
-
...manifest,
|
|
221
|
-
screens: {
|
|
222
|
-
...manifest.screens,
|
|
223
|
-
[activeScreenId]: {
|
|
224
|
-
...screen,
|
|
225
|
-
root: newRoot,
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
429
|
export function insertStudioManifestNodeAtPlacement(args) {
|
|
231
430
|
const { manifest, activeScreenId, placement, newNode, componentMeta } = args;
|
|
232
431
|
if (!activeScreenId)
|
|
233
432
|
return null;
|
|
234
|
-
const screen = manifest
|
|
433
|
+
const screen = resolveCanonicalStudioScreen(manifest, activeScreenId);
|
|
235
434
|
if (!screen)
|
|
236
435
|
return null;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
index: placement.index,
|
|
243
|
-
newNode,
|
|
436
|
+
const insertion = insertNodeAtPlacement({
|
|
437
|
+
root: screen.root,
|
|
438
|
+
placement,
|
|
439
|
+
componentMeta,
|
|
440
|
+
makeNode: () => newNode,
|
|
244
441
|
});
|
|
245
|
-
if (!insertion
|
|
442
|
+
if (!insertion)
|
|
246
443
|
return null;
|
|
247
444
|
return {
|
|
248
445
|
manifest: {
|
|
@@ -251,7 +448,7 @@ export function insertStudioManifestNodeAtPlacement(args) {
|
|
|
251
448
|
...manifest.screens,
|
|
252
449
|
[activeScreenId]: {
|
|
253
450
|
...screen,
|
|
254
|
-
root: insertion.
|
|
451
|
+
root: insertion.root,
|
|
255
452
|
},
|
|
256
453
|
},
|
|
257
454
|
},
|
|
@@ -262,32 +459,16 @@ export function moveStudioManifestNodeToPlacement(args) {
|
|
|
262
459
|
const { manifest, activeScreenId, nodeId, placement, componentMeta } = args;
|
|
263
460
|
if (!activeScreenId)
|
|
264
461
|
return null;
|
|
265
|
-
const screen = manifest
|
|
462
|
+
const screen = resolveCanonicalStudioScreen(manifest, activeScreenId);
|
|
266
463
|
if (!screen)
|
|
267
464
|
return null;
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (isDescendantNode(source.node, placement.parentId))
|
|
274
|
-
return null;
|
|
275
|
-
const adjustedPlacement = adjustMovePlacement({ source, placement });
|
|
276
|
-
if (!adjustedPlacement)
|
|
277
|
-
return null;
|
|
278
|
-
const removed = removeNodeForMove({ node: screen.root, nodeId });
|
|
279
|
-
if (!removed.removedNode)
|
|
280
|
-
return null;
|
|
281
|
-
if (!validateManifestNodePlacement(removed.node, adjustedPlacement, removed.removedNode.type, componentMeta)) {
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
const inserted = insertChildAtIndex({
|
|
285
|
-
node: removed.node,
|
|
286
|
-
parentId: adjustedPlacement.parentId,
|
|
287
|
-
index: adjustedPlacement.index,
|
|
288
|
-
newNode: removed.removedNode,
|
|
465
|
+
const movement = moveNodeToPlacement({
|
|
466
|
+
root: screen.root,
|
|
467
|
+
nodeId,
|
|
468
|
+
placement,
|
|
469
|
+
componentMeta,
|
|
289
470
|
});
|
|
290
|
-
if (!
|
|
471
|
+
if (!movement)
|
|
291
472
|
return null;
|
|
292
473
|
return {
|
|
293
474
|
manifest: {
|
|
@@ -296,11 +477,11 @@ export function moveStudioManifestNodeToPlacement(args) {
|
|
|
296
477
|
...manifest.screens,
|
|
297
478
|
[activeScreenId]: {
|
|
298
479
|
...screen,
|
|
299
|
-
root:
|
|
480
|
+
root: movement.root,
|
|
300
481
|
},
|
|
301
482
|
},
|
|
302
483
|
},
|
|
303
|
-
movedNodeId:
|
|
484
|
+
movedNodeId: movement.movedNodeId,
|
|
304
485
|
};
|
|
305
486
|
}
|
|
306
487
|
export function updateStudioManifestDataBindings(manifest, dataBindings) {
|
|
@@ -453,7 +634,10 @@ export function findRoutesAtParentPath(routes, parentPath) {
|
|
|
453
634
|
const [segment, ...rest] = parentPath;
|
|
454
635
|
if (!segment)
|
|
455
636
|
return null;
|
|
456
|
-
const
|
|
637
|
+
const matches = routes.filter((item) => item.name === segment);
|
|
638
|
+
if (matches.length !== 1)
|
|
639
|
+
return null;
|
|
640
|
+
const [route] = matches;
|
|
457
641
|
if (!route?.navigator?.routes)
|
|
458
642
|
return null;
|
|
459
643
|
return findRoutesAtParentPath(route.navigator.routes, rest);
|
|
@@ -462,8 +646,12 @@ export function insertRouteAtParentPath(routes, parentPath, newRoute) {
|
|
|
462
646
|
if (parentPath.length === 0)
|
|
463
647
|
return [...routes, newRoute];
|
|
464
648
|
const [segment, ...rest] = parentPath;
|
|
649
|
+
const matches = routes.filter((route) => route.name === segment && route.navigator);
|
|
650
|
+
if (matches.length !== 1)
|
|
651
|
+
return routes;
|
|
652
|
+
const [targetRoute] = matches;
|
|
465
653
|
return routes.map((route) => {
|
|
466
|
-
if (route
|
|
654
|
+
if (route !== targetRoute || !route.navigator?.routes)
|
|
467
655
|
return route;
|
|
468
656
|
return {
|
|
469
657
|
...route,
|
|
@@ -480,7 +668,10 @@ export function findNavigatorAtPath(navigator, parentPath) {
|
|
|
480
668
|
const [segment, ...rest] = parentPath;
|
|
481
669
|
if (!segment)
|
|
482
670
|
return null;
|
|
483
|
-
const
|
|
671
|
+
const matches = navigator.routes.filter((item) => item.name === segment);
|
|
672
|
+
if (matches.length !== 1)
|
|
673
|
+
return null;
|
|
674
|
+
const [route] = matches;
|
|
484
675
|
if (!route?.navigator)
|
|
485
676
|
return null;
|
|
486
677
|
return findNavigatorAtPath(route.navigator, rest);
|
|
@@ -489,10 +680,14 @@ export function updateNavigatorAtPath(navigator, parentPath, updater) {
|
|
|
489
680
|
if (parentPath.length === 0)
|
|
490
681
|
return updater(navigator);
|
|
491
682
|
const [segment, ...rest] = parentPath;
|
|
683
|
+
const matches = navigator.routes.filter((route) => route.name === segment && route.navigator);
|
|
684
|
+
if (matches.length !== 1)
|
|
685
|
+
return navigator;
|
|
686
|
+
const [targetRoute] = matches;
|
|
492
687
|
return {
|
|
493
688
|
...navigator,
|
|
494
689
|
routes: navigator.routes.map((route) => {
|
|
495
|
-
if (route
|
|
690
|
+
if (route !== targetRoute || !route.navigator)
|
|
496
691
|
return route;
|
|
497
692
|
return {
|
|
498
693
|
...route,
|
|
@@ -502,6 +697,8 @@ export function updateNavigatorAtPath(navigator, parentPath, updater) {
|
|
|
502
697
|
};
|
|
503
698
|
}
|
|
504
699
|
export function setStudioManifestNavigatorType(manifest, type) {
|
|
700
|
+
if (!NAVIGATOR_TYPES.includes(type))
|
|
701
|
+
return manifest;
|
|
505
702
|
const primaryNavigatorPath = getPrimaryNavigatorPath(manifest.navigator.routes);
|
|
506
703
|
const currentNavigator = findNavigatorAtPath(manifest.navigator, primaryNavigatorPath);
|
|
507
704
|
if (!currentNavigator || currentNavigator.type === type)
|
|
@@ -522,8 +719,9 @@ export function setStudioManifestNavigatorInitialRoute(manifest, routeName) {
|
|
|
522
719
|
const currentNavigator = findNavigatorAtPath(manifest.navigator, primaryNavigatorPath);
|
|
523
720
|
if (!currentNavigator)
|
|
524
721
|
return manifest;
|
|
525
|
-
if (
|
|
722
|
+
if (currentNavigator.routes.filter((route) => route.name === normalizedRoute).length !== 1) {
|
|
526
723
|
return manifest;
|
|
724
|
+
}
|
|
527
725
|
if (currentNavigator.initialRouteName === normalizedRoute)
|
|
528
726
|
return manifest;
|
|
529
727
|
return {
|
|
@@ -534,26 +732,82 @@ export function setStudioManifestNavigatorInitialRoute(manifest, routeName) {
|
|
|
534
732
|
})),
|
|
535
733
|
};
|
|
536
734
|
}
|
|
735
|
+
export function setStudioManifestRoutePrimaryNavigationVisibility(args) {
|
|
736
|
+
const { manifest, parentPath, routeName, showInPrimaryNavigation } = args;
|
|
737
|
+
const navigator = findNavigatorAtPath(manifest.navigator, parentPath);
|
|
738
|
+
if (!navigator)
|
|
739
|
+
return manifest;
|
|
740
|
+
const matches = navigator.routes.filter((route) => route.name === routeName);
|
|
741
|
+
if (matches.length !== 1)
|
|
742
|
+
return manifest;
|
|
743
|
+
const [matchedRoute] = matches;
|
|
744
|
+
if (!matchedRoute)
|
|
745
|
+
return manifest;
|
|
746
|
+
const alreadyCanonical = showInPrimaryNavigation
|
|
747
|
+
? matchedRoute.showInPrimaryNavigation === undefined
|
|
748
|
+
: matchedRoute.showInPrimaryNavigation === false;
|
|
749
|
+
if (alreadyCanonical)
|
|
750
|
+
return manifest;
|
|
751
|
+
return {
|
|
752
|
+
...manifest,
|
|
753
|
+
navigator: updateNavigatorAtPath(manifest.navigator, parentPath, (current) => ({
|
|
754
|
+
...current,
|
|
755
|
+
routes: current.routes.map((route) => {
|
|
756
|
+
if (route !== matchedRoute)
|
|
757
|
+
return route;
|
|
758
|
+
if (!showInPrimaryNavigation) {
|
|
759
|
+
return { ...route, showInPrimaryNavigation: false };
|
|
760
|
+
}
|
|
761
|
+
const { showInPrimaryNavigation: _visibility, ...visibleRoute } = route;
|
|
762
|
+
return visibleRoute;
|
|
763
|
+
}),
|
|
764
|
+
})),
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
export function moveStudioManifestRoute(args) {
|
|
768
|
+
const { manifest, parentPath, routeName, toIndex } = args;
|
|
769
|
+
if (!Number.isInteger(toIndex))
|
|
770
|
+
return manifest;
|
|
771
|
+
const navigator = findNavigatorAtPath(manifest.navigator, parentPath);
|
|
772
|
+
if (!navigator || toIndex < 0 || toIndex >= navigator.routes.length)
|
|
773
|
+
return manifest;
|
|
774
|
+
const matchingIndexes = navigator.routes.flatMap((route, index) => route.name === routeName ? [index] : []);
|
|
775
|
+
if (matchingIndexes.length !== 1)
|
|
776
|
+
return manifest;
|
|
777
|
+
const [fromIndex] = matchingIndexes;
|
|
778
|
+
if (fromIndex === undefined || fromIndex === toIndex)
|
|
779
|
+
return manifest;
|
|
780
|
+
return {
|
|
781
|
+
...manifest,
|
|
782
|
+
navigator: updateNavigatorAtPath(manifest.navigator, parentPath, (current) => {
|
|
783
|
+
const routes = [...current.routes];
|
|
784
|
+
const [route] = routes.splice(fromIndex, 1);
|
|
785
|
+
if (!route)
|
|
786
|
+
return current;
|
|
787
|
+
routes.splice(toIndex, 0, route);
|
|
788
|
+
return { ...current, routes };
|
|
789
|
+
}),
|
|
790
|
+
};
|
|
791
|
+
}
|
|
537
792
|
export function addStudioManifestScreen(args) {
|
|
538
793
|
const { manifest, activeScreenId, createId = generateManifestStateId } = args;
|
|
794
|
+
if (!hasCanonicalStudioScreenRegistryIdentity(manifest.screens)) {
|
|
795
|
+
return { manifest, activeScreenId };
|
|
796
|
+
}
|
|
539
797
|
const trimmedName = args.name.trim();
|
|
540
798
|
if (!trimmedName)
|
|
541
799
|
return { manifest, activeScreenId };
|
|
542
800
|
const baseRouteName = normalizeRouteName(trimmedName);
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
const
|
|
548
|
-
? findRoutesAtParentPath(manifest.navigator.routes, activeParentPath)
|
|
549
|
-
: null;
|
|
550
|
-
const parentPath = activeParentPath && activeParentRoutes ? activeParentPath : fallbackParentPath;
|
|
551
|
-
const siblingRoutes = findRoutesAtParentPath(manifest.navigator.routes, parentPath) ?? [];
|
|
801
|
+
const parentPath = args.parentPath ?? getPrimaryNavigatorPath(manifest.navigator.routes);
|
|
802
|
+
const siblingRoutes = findRoutesAtParentPath(manifest.navigator.routes, parentPath);
|
|
803
|
+
if (!siblingRoutes)
|
|
804
|
+
return { manifest, activeScreenId };
|
|
805
|
+
const existingScreenIds = new Set(Object.values(manifest.screens).map((screen) => screen.id));
|
|
552
806
|
let screenId = createId('Screen');
|
|
553
|
-
while (manifest.screens[screenId]) {
|
|
807
|
+
while (manifest.screens[screenId] || existingScreenIds.has(screenId)) {
|
|
554
808
|
screenId = createId('Screen');
|
|
555
809
|
}
|
|
556
|
-
const existingPatterns = new Set(
|
|
810
|
+
const existingPatterns = new Set(collectCanonicalRoutePatterns(manifest.navigator.routes));
|
|
557
811
|
const routeName = makeUniqueRouteNameForParent(baseRouteName, siblingRoutes, parentPath, existingPatterns);
|
|
558
812
|
const newScreen = {
|
|
559
813
|
id: screenId,
|
|
@@ -581,46 +835,33 @@ export function addStudioManifestScreen(args) {
|
|
|
581
835
|
};
|
|
582
836
|
}
|
|
583
837
|
export function deleteStudioManifestScreen(manifest, screenId, activeScreenId) {
|
|
838
|
+
if (!hasCanonicalStudioScreenRegistryIdentity(manifest.screens)) {
|
|
839
|
+
return { manifest, activeScreenId };
|
|
840
|
+
}
|
|
584
841
|
if (Object.keys(manifest.screens).length <= 1)
|
|
585
842
|
return { manifest, activeScreenId };
|
|
843
|
+
const deletedScreen = manifest.screens[screenId];
|
|
844
|
+
if (!deletedScreen)
|
|
845
|
+
return { manifest, activeScreenId };
|
|
586
846
|
const { [screenId]: _deletedScreen, ...remainingScreens } = manifest.screens;
|
|
587
847
|
const remainingScreenIds = Object.keys(remainingScreens);
|
|
588
|
-
|
|
589
|
-
if (safeRoutes.length === 0 && remainingScreenIds.length > 0) {
|
|
590
|
-
const [fallbackScreenId] = remainingScreenIds;
|
|
591
|
-
if (!fallbackScreenId)
|
|
592
|
-
return { manifest, activeScreenId };
|
|
593
|
-
const fallbackScreen = remainingScreens[fallbackScreenId];
|
|
594
|
-
safeRoutes = [
|
|
595
|
-
{
|
|
596
|
-
name: makeUniqueSiblingRouteName('screen', []),
|
|
597
|
-
label: fallbackScreen?.name ?? 'Screen',
|
|
598
|
-
screenId: fallbackScreenId,
|
|
599
|
-
},
|
|
600
|
-
];
|
|
601
|
-
}
|
|
848
|
+
const safeRoutes = removeScreenIdFromRoutes(manifest.navigator.routes, screenId);
|
|
602
849
|
const orderedScreenIds = listScreenIdsInRouteOrder(safeRoutes).filter((id) => !!remainingScreens[id]);
|
|
603
850
|
const nextActiveScreenId = !activeScreenId || activeScreenId === screenId || !remainingScreens[activeScreenId]
|
|
604
851
|
? (orderedScreenIds[0] ?? remainingScreenIds[0] ?? null)
|
|
605
852
|
: activeScreenId;
|
|
853
|
+
const deletedNodeIds = collectNodeIds(deletedScreen.root);
|
|
854
|
+
const dataBindings = Object.fromEntries(Object.entries(manifest.dataBindings ?? {}).filter(([componentId, binding]) => !deletedNodeIds.has(componentId) && !deletedNodeIds.has(binding.componentId)));
|
|
606
855
|
return {
|
|
607
856
|
activeScreenId: nextActiveScreenId,
|
|
608
857
|
manifest: {
|
|
609
858
|
...manifest,
|
|
859
|
+
dataBindings,
|
|
610
860
|
screens: remainingScreens,
|
|
611
|
-
navigator: {
|
|
861
|
+
navigator: normalizeNavigatorAfterRouteUpdate({
|
|
612
862
|
...manifest.navigator,
|
|
613
863
|
routes: safeRoutes,
|
|
614
|
-
},
|
|
615
|
-
},
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
export function reorderStudioManifestScreens(manifest, newRoutes) {
|
|
619
|
-
return {
|
|
620
|
-
...manifest,
|
|
621
|
-
navigator: {
|
|
622
|
-
...manifest.navigator,
|
|
623
|
-
routes: newRoutes,
|
|
864
|
+
}),
|
|
624
865
|
},
|
|
625
866
|
};
|
|
626
867
|
}
|
|
@@ -686,6 +927,17 @@ function normalizeRouteName(value) {
|
|
|
686
927
|
.replace(/[^a-z0-9]+/g, '-')
|
|
687
928
|
.replace(/^-+|-+$/g, '') || 'screen');
|
|
688
929
|
}
|
|
930
|
+
function collectCanonicalRoutePatterns(routes, runtimePathPrefix = []) {
|
|
931
|
+
const patterns = [];
|
|
932
|
+
for (const route of routes) {
|
|
933
|
+
const runtimePath = appendRuntimeRoutePath(runtimePathPrefix, route);
|
|
934
|
+
patterns.push(toCanonicalRoutePattern(runtimePath));
|
|
935
|
+
if (route.navigator) {
|
|
936
|
+
patterns.push(...collectCanonicalRoutePatterns(route.navigator.routes, runtimePath));
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return patterns;
|
|
940
|
+
}
|
|
689
941
|
function cloneNodeWithNewIds(node, createId) {
|
|
690
942
|
const clonedNode = {
|
|
691
943
|
...node,
|
|
@@ -728,132 +980,15 @@ function removeNodeFromManifestTree(root, nodeId) {
|
|
|
728
980
|
const hasChanged = nextChildren.some((child, index) => child !== root.children?.[index]);
|
|
729
981
|
return hasChanged ? { ...root, children: nextChildren } : root;
|
|
730
982
|
}
|
|
731
|
-
function
|
|
732
|
-
|
|
733
|
-
return root;
|
|
734
|
-
const index = root.children.findIndex((child) => child.id === nodeId);
|
|
735
|
-
if (index !== -1) {
|
|
736
|
-
const targetIndex = direction === 'up' ? index - 1 : index + 1;
|
|
737
|
-
if (targetIndex < 0 || targetIndex >= root.children.length)
|
|
738
|
-
return root;
|
|
739
|
-
const currentNode = root.children[index];
|
|
740
|
-
const targetNode = root.children[targetIndex];
|
|
741
|
-
if (!currentNode || !targetNode)
|
|
742
|
-
return root;
|
|
743
|
-
const nextChildren = [...root.children];
|
|
744
|
-
nextChildren[index] = targetNode;
|
|
745
|
-
nextChildren[targetIndex] = currentNode;
|
|
746
|
-
return { ...root, children: nextChildren };
|
|
747
|
-
}
|
|
748
|
-
const nextChildren = root.children.map((child) => moveNodeInManifestTree(child, nodeId, direction));
|
|
749
|
-
const hasChanged = nextChildren.some((child, childIndex) => child !== root.children?.[childIndex]);
|
|
750
|
-
return hasChanged ? { ...root, children: nextChildren } : root;
|
|
751
|
-
}
|
|
752
|
-
function findNodeWithParent(root, nodeId) {
|
|
753
|
-
if (root.id === nodeId)
|
|
754
|
-
return { node: root, parent: null, index: -1 };
|
|
983
|
+
function collectNodeIds(root) {
|
|
984
|
+
const ids = new Set();
|
|
755
985
|
const visit = (node) => {
|
|
756
|
-
|
|
757
|
-
for (const
|
|
758
|
-
|
|
759
|
-
return { node: child, parent: node, index };
|
|
760
|
-
const nested = visit(child);
|
|
761
|
-
if (nested)
|
|
762
|
-
return nested;
|
|
763
|
-
}
|
|
764
|
-
return null;
|
|
986
|
+
ids.add(node.id);
|
|
987
|
+
for (const child of node.children ?? [])
|
|
988
|
+
visit(child);
|
|
765
989
|
};
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
function validateManifestNodePlacement(root, placement, childType, componentMeta) {
|
|
769
|
-
const parent = findNodeInManifest(root, placement.parentId);
|
|
770
|
-
if (!parent)
|
|
771
|
-
return false;
|
|
772
|
-
const meta = componentMeta[parent.type];
|
|
773
|
-
if (!meta?.allowedChildren.includes(childType))
|
|
774
|
-
return false;
|
|
775
|
-
const children = parent.children ?? [];
|
|
776
|
-
if (placement.referenceId && !children.some((child) => child.id === placement.referenceId)) {
|
|
777
|
-
return false;
|
|
778
|
-
}
|
|
779
|
-
return placement.index >= 0 && placement.index <= children.length;
|
|
780
|
-
}
|
|
781
|
-
function insertChildAtIndex(args) {
|
|
782
|
-
const { node, parentId, index, newNode } = args;
|
|
783
|
-
if (node.id === parentId) {
|
|
784
|
-
const children = node.children ?? [];
|
|
785
|
-
if (index < 0 || index > children.length)
|
|
786
|
-
return { node, inserted: false };
|
|
787
|
-
return {
|
|
788
|
-
node: {
|
|
789
|
-
...node,
|
|
790
|
-
children: [...children.slice(0, index), newNode, ...children.slice(index)],
|
|
791
|
-
},
|
|
792
|
-
inserted: true,
|
|
793
|
-
};
|
|
794
|
-
}
|
|
795
|
-
if (!node.children?.length)
|
|
796
|
-
return { node, inserted: false };
|
|
797
|
-
const results = node.children.map((child) => insertChildAtIndex({ node: child, parentId, index, newNode }));
|
|
798
|
-
const inserted = results.some((result) => result.inserted);
|
|
799
|
-
return inserted
|
|
800
|
-
? { node: { ...node, children: results.map((result) => result.node) }, inserted }
|
|
801
|
-
: { node, inserted };
|
|
802
|
-
}
|
|
803
|
-
function isDescendantNode(node, descendantId) {
|
|
804
|
-
for (const child of node.children ?? []) {
|
|
805
|
-
if (child.id === descendantId || isDescendantNode(child, descendantId))
|
|
806
|
-
return true;
|
|
807
|
-
}
|
|
808
|
-
return false;
|
|
809
|
-
}
|
|
810
|
-
function removeNodeForMove(args) {
|
|
811
|
-
const { node, nodeId } = args;
|
|
812
|
-
const children = node.children ?? [];
|
|
813
|
-
const directIndex = children.findIndex((child) => child.id === nodeId);
|
|
814
|
-
if (directIndex !== -1) {
|
|
815
|
-
const removedNode = children[directIndex];
|
|
816
|
-
if (!removedNode)
|
|
817
|
-
return { node, removedNode: null };
|
|
818
|
-
return {
|
|
819
|
-
node: {
|
|
820
|
-
...node,
|
|
821
|
-
children: children.filter((child) => child.id !== nodeId),
|
|
822
|
-
},
|
|
823
|
-
removedNode,
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
const nextChildren = [];
|
|
827
|
-
let removedNode = null;
|
|
828
|
-
for (const child of children) {
|
|
829
|
-
if (removedNode) {
|
|
830
|
-
nextChildren.push(child);
|
|
831
|
-
continue;
|
|
832
|
-
}
|
|
833
|
-
const { node: nextChild, removedNode: nextRemovedNode } = removeNodeForMove({
|
|
834
|
-
node: child,
|
|
835
|
-
nodeId,
|
|
836
|
-
});
|
|
837
|
-
if (nextRemovedNode)
|
|
838
|
-
removedNode = nextRemovedNode;
|
|
839
|
-
nextChildren.push(nextChild);
|
|
840
|
-
}
|
|
841
|
-
if (!removedNode)
|
|
842
|
-
return { node, removedNode: null };
|
|
843
|
-
return { node: { ...node, children: nextChildren }, removedNode };
|
|
844
|
-
}
|
|
845
|
-
function adjustMovePlacement(args) {
|
|
846
|
-
const { source, placement } = args;
|
|
847
|
-
if (!source.parent)
|
|
848
|
-
return null;
|
|
849
|
-
if (placement.referenceId === source.node.id)
|
|
850
|
-
return null;
|
|
851
|
-
if (placement.parentId !== source.parent.id)
|
|
852
|
-
return placement;
|
|
853
|
-
const adjustedIndex = source.index < placement.index ? placement.index - 1 : placement.index;
|
|
854
|
-
if (adjustedIndex === source.index)
|
|
855
|
-
return null;
|
|
856
|
-
return { ...placement, index: adjustedIndex };
|
|
990
|
+
visit(root);
|
|
991
|
+
return ids;
|
|
857
992
|
}
|
|
858
993
|
function normalizeNavigatorAfterRouteUpdate(navigator) {
|
|
859
994
|
const nextRoutes = navigator.routes;
|