a88d1f4442
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
119 lines
4.6 KiB
TypeScript
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,
|
|
};
|
|
};
|