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:
+7
-1
@@ -16,6 +16,7 @@ export const fromCreateNavigationMenuItemInputToFlatNavigationMenuItemToCreate =
|
||||
flatNavigationMenuItemMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatViewMaps,
|
||||
flatPageLayoutMaps,
|
||||
}: {
|
||||
createNavigationMenuItemInput: CreateNavigationMenuItemInput;
|
||||
workspaceId: string;
|
||||
@@ -23,7 +24,7 @@ export const fromCreateNavigationMenuItemInputToFlatNavigationMenuItemToCreate =
|
||||
flatNavigationMenuItemMaps: FlatNavigationMenuItemMaps;
|
||||
} & Pick<
|
||||
AllFlatEntityMaps,
|
||||
'flatObjectMetadataMaps' | 'flatViewMaps'
|
||||
'flatObjectMetadataMaps' | 'flatViewMaps' | 'flatPageLayoutMaps'
|
||||
>): FlatNavigationMenuItem => {
|
||||
const id = createNavigationMenuItemInput.id ?? uuidv4();
|
||||
const now = new Date().toISOString();
|
||||
@@ -52,6 +53,7 @@ export const fromCreateNavigationMenuItemInputToFlatNavigationMenuItemToCreate =
|
||||
targetObjectMetadataUniversalIdentifier,
|
||||
viewUniversalIdentifier,
|
||||
folderUniversalIdentifier,
|
||||
pageLayoutUniversalIdentifier,
|
||||
} = resolveEntityRelationUniversalIdentifiers({
|
||||
metadataName: 'navigationMenuItem',
|
||||
foreignKeyValues: {
|
||||
@@ -59,11 +61,13 @@ export const fromCreateNavigationMenuItemInputToFlatNavigationMenuItemToCreate =
|
||||
createNavigationMenuItemInput.targetObjectMetadataId,
|
||||
viewId: createNavigationMenuItemInput.viewId,
|
||||
folderId: createNavigationMenuItemInput.folderId,
|
||||
pageLayoutId: createNavigationMenuItemInput.pageLayoutId,
|
||||
},
|
||||
flatEntityMaps: {
|
||||
flatObjectMetadataMaps,
|
||||
flatViewMaps,
|
||||
flatNavigationMenuItemMaps,
|
||||
flatPageLayoutMaps,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -80,6 +84,8 @@ export const fromCreateNavigationMenuItemInputToFlatNavigationMenuItemToCreate =
|
||||
viewUniversalIdentifier,
|
||||
folderId: createNavigationMenuItemInput.folderId ?? null,
|
||||
folderUniversalIdentifier,
|
||||
pageLayoutId: createNavigationMenuItemInput.pageLayoutId ?? null,
|
||||
pageLayoutUniversalIdentifier,
|
||||
name: createNavigationMenuItemInput.name ?? null,
|
||||
link: createNavigationMenuItemInput.link ?? null,
|
||||
icon: createNavigationMenuItemInput.icon ?? null,
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ export const fromFlatNavigationMenuItemToNavigationMenuItemDto = (
|
||||
link: flatNavigationMenuItem.link ?? undefined,
|
||||
icon: flatNavigationMenuItem.icon ?? undefined,
|
||||
color: flatNavigationMenuItem.color ?? undefined,
|
||||
pageLayoutId: flatNavigationMenuItem.pageLayoutId ?? undefined,
|
||||
position: flatNavigationMenuItem.position,
|
||||
workspaceId: flatNavigationMenuItem.workspaceId,
|
||||
applicationId: flatNavigationMenuItem.applicationId ?? undefined,
|
||||
|
||||
+19
@@ -13,6 +13,7 @@ export const fromNavigationMenuItemEntityToFlatNavigationMenuItem = ({
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
navigationMenuItemIdToUniversalIdentifierMap,
|
||||
viewIdToUniversalIdentifierMap,
|
||||
pageLayoutIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'navigationMenuItem'>): FlatNavigationMenuItem => {
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
@@ -73,6 +74,22 @@ export const fromNavigationMenuItemEntityToFlatNavigationMenuItem = ({
|
||||
}
|
||||
}
|
||||
|
||||
let pageLayoutUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(navigationMenuItemEntity.pageLayoutId)) {
|
||||
pageLayoutUniversalIdentifier =
|
||||
pageLayoutIdToUniversalIdentifierMap.get(
|
||||
navigationMenuItemEntity.pageLayoutId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(pageLayoutUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`PageLayout with id ${navigationMenuItemEntity.pageLayoutId} not found for navigationMenuItem ${navigationMenuItemEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: navigationMenuItemEntity.id,
|
||||
type: navigationMenuItemEntity.type,
|
||||
@@ -85,6 +102,7 @@ export const fromNavigationMenuItemEntityToFlatNavigationMenuItem = ({
|
||||
link: navigationMenuItemEntity.link,
|
||||
icon: navigationMenuItemEntity.icon,
|
||||
color: navigationMenuItemEntity.color,
|
||||
pageLayoutId: navigationMenuItemEntity.pageLayoutId,
|
||||
position: navigationMenuItemEntity.position,
|
||||
workspaceId: navigationMenuItemEntity.workspaceId,
|
||||
universalIdentifier: navigationMenuItemEntity.universalIdentifier,
|
||||
@@ -95,5 +113,6 @@ export const fromNavigationMenuItemEntityToFlatNavigationMenuItem = ({
|
||||
targetObjectMetadataUniversalIdentifier,
|
||||
folderUniversalIdentifier,
|
||||
viewUniversalIdentifier,
|
||||
pageLayoutUniversalIdentifier,
|
||||
};
|
||||
};
|
||||
|
||||
+20
-1
@@ -1,5 +1,6 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util';
|
||||
import { FLAT_NAVIGATION_MENU_ITEM_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-navigation-menu-item/constants/flat-navigation-menu-item-editable-properties.constant';
|
||||
@@ -15,13 +16,17 @@ import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-
|
||||
export const fromUpdateNavigationMenuItemInputToFlatNavigationMenuItemToUpdateOrThrow =
|
||||
({
|
||||
flatNavigationMenuItemMaps,
|
||||
flatPageLayoutMaps,
|
||||
updateNavigationMenuItemInput,
|
||||
}: {
|
||||
flatNavigationMenuItemMaps: FlatNavigationMenuItemMaps;
|
||||
updateNavigationMenuItemInput: UpdateNavigationMenuItemInput & {
|
||||
id: string;
|
||||
};
|
||||
}): FlatNavigationMenuItem => {
|
||||
} & Pick<
|
||||
AllFlatEntityMaps,
|
||||
'flatPageLayoutMaps'
|
||||
>): FlatNavigationMenuItem => {
|
||||
const existingFlatNavigationMenuItem = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: updateNavigationMenuItemInput.id,
|
||||
flatEntityMaps: flatNavigationMenuItemMaps,
|
||||
@@ -59,5 +64,19 @@ export const fromUpdateNavigationMenuItemInputToFlatNavigationMenuItemToUpdateOr
|
||||
folderUniversalIdentifier;
|
||||
}
|
||||
|
||||
if (updates.pageLayoutId !== undefined) {
|
||||
const { pageLayoutUniversalIdentifier } =
|
||||
resolveEntityRelationUniversalIdentifiers({
|
||||
metadataName: 'navigationMenuItem',
|
||||
foreignKeyValues: {
|
||||
pageLayoutId: flatNavigationMenuItemToUpdate.pageLayoutId,
|
||||
},
|
||||
flatEntityMaps: { flatPageLayoutMaps },
|
||||
});
|
||||
|
||||
flatNavigationMenuItemToUpdate.pageLayoutUniversalIdentifier =
|
||||
pageLayoutUniversalIdentifier;
|
||||
}
|
||||
|
||||
return flatNavigationMenuItemToUpdate;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user