Files
twenty/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-navigation-menu-item-entity-to-flat-navigation-menu-item.util.ts
T
Raphaël Bosi a88d1f4442 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
2026-04-14 15:52:45 +00:00

119 lines
4.6 KiB
TypeScript

import { isDefined } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type FlatNavigationMenuItem } from 'src/engine/metadata-modules/flat-navigation-menu-item/types/flat-navigation-menu-item.type';
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
export const fromNavigationMenuItemEntityToFlatNavigationMenuItem = ({
entity: navigationMenuItemEntity,
applicationIdToUniversalIdentifierMap,
objectMetadataIdToUniversalIdentifierMap,
navigationMenuItemIdToUniversalIdentifierMap,
viewIdToUniversalIdentifierMap,
pageLayoutIdToUniversalIdentifierMap,
}: FromEntityToFlatEntityArgs<'navigationMenuItem'>): FlatNavigationMenuItem => {
const applicationUniversalIdentifier =
applicationIdToUniversalIdentifierMap.get(
navigationMenuItemEntity.applicationId,
);
if (!isDefined(applicationUniversalIdentifier)) {
throw new FlatEntityMapsException(
`Application with id ${navigationMenuItemEntity.applicationId} not found for navigationMenuItem ${navigationMenuItemEntity.id}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
let targetObjectMetadataUniversalIdentifier: string | null = null;
if (isDefined(navigationMenuItemEntity.targetObjectMetadataId)) {
targetObjectMetadataUniversalIdentifier =
objectMetadataIdToUniversalIdentifierMap.get(
navigationMenuItemEntity.targetObjectMetadataId,
) ?? null;
if (!isDefined(targetObjectMetadataUniversalIdentifier)) {
throw new FlatEntityMapsException(
`ObjectMetadata with id ${navigationMenuItemEntity.targetObjectMetadataId} not found for navigationMenuItem ${navigationMenuItemEntity.id}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
}
let folderUniversalIdentifier: string | null = null;
if (isDefined(navigationMenuItemEntity.folderId)) {
folderUniversalIdentifier =
navigationMenuItemIdToUniversalIdentifierMap.get(
navigationMenuItemEntity.folderId,
) ?? null;
if (!isDefined(folderUniversalIdentifier)) {
throw new FlatEntityMapsException(
`NavigationMenuItem (folder) with id ${navigationMenuItemEntity.folderId} not found for navigationMenuItem ${navigationMenuItemEntity.id}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
}
let viewUniversalIdentifier: string | null = null;
if (isDefined(navigationMenuItemEntity.viewId)) {
viewUniversalIdentifier =
viewIdToUniversalIdentifierMap.get(navigationMenuItemEntity.viewId) ??
null;
if (!isDefined(viewUniversalIdentifier)) {
throw new FlatEntityMapsException(
`View with id ${navigationMenuItemEntity.viewId} not found for navigationMenuItem ${navigationMenuItemEntity.id}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
}
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,
userWorkspaceId: navigationMenuItemEntity.userWorkspaceId,
targetRecordId: navigationMenuItemEntity.targetRecordId,
targetObjectMetadataId: navigationMenuItemEntity.targetObjectMetadataId,
viewId: navigationMenuItemEntity.viewId,
folderId: navigationMenuItemEntity.folderId,
name: navigationMenuItemEntity.name,
link: navigationMenuItemEntity.link,
icon: navigationMenuItemEntity.icon,
color: navigationMenuItemEntity.color,
pageLayoutId: navigationMenuItemEntity.pageLayoutId,
position: navigationMenuItemEntity.position,
workspaceId: navigationMenuItemEntity.workspaceId,
universalIdentifier: navigationMenuItemEntity.universalIdentifier,
applicationId: navigationMenuItemEntity.applicationId,
createdAt: navigationMenuItemEntity.createdAt.toISOString(),
updatedAt: navigationMenuItemEntity.updatedAt.toISOString(),
applicationUniversalIdentifier,
targetObjectMetadataUniversalIdentifier,
folderUniversalIdentifier,
viewUniversalIdentifier,
pageLayoutUniversalIdentifier,
};
};