Introduce standalone page (#19675)
Add support for standalone pages: a new `PageLayout` type (`STANDALONE_PAGE`) that can be rendered independently at `/page/:pageLayoutId`, not tied to any record or object context. - New `STANDALONE_PAGE` page layout type - New `PAGE_LAYOUT` navigation menu item type: adds a `pageLayoutId` foreign key to `NavigationMenuItemEntity`, allowing sidebar items to link directly to standalone pages - New `GLOBAL_OBJECT_CONTEXT` command menu availability type: separates object-context-dependent commands (Create Record, Import, Export, See Deleted, Create View, Hide Deleted) from truly global ones, so standalone pages only show relevant commands - Frontend routing & rendering: adds a `/page/:pageLayoutId` route with its own page component, header, and command menu - Widget rendering refactor - Instance commands: two fast 1.22 migrations: `pageLayoutId` column + `STANDALONE_PAGE` enum, and `GLOBAL_OBJECT_CONTEXT` availability type enum - Workspace command: backfills existing command menu items from `GLOBAL` to `GLOBAL_OBJECT_CONTEXT` where appropriate - Dev seeds: adds a sample "Star History" standalone page with an iframe widget for local development
This commit is contained in:
+6
-6
@@ -949,12 +949,6 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
...aggregatedOrchestratorActionsReport.commandMenuItem.update,
|
||||
///
|
||||
|
||||
// Navigation Menu Items
|
||||
...aggregatedOrchestratorActionsReport.navigationMenuItem.delete,
|
||||
...aggregatedOrchestratorActionsReport.navigationMenuItem.create,
|
||||
...aggregatedOrchestratorActionsReport.navigationMenuItem.update,
|
||||
///
|
||||
|
||||
// Page layouts
|
||||
...aggregatedOrchestratorActionsReport.pageLayout.delete,
|
||||
...aggregatedOrchestratorActionsReport.pageLayout.create,
|
||||
@@ -973,6 +967,12 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
...aggregatedOrchestratorActionsReport.pageLayoutWidget.update,
|
||||
///
|
||||
|
||||
// Navigation Menu Items
|
||||
...aggregatedOrchestratorActionsReport.navigationMenuItem.delete,
|
||||
...aggregatedOrchestratorActionsReport.navigationMenuItem.create,
|
||||
...aggregatedOrchestratorActionsReport.navigationMenuItem.update,
|
||||
///
|
||||
|
||||
// Row level permission predicate groups
|
||||
...aggregatedOrchestratorActionsReport
|
||||
.rowLevelPermissionPredicateGroup.delete,
|
||||
|
||||
+17
@@ -28,6 +28,7 @@ export class FlatNavigationMenuItemValidatorService {
|
||||
hasTargetObjectMetadataId,
|
||||
hasViewId,
|
||||
hasLink,
|
||||
hasPageLayoutId,
|
||||
name,
|
||||
}: {
|
||||
type: NavigationMenuItemType | null | undefined;
|
||||
@@ -35,6 +36,7 @@ export class FlatNavigationMenuItemValidatorService {
|
||||
hasTargetObjectMetadataId: boolean;
|
||||
hasViewId: boolean;
|
||||
hasLink: boolean;
|
||||
hasPageLayoutId: boolean;
|
||||
name: string | null | undefined;
|
||||
}): FlatEntityValidationError<NavigationMenuItemExceptionCode>[] {
|
||||
const errors: FlatEntityValidationError<NavigationMenuItemExceptionCode>[] =
|
||||
@@ -96,6 +98,15 @@ export class FlatNavigationMenuItemValidatorService {
|
||||
});
|
||||
}
|
||||
break;
|
||||
case NavigationMenuItemType.PAGE_LAYOUT:
|
||||
if (!hasPageLayoutId) {
|
||||
errors.push({
|
||||
code: NavigationMenuItemExceptionCode.INVALID_NAVIGATION_MENU_ITEM_INPUT,
|
||||
message: t`pageLayoutId is required for PAGE_LAYOUT type`,
|
||||
userFriendlyMessage: msg`pageLayoutId is required for PAGE_LAYOUT type`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return errors;
|
||||
@@ -188,6 +199,9 @@ export class FlatNavigationMenuItemValidatorService {
|
||||
hasLink:
|
||||
isDefined(flatNavigationMenuItem.link) &&
|
||||
isNonEmptyString(flatNavigationMenuItem.link),
|
||||
hasPageLayoutId: isDefined(
|
||||
flatNavigationMenuItem.pageLayoutUniversalIdentifier,
|
||||
),
|
||||
name: flatNavigationMenuItem.name,
|
||||
});
|
||||
|
||||
@@ -321,6 +335,9 @@ export class FlatNavigationMenuItemValidatorService {
|
||||
),
|
||||
hasViewId: isDefined(toFlatNavigationMenuItem.viewUniversalIdentifier),
|
||||
hasLink: isNonEmptyString((toFlatNavigationMenuItem.link ?? '').trim()),
|
||||
hasPageLayoutId: isDefined(
|
||||
toFlatNavigationMenuItem.pageLayoutUniversalIdentifier,
|
||||
),
|
||||
name: isDefined(nameUpdate) ? nameUpdate : toFlatNavigationMenuItem.name,
|
||||
});
|
||||
|
||||
|
||||
+2
-1
@@ -30,7 +30,7 @@ export class CreateNavigationMenuItemActionHandlerService extends WorkspaceMigra
|
||||
flatApplication,
|
||||
workspaceId,
|
||||
}: WorkspaceMigrationActionRunnerArgs<UniversalCreateNavigationMenuItemAction>): Promise<FlatCreateNavigationMenuItemAction> {
|
||||
const { targetObjectMetadataId, folderId, viewId } =
|
||||
const { targetObjectMetadataId, folderId, viewId, pageLayoutId } =
|
||||
resolveUniversalRelationIdentifiersToIds({
|
||||
flatEntityMaps: allFlatEntityMaps,
|
||||
metadataName: action.metadataName,
|
||||
@@ -49,6 +49,7 @@ export class CreateNavigationMenuItemActionHandlerService extends WorkspaceMigra
|
||||
targetObjectMetadataId,
|
||||
folderId,
|
||||
viewId,
|
||||
pageLayoutId,
|
||||
applicationId: flatApplication.id,
|
||||
id: action.id ?? v4(),
|
||||
workspaceId,
|
||||
|
||||
Reference in New Issue
Block a user