From a88d1f4442ba2442b9c8039ea8b932c997134955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:52:45 +0200 Subject: [PATCH] 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 --- .../src/metadata/generated/schema.graphql | 6 + .../src/metadata/generated/schema.ts | 19 +- .../src/metadata/generated/types.ts | 9 + .../src/generated-metadata/graphql.ts | 44 ++-- ...sePageChangeEffectNavigateLocation.test.ts | 50 +++- .../usePageChangeEffectNavigateLocation.ts | 35 ++- .../modules/ai/hooks/useBrowsingContext.ts | 10 +- .../modules/app/hooks/useCreateAppRouter.tsx | 14 + .../components/StandalonePageCommandMenu.tsx | 110 ++++++++ .../constants/EmptyCommandMenuContextApi.ts | 4 +- .../CommandMenuContextProviderContent.tsx | 2 + .../SidePanelCommandMenuItemDisplayPage.tsx | 1 + .../PinnedCommandMenuItemButtonsEditMode.tsx | 1 + .../SidePanelCommandMenuItemEditPage.tsx | 25 +- .../buildTriggerWorkflowVersionPayloads.ts | 1 + .../__tests__/useCloseCommandMenu.test.tsx | 4 +- .../hooks/useCommandMenuContextApi.ts | 28 +- .../types/CommandMenuItemContainerType.ts | 1 + .../utils/doesCommandMenuItemMatchPageType.ts | 20 ++ .../__stories__/CommandMenu.stories.tsx | 14 +- .../components/MainContextStoreProvider.tsx | 3 + .../MainContextStoreProviderEffect.tsx | 30 ++- ...ntextStoreCurrentPageTypeComponentState.ts | 10 + .../types/ContextStoreViewType.ts | 1 - .../context-store/utils/getPageType.ts | 31 +++ .../context-store/utils/getViewType.ts | 12 - .../graphql/queries/findOnePageLayoutType.ts | 10 + ...avigationMenuItemDefaultColorPageLayout.ts | 1 + .../fragments/navigationMenuItemFragment.ts | 1 + .../utils/filterAndSortNavigationMenuItems.ts | 3 + .../utils/getNavigationMenuItemColor.ts | 7 + .../components/NavigationMenuItemDisplay.tsx | 16 ++ .../components/NavigationMenuItemIcon.tsx | 23 ++ .../NavigationMenuItemPageLayoutDisplay.tsx | 45 ++++ ...ageLayoutNavigationMenuItemComputedLink.ts | 15 ++ .../components/WorkspaceSectionContainer.tsx | 3 +- .../getNavigationMenuItemComputedLink.ts | 3 + ...rkspaceSidebarOrphanItemsInDisplayOrder.ts | 5 +- .../components/PageLayoutContent.tsx | 19 ++ .../components/PageLayoutEditModeProvider.tsx | 9 + .../PageLayoutInitializationQueryEffect.tsx | 77 ++++-- .../components/PageLayoutRenderer.tsx | 11 +- .../shouldEnableTabEditingFeatures.test.ts | 13 +- .../utils/shouldEnableTabEditingFeatures.ts | 5 +- .../NonRecordPageWidgetRenderer.tsx | 39 +++ .../components/RecordPageWidgetRenderer.tsx | 88 +++++++ .../StandaloneWidgetPlaceholder.tsx | 43 ++++ .../widgets/components/WidgetCardShell.tsx | 140 ++++++++++ .../widgets/components/WidgetPlaceholder.tsx | 5 + .../widgets/components/WidgetRenderer.tsx | 243 +----------------- .../widgets/hooks/useWidgetRendererState.ts | 121 +++++++++ .../widgets/types/WidgetCardVariant.ts | 1 + .../__tests__/getWidgetCardVariant.test.ts | 24 ++ .../widgets/utils/getWidgetCardVariant.ts | 4 + .../widget-card/components/WidgetCard.tsx | 18 +- .../components/WidgetCardContent.tsx | 3 +- .../useOpenRecordInSidePanel.test.tsx | 26 +- .../hooks/useOpenRecordInSidePanel.ts | 13 +- .../root/components/SidePanelRootPage.tsx | 92 ------- .../page-layout/StandalonePageHeader.tsx | 38 +++ .../page-layout/StandalonePageLayoutPage.tsx | 45 ++++ .../testing/jest/JestContextStoreSetter.tsx | 12 + ...ataAndApolloMocksAndCommandMenuWrapper.tsx | 2 + ...nditional-availability-expressions.test.ts | 20 +- ...-fast-1775752781995-add-standalone-page.ts | 105 ++++++++ ...-to-command-menu-item-availability-type.ts | 54 ++++ .../1-23-upgrade-version-command.module.ts | 6 +- ...ject-context-command-menu-items.command.ts | 143 +++++++++++ .../instance-commands.constant.ts | 4 + ...o-universal-flat-command-menu-item.util.ts | 1 + ...niversal-flat-navigation-menu-item.util.ts | 2 + .../open-api/utils/components.utils.ts | 21 +- ...ommand-menu-item-availability-type.enum.ts | 1 + ...configuration-by-metadata-name.constant.ts | 5 + ...ny-to-one-metadata-foreign-key.constant.ts | 3 + ...many-to-one-metadata-relations.constant.ts | 7 + ...quired-metadata-for-validation.constant.ts | 1 + ...a-related-metadata-names.util.spec.ts.snap | 1 + ...ata-names-children-first.util.spec.ts.snap | 2 +- ...-menu-item-editable-properties.constant.ts | 1 + .../flat-navigation-menu-item.module.ts | 2 + ...-navigation-menu-item-map-cache.service.ts | 60 +++-- ...lat-navigation-menu-item-to-create.util.ts | 8 +- ...u-item-to-navigation-menu-item-dto.util.ts | 1 + ...ntity-to-flat-navigation-menu-item.util.ts | 19 ++ ...ation-menu-item-to-update-or-throw.util.ts | 21 +- .../dtos/create-navigation-menu-item.input.ts | 5 + .../dtos/navigation-menu-item.dto.ts | 5 + .../dtos/update-navigation-menu-item.input.ts | 5 + .../entities/navigation-menu-item.entity.ts | 18 +- .../navigation-menu-item.service.ts | 11 +- .../object-metadata.service.ts | 2 + .../enums/page-layout-type.enum.ts | 1 + .../navigation-menu-item-seeds.constant.ts | 3 + .../constants/page-layout-seeds.constant.ts | 1 + .../page-layout-tab-seeds.constant.ts | 1 + .../page-layout-widget-seeds.constant.ts | 1 + ...et-navigation-menu-item-data-seeds.util.ts | 56 ++++ .../utils/get-page-layout-data-seeds.util.ts | 141 +++++++--- .../get-page-layout-tab-data-seeds.util.ts | 152 +++++------ .../get-page-layout-widget-data-seeds.util.ts | 109 +++++++- .../core/utils/seed-page-layout-tabs.util.ts | 39 --- .../utils/seed-page-layout-widgets.util.ts | 68 ----- .../core/utils/seed-page-layouts.util.ts | 98 +++++-- .../data/services/dev-seeder-data.service.ts | 19 -- .../dev-seeder/services/dev-seeder.service.ts | 66 ++--- .../standard-command-menu-item.constant.ts | 12 +- ...navigation-menu-item-flat-metadata.util.ts | 2 + ...ion-menu-item-folder-flat-metadata.util.ts | 4 + ...ce-migration-build-orchestrator.service.ts | 12 +- ...-navigation-menu-item-validator.service.ts | 17 ++ ...gation-menu-item-action-handler.service.ts | 3 +- .../application/frontComponentManifestType.ts | 6 +- .../navigationMenuItemManifestType.ts | 1 + packages/twenty-shared/src/types/AppPath.ts | 1 + .../src/types/CommandMenuContextApi.ts | 4 +- .../types/CommandMenuContextApiPageType.ts | 4 - .../src/types/ContextStorePageType.ts | 6 + .../src/types/NavigationMenuItemType.ts | 1 + packages/twenty-shared/src/types/index.ts | 2 +- ...eConditionalAvailabilityExpression.test.ts | 7 +- ...interpolateCommandMenuItemTemplate.test.ts | 7 +- 122 files changed, 2250 insertions(+), 836 deletions(-) create mode 100644 packages/twenty-front/src/modules/command-menu-item/components/StandalonePageCommandMenu.tsx create mode 100644 packages/twenty-front/src/modules/command-menu-item/utils/doesCommandMenuItemMatchPageType.ts create mode 100644 packages/twenty-front/src/modules/context-store/states/contextStoreCurrentPageTypeComponentState.ts create mode 100644 packages/twenty-front/src/modules/context-store/utils/getPageType.ts create mode 100644 packages/twenty-front/src/modules/dashboards/graphql/queries/findOnePageLayoutType.ts create mode 100644 packages/twenty-front/src/modules/navigation-menu-item/common/constants/NavigationMenuItemDefaultColorPageLayout.ts create mode 100644 packages/twenty-front/src/modules/navigation-menu-item/display/page-layout/components/NavigationMenuItemPageLayoutDisplay.tsx create mode 100644 packages/twenty-front/src/modules/navigation-menu-item/display/page-layout/utils/getPageLayoutNavigationMenuItemComputedLink.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/components/NonRecordPageWidgetRenderer.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/components/RecordPageWidgetRenderer.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/components/StandaloneWidgetPlaceholder.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/components/WidgetCardShell.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/hooks/useWidgetRendererState.ts delete mode 100644 packages/twenty-front/src/modules/side-panel/pages/root/components/SidePanelRootPage.tsx create mode 100644 packages/twenty-front/src/pages/page-layout/StandalonePageHeader.tsx create mode 100644 packages/twenty-front/src/pages/page-layout/StandalonePageLayoutPage.tsx create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-instance-command-fast-1775752781995-add-standalone-page.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-instance-command-fast-1776090711153-add-global-object-context-to-command-menu-item-availability-type.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-workspace-command-1780000005000-update-global-object-context-command-menu-items.command.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/constants/navigation-menu-item-seeds.constant.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/get-navigation-menu-item-data-seeds.util.ts delete mode 100644 packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-tabs.util.ts delete mode 100644 packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-widgets.util.ts delete mode 100644 packages/twenty-shared/src/types/CommandMenuContextApiPageType.ts create mode 100644 packages/twenty-shared/src/types/ContextStorePageType.ts diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index 3d241cac5a..b17a701e96 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -1257,6 +1257,7 @@ enum PageLayoutType { RECORD_INDEX RECORD_PAGE DASHBOARD + STANDALONE_PAGE } type Analytics { @@ -1485,6 +1486,7 @@ type NavigationMenuItem { icon: String color: String folderId: UUID + pageLayoutId: UUID position: Float! applicationId: UUID createdAt: DateTime! @@ -1498,6 +1500,7 @@ enum NavigationMenuItemType { LINK OBJECT RECORD + PAGE_LAYOUT } type ObjectRecordEventProperties { @@ -2717,6 +2720,7 @@ enum EngineComponentKey { enum CommandMenuItemAvailabilityType { GLOBAL + GLOBAL_OBJECT_CONTEXT RECORD_SELECTION FALLBACK } @@ -3740,6 +3744,7 @@ input CreateNavigationMenuItemInput { icon: String color: String folderId: UUID + pageLayoutId: UUID position: Float } @@ -3758,6 +3763,7 @@ input UpdateNavigationMenuItemInput { link: String icon: String color: String + pageLayoutId: UUID } """The `Upload` scalar type represents a file upload.""" diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index 6174bfc6fb..2f9685f45b 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -981,7 +981,7 @@ export interface PageLayout { __typename: 'PageLayout' } -export type PageLayoutType = 'RECORD_INDEX' | 'RECORD_PAGE' | 'DASHBOARD' +export type PageLayoutType = 'RECORD_INDEX' | 'RECORD_PAGE' | 'DASHBOARD' | 'STANDALONE_PAGE' export interface Analytics { /** Boolean that confirms query was dispatched */ @@ -1210,6 +1210,7 @@ export interface NavigationMenuItem { icon?: Scalars['String'] color?: Scalars['String'] folderId?: Scalars['UUID'] + pageLayoutId?: Scalars['UUID'] position: Scalars['Float'] applicationId?: Scalars['UUID'] createdAt: Scalars['DateTime'] @@ -1218,7 +1219,7 @@ export interface NavigationMenuItem { __typename: 'NavigationMenuItem' } -export type NavigationMenuItemType = 'VIEW' | 'FOLDER' | 'LINK' | 'OBJECT' | 'RECORD' +export type NavigationMenuItemType = 'VIEW' | 'FOLDER' | 'LINK' | 'OBJECT' | 'RECORD' | 'PAGE_LAYOUT' export interface ObjectRecordEventProperties { updatedFields?: Scalars['String'][] @@ -2368,7 +2369,7 @@ export interface CommandMenuItem { export type EngineComponentKey = 'NAVIGATE_TO_NEXT_RECORD' | 'NAVIGATE_TO_PREVIOUS_RECORD' | 'CREATE_NEW_RECORD' | 'DELETE_RECORDS' | 'RESTORE_RECORDS' | 'DESTROY_RECORDS' | 'ADD_TO_FAVORITES' | 'REMOVE_FROM_FAVORITES' | 'EXPORT_NOTE_TO_PDF' | 'EXPORT_RECORDS' | 'UPDATE_MULTIPLE_RECORDS' | 'MERGE_MULTIPLE_RECORDS' | 'IMPORT_RECORDS' | 'EXPORT_VIEW' | 'SEE_DELETED_RECORDS' | 'CREATE_NEW_VIEW' | 'HIDE_DELETED_RECORDS' | 'EDIT_RECORD_PAGE_LAYOUT' | 'EDIT_DASHBOARD_LAYOUT' | 'SAVE_DASHBOARD_LAYOUT' | 'CANCEL_DASHBOARD_LAYOUT' | 'DUPLICATE_DASHBOARD' | 'ACTIVATE_WORKFLOW' | 'DEACTIVATE_WORKFLOW' | 'DISCARD_DRAFT_WORKFLOW' | 'TEST_WORKFLOW' | 'SEE_ACTIVE_VERSION_WORKFLOW' | 'SEE_RUNS_WORKFLOW' | 'SEE_VERSIONS_WORKFLOW' | 'ADD_NODE_WORKFLOW' | 'TIDY_UP_WORKFLOW' | 'DUPLICATE_WORKFLOW' | 'SEE_VERSION_WORKFLOW_RUN' | 'SEE_WORKFLOW_WORKFLOW_RUN' | 'STOP_WORKFLOW_RUN' | 'SEE_RUNS_WORKFLOW_VERSION' | 'SEE_WORKFLOW_WORKFLOW_VERSION' | 'USE_AS_DRAFT_WORKFLOW_VERSION' | 'SEE_VERSIONS_WORKFLOW_VERSION' | 'SEARCH_RECORDS' | 'SEARCH_RECORDS_FALLBACK' | 'ASK_AI' | 'VIEW_PREVIOUS_AI_CHATS' | 'NAVIGATION' | 'TRIGGER_WORKFLOW_VERSION' | 'FRONT_COMPONENT_RENDERER' | 'REPLY_TO_EMAIL_THREAD' | 'COMPOSE_EMAIL' | 'GO_TO_PEOPLE' | 'GO_TO_COMPANIES' | 'GO_TO_DASHBOARDS' | 'GO_TO_OPPORTUNITIES' | 'GO_TO_SETTINGS' | 'GO_TO_TASKS' | 'GO_TO_NOTES' | 'GO_TO_WORKFLOWS' | 'GO_TO_RUNS' | 'DELETE_SINGLE_RECORD' | 'DELETE_MULTIPLE_RECORDS' | 'RESTORE_SINGLE_RECORD' | 'RESTORE_MULTIPLE_RECORDS' | 'DESTROY_SINGLE_RECORD' | 'DESTROY_MULTIPLE_RECORDS' | 'EXPORT_FROM_RECORD_INDEX' | 'EXPORT_FROM_RECORD_SHOW' | 'EXPORT_MULTIPLE_RECORDS' -export type CommandMenuItemAvailabilityType = 'GLOBAL' | 'RECORD_SELECTION' | 'FALLBACK' +export type CommandMenuItemAvailabilityType = 'GLOBAL' | 'GLOBAL_OBJECT_CONTEXT' | 'RECORD_SELECTION' | 'FALLBACK' export type CommandMenuItemPayload = (PathCommandMenuItemPayload | ObjectMetadataCommandMenuItemPayload) & { __isUnion?: true } @@ -4459,6 +4460,7 @@ export interface NavigationMenuItemGenqlSelection{ icon?: boolean | number color?: boolean | number folderId?: boolean | number + pageLayoutId?: boolean | number position?: boolean | number applicationId?: boolean | number createdAt?: boolean | number @@ -6567,7 +6569,7 @@ export interface AddQuerySubscriptionInput {eventStreamId: Scalars['String'],que export interface RemoveQueryFromEventStreamInput {eventStreamId: Scalars['String'],queryId: Scalars['String']} -export interface CreateNavigationMenuItemInput {id?: (Scalars['UUID'] | null),userWorkspaceId?: (Scalars['UUID'] | null),targetRecordId?: (Scalars['UUID'] | null),targetObjectMetadataId?: (Scalars['UUID'] | null),viewId?: (Scalars['UUID'] | null),type: NavigationMenuItemType,name?: (Scalars['String'] | null),link?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),color?: (Scalars['String'] | null),folderId?: (Scalars['UUID'] | null),position?: (Scalars['Float'] | null)} +export interface CreateNavigationMenuItemInput {id?: (Scalars['UUID'] | null),userWorkspaceId?: (Scalars['UUID'] | null),targetRecordId?: (Scalars['UUID'] | null),targetObjectMetadataId?: (Scalars['UUID'] | null),viewId?: (Scalars['UUID'] | null),type: NavigationMenuItemType,name?: (Scalars['String'] | null),link?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),color?: (Scalars['String'] | null),folderId?: (Scalars['UUID'] | null),pageLayoutId?: (Scalars['UUID'] | null),position?: (Scalars['Float'] | null)} export interface UpdateOneNavigationMenuItemInput { /** The id of the record to update */ @@ -6575,7 +6577,7 @@ id: Scalars['UUID'], /** The record to update */ update: UpdateNavigationMenuItemInput} -export interface UpdateNavigationMenuItemInput {folderId?: (Scalars['UUID'] | null),position?: (Scalars['Float'] | null),name?: (Scalars['String'] | null),link?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),color?: (Scalars['String'] | null)} +export interface UpdateNavigationMenuItemInput {folderId?: (Scalars['UUID'] | null),position?: (Scalars['Float'] | null),name?: (Scalars['String'] | null),link?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),color?: (Scalars['String'] | null),pageLayoutId?: (Scalars['UUID'] | null)} export interface CreateViewFilterGroupInput {id?: (Scalars['UUID'] | null),parentViewFilterGroupId?: (Scalars['UUID'] | null),logicalOperator?: (ViewFilterGroupLogicalOperator | null),positionInViewFilterGroup?: (Scalars['Float'] | null),viewId: Scalars['UUID']} @@ -9405,7 +9407,8 @@ export const enumFieldDisplayMode = { export const enumPageLayoutType = { RECORD_INDEX: 'RECORD_INDEX' as const, RECORD_PAGE: 'RECORD_PAGE' as const, - DASHBOARD: 'DASHBOARD' as const + DASHBOARD: 'DASHBOARD' as const, + STANDALONE_PAGE: 'STANDALONE_PAGE' as const } export const enumBillingPlanKey = { @@ -9444,7 +9447,8 @@ export const enumNavigationMenuItemType = { FOLDER: 'FOLDER' as const, LINK: 'LINK' as const, OBJECT: 'OBJECT' as const, - RECORD: 'RECORD' as const + RECORD: 'RECORD' as const, + PAGE_LAYOUT: 'PAGE_LAYOUT' as const } export const enumMetadataEventAction = { @@ -9682,6 +9686,7 @@ export const enumEngineComponentKey = { export const enumCommandMenuItemAvailabilityType = { GLOBAL: 'GLOBAL' as const, + GLOBAL_OBJECT_CONTEXT: 'GLOBAL_OBJECT_CONTEXT' as const, RECORD_SELECTION: 'RECORD_SELECTION' as const, FALLBACK: 'FALLBACK' as const } diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index 7aff92da38..541c6608d5 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -3056,6 +3056,9 @@ export default { "folderId": [ 3 ], + "pageLayoutId": [ + 3 + ], "position": [ 11 ], @@ -9692,6 +9695,9 @@ export default { "folderId": [ 3 ], + "pageLayoutId": [ + 3 + ], "position": [ 11 ], @@ -9729,6 +9735,9 @@ export default { "color": [ 1 ], + "pageLayoutId": [ + 3 + ], "__typename": [ 1 ] diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index a552c1e934..d5bc9a198d 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1013,6 +1013,7 @@ export type CommandMenuItem = { export enum CommandMenuItemAvailabilityType { FALLBACK = 'FALLBACK', GLOBAL = 'GLOBAL', + GLOBAL_OBJECT_CONTEXT = 'GLOBAL_OBJECT_CONTEXT', RECORD_SELECTION = 'RECORD_SELECTION' } @@ -1257,6 +1258,7 @@ export type CreateNavigationMenuItemInput = { id?: InputMaybe; link?: InputMaybe; name?: InputMaybe; + pageLayoutId?: InputMaybe; position?: InputMaybe; targetObjectMetadataId?: InputMaybe; targetRecordId?: InputMaybe; @@ -3913,6 +3915,7 @@ export type NavigationMenuItem = { id: Scalars['UUID']; link?: Maybe; name?: Maybe; + pageLayoutId?: Maybe; position: Scalars['Float']; targetObjectMetadataId?: Maybe; targetRecordId?: Maybe; @@ -3927,6 +3930,7 @@ export enum NavigationMenuItemType { FOLDER = 'FOLDER', LINK = 'LINK', OBJECT = 'OBJECT', + PAGE_LAYOUT = 'PAGE_LAYOUT', RECORD = 'RECORD', VIEW = 'VIEW' } @@ -4171,7 +4175,8 @@ export enum PageLayoutTabLayoutMode { export enum PageLayoutType { DASHBOARD = 'DASHBOARD', RECORD_INDEX = 'RECORD_INDEX', - RECORD_PAGE = 'RECORD_PAGE' + RECORD_PAGE = 'RECORD_PAGE', + STANDALONE_PAGE = 'STANDALONE_PAGE' } export type PageLayoutWidget = { @@ -5562,6 +5567,7 @@ export type UpdateNavigationMenuItemInput = { icon?: InputMaybe; link?: InputMaybe; name?: InputMaybe; + pageLayoutId?: InputMaybe; position?: InputMaybe; }; @@ -6944,6 +6950,13 @@ export type FindOnePageLayoutQueryVariables = Exact<{ export type FindOnePageLayoutQuery = { __typename?: 'Query', getPageLayout?: { __typename?: 'PageLayout', id: string, name: string, objectMetadataId?: string | null, type: PageLayoutType, defaultTabToFocusOnMobileAndSidePanelId?: string | null, createdAt: string, updatedAt: string, tabs?: Array<{ __typename?: 'PageLayoutTab', id: string, applicationId: string, title: string, icon?: string | null, position: number, layoutMode?: PageLayoutTabLayoutMode | null, pageLayoutId: string, isActive: boolean, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: string, title: string, type: WidgetType, objectMetadataId?: string | null, createdAt: string, updatedAt: string, isActive: boolean, deletedAt?: string | null, conditionalDisplay?: any | null, conditionalAvailabilityExpression?: string | null, pageLayoutTabId: string, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, position?: { __typename?: 'PageLayoutWidgetCanvasPosition', layoutMode: PageLayoutTabLayoutMode } | { __typename?: 'PageLayoutWidgetGridPosition', layoutMode: PageLayoutTabLayoutMode, row: number, column: number, rowSpan: number, columnSpan: number } | { __typename?: 'PageLayoutWidgetVerticalListPosition', layoutMode: PageLayoutTabLayoutMode, index: number } | null, configuration: { __typename?: 'AggregateChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null, ratioAggregateConfig?: { __typename?: 'RatioAggregateConfig', fieldMetadataId: string, optionValue: string } | null } | { __typename?: 'BarChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: string, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, primaryAxisManualSortOrder?: Array | null, secondaryAxisGroupByFieldMetadataId?: string | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisManualSortOrder?: Array | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, layout: BarChartLayout, isCumulative?: boolean | null, splitMultiValueFields?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'CalendarConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'EmailThreadConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'EmailsConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FieldConfiguration', configurationType: WidgetConfigurationType, fieldDisplayMode: FieldDisplayMode, fieldMetadataId: string } | { __typename?: 'FieldRichTextConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FieldsConfiguration', configurationType: WidgetConfigurationType, viewId?: string | null, newFieldDefaultVisibility?: boolean | null, shouldAllowUserToSeeHiddenFields?: boolean | null } | { __typename?: 'FilesConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FrontComponentConfiguration', configurationType: WidgetConfigurationType, frontComponentId: string } | { __typename?: 'GaugeChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', configurationType: WidgetConfigurationType, url?: string | null } | { __typename?: 'LineChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: string, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, primaryAxisManualSortOrder?: Array | null, secondaryAxisGroupByFieldMetadataId?: string | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisManualSortOrder?: Array | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, splitMultiValueFields?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'NotesConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'PieChartConfiguration', configurationType: WidgetConfigurationType, groupByFieldMetadataId: string, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, manualSortOrder?: Array | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, hideEmptyCategory?: boolean | null, splitMultiValueFields?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'RecordTableConfiguration', configurationType: WidgetConfigurationType, viewId?: string | null } | { __typename?: 'StandaloneRichTextConfiguration', configurationType: WidgetConfigurationType, body: { __typename?: 'RichTextBody', blocknote?: string | null, markdown?: string | null } } | { __typename?: 'TasksConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'TimelineConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'ViewConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowRunConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowVersionConfiguration', configurationType: WidgetConfigurationType } }> | null }> | null } | null }; +export type FindOnePageLayoutTypeQueryVariables = Exact<{ + id: Scalars['String']; +}>; + + +export type FindOnePageLayoutTypeQuery = { __typename?: 'Query', getPageLayout?: { __typename?: 'PageLayout', id: string, type: PageLayoutType } | null }; + export type UploadEmailAttachmentFileMutationVariables = Exact<{ file: Scalars['Upload']; }>; @@ -7073,42 +7086,42 @@ export type FindMarketplaceAppDetailQueryVariables = Exact<{ export type FindMarketplaceAppDetailQuery = { __typename?: 'Query', findMarketplaceAppDetail: { __typename?: 'MarketplaceAppDetail', id: string, universalIdentifier: string, name: string, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, manifest?: any | null } }; -export type NavigationMenuItemFieldsFragment = { __typename?: 'NavigationMenuItem', id: string, type: NavigationMenuItemType, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string }; +export type NavigationMenuItemFieldsFragment = { __typename?: 'NavigationMenuItem', id: string, type: NavigationMenuItemType, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, pageLayoutId?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string }; -export type NavigationMenuItemQueryFieldsFragment = { __typename?: 'NavigationMenuItem', id: string, type: NavigationMenuItemType, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null }; +export type NavigationMenuItemQueryFieldsFragment = { __typename?: 'NavigationMenuItem', id: string, type: NavigationMenuItemType, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, pageLayoutId?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null }; export type CreateManyNavigationMenuItemsMutationVariables = Exact<{ inputs: Array | CreateNavigationMenuItemInput; }>; -export type CreateManyNavigationMenuItemsMutation = { __typename?: 'Mutation', createManyNavigationMenuItems: Array<{ __typename?: 'NavigationMenuItem', id: string, type: NavigationMenuItemType, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null }> }; +export type CreateManyNavigationMenuItemsMutation = { __typename?: 'Mutation', createManyNavigationMenuItems: Array<{ __typename?: 'NavigationMenuItem', id: string, type: NavigationMenuItemType, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, pageLayoutId?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null }> }; export type DeleteManyNavigationMenuItemsMutationVariables = Exact<{ ids: Array | Scalars['UUID']; }>; -export type DeleteManyNavigationMenuItemsMutation = { __typename?: 'Mutation', deleteManyNavigationMenuItems: Array<{ __typename?: 'NavigationMenuItem', id: string, type: NavigationMenuItemType, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null }> }; +export type DeleteManyNavigationMenuItemsMutation = { __typename?: 'Mutation', deleteManyNavigationMenuItems: Array<{ __typename?: 'NavigationMenuItem', id: string, type: NavigationMenuItemType, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, pageLayoutId?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null }> }; export type UpdateManyNavigationMenuItemsMutationVariables = Exact<{ inputs: Array | UpdateOneNavigationMenuItemInput; }>; -export type UpdateManyNavigationMenuItemsMutation = { __typename?: 'Mutation', updateManyNavigationMenuItems: Array<{ __typename?: 'NavigationMenuItem', id: string, type: NavigationMenuItemType, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null }> }; +export type UpdateManyNavigationMenuItemsMutation = { __typename?: 'Mutation', updateManyNavigationMenuItems: Array<{ __typename?: 'NavigationMenuItem', id: string, type: NavigationMenuItemType, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, pageLayoutId?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null }> }; export type FindManyNavigationMenuItemsQueryVariables = Exact<{ [key: string]: never; }>; -export type FindManyNavigationMenuItemsQuery = { __typename?: 'Query', navigationMenuItems: Array<{ __typename?: 'NavigationMenuItem', id: string, type: NavigationMenuItemType, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null }> }; +export type FindManyNavigationMenuItemsQuery = { __typename?: 'Query', navigationMenuItems: Array<{ __typename?: 'NavigationMenuItem', id: string, type: NavigationMenuItemType, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, pageLayoutId?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null }> }; export type FindOneNavigationMenuItemQueryVariables = Exact<{ id: Scalars['UUID']; }>; -export type FindOneNavigationMenuItemQuery = { __typename?: 'Query', navigationMenuItem?: { __typename?: 'NavigationMenuItem', id: string, type: NavigationMenuItemType, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null } | null }; +export type FindOneNavigationMenuItemQuery = { __typename?: 'Query', navigationMenuItem?: { __typename?: 'NavigationMenuItem', id: string, type: NavigationMenuItemType, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, pageLayoutId?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null } | null }; export type ObjectMetadataFieldsFragment = { __typename?: 'Object', id: string, universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, color?: string | null, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, universalIdentifier: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }; @@ -8549,8 +8562,8 @@ export const PageLayoutTabFragmentFragmentDoc = {"kind":"Document","definitions" export const PageLayoutFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PageLayoutFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PageLayout"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"defaultTabToFocusOnMobileAndSidePanelId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"tabs"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PageLayoutTabFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PageLayoutWidgetFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PageLayoutWidget"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"deletedAt"}},{"kind":"Field","name":{"kind":"Name","value":"conditionalDisplay"}},{"kind":"Field","name":{"kind":"Name","value":"conditionalAvailabilityExpression"}},{"kind":"Field","name":{"kind":"Name","value":"gridPosition"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"column"}},{"kind":"Field","name":{"kind":"Name","value":"columnSpan"}},{"kind":"Field","name":{"kind":"Name","value":"row"}},{"kind":"Field","name":{"kind":"Name","value":"rowSpan"}}]}},{"kind":"Field","name":{"kind":"Name","value":"position"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PageLayoutWidgetGridPosition"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"layoutMode"}},{"kind":"Field","name":{"kind":"Name","value":"row"}},{"kind":"Field","name":{"kind":"Name","value":"column"}},{"kind":"Field","name":{"kind":"Name","value":"rowSpan"}},{"kind":"Field","name":{"kind":"Name","value":"columnSpan"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PageLayoutWidgetVerticalListPosition"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"layoutMode"}},{"kind":"Field","name":{"kind":"Name","value":"index"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PageLayoutWidgetCanvasPosition"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"layoutMode"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"configuration"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BarChartConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateOperation"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisGroupByFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisGroupBySubFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisDateGranularity"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisOrderBy"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisManualSortOrder"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisGroupByFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisGroupBySubFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisGroupByDateGranularity"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisOrderBy"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisManualSortOrder"}},{"kind":"Field","name":{"kind":"Name","value":"omitNullValues"}},{"kind":"Field","name":{"kind":"Name","value":"axisNameDisplay"}},{"kind":"Field","name":{"kind":"Name","value":"displayDataLabel"}},{"kind":"Field","name":{"kind":"Name","value":"displayLegend"}},{"kind":"Field","name":{"kind":"Name","value":"rangeMin"}},{"kind":"Field","name":{"kind":"Name","value":"rangeMax"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"filter"}},{"kind":"Field","name":{"kind":"Name","value":"groupMode"}},{"kind":"Field","name":{"kind":"Name","value":"layout"}},{"kind":"Field","name":{"kind":"Name","value":"isCumulative"}},{"kind":"Field","name":{"kind":"Name","value":"splitMultiValueFields"}},{"kind":"Field","name":{"kind":"Name","value":"timezone"}},{"kind":"Field","name":{"kind":"Name","value":"firstDayOfTheWeek"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LineChartConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateOperation"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisGroupByFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisGroupBySubFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisDateGranularity"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisOrderBy"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisManualSortOrder"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisGroupByFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisGroupBySubFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisGroupByDateGranularity"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisOrderBy"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisManualSortOrder"}},{"kind":"Field","name":{"kind":"Name","value":"omitNullValues"}},{"kind":"Field","name":{"kind":"Name","value":"axisNameDisplay"}},{"kind":"Field","name":{"kind":"Name","value":"displayDataLabel"}},{"kind":"Field","name":{"kind":"Name","value":"displayLegend"}},{"kind":"Field","name":{"kind":"Name","value":"rangeMin"}},{"kind":"Field","name":{"kind":"Name","value":"rangeMax"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"filter"}},{"kind":"Field","name":{"kind":"Name","value":"isStacked"}},{"kind":"Field","name":{"kind":"Name","value":"isCumulative"}},{"kind":"Field","name":{"kind":"Name","value":"splitMultiValueFields"}},{"kind":"Field","name":{"kind":"Name","value":"timezone"}},{"kind":"Field","name":{"kind":"Name","value":"firstDayOfTheWeek"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PieChartConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"groupByFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateOperation"}},{"kind":"Field","name":{"kind":"Name","value":"groupBySubFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"dateGranularity"}},{"kind":"Field","name":{"kind":"Name","value":"orderBy"}},{"kind":"Field","name":{"kind":"Name","value":"manualSortOrder"}},{"kind":"Field","name":{"kind":"Name","value":"displayDataLabel"}},{"kind":"Field","name":{"kind":"Name","value":"showCenterMetric"}},{"kind":"Field","name":{"kind":"Name","value":"displayLegend"}},{"kind":"Field","name":{"kind":"Name","value":"hideEmptyCategory"}},{"kind":"Field","name":{"kind":"Name","value":"splitMultiValueFields"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"filter"}},{"kind":"Field","name":{"kind":"Name","value":"timezone"}},{"kind":"Field","name":{"kind":"Name","value":"firstDayOfTheWeek"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AggregateChartConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateOperation"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"displayDataLabel"}},{"kind":"Field","name":{"kind":"Name","value":"format"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"filter"}},{"kind":"Field","name":{"kind":"Name","value":"prefix"}},{"kind":"Field","name":{"kind":"Name","value":"suffix"}},{"kind":"Field","name":{"kind":"Name","value":"timezone"}},{"kind":"Field","name":{"kind":"Name","value":"firstDayOfTheWeek"}},{"kind":"Field","name":{"kind":"Name","value":"ratioAggregateConfig"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"optionValue"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"GaugeChartConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateOperation"}},{"kind":"Field","name":{"kind":"Name","value":"displayDataLabel"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"filter"}},{"kind":"Field","name":{"kind":"Name","value":"timezone"}},{"kind":"Field","name":{"kind":"Name","value":"firstDayOfTheWeek"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"IframeConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StandaloneRichTextConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"body"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"blocknote"}},{"kind":"Field","name":{"kind":"Name","value":"markdown"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CalendarConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EmailsConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EmailThreadConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"fieldDisplayMode"}},{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldRichTextConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldsConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"newFieldDefaultVisibility"}},{"kind":"Field","name":{"kind":"Name","value":"shouldAllowUserToSeeHiddenFields"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FilesConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotesConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TasksConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ViewConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RecordTableConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkflowConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkflowRunConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkflowVersionConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FrontComponentConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"frontComponentId"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutTabId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PageLayoutTabFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PageLayoutTab"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"layoutMode"}},{"kind":"Field","name":{"kind":"Name","value":"widgets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PageLayoutWidgetFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const MarketplaceAppDetailFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarketplaceAppDetailFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarketplaceAppDetail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"manifest"}}]}}]} as unknown as DocumentNode; export const MarketplaceAppFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarketplaceAppFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarketplaceApp"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"category"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}}]}}]} as unknown as DocumentNode; -export const NavigationMenuItemFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; -export const NavigationMenuItemQueryFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const NavigationMenuItemFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const NavigationMenuItemQueryFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const PublicConnectionParamsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PublicConnectionParams"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PublicConnectionParametersOutput"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]} as unknown as DocumentNode; export const UserInfoFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserInfoFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; export const ApplicationRegistrationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; @@ -8644,6 +8657,7 @@ export const UpdateCommandMenuItemDocument = {"kind":"Document","definitions":[{ export const FindManyCommandMenuItemsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyCommandMenuItems"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"commandMenuItems"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CommandMenuItemFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CommandMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CommandMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workflowVersionId"}},{"kind":"Field","name":{"kind":"Name","value":"frontComponentId"}},{"kind":"Field","name":{"kind":"Name","value":"frontComponent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isHeadless"}}]}},{"kind":"Field","name":{"kind":"Name","value":"engineComponentKey"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"shortLabel"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"isPinned"}},{"kind":"Field","name":{"kind":"Name","value":"payload"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PathCommandMenuItemPayload"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"path"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ObjectMetadataCommandMenuItemPayload"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"objectMetadataItemId"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"hotKeys"}},{"kind":"Field","name":{"kind":"Name","value":"conditionalAvailabilityExpression"}},{"kind":"Field","name":{"kind":"Name","value":"availabilityType"}},{"kind":"Field","name":{"kind":"Name","value":"availabilityObjectMetadataId"}}]}}]} as unknown as DocumentNode; export const DuplicateDashboardDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DuplicateDashboard"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"duplicateDashboard"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; export const FindOnePageLayoutDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOnePageLayout"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getPageLayout"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PageLayoutFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PageLayoutWidgetFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PageLayoutWidget"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"deletedAt"}},{"kind":"Field","name":{"kind":"Name","value":"conditionalDisplay"}},{"kind":"Field","name":{"kind":"Name","value":"conditionalAvailabilityExpression"}},{"kind":"Field","name":{"kind":"Name","value":"gridPosition"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"column"}},{"kind":"Field","name":{"kind":"Name","value":"columnSpan"}},{"kind":"Field","name":{"kind":"Name","value":"row"}},{"kind":"Field","name":{"kind":"Name","value":"rowSpan"}}]}},{"kind":"Field","name":{"kind":"Name","value":"position"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PageLayoutWidgetGridPosition"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"layoutMode"}},{"kind":"Field","name":{"kind":"Name","value":"row"}},{"kind":"Field","name":{"kind":"Name","value":"column"}},{"kind":"Field","name":{"kind":"Name","value":"rowSpan"}},{"kind":"Field","name":{"kind":"Name","value":"columnSpan"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PageLayoutWidgetVerticalListPosition"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"layoutMode"}},{"kind":"Field","name":{"kind":"Name","value":"index"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PageLayoutWidgetCanvasPosition"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"layoutMode"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"configuration"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BarChartConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateOperation"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisGroupByFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisGroupBySubFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisDateGranularity"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisOrderBy"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisManualSortOrder"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisGroupByFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisGroupBySubFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisGroupByDateGranularity"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisOrderBy"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisManualSortOrder"}},{"kind":"Field","name":{"kind":"Name","value":"omitNullValues"}},{"kind":"Field","name":{"kind":"Name","value":"axisNameDisplay"}},{"kind":"Field","name":{"kind":"Name","value":"displayDataLabel"}},{"kind":"Field","name":{"kind":"Name","value":"displayLegend"}},{"kind":"Field","name":{"kind":"Name","value":"rangeMin"}},{"kind":"Field","name":{"kind":"Name","value":"rangeMax"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"filter"}},{"kind":"Field","name":{"kind":"Name","value":"groupMode"}},{"kind":"Field","name":{"kind":"Name","value":"layout"}},{"kind":"Field","name":{"kind":"Name","value":"isCumulative"}},{"kind":"Field","name":{"kind":"Name","value":"splitMultiValueFields"}},{"kind":"Field","name":{"kind":"Name","value":"timezone"}},{"kind":"Field","name":{"kind":"Name","value":"firstDayOfTheWeek"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LineChartConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateOperation"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisGroupByFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisGroupBySubFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisDateGranularity"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisOrderBy"}},{"kind":"Field","name":{"kind":"Name","value":"primaryAxisManualSortOrder"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisGroupByFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisGroupBySubFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisGroupByDateGranularity"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisOrderBy"}},{"kind":"Field","name":{"kind":"Name","value":"secondaryAxisManualSortOrder"}},{"kind":"Field","name":{"kind":"Name","value":"omitNullValues"}},{"kind":"Field","name":{"kind":"Name","value":"axisNameDisplay"}},{"kind":"Field","name":{"kind":"Name","value":"displayDataLabel"}},{"kind":"Field","name":{"kind":"Name","value":"displayLegend"}},{"kind":"Field","name":{"kind":"Name","value":"rangeMin"}},{"kind":"Field","name":{"kind":"Name","value":"rangeMax"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"filter"}},{"kind":"Field","name":{"kind":"Name","value":"isStacked"}},{"kind":"Field","name":{"kind":"Name","value":"isCumulative"}},{"kind":"Field","name":{"kind":"Name","value":"splitMultiValueFields"}},{"kind":"Field","name":{"kind":"Name","value":"timezone"}},{"kind":"Field","name":{"kind":"Name","value":"firstDayOfTheWeek"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PieChartConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"groupByFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateOperation"}},{"kind":"Field","name":{"kind":"Name","value":"groupBySubFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"dateGranularity"}},{"kind":"Field","name":{"kind":"Name","value":"orderBy"}},{"kind":"Field","name":{"kind":"Name","value":"manualSortOrder"}},{"kind":"Field","name":{"kind":"Name","value":"displayDataLabel"}},{"kind":"Field","name":{"kind":"Name","value":"showCenterMetric"}},{"kind":"Field","name":{"kind":"Name","value":"displayLegend"}},{"kind":"Field","name":{"kind":"Name","value":"hideEmptyCategory"}},{"kind":"Field","name":{"kind":"Name","value":"splitMultiValueFields"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"filter"}},{"kind":"Field","name":{"kind":"Name","value":"timezone"}},{"kind":"Field","name":{"kind":"Name","value":"firstDayOfTheWeek"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AggregateChartConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateOperation"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"displayDataLabel"}},{"kind":"Field","name":{"kind":"Name","value":"format"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"filter"}},{"kind":"Field","name":{"kind":"Name","value":"prefix"}},{"kind":"Field","name":{"kind":"Name","value":"suffix"}},{"kind":"Field","name":{"kind":"Name","value":"timezone"}},{"kind":"Field","name":{"kind":"Name","value":"firstDayOfTheWeek"}},{"kind":"Field","name":{"kind":"Name","value":"ratioAggregateConfig"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"optionValue"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"GaugeChartConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateOperation"}},{"kind":"Field","name":{"kind":"Name","value":"displayDataLabel"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"filter"}},{"kind":"Field","name":{"kind":"Name","value":"timezone"}},{"kind":"Field","name":{"kind":"Name","value":"firstDayOfTheWeek"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"IframeConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StandaloneRichTextConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"body"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"blocknote"}},{"kind":"Field","name":{"kind":"Name","value":"markdown"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CalendarConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EmailsConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EmailThreadConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"fieldDisplayMode"}},{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldRichTextConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldsConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"newFieldDefaultVisibility"}},{"kind":"Field","name":{"kind":"Name","value":"shouldAllowUserToSeeHiddenFields"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FilesConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotesConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TasksConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ViewConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RecordTableConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkflowConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkflowRunConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkflowVersionConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FrontComponentConfiguration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configurationType"}},{"kind":"Field","name":{"kind":"Name","value":"frontComponentId"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutTabId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PageLayoutTabFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PageLayoutTab"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"layoutMode"}},{"kind":"Field","name":{"kind":"Name","value":"widgets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PageLayoutWidgetFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PageLayoutFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PageLayout"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"defaultTabToFocusOnMobileAndSidePanelId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"tabs"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PageLayoutTabFragment"}}]}}]}}]} as unknown as DocumentNode; +export const FindOnePageLayoutTypeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOnePageLayoutType"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getPageLayout"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}}]}}]} as unknown as DocumentNode; export const UploadEmailAttachmentFileDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UploadEmailAttachmentFile"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"file"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Upload"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uploadEmailAttachmentFile"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"file"},"value":{"kind":"Variable","name":{"kind":"Name","value":"file"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"path"}},{"kind":"Field","name":{"kind":"Name","value":"size"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]} as unknown as DocumentNode; export const UploadFilesFieldFileDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UploadFilesFieldFile"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"file"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Upload"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fieldMetadataId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uploadFilesFieldFile"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"file"},"value":{"kind":"Variable","name":{"kind":"Name","value":"file"}}},{"kind":"Argument","name":{"kind":"Name","value":"fieldMetadataId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fieldMetadataId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"path"}},{"kind":"Field","name":{"kind":"Name","value":"size"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]} as unknown as DocumentNode; export const UploadWorkflowFileDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UploadWorkflowFile"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"file"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Upload"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uploadWorkflowFile"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"file"},"value":{"kind":"Variable","name":{"kind":"Name","value":"file"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"path"}},{"kind":"Field","name":{"kind":"Name","value":"size"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]} as unknown as DocumentNode; @@ -8662,11 +8676,11 @@ export const InstallMarketplaceAppDocument = {"kind":"Document","definitions":[{ export const UpgradeApplicationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpgradeApplication"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appRegistrationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"targetVersion"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"upgradeApplication"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"appRegistrationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appRegistrationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"targetVersion"},"value":{"kind":"Variable","name":{"kind":"Name","value":"targetVersion"}}}]}]}}]} as unknown as DocumentNode; export const FindManyMarketplaceAppsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyMarketplaceApps"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findManyMarketplaceApps"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MarketplaceAppFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarketplaceAppFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarketplaceApp"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"category"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}}]}}]} as unknown as DocumentNode; export const FindMarketplaceAppDetailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindMarketplaceAppDetail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"universalIdentifier"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findMarketplaceAppDetail"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"universalIdentifier"},"value":{"kind":"Variable","name":{"kind":"Name","value":"universalIdentifier"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MarketplaceAppDetailFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarketplaceAppDetailFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarketplaceAppDetail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"manifest"}}]}}]} as unknown as DocumentNode; -export const CreateManyNavigationMenuItemsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateManyNavigationMenuItems"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"inputs"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateNavigationMenuItemInput"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createManyNavigationMenuItems"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"inputs"},"value":{"kind":"Variable","name":{"kind":"Name","value":"inputs"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}}]} as unknown as DocumentNode; -export const DeleteManyNavigationMenuItemsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteManyNavigationMenuItems"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"ids"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteManyNavigationMenuItems"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"ids"},"value":{"kind":"Variable","name":{"kind":"Name","value":"ids"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}}]} as unknown as DocumentNode; -export const UpdateManyNavigationMenuItemsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateManyNavigationMenuItems"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"inputs"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateOneNavigationMenuItemInput"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateManyNavigationMenuItems"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"inputs"},"value":{"kind":"Variable","name":{"kind":"Name","value":"inputs"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}}]} as unknown as DocumentNode; -export const FindManyNavigationMenuItemsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyNavigationMenuItems"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"navigationMenuItems"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}}]} as unknown as DocumentNode; -export const FindOneNavigationMenuItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneNavigationMenuItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"navigationMenuItem"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}}]} as unknown as DocumentNode; +export const CreateManyNavigationMenuItemsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateManyNavigationMenuItems"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"inputs"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateNavigationMenuItemInput"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createManyNavigationMenuItems"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"inputs"},"value":{"kind":"Variable","name":{"kind":"Name","value":"inputs"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}}]} as unknown as DocumentNode; +export const DeleteManyNavigationMenuItemsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteManyNavigationMenuItems"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"ids"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteManyNavigationMenuItems"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"ids"},"value":{"kind":"Variable","name":{"kind":"Name","value":"ids"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}}]} as unknown as DocumentNode; +export const UpdateManyNavigationMenuItemsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateManyNavigationMenuItems"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"inputs"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateOneNavigationMenuItemInput"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateManyNavigationMenuItems"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"inputs"},"value":{"kind":"Variable","name":{"kind":"Name","value":"inputs"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}}]} as unknown as DocumentNode; +export const FindManyNavigationMenuItemsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyNavigationMenuItems"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"navigationMenuItems"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}}]} as unknown as DocumentNode; +export const FindOneNavigationMenuItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneNavigationMenuItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"navigationMenuItem"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}}]} as unknown as DocumentNode; export const CreateOneObjectMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOneObjectMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateOneObjectInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneObject"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}},{"kind":"Field","name":{"kind":"Name","value":"labelSingular"}},{"kind":"Field","name":{"kind":"Name","value":"labelPlural"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isSearchable"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifierFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifierFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"isLabelSyncedWithName"}},{"kind":"Field","name":{"kind":"Name","value":"fieldsList"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isSystem"}},{"kind":"Field","name":{"kind":"Name","value":"isUIReadOnly"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"isUnique"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"defaultValue"}},{"kind":"Field","name":{"kind":"Name","value":"options"}},{"kind":"Field","name":{"kind":"Name","value":"settings"}},{"kind":"Field","name":{"kind":"Name","value":"isLabelSyncedWithName"}},{"kind":"Field","name":{"kind":"Name","value":"morphId"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"relation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"sourceObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}},{"kind":"Field","name":{"kind":"Name","value":"sourceFieldMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"targetFieldMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"morphRelations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"sourceObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}},{"kind":"Field","name":{"kind":"Name","value":"sourceFieldMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"targetFieldMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const CreateOneFieldMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOneFieldMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateOneFieldMetadataInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneField"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isUnique"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"settings"}},{"kind":"Field","name":{"kind":"Name","value":"defaultValue"}},{"kind":"Field","name":{"kind":"Name","value":"options"}},{"kind":"Field","name":{"kind":"Name","value":"isLabelSyncedWithName"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"object"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"relation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"sourceObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}},{"kind":"Field","name":{"kind":"Name","value":"sourceFieldMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"targetFieldMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"morphRelations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"sourceObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}},{"kind":"Field","name":{"kind":"Name","value":"sourceFieldMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"targetFieldMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const UpdateOneFieldMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateOneFieldMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"idToUpdate"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"updatePayload"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateFieldInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateOneField"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"idToUpdate"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"update"},"value":{"kind":"Variable","name":{"kind":"Name","value":"updatePayload"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isUnique"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"settings"}},{"kind":"Field","name":{"kind":"Name","value":"isLabelSyncedWithName"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"object"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts b/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts index bff5bd11e7..32960e914e 100644 --- a/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts +++ b/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts @@ -2,12 +2,13 @@ import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair'; import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath'; import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus'; import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo'; +import { useQuery } from '@apollo/client/react'; import { useParams } from 'react-router-dom'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { AppPath, SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; -import { OnboardingStatus } from '~/generated-metadata/graphql'; +import { OnboardingStatus, PageLayoutType } from '~/generated-metadata/graphql'; import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace'; import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation'; @@ -58,11 +59,23 @@ const setupMockIsOnAWorkspace = (isOnAWorkspace: boolean) => { }); }; +jest.mock('@apollo/client/react'); +const setupMockUseQuery = (result?: { data?: unknown; loading?: boolean }) => { + jest.mocked(useQuery).mockReturnValueOnce({ + data: result?.data ?? undefined, + loading: result?.loading ?? false, + } as ReturnType); +}; + jest.mock('react-router-dom'); -const setupMockUseParams = (objectNamePlural?: string) => { - jest - .mocked(useParams) - .mockReturnValueOnce({ objectNamePlural: objectNamePlural ?? '' }); +const setupMockUseParams = ( + objectNamePlural?: string, + pageLayoutId?: string, +) => { + jest.mocked(useParams).mockReturnValueOnce({ + objectNamePlural: objectNamePlural ?? '', + pageLayoutId, + }); }; jest.mock('@/ui/utilities/state/jotai/hooks/useAtomStateValue'); @@ -92,6 +105,8 @@ const testCases: { objectNamePluralFromMetadata?: string; verifyEmailRedirectPath?: string; returnToPath?: string; + pageLayoutId?: string; + useQueryResult?: { data?: unknown; loading?: boolean }; }[] = [ { loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, { loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, @@ -277,6 +292,20 @@ const testCases: { { loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, { loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, + { loc: AppPath.PageLayoutPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.PageLayoutPage, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.PageLayoutPage, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.PageLayoutPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.PageLayoutPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.PageLayoutPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.PageLayoutPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.PageLayoutPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.PageLayoutPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, + { loc: AppPath.PageLayoutPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined, pageLayoutId: 'valid-id', useQueryResult: { loading: true } }, + { loc: AppPath.PageLayoutPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: AppPath.NotFound, pageLayoutId: 'non-existent-id', useQueryResult: { data: { getPageLayout: null }, loading: false } }, + { loc: AppPath.PageLayoutPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: AppPath.NotFound, pageLayoutId: 'wrong-type-id', useQueryResult: { data: { getPageLayout: { type: PageLayoutType.RECORD_PAGE } }, loading: false } }, + { loc: AppPath.PageLayoutPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined, pageLayoutId: 'valid-standalone-id', useQueryResult: { data: { getPageLayout: { type: PageLayoutType.STANDALONE_PAGE } }, loading: false } }, + { loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, { loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, { loc: AppPath.SettingsCatchAll, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, @@ -350,6 +379,8 @@ describe('usePageChangeEffectNavigateLocation', () => { objectNamePluralFromMetadata, verifyEmailRedirectPath, returnToPath, + pageLayoutId, + useQueryResult, res, }) => { setupMockIsMatchingLocation(loc); @@ -357,7 +388,8 @@ describe('usePageChangeEffectNavigateLocation', () => { setupMockIsWorkspaceActivationStatusEqualsTo(isWorkspaceSuspended); setupMockHasAccessTokenPair(hasAccessTokenPair); setupMockIsOnAWorkspace(isOnAWorkspace ?? true); - setupMockUseParams(objectNamePluralFromParams); + setupMockUseQuery(useQueryResult); + setupMockUseParams(objectNamePluralFromParams, pageLayoutId); setupMockState( objectNamePluralFromMetadata, verifyEmailRedirectPath, @@ -379,6 +411,12 @@ describe('usePageChangeEffectNavigateLocation', () => { ['nonExistingObjectInParam', 'existingObjectInParam:false'].length + ['caseWithRedirectionToVerifyEmailRedirectPath', 'caseWithout'] .length + + [ + 'pageLayout:loading', + 'pageLayout:missing', + 'pageLayout:wrongType', + 'pageLayout:validStandalone', + ].length + ['returnToPath:verify', 'returnToPath:signInUp', 'returnToPath:index'] .length + ['notOnWorkspace:verify', 'notOnWorkspace:signInUp'].length, diff --git a/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts b/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts index 601112e2ed..7100ef81f0 100644 --- a/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts +++ b/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts @@ -11,12 +11,17 @@ import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo'; import { isValidReturnToPath } from '@/auth/utils/isValidReturnToPath'; +import { useQuery } from '@apollo/client/react'; import { isNonEmptyString } from '@sniptt/guards'; import { useLocation, useParams } from 'react-router-dom'; import { AppPath, SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; -import { OnboardingStatus } from '~/generated-metadata/graphql'; +import { + FindOnePageLayoutTypeDocument, + OnboardingStatus, + PageLayoutType, +} from '~/generated-metadata/graphql'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; const readReturnToPathFromUrlSearchParams = (): string | null => { @@ -39,11 +44,27 @@ export const usePageChangeEffectNavigateLocation = () => { const someMatchingLocationOf = (appPaths: AppPath[]): boolean => appPaths.some((appPath) => isMatchingLocation(location, appPath)); - const objectNamePlural = useParams().objectNamePlural ?? ''; + const params = useParams(); + + const objectNamePlural = params.objectNamePlural ?? ''; const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector); const objectMetadataItem = objectMetadataItems?.find( (objectMetadataItem) => objectMetadataItem.namePlural === objectNamePlural, ); + + const pageLayoutId = params.pageLayoutId; + const isOnPageLayoutPage = isMatchingLocation( + location, + AppPath.PageLayoutPage, + ); + + const { data: pageLayoutData, loading: isPageLayoutLoading } = useQuery( + FindOnePageLayoutTypeDocument, + { + variables: { id: pageLayoutId ?? '' }, + skip: !isOnPageLayoutPage || !isDefined(pageLayoutId), + }, + ); const verifyEmailRedirectPath = useAtomStateValue( verifyEmailRedirectPathState, ); @@ -157,5 +178,15 @@ export const usePageChangeEffectNavigateLocation = () => { return AppPath.NotFound; } + if ( + isOnPageLayoutPage && + isDefined(pageLayoutId) && + !isPageLayoutLoading && + (!isDefined(pageLayoutData?.getPageLayout) || + pageLayoutData.getPageLayout.type !== PageLayoutType.STANDALONE_PAGE) + ) { + return AppPath.NotFound; + } + return; }; diff --git a/packages/twenty-front/src/modules/ai/hooks/useBrowsingContext.ts b/packages/twenty-front/src/modules/ai/hooks/useBrowsingContext.ts index 85c0de3b35..f314df91e6 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useBrowsingContext.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useBrowsingContext.ts @@ -6,9 +6,11 @@ import { type BrowsingContext } from '@/ai/types/BrowsingContext'; import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; +import { contextStoreCurrentPageTypeComponentState } from '@/context-store/states/contextStoreCurrentPageTypeComponentState'; import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; +import { ContextStorePageType } from 'twenty-shared/types'; import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; @@ -23,6 +25,12 @@ export const useGetBrowsingContext = () => { const getBrowsingContext = useCallback((): BrowsingContext | null => { const instanceId = MAIN_CONTEXT_STORE_INSTANCE_ID; + const pageType = store.get( + contextStoreCurrentPageTypeComponentState.atomFamily({ + instanceId, + }), + ); + const viewType = store.get( contextStoreCurrentViewTypeComponentState.atomFamily({ instanceId, @@ -45,7 +53,7 @@ export const useGetBrowsingContext = () => { return null; } - if (viewType === ContextStoreViewType.ShowPage) { + if (pageType === ContextStorePageType.Record) { const targetedRecordsRule = store.get( contextStoreTargetedRecordsRuleComponentState.atomFamily({ instanceId, diff --git a/packages/twenty-front/src/modules/app/hooks/useCreateAppRouter.tsx b/packages/twenty-front/src/modules/app/hooks/useCreateAppRouter.tsx index 9f77653333..7cfa4cd001 100644 --- a/packages/twenty-front/src/modules/app/hooks/useCreateAppRouter.tsx +++ b/packages/twenty-front/src/modules/app/hooks/useCreateAppRouter.tsx @@ -94,6 +94,12 @@ const BookCall = lazy(() => })), ); +const StandalonePageLayoutPage = lazy(() => + import('~/pages/page-layout/StandalonePageLayoutPage').then((module) => ({ + default: module.StandalonePageLayoutPage, + })), +); + const NotFound = lazy(() => import('~/pages/not-found/NotFound').then((module) => ({ default: module.NotFound, @@ -220,6 +226,14 @@ export const useCreateAppRouter = ( } /> + + + + } + /> { + const store = useStore(); + const isMobile = useIsMobile(); + const commandMenuItems = useAtomStateValue(commandMenuItemsSelector); + const currentWorkspace = useAtomStateValue(currentWorkspaceState); + const { objectMetadataItems } = useObjectMetadataItems(); + + const commandMenuContextApi = useMemo(() => { + const featureFlags: Record = {}; + + for (const flag of currentWorkspace?.featureFlags ?? []) { + featureFlags[flag.key] = flag.value === true; + } + + const targetObjectReadPermissions: Record = {}; + const targetObjectWritePermissions: Record = {}; + + for (const metadataItem of objectMetadataItems) { + const permissions = store.get( + objectPermissionsFamilySelector.selectorFamily({ + objectNameSingular: metadataItem.nameSingular, + }), + ); + targetObjectReadPermissions[metadataItem.nameSingular] = + permissions.canRead; + targetObjectWritePermissions[metadataItem.nameSingular] = + permissions.canUpdate; + } + + return { + pageType: ContextStorePageType.Standalone, + isInSidePanel: false, + isPageInEditMode: false, + favoriteRecordIds: [], + isSelectAll: false, + hasAnySoftDeleteFilterOnView: false, + numberOfSelectedRecords: 0, + objectPermissions: { + canReadObjectRecords: false, + canUpdateObjectRecords: false, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + restrictedFields: {}, + objectMetadataId: '', + rowLevelPermissionPredicates: [], + rowLevelPermissionPredicateGroups: [], + }, + selectedRecords: [], + featureFlags, + targetObjectReadPermissions, + targetObjectWritePermissions, + objectMetadataItem: {}, + objectMetadataLabel: '', + }; + }, [currentWorkspace?.featureFlags, objectMetadataItems, store]); + + const filteredCommandMenuItems = useMemo(() => { + return commandMenuItems + .filter(doesCommandMenuItemMatchObjectMetadataId(undefined)) + .filter( + (item) => + item.availabilityType !== + CommandMenuItemAvailabilityType.RECORD_SELECTION && + item.availabilityType !== + CommandMenuItemAvailabilityType.GLOBAL_OBJECT_CONTEXT, + ) + .filter((item) => + evaluateConditionalAvailabilityExpression( + item.conditionalAvailabilityExpression, + commandMenuContextApi, + ), + ) + .sort( + (firstItem, secondItem) => firstItem.position - secondItem.position, + ); + }, [commandMenuItems, commandMenuContextApi]); + + return ( + + {!isMobile && } + + + ); +}; diff --git a/packages/twenty-front/src/modules/command-menu-item/constants/EmptyCommandMenuContextApi.ts b/packages/twenty-front/src/modules/command-menu-item/constants/EmptyCommandMenuContextApi.ts index b94e4bc366..64432d01fe 100644 --- a/packages/twenty-front/src/modules/command-menu-item/constants/EmptyCommandMenuContextApi.ts +++ b/packages/twenty-front/src/modules/command-menu-item/constants/EmptyCommandMenuContextApi.ts @@ -1,10 +1,10 @@ import { - CommandMenuContextApiPageType, + ContextStorePageType, type CommandMenuContextApi, } from 'twenty-shared/types'; export const EMPTY_COMMAND_MENU_CONTEXT_API: CommandMenuContextApi = { - pageType: CommandMenuContextApiPageType.INDEX_PAGE, + pageType: ContextStorePageType.Index, isInSidePanel: false, isPageInEditMode: false, favoriteRecordIds: [], diff --git a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx index 07cf6f59fc..327a73cec8 100644 --- a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx @@ -4,6 +4,7 @@ import { } from '@/command-menu-item/contexts/CommandMenuContext'; import { commandMenuItemsSelector } from '@/command-menu-item/states/commandMenuItemsSelector'; import { doesCommandMenuItemMatchObjectMetadataId } from '@/command-menu-item/utils/doesCommandMenuItemMatchObjectMetadataId'; +import { doesCommandMenuItemMatchPageType } from '@/command-menu-item/utils/doesCommandMenuItemMatchPageType'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { useMemo } from 'react'; import { type CommandMenuContextApi } from 'twenty-shared/types'; @@ -32,6 +33,7 @@ export const CommandMenuContextProviderContent = ({ .filter( doesCommandMenuItemMatchObjectMetadataId(currentObjectMetadataItemId), ) + .filter(doesCommandMenuItemMatchPageType(commandMenuContextApi.pageType)) .filter((item) => evaluateConditionalAvailabilityExpression( item.conditionalAvailabilityExpression, diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/SidePanelCommandMenuItemDisplayPage.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/SidePanelCommandMenuItemDisplayPage.tsx index fd44b5c30f..abb1ae1bc5 100644 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/SidePanelCommandMenuItemDisplayPage.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/display/components/SidePanelCommandMenuItemDisplayPage.tsx @@ -20,6 +20,7 @@ export const SidePanelCommandMenuItemDisplayPage = () => { const sidePanelSearch = useAtomStateValue(sidePanelSearchState); const { commandMenuItems, commandMenuContextApi } = useContext(CommandMenuContext); + const commandMenuPinnedInlineLayout = useAtomStateValue( commandMenuPinnedInlineLayoutState, ); diff --git a/packages/twenty-front/src/modules/command-menu-item/edit/components/PinnedCommandMenuItemButtonsEditMode.tsx b/packages/twenty-front/src/modules/command-menu-item/edit/components/PinnedCommandMenuItemButtonsEditMode.tsx index abf54726c7..ecbdeba6a6 100644 --- a/packages/twenty-front/src/modules/command-menu-item/edit/components/PinnedCommandMenuItemButtonsEditMode.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/edit/components/PinnedCommandMenuItemButtonsEditMode.tsx @@ -62,6 +62,7 @@ export const PinnedCommandMenuItemButtonsEditMode = () => { () => new Set([ CommandMenuItemAvailabilityType.GLOBAL, + CommandMenuItemAvailabilityType.GLOBAL_OBJECT_CONTEXT, mainContextStoreHasSelectedRecords ? CommandMenuItemAvailabilityType.RECORD_SELECTION : CommandMenuItemAvailabilityType.FALLBACK, diff --git a/packages/twenty-front/src/modules/command-menu-item/edit/components/SidePanelCommandMenuItemEditPage.tsx b/packages/twenty-front/src/modules/command-menu-item/edit/components/SidePanelCommandMenuItemEditPage.tsx index 3e33fed9e4..afbbdec8be 100644 --- a/packages/twenty-front/src/modules/command-menu-item/edit/components/SidePanelCommandMenuItemEditPage.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/edit/components/SidePanelCommandMenuItemEditPage.tsx @@ -21,7 +21,7 @@ import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomState import { type DropResult } from '@hello-pangea/dnd'; import { styled } from '@linaria/react'; import { useLingui } from '@lingui/react/macro'; -import { CommandMenuContextApiPageType } from 'twenty-shared/types'; +import { ContextStorePageType } from 'twenty-shared/types'; import { interpolateCommandMenuItemTemplate, isDefined, @@ -71,9 +71,13 @@ export const SidePanelCommandMenuItemEditPage = () => { const currentObjectMetadataItemId = commandMenuContextApi.objectMetadataItem.id; + const hasObjectContext = isDefined(currentObjectMetadataItemId); + const isRecordPage = - commandMenuContextApi.pageType === - CommandMenuContextApiPageType.RECORD_PAGE; + commandMenuContextApi.pageType === ContextStorePageType.Record; + + const isIndexPage = + commandMenuContextApi.pageType === ContextStorePageType.Index; const mainContextStoreHasSelectedRecords = useAtomStateValue( mainContextStoreHasSelectedRecordsSelector, @@ -93,6 +97,9 @@ export const SidePanelCommandMenuItemEditPage = () => { const allowedAvailabilityTypes = new Set([ CommandMenuItemAvailabilityType.GLOBAL, + ...(isIndexPage || isRecordPage + ? [CommandMenuItemAvailabilityType.GLOBAL_OBJECT_CONTEXT] + : []), mainContextStoreHasSelectedRecords ? CommandMenuItemAvailabilityType.RECORD_SELECTION : CommandMenuItemAvailabilityType.FALLBACK, @@ -231,11 +238,13 @@ export const SidePanelCommandMenuItemEditPage = () => { return ( - - - + {hasObjectContext && ( + + + + )} diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildTriggerWorkflowVersionPayloads.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildTriggerWorkflowVersionPayloads.ts index c42c76cf68..2ac22af582 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildTriggerWorkflowVersionPayloads.ts +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildTriggerWorkflowVersionPayloads.ts @@ -74,6 +74,7 @@ export const buildTriggerWorkflowVersionPayloads = ({ return payloads; } case CommandMenuItemAvailabilityTypeEnum.GLOBAL: + case CommandMenuItemAvailabilityTypeEnum.GLOBAL_OBJECT_CONTEXT: case CommandMenuItemAvailabilityTypeEnum.FALLBACK: { return payloads; } diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx index 1208b26bc6..01da14f6f0 100644 --- a/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx @@ -3,7 +3,7 @@ import { useCloseCommandMenu } from '@/command-menu-item/hooks/useCloseCommandMe import { type CommandMenuItemContainerType } from '@/command-menu-item/types/CommandMenuItemContainerType'; import { act, renderHook } from '@testing-library/react'; import { type ReactNode } from 'react'; -import { CommandMenuContextApiPageType } from 'twenty-shared/types'; +import { ContextStorePageType } from 'twenty-shared/types'; const TEST_COMMAND_MENU_ID = 'test-cmd-menu-1'; @@ -44,7 +44,7 @@ const getWrapper = displayType: 'button', commandMenuItems: [], commandMenuContextApi: { - pageType: CommandMenuContextApiPageType.INDEX_PAGE, + pageType: ContextStorePageType.Index, isInSidePanel, isPageInEditMode: false, favoriteRecordIds: [], diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuContextApi.ts b/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuContextApi.ts index 261e53abea..bc9b8e2e72 100644 --- a/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuContextApi.ts +++ b/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuContextApi.ts @@ -2,17 +2,17 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { objectPermissionsFamilySelector } from '@/auth/states/objectPermissionsFamilySelector'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; -import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; +import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; +import { contextStoreCurrentPageTypeComponentState } from '@/context-store/states/contextStoreCurrentPageTypeComponentState'; import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState'; import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject'; import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView'; -import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore'; import { recordStoreRecordsSelector } from '@/object-record/record-store/states/selectors/recordStoreRecordsSelector'; +import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId'; import { SIDE_PANEL_COMPONENT_INSTANCE_ID } from '@/side-panel/constants/SidePanelComponentInstanceId'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue'; @@ -22,7 +22,7 @@ import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomState import { isNonEmptyArray } from '@sniptt/guards'; import { useStore } from 'jotai'; import { - CommandMenuContextApiPageType, + ContextStorePageType, type CommandMenuContextApi, } from 'twenty-shared/types'; import { isDefined, resolveObjectMetadataLabel } from 'twenty-shared/utils'; @@ -93,25 +93,31 @@ export const useCommandMenuContextApi = (): CommandMenuContextApi => { rowLevelPermissionPredicateGroups: [], }; - const { recordIndexId } = useRecordIndexIdFromCurrentContextStore(); + const contextStoreCurrentViewId = useAtomComponentStateValue( + contextStoreCurrentViewIdComponentState, + ); + + const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId( + objectMetadataItem?.namePlural ?? '', + contextStoreCurrentViewId ?? '', + ); const hasAnySoftDeleteFilterOnView = useAtomComponentSelectorValue( hasAnySoftDeleteFilterOnViewComponentSelector, recordIndexId, ); - const contextStoreCurrentViewType = useAtomComponentStateValue( - contextStoreCurrentViewTypeComponentState, + const contextStoreCurrentPageType = useAtomComponentStateValue( + contextStoreCurrentPageTypeComponentState, ); const contextStoreIsPageInEditMode = useAtomComponentStateValue( contextStoreIsPageInEditModeComponentState, ); - const pageType = - contextStoreCurrentViewType === ContextStoreViewType.ShowPage - ? CommandMenuContextApiPageType.RECORD_PAGE - : CommandMenuContextApiPageType.INDEX_PAGE; + const pageType = isDefined(contextStoreCurrentPageType) + ? contextStoreCurrentPageType + : ContextStorePageType.Index; const isSelectAll = contextStoreTargetedRecordsRule.mode === 'exclusion'; diff --git a/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemContainerType.ts b/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemContainerType.ts index 08bda5bd3e..3ad991db0d 100644 --- a/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemContainerType.ts +++ b/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemContainerType.ts @@ -3,4 +3,5 @@ export type CommandMenuItemContainerType = | 'index-page-header' | 'index-page-dropdown' | 'show-page-header' + | 'standalone-page-header' | 'command-menu-show-page-dropdown'; diff --git a/packages/twenty-front/src/modules/command-menu-item/utils/doesCommandMenuItemMatchPageType.ts b/packages/twenty-front/src/modules/command-menu-item/utils/doesCommandMenuItemMatchPageType.ts new file mode 100644 index 0000000000..cfbfef2290 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/utils/doesCommandMenuItemMatchPageType.ts @@ -0,0 +1,20 @@ +import { ContextStorePageType } from 'twenty-shared/types'; +import { + CommandMenuItemAvailabilityType, + type CommandMenuItemFieldsFragment, +} from '~/generated-metadata/graphql'; + +const PAGE_TYPES_WITH_RECORD_CONTEXT = new Set([ + ContextStorePageType.Index, + ContextStorePageType.Record, +]); + +const AVAILABILITY_TYPES_REQUIRING_RECORD_CONTEXT = new Set([ + CommandMenuItemAvailabilityType.GLOBAL_OBJECT_CONTEXT, + CommandMenuItemAvailabilityType.RECORD_SELECTION, +]); + +export const doesCommandMenuItemMatchPageType = + (pageType: ContextStorePageType) => (item: CommandMenuItemFieldsFragment) => + !AVAILABILITY_TYPES_REQUIRING_RECORD_CONTEXT.has(item.availabilityType) || + PAGE_TYPES_WITH_RECORD_CONTEXT.has(pageType); diff --git a/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx b/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx index e6312a3512..bde6179aa9 100644 --- a/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx @@ -26,15 +26,17 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceSta import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext'; import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; +import { contextStoreCurrentPageTypeComponentState } from '@/context-store/states/contextStoreCurrentPageTypeComponentState'; import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; +import { ContextStorePageType } from 'twenty-shared/types'; import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector'; import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper'; import { SidePanelRouter } from '@/side-panel/components/SidePanelRouter'; import { SIDE_PANEL_COMPONENT_INSTANCE_ID } from '@/side-panel/constants/SidePanelComponentInstanceId'; import { SIDE_PANEL_FOCUS_ID } from '@/side-panel/constants/SidePanelFocusId'; -import { type SidePanelRootPage } from '@/side-panel/pages/root/components/SidePanelRootPage'; +import { type SidePanelCommandMenuItemDisplayPage } from '@/command-menu-item/display/components/SidePanelCommandMenuItemDisplayPage'; import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState'; import { sidePanelNavigationStackState } from '@/side-panel/states/sidePanelNavigationStackState'; import { sidePanelPageInfoState } from '@/side-panel/states/sidePanelPageInfoState'; @@ -75,7 +77,7 @@ const ContextStoreDecorator: Decorator = (Story) => { ); }; -const meta: Meta = { +const meta: Meta = { title: 'Modules/CommandMenu/CommandMenu', component: SidePanelRouter, decorators: [ @@ -131,6 +133,12 @@ const meta: Meta = { }), ContextStoreViewType.Table, ); + jotaiStore.set( + contextStoreCurrentPageTypeComponentState.atomFamily({ + instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID, + }), + ContextStorePageType.Index, + ); return ; }, @@ -145,7 +153,7 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const DefaultWithoutSearch: Story = { play: async ({ canvasElement }) => { diff --git a/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx b/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx index f5c02512b5..e68208d720 100644 --- a/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx +++ b/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx @@ -47,6 +47,7 @@ export const MainContextStoreProvider = () => { AppPath.RecordIndexPage, ); const isRecordShowPage = isMatchingLocation(location, AppPath.RecordShowPage); + const isStandalonePage = isMatchingLocation(location, AppPath.PageLayoutPage); const isSettingsPage = useIsSettingsPage(); const showAuthModal = useShowAuthModal(); @@ -118,6 +119,7 @@ export const MainContextStoreProvider = () => { const shouldComputeContextStore = (isRecordIndexPage || isRecordShowPage || + isStandalonePage || isSettingsPage || showAuthModal) && metadataStore.status === 'up-to-date'; @@ -132,6 +134,7 @@ export const MainContextStoreProvider = () => { objectMetadataItem={objectMetadataItem} isRecordIndexPage={isRecordIndexPage} isRecordShowPage={isRecordShowPage} + isStandalonePage={isStandalonePage} isSettingsPage={isSettingsPage} /> ); diff --git a/packages/twenty-front/src/modules/context-store/components/MainContextStoreProviderEffect.tsx b/packages/twenty-front/src/modules/context-store/components/MainContextStoreProviderEffect.tsx index c900a7683d..1854918aec 100644 --- a/packages/twenty-front/src/modules/context-store/components/MainContextStoreProviderEffect.tsx +++ b/packages/twenty-front/src/modules/context-store/components/MainContextStoreProviderEffect.tsx @@ -1,7 +1,9 @@ import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; +import { contextStoreCurrentPageTypeComponentState } from '@/context-store/states/contextStoreCurrentPageTypeComponentState'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; +import { getPageType } from '@/context-store/utils/getPageType'; import { getViewType } from '@/context-store/utils/getViewType'; import { useSetLastVisitedObjectMetadataId } from '@/navigation/hooks/useSetLastVisitedObjectMetadataId'; import { useSetLastVisitedViewForObjectMetadataNamePlural } from '@/navigation/hooks/useSetLastVisitedViewForObjectMetadataNamePlural'; @@ -16,6 +18,7 @@ type MainContextStoreProviderEffectProps = { objectMetadataItem?: EnrichedObjectMetadataItem; isRecordIndexPage: boolean; isRecordShowPage: boolean; + isStandalonePage: boolean; isSettingsPage: boolean; }; @@ -24,6 +27,7 @@ export const MainContextStoreProviderEffect = ({ objectMetadataItem, isRecordIndexPage, isRecordShowPage, + isStandalonePage, isSettingsPage, }: MainContextStoreProviderEffectProps) => { const { setLastVisitedViewForObjectMetadataNamePlural } = @@ -44,6 +48,12 @@ export const MainContextStoreProviderEffect = ({ MAIN_CONTEXT_STORE_INSTANCE_ID, ); + const [contextStoreCurrentPageType, setContextStoreCurrentPageType] = + useAtomComponentState( + contextStoreCurrentPageTypeComponentState, + MAIN_CONTEXT_STORE_INSTANCE_ID, + ); + const [ contextStoreCurrentObjectMetadataItemId, setContextStoreCurrentObjectMetadataItemId, @@ -100,8 +110,6 @@ export const MainContextStoreProviderEffect = ({ useEffect(() => { const viewType = getViewType({ - isSettingsPage, - isRecordShowPage, isRecordIndexPage, view, }); @@ -113,9 +121,27 @@ export const MainContextStoreProviderEffect = ({ contextStoreCurrentViewType, setContextStoreCurrentViewType, view, + isRecordIndexPage, + ]); + + useEffect(() => { + const pageType = getPageType({ + isSettingsPage, + isRecordShowPage, + isRecordIndexPage, + isStandalonePage, + }); + + if (contextStoreCurrentPageType !== pageType) { + setContextStoreCurrentPageType(pageType); + } + }, [ + contextStoreCurrentPageType, + setContextStoreCurrentPageType, isSettingsPage, isRecordShowPage, isRecordIndexPage, + isStandalonePage, ]); return null; diff --git a/packages/twenty-front/src/modules/context-store/states/contextStoreCurrentPageTypeComponentState.ts b/packages/twenty-front/src/modules/context-store/states/contextStoreCurrentPageTypeComponentState.ts new file mode 100644 index 0000000000..00c8720b26 --- /dev/null +++ b/packages/twenty-front/src/modules/context-store/states/contextStoreCurrentPageTypeComponentState.ts @@ -0,0 +1,10 @@ +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; +import { type ContextStorePageType } from 'twenty-shared/types'; +import { createAtomComponentState } from '@/ui/utilities/state/jotai/utils/createAtomComponentState'; + +export const contextStoreCurrentPageTypeComponentState = + createAtomComponentState({ + key: 'contextStoreCurrentPageTypeComponentState', + defaultValue: null, + componentInstanceContext: ContextStoreComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/context-store/types/ContextStoreViewType.ts b/packages/twenty-front/src/modules/context-store/types/ContextStoreViewType.ts index 3ac9f793a8..91c2afcb9a 100644 --- a/packages/twenty-front/src/modules/context-store/types/ContextStoreViewType.ts +++ b/packages/twenty-front/src/modules/context-store/types/ContextStoreViewType.ts @@ -1,5 +1,4 @@ export enum ContextStoreViewType { Table = 'table', Kanban = 'kanban', - ShowPage = 'show-page', } diff --git a/packages/twenty-front/src/modules/context-store/utils/getPageType.ts b/packages/twenty-front/src/modules/context-store/utils/getPageType.ts new file mode 100644 index 0000000000..434a66b80c --- /dev/null +++ b/packages/twenty-front/src/modules/context-store/utils/getPageType.ts @@ -0,0 +1,31 @@ +import { ContextStorePageType } from 'twenty-shared/types'; + +export const getPageType = ({ + isSettingsPage, + isRecordShowPage, + isRecordIndexPage, + isStandalonePage, +}: { + isSettingsPage: boolean; + isRecordShowPage: boolean; + isRecordIndexPage: boolean; + isStandalonePage: boolean; +}): ContextStorePageType | null => { + if (isSettingsPage) { + return ContextStorePageType.Settings; + } + + if (isRecordIndexPage) { + return ContextStorePageType.Index; + } + + if (isRecordShowPage) { + return ContextStorePageType.Record; + } + + if (isStandalonePage) { + return ContextStorePageType.Standalone; + } + + return null; +}; diff --git a/packages/twenty-front/src/modules/context-store/utils/getViewType.ts b/packages/twenty-front/src/modules/context-store/utils/getViewType.ts index b056d1e061..cb03a0d1e3 100644 --- a/packages/twenty-front/src/modules/context-store/utils/getViewType.ts +++ b/packages/twenty-front/src/modules/context-store/utils/getViewType.ts @@ -3,29 +3,17 @@ import { type View } from '@/views/types/View'; import { ViewType } from '@/views/types/ViewType'; export const getViewType = ({ - isSettingsPage, - isRecordShowPage, isRecordIndexPage, view, }: { - isSettingsPage: boolean; - isRecordShowPage: boolean; isRecordIndexPage: boolean; view?: View; }) => { - if (isSettingsPage) { - return null; - } - if (isRecordIndexPage) { return view?.type === ViewType.KANBAN ? ContextStoreViewType.Kanban : ContextStoreViewType.Table; } - if (isRecordShowPage) { - return ContextStoreViewType.ShowPage; - } - return null; }; diff --git a/packages/twenty-front/src/modules/dashboards/graphql/queries/findOnePageLayoutType.ts b/packages/twenty-front/src/modules/dashboards/graphql/queries/findOnePageLayoutType.ts new file mode 100644 index 0000000000..29ce9a96cd --- /dev/null +++ b/packages/twenty-front/src/modules/dashboards/graphql/queries/findOnePageLayoutType.ts @@ -0,0 +1,10 @@ +import { gql } from '@apollo/client'; + +export const FIND_ONE_PAGE_LAYOUT_TYPE = gql` + query FindOnePageLayoutType($id: String!) { + getPageLayout(id: $id) { + id + type + } + } +`; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/common/constants/NavigationMenuItemDefaultColorPageLayout.ts b/packages/twenty-front/src/modules/navigation-menu-item/common/constants/NavigationMenuItemDefaultColorPageLayout.ts new file mode 100644 index 0000000000..d0b8a087ae --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/common/constants/NavigationMenuItemDefaultColorPageLayout.ts @@ -0,0 +1 @@ +export const DEFAULT_NAVIGATION_MENU_ITEM_COLOR_PAGE_LAYOUT = 'blue'; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/common/graphql/fragments/navigationMenuItemFragment.ts b/packages/twenty-front/src/modules/navigation-menu-item/common/graphql/fragments/navigationMenuItemFragment.ts index 060d6f613f..94a05a9168 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/common/graphql/fragments/navigationMenuItemFragment.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/common/graphql/fragments/navigationMenuItemFragment.ts @@ -13,6 +13,7 @@ export const NAVIGATION_MENU_ITEM_FRAGMENT = gql` link icon color + pageLayoutId position applicationId createdAt diff --git a/packages/twenty-front/src/modules/navigation-menu-item/common/utils/filterAndSortNavigationMenuItems.ts b/packages/twenty-front/src/modules/navigation-menu-item/common/utils/filterAndSortNavigationMenuItems.ts index 9a76366fbe..92d89ea5bd 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/common/utils/filterAndSortNavigationMenuItems.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/common/utils/filterAndSortNavigationMenuItems.ts @@ -21,6 +21,9 @@ export const filterAndSortNavigationMenuItems = ( if (item.type === NavigationMenuItemType.LINK) { return true; } + if (item.type === NavigationMenuItemType.PAGE_LAYOUT) { + return isDefined(item.pageLayoutId); + } if (item.type === NavigationMenuItemType.OBJECT) { return ( isDefined(item.targetObjectMetadataId) && diff --git a/packages/twenty-front/src/modules/navigation-menu-item/common/utils/getNavigationMenuItemColor.ts b/packages/twenty-front/src/modules/navigation-menu-item/common/utils/getNavigationMenuItemColor.ts index 3430671326..eb2ad4a92b 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/common/utils/getNavigationMenuItemColor.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/common/utils/getNavigationMenuItemColor.ts @@ -2,6 +2,7 @@ import { isNonEmptyString } from '@sniptt/guards'; import { DEFAULT_NAVIGATION_MENU_ITEM_COLOR_FOLDER } from '@/navigation-menu-item/common/constants/NavigationMenuItemDefaultColorFolder'; import { DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK } from '@/navigation-menu-item/common/constants/NavigationMenuItemDefaultColorLink'; +import { DEFAULT_NAVIGATION_MENU_ITEM_COLOR_PAGE_LAYOUT } from '@/navigation-menu-item/common/constants/NavigationMenuItemDefaultColorPageLayout'; import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem'; import { getObjectColorWithFallback } from '@/object-metadata/utils/getObjectColorWithFallback'; import { NavigationMenuItemType } from 'twenty-shared/types'; @@ -27,6 +28,12 @@ export const getNavigationMenuItemColor = ( return DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK; } + if (navigationMenuItem.type === NavigationMenuItemType.PAGE_LAYOUT) { + return isNonEmptyString(navigationMenuItem.color) + ? (navigationMenuItem.color as ThemeColor) + : DEFAULT_NAVIGATION_MENU_ITEM_COLOR_PAGE_LAYOUT; + } + if ( navigationMenuItem.type === NavigationMenuItemType.OBJECT || navigationMenuItem.type === NavigationMenuItemType.VIEW diff --git a/packages/twenty-front/src/modules/navigation-menu-item/display/components/NavigationMenuItemDisplay.tsx b/packages/twenty-front/src/modules/navigation-menu-item/display/components/NavigationMenuItemDisplay.tsx index adf7597d85..1b716041c1 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/display/components/NavigationMenuItemDisplay.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/display/components/NavigationMenuItemDisplay.tsx @@ -2,6 +2,7 @@ import { NavigationMenuItemType } from 'twenty-shared/types'; import { NavigationMenuItemFolder } from '@/navigation-menu-item/display/folder/components/NavigationMenuItemFolder'; import { NavigationMenuItemLinkDisplay } from '@/navigation-menu-item/display/link/components/NavigationMenuItemLinkDisplay'; import { NavigationMenuItemObjectDisplay } from '@/navigation-menu-item/display/object/components/NavigationMenuItemObjectDisplay'; +import { NavigationMenuItemPageLayoutDisplay } from '@/navigation-menu-item/display/page-layout/components/NavigationMenuItemPageLayoutDisplay'; import type { NavigationMenuItemSectionContentProps } from '@/navigation-menu-item/display/sections/types/NavigationMenuItemSectionContentProps'; type NavigationMenuItemDisplayProps = NavigationMenuItemSectionContentProps; @@ -49,6 +50,21 @@ export const NavigationMenuItemDisplay = ({ readOnly={readOnly} /> ); + case NavigationMenuItemType.PAGE_LAYOUT: + return ( + + ); default: return ( + + + ); + } + if (navigationMenuItem.type === NavigationMenuItemType.LINK) { const computedLink = getNavigationMenuItemComputedLink( navigationMenuItem, diff --git a/packages/twenty-front/src/modules/navigation-menu-item/display/page-layout/components/NavigationMenuItemPageLayoutDisplay.tsx b/packages/twenty-front/src/modules/navigation-menu-item/display/page-layout/components/NavigationMenuItemPageLayoutDisplay.tsx new file mode 100644 index 0000000000..265ecb895b --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/display/page-layout/components/NavigationMenuItemPageLayoutDisplay.tsx @@ -0,0 +1,45 @@ +import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState'; +import { NavigationMenuItemIcon } from '@/navigation-menu-item/display/components/NavigationMenuItemIcon'; +import { getPageLayoutNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/page-layout/utils/getPageLayoutNavigationMenuItemComputedLink'; +import type { NavigationMenuItemSectionContentProps } from '@/navigation-menu-item/display/sections/types/NavigationMenuItemSectionContentProps'; +import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; +import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; + +type NavigationMenuItemPageLayoutDisplayProps = + NavigationMenuItemSectionContentProps; + +export const NavigationMenuItemPageLayoutDisplay = ({ + item, + editModeProps, + isDragging, + rightOptions, +}: NavigationMenuItemPageLayoutDisplayProps) => { + const isLayoutCustomizationModeEnabled = useAtomStateValue( + isLayoutCustomizationModeEnabledState, + ); + + const label = item.name ?? ''; + const computedLink = getPageLayoutNavigationMenuItemComputedLink(item); + + return ( + } + active={false} + isSelectedInEditMode={editModeProps?.isSelectedInEditMode} + isDragging={isDragging} + triggerEvent="CLICK" + rightOptions={rightOptions} + /> + ); +}; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/display/page-layout/utils/getPageLayoutNavigationMenuItemComputedLink.ts b/packages/twenty-front/src/modules/navigation-menu-item/display/page-layout/utils/getPageLayoutNavigationMenuItemComputedLink.ts new file mode 100644 index 0000000000..80d3a9dc17 --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/display/page-layout/utils/getPageLayoutNavigationMenuItemComputedLink.ts @@ -0,0 +1,15 @@ +import { AppPath } from 'twenty-shared/types'; +import { getAppPath, isDefined } from 'twenty-shared/utils'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; + +export const getPageLayoutNavigationMenuItemComputedLink = ( + item: Pick, +): string => { + if (!isDefined(item.pageLayoutId)) { + return ''; + } + + return getAppPath(AppPath.PageLayoutPage, { + pageLayoutId: item.pageLayoutId, + }); +}; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/display/sections/workspace/components/WorkspaceSectionContainer.tsx b/packages/twenty-front/src/modules/navigation-menu-item/display/sections/workspace/components/WorkspaceSectionContainer.tsx index f723c913a0..5d84720344 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/display/sections/workspace/components/WorkspaceSectionContainer.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/display/sections/workspace/components/WorkspaceSectionContainer.tsx @@ -86,7 +86,8 @@ export const WorkspaceSectionContainer = ({ const itemType = item.type; if ( itemType === NavigationMenuItemType.FOLDER || - itemType === NavigationMenuItemType.LINK + itemType === NavigationMenuItemType.LINK || + itemType === NavigationMenuItemType.PAGE_LAYOUT ) { return true; } diff --git a/packages/twenty-front/src/modules/navigation-menu-item/display/utils/getNavigationMenuItemComputedLink.ts b/packages/twenty-front/src/modules/navigation-menu-item/display/utils/getNavigationMenuItemComputedLink.ts index 71170425c4..6f751adab7 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/display/utils/getNavigationMenuItemComputedLink.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/display/utils/getNavigationMenuItemComputedLink.ts @@ -1,5 +1,6 @@ import { getLinkNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/link/utils/getLinkNavigationMenuItemComputedLink'; import { getObjectNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/object/utils/getObjectNavigationMenuItemComputedLink'; +import { getPageLayoutNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/page-layout/utils/getPageLayoutNavigationMenuItemComputedLink'; import { getRecordNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/record/utils/getRecordNavigationMenuItemComputedLink'; import { getViewNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/view/utils/getViewNavigationMenuItemComputedLink'; import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem'; @@ -29,6 +30,8 @@ export const getNavigationMenuItemComputedLink = ( return getLinkNavigationMenuItemComputedLink(item); case NavigationMenuItemType.RECORD: return getRecordNavigationMenuItemComputedLink(item, objectMetadataItems); + case NavigationMenuItemType.PAGE_LAYOUT: + return getPageLayoutNavigationMenuItemComputedLink(item); default: return ''; } diff --git a/packages/twenty-front/src/modules/navigation-menu-item/display/utils/getWorkspaceSidebarOrphanItemsInDisplayOrder.ts b/packages/twenty-front/src/modules/navigation-menu-item/display/utils/getWorkspaceSidebarOrphanItemsInDisplayOrder.ts index d71379a525..1bffd7bd50 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/display/utils/getWorkspaceSidebarOrphanItemsInDisplayOrder.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/display/utils/getWorkspaceSidebarOrphanItemsInDisplayOrder.ts @@ -49,7 +49,10 @@ export const getWorkspaceSidebarOrphanItemsInDisplayOrder = ({ } const rowSource = isDefined(validItem) ? validItem : item; - if (rowSource.type === NavigationMenuItemType.LINK) { + if ( + rowSource.type === NavigationMenuItemType.LINK || + rowSource.type === NavigationMenuItemType.PAGE_LAYOUT + ) { acc.push(rowSource); return acc; } diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx index fb619aeb49..aa6a5f89d8 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx @@ -7,14 +7,21 @@ import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageL import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode'; import { usePageLayoutTabWithVisibleWidgetsOrThrow } from '@/page-layout/hooks/usePageLayoutTabWithVisibleWidgetsOrThrow'; import { useReorderPageLayoutWidgets } from '@/page-layout/hooks/useReorderPageLayoutWidgets'; +import { StandaloneWidgetPlaceholder } from '@/page-layout/widgets/components/StandaloneWidgetPlaceholder'; import { RecordPageAddWidgetSection } from '@/page-layout/widgets/components/RecordPageAddWidgetSection'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { styled } from '@linaria/react'; import { FeatureFlagKey, PageLayoutTabLayoutMode, PageLayoutType, } from '~/generated-metadata/graphql'; +const StyledEmptyStandalonePageContainer = styled.div` + display: grid; + height: 100%; +`; + export const PageLayoutContent = () => { const isPageLayoutInEditMode = useIsPageLayoutInEditMode(); @@ -38,6 +45,18 @@ export const PageLayoutContent = () => { const isCanvasLayout = layoutMode === PageLayoutTabLayoutMode.CANVAS; const isVerticalList = layoutMode === PageLayoutTabLayoutMode.VERTICAL_LIST; + const isEmptyStandalonePage = + currentPageLayout.type === PageLayoutType.STANDALONE_PAGE && + activeTab.widgets.length === 0; + + if (isEmptyStandalonePage) { + return ( + + + + ); + } + if (isCanvasLayout) { return ; } diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutEditModeProvider.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutEditModeProvider.tsx index a9104d215a..4631b377bc 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutEditModeProvider.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutEditModeProvider.tsx @@ -1,5 +1,6 @@ import { DashboardPageLayoutEditModeProvider } from '@/page-layout/components/DashboardPageLayoutEditModeProvider'; import { RecordPageLayoutEditModeProvider } from '@/page-layout/components/RecordPageLayoutEditModeProvider'; +import { PageLayoutEditModeProviderContext } from '@/page-layout/contexts/PageLayoutEditModeContext'; import { type ReactNode } from 'react'; import { PageLayoutType } from '~/generated-metadata/graphql'; @@ -14,6 +15,14 @@ export const PageLayoutEditModeProvider = ({ pageLayoutId, children, }: PageLayoutEditModeProviderProps) => { + if (layoutType === PageLayoutType.STANDALONE_PAGE) { + return ( + + {children} + + ); + } + if (layoutType === PageLayoutType.RECORD_PAGE) { return ( diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx index d4e655bbde..4f389ef2b2 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx @@ -8,6 +8,7 @@ import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayo import { type PageLayout } from '@/page-layout/types/PageLayout'; import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts'; import { isPageLayoutEmpty } from '@/page-layout/utils/isPageLayoutEmpty'; +import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState'; import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; @@ -21,24 +22,16 @@ type PageLayoutInitializationQueryEffectProps = { pageLayoutId: string; }; -export const PageLayoutInitializationQueryEffect = ({ +const PageLayoutInitializationEffect = ({ pageLayoutId, -}: PageLayoutInitializationQueryEffectProps) => { + pageLayout, +}: { + pageLayoutId: string; + pageLayout: PageLayout | undefined; +}) => { const [pageLayoutIsInitialized, setPageLayoutIsInitialized] = useAtomComponentState(pageLayoutIsInitializedComponentState); - const featureFlags = useFeatureFlagsMap(); - const isRecordPageLayoutEditingEnabled = - featureFlags[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED]; - - const basePageLayout = useBasePageLayout(pageLayoutId); - const pageLayoutWithRelationWidgets = - usePageLayoutWithRelationWidgets(basePageLayout); - - const pageLayout = isRecordPageLayoutEditingEnabled - ? basePageLayout - : pageLayoutWithRelationWidgets; - const { setIsPageLayoutInEditMode } = useSetIsPageLayoutInEditMode(pageLayoutId); @@ -55,8 +48,6 @@ export const PageLayoutInitializationQueryEffect = ({ const initializePageLayout = useCallback( (layout: PageLayout) => { - const isRecordPageLayout = layout.type === PageLayoutType.RECORD_PAGE; - const currentPersisted = store.get( pageLayoutPersistedComponentCallbackState, ); @@ -78,7 +69,9 @@ export const PageLayoutInitializationQueryEffect = ({ const tabLayouts = convertPageLayoutToTabLayouts(layout); store.set(pageLayoutCurrentLayoutsComponentCallbackState, tabLayouts); - if (!isRecordPageLayout) { + const isDashboardLayout = layout.type === PageLayoutType.DASHBOARD; + + if (isDashboardLayout) { const shouldEnterDashboardEditMode = isPageLayoutEmpty(layout); setIsPageLayoutInEditMode(shouldEnterDashboardEditMode); } @@ -106,3 +99,53 @@ export const PageLayoutInitializationQueryEffect = ({ return null; }; + +const PageLayoutInitializationWithRelationWidgets = ({ + pageLayoutId, + basePageLayout, +}: { + pageLayoutId: string; + basePageLayout: PageLayout | undefined; +}) => { + const pageLayout = usePageLayoutWithRelationWidgets(basePageLayout); + + return ( + + ); +}; + +// oxlint-disable-next-line twenty/effect-components +export const PageLayoutInitializationQueryEffect = ({ + pageLayoutId, +}: PageLayoutInitializationQueryEffectProps) => { + const { layoutType } = useLayoutRenderingContext(); + + const featureFlags = useFeatureFlagsMap(); + const isRecordPageLayoutEditingEnabled = + featureFlags[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED]; + + const basePageLayout = useBasePageLayout(pageLayoutId); + + const needsRelationWidgets = + layoutType === PageLayoutType.RECORD_PAGE && + !isRecordPageLayoutEditingEnabled; + + if (needsRelationWidgets) { + return ( + + ); + } + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx index c4ee61d16b..1773606f76 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx @@ -10,7 +10,7 @@ import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/con import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; -import { FeatureFlagKey } from '~/generated-metadata/graphql'; +import { FeatureFlagKey, PageLayoutType } from '~/generated-metadata/graphql'; type PageLayoutRendererProps = { pageLayoutId: string; @@ -48,9 +48,12 @@ export const PageLayoutRenderer = ({ > - {!isRecordPageLayoutEditingEnabled && ( - - )} + {!isRecordPageLayoutEditingEnabled && + layoutType === PageLayoutType.RECORD_PAGE && ( + + )} diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/shouldEnableTabEditingFeatures.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/shouldEnableTabEditingFeatures.test.ts index 52bb8d6bf1..7ba93cebb1 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/__tests__/shouldEnableTabEditingFeatures.test.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/__tests__/shouldEnableTabEditingFeatures.test.ts @@ -7,6 +7,13 @@ describe('shouldEnableTabEditingFeatures', () => { expect(result).toBe(true); }); + it('should return true for STANDALONE_PAGE layout type', () => { + const result = shouldEnableTabEditingFeatures( + PageLayoutType.STANDALONE_PAGE, + ); + expect(result).toBe(true); + }); + it('should return false for RECORD_PAGE layout type without flag', () => { const result = shouldEnableTabEditingFeatures(PageLayoutType.RECORD_PAGE); expect(result).toBe(false); @@ -42,11 +49,15 @@ describe('shouldEnableTabEditingFeatures', () => { }); describe('behavior validation', () => { - it('should enable tab editing features only for dashboards and record pages with flag', () => { + it('should enable tab editing features only for dashboards, standalone pages, and record pages with flag', () => { expect(shouldEnableTabEditingFeatures(PageLayoutType.DASHBOARD)).toBe( true, ); + expect( + shouldEnableTabEditingFeatures(PageLayoutType.STANDALONE_PAGE), + ).toBe(true); + expect(shouldEnableTabEditingFeatures(PageLayoutType.RECORD_PAGE)).toBe( false, ); diff --git a/packages/twenty-front/src/modules/page-layout/utils/shouldEnableTabEditingFeatures.ts b/packages/twenty-front/src/modules/page-layout/utils/shouldEnableTabEditingFeatures.ts index dca137ddff..724d32df2d 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/shouldEnableTabEditingFeatures.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/shouldEnableTabEditingFeatures.ts @@ -4,7 +4,10 @@ export const shouldEnableTabEditingFeatures = ( pageLayoutType: PageLayoutType, isRecordPageGlobalEditionEnabled?: boolean, ): boolean => { - if (pageLayoutType === PageLayoutType.DASHBOARD) { + if ( + pageLayoutType === PageLayoutType.DASHBOARD || + pageLayoutType === PageLayoutType.STANDALONE_PAGE + ) { return true; } diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/NonRecordPageWidgetRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/NonRecordPageWidgetRenderer.tsx new file mode 100644 index 0000000000..874aeb508d --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/NonRecordPageWidgetRenderer.tsx @@ -0,0 +1,39 @@ +import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; +import { WidgetCardShell } from '@/page-layout/widgets/components/WidgetCardShell'; +import { useWidgetRendererState } from '@/page-layout/widgets/hooks/useWidgetRendererState'; + +type NonRecordPageWidgetRendererProps = { + widget: PageLayoutWidget; +}; + +export const NonRecordPageWidgetRenderer = ({ + widget, +}: NonRecordPageWidgetRendererProps) => { + const state = useWidgetRendererState(widget); + + const isCanvasVariant = state.variant === 'canvas'; + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/RecordPageWidgetRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/RecordPageWidgetRenderer.tsx new file mode 100644 index 0000000000..2576c0d56e --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/RecordPageWidgetRenderer.tsx @@ -0,0 +1,88 @@ +import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; +import { WidgetCardShell } from '@/page-layout/widgets/components/WidgetCardShell'; +import { useWidgetActions } from '@/page-layout/widgets/hooks/useWidgetActions'; +import { useWidgetRendererState } from '@/page-layout/widgets/hooks/useWidgetRendererState'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { styled } from '@linaria/react'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { + FeatureFlagKey, + PageLayoutType, + WidgetType, +} from '~/generated-metadata/graphql'; + +const StyledEditingWidgetWrapper = styled.div` + padding: ${themeCssVariables.spacing[2]}; +`; + +type RecordPageWidgetRendererProps = { + widget: PageLayoutWidget; +}; + +export const RecordPageWidgetRenderer = ({ + widget, +}: RecordPageWidgetRendererProps) => { + const state = useWidgetRendererState(widget); + + const isRecordPageGlobalEditionEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED, + ); + + const isRecordPageLayout = + state.currentPageLayout.type === PageLayoutType.RECORD_PAGE; + + const isReorderEnabled = + !isRecordPageLayout || + (isRecordPageLayout && isRecordPageGlobalEditionEnabled); + + const isDeletingWidgetEnabled = + !isRecordPageLayout || + (isRecordPageLayout && isRecordPageGlobalEditionEnabled); + + const isWidgetEditable = + state.isPageLayoutInEditMode && + (!isRecordPageLayout || + (isRecordPageLayout && isRecordPageGlobalEditionEnabled) || + widget.type === WidgetType.FIELDS || + widget.type === WidgetType.FIELD); + + const actions = useWidgetActions({ widget }); + + // TODO: remove once all record page layouts widgets use the editable contain in edit mode + const shouldWrapWithEditingWrapper = + isWidgetEditable && + state.variant === 'side-column' && + !isRecordPageGlobalEditionEnabled; + + const isCanvasVariant = state.variant === 'canvas'; + + const shell = ( + + ); + + if (shouldWrapWithEditingWrapper) { + return {shell}; + } + + return shell; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/StandaloneWidgetPlaceholder.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/StandaloneWidgetPlaceholder.tsx new file mode 100644 index 0000000000..467b4063bf --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/StandaloneWidgetPlaceholder.tsx @@ -0,0 +1,43 @@ +import { styled } from '@linaria/react'; +import { Trans } from '@lingui/react/macro'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { + AnimatedPlaceholder, + AnimatedPlaceholderEmptyContainer, + AnimatedPlaceholderEmptySubTitle, + AnimatedPlaceholderEmptyTextContainer, + AnimatedPlaceholderEmptyTitle, + EMPTY_PLACEHOLDER_TRANSITION_PROPS, +} from 'twenty-ui/layout'; + +const StyledPlaceholderContainer = styled.div` + background: ${themeCssVariables.background.secondary}; + box-sizing: border-box; + display: flex; + flex-direction: column; + height: 100%; + padding: ${themeCssVariables.spacing[2]}; + position: relative; + width: 100%; +`; + +export const StandaloneWidgetPlaceholder = () => { + return ( + + + + + + Nothing to see + + + This page has no content + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetCardShell.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetCardShell.tsx new file mode 100644 index 0000000000..643785a330 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetCardShell.tsx @@ -0,0 +1,140 @@ +import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; +import { PageLayoutWidgetForbiddenDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetForbiddenDisplay'; +import { PageLayoutWidgetInvalidConfigDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetInvalidConfigDisplay'; +import { WidgetContentRenderer } from '@/page-layout/widgets/components/WidgetContentRenderer'; +import { WidgetComponentInstanceContext } from '@/page-layout/widgets/states/contexts/WidgetComponentInstanceContext'; +import { type WidgetAccessDenialInfo } from '@/page-layout/widgets/types/WidgetAccessDenialInfo'; +import { type WidgetAction } from '@/page-layout/widgets/types/WidgetAction'; +import { type WidgetCardVariant } from '@/page-layout/widgets/types/WidgetCardVariant'; +import { WidgetCard } from '@/page-layout/widgets/widget-card/components/WidgetCard'; +import { WidgetCardContent } from '@/page-layout/widgets/widget-card/components/WidgetCardContent'; +import { WidgetCardHeader } from '@/page-layout/widgets/widget-card/components/WidgetCardHeader'; +import { styled } from '@linaria/react'; +import { type MouseEvent, useContext } from 'react'; +import { ErrorBoundary } from 'react-error-boundary'; +import { IconLock } from 'twenty-ui/display'; +import { ThemeContext } from 'twenty-ui/theme-constants'; +import { WidgetType } from '~/generated-metadata/graphql'; + +const StyledNoAccessContainer = styled.div` + align-items: center; + display: flex; + justify-content: center; +`; + +type WidgetCardShellProps = { + widget: PageLayoutWidget; + variant: WidgetCardVariant; + isEditable: boolean; + isEditing: boolean; + isDragging: boolean; + isResizing: boolean; + isLastWidget: boolean; + showHeader: boolean; + hasAccess: boolean; + restriction: WidgetAccessDenialInfo; + actions: WidgetAction[]; + isInVerticalListTab: boolean; + isMobile: boolean; + isReorderEnabled: boolean; + isDeletingWidgetEnabled: boolean; + onClick?: () => void; + onRemove: (e?: MouseEvent) => void; + onMouseEnter?: () => void; + onMouseLeave?: () => void; +}; + +export const WidgetCardShell = ({ + widget, + variant, + isEditable, + isEditing, + isDragging, + isResizing, + isLastWidget, + showHeader, + hasAccess, + restriction, + actions, + isInVerticalListTab, + isMobile, + isReorderEnabled, + isDeletingWidgetEnabled, + onClick, + onRemove, + onMouseEnter, + onMouseLeave, +}: WidgetCardShellProps) => { + const { theme } = useContext(ThemeContext); + + return ( + + + {showHeader && ( + + ) + } + /> + )} + + + {hasAccess ? ( + + + + ) : ( + + + + )} + + + + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetPlaceholder.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetPlaceholder.tsx index 81a064c8bb..a4357855af 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetPlaceholder.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetPlaceholder.tsx @@ -1,5 +1,6 @@ import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow'; import { DashboardWidgetPlaceholder } from '@/page-layout/widgets/components/DashboardWidgetPlaceholder'; +import { StandaloneWidgetPlaceholder } from '@/page-layout/widgets/components/StandaloneWidgetPlaceholder'; import { PageLayoutType } from '~/generated-metadata/graphql'; export const WidgetPlaceholder = () => { @@ -9,6 +10,10 @@ export const WidgetPlaceholder = () => { return ; } + if (currentPageLayout.type === PageLayoutType.STANDALONE_PAGE) { + return ; + } + // TODO: Implement RecordPageWidgetPlaceholder when needed return ; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx index 1340fc2c6e..023959d8fb 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx @@ -1,246 +1,19 @@ -import { usePageLayoutContentContext } from '@/page-layout/contexts/PageLayoutContentContext'; -import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow'; -import { useDeletePageLayoutWidget } from '@/page-layout/hooks/useDeletePageLayoutWidget'; -import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode'; -import { pageLayoutDraggingWidgetIdComponentState } from '@/page-layout/states/pageLayoutDraggingWidgetIdComponentState'; -import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState'; -import { pageLayoutResizingWidgetIdComponentState } from '@/page-layout/states/pageLayoutResizingWidgetIdComponentState'; import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; -import { PageLayoutWidgetForbiddenDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetForbiddenDisplay'; -import { PageLayoutWidgetInvalidConfigDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetInvalidConfigDisplay'; -import { WidgetContentRenderer } from '@/page-layout/widgets/components/WidgetContentRenderer'; -import { useIsCurrentWidgetLastOfTab } from '@/page-layout/widgets/hooks/useIsCurrentWidgetLastOfTab'; -import { useIsInPinnedTab } from '@/page-layout/widgets/hooks/useIsInPinnedTab'; -import { useWidgetActions } from '@/page-layout/widgets/hooks/useWidgetActions'; -import { useWidgetPermissions } from '@/page-layout/widgets/hooks/useWidgetPermissions'; -import { WidgetComponentInstanceContext } from '@/page-layout/widgets/states/contexts/WidgetComponentInstanceContext'; -import { widgetCardHoveredComponentFamilyState } from '@/page-layout/widgets/states/widgetCardHoveredComponentFamilyState'; -import { getWidgetCardVariant } from '@/page-layout/widgets/utils/getWidgetCardVariant'; -import { WidgetCard } from '@/page-layout/widgets/widget-card/components/WidgetCard'; -import { WidgetCardContent } from '@/page-layout/widgets/widget-card/components/WidgetCardContent'; -import { WidgetCardHeader } from '@/page-layout/widgets/widget-card/components/WidgetCardHeader'; -import { useOpenWidgetSettingsInSidePanel } from '@/side-panel/hooks/useOpenWidgetSettingsInSidePanel'; +import { NonRecordPageWidgetRenderer } from '@/page-layout/widgets/components/NonRecordPageWidgetRenderer'; +import { RecordPageWidgetRenderer } from '@/page-layout/widgets/components/RecordPageWidgetRenderer'; import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; -import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; -import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { useSetAtomComponentFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentFamilyState'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; -import { styled } from '@linaria/react'; -import { type MouseEvent, useContext } from 'react'; -import { ErrorBoundary } from 'react-error-boundary'; -import { IconLock } from 'twenty-ui/display'; -import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; -import { - FeatureFlagKey, - PageLayoutTabLayoutMode, - PageLayoutType, - WidgetType, -} from '~/generated-metadata/graphql'; - -const StyledEditingWidgetWrapper = styled.div` - padding: ${themeCssVariables.spacing[2]}; -`; - -const StyledNoAccessContainer = styled.div` - align-items: center; - display: flex; - justify-content: center; -`; +import { PageLayoutType } from '~/generated-metadata/graphql'; type WidgetRendererProps = { widget: PageLayoutWidget; }; export const WidgetRenderer = ({ widget }: WidgetRendererProps) => { - const { theme } = useContext(ThemeContext); - const { deletePageLayoutWidget } = useDeletePageLayoutWidget(); - const { openWidgetSettingsInSidePanel } = useOpenWidgetSettingsInSidePanel(); + const { layoutType } = useLayoutRenderingContext(); - const isPageLayoutInEditMode = useIsPageLayoutInEditMode(); + if (layoutType === PageLayoutType.RECORD_PAGE) { + return ; + } - const pageLayoutDraggingWidgetId = useAtomComponentStateValue( - pageLayoutDraggingWidgetIdComponentState, - ); - - const pageLayoutResizingWidgetId = useAtomComponentStateValue( - pageLayoutResizingWidgetIdComponentState, - ); - - const pageLayoutEditingWidgetId = useAtomComponentStateValue( - pageLayoutEditingWidgetIdComponentState, - ); - - const isEditing = pageLayoutEditingWidgetId === widget.id; - - const isDragging = pageLayoutDraggingWidgetId === widget.id; - - const isResizing = pageLayoutResizingWidgetId === widget.id; - - const { hasAccess, restriction } = useWidgetPermissions(widget); - - const { layoutMode } = usePageLayoutContentContext(); - const { isInPinnedTab } = useIsInPinnedTab(); - const { isInSidePanel } = useLayoutRenderingContext(); - const isMobile = useIsMobile(); - - const { currentPageLayout } = useCurrentPageLayoutOrThrow(); - - const isRecordPageGlobalEditionEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED, - ); - - const isRecordPageLayout = - currentPageLayout.type === PageLayoutType.RECORD_PAGE; - - const isLastWidget = useIsCurrentWidgetLastOfTab(widget.id); - - const isReorderEnabled = - !isRecordPageLayout || - (isRecordPageLayout && isRecordPageGlobalEditionEnabled); - - const isDeletingWidgetEnabled = - !isRecordPageLayout || - (isRecordPageLayout && isRecordPageGlobalEditionEnabled); - - const isWidgetEditable = - isPageLayoutInEditMode && - (!isRecordPageLayout || - (isRecordPageLayout && isRecordPageGlobalEditionEnabled) || - widget.type === WidgetType.FIELDS || - widget.type === WidgetType.FIELD); - - // TODO: when we have more widgets without headers, we should use a more generic approach to hide the header - // each widget type could have metadata (e.g., hasHeader: boolean or headerMode: 'always' | 'editOnly' | 'never') - const isHeaderHiddenInViewMode = - widget.type === WidgetType.STANDALONE_RICH_TEXT || - widget.type === WidgetType.EMAIL_THREAD; - const hideHeaderInViewMode = - isHeaderHiddenInViewMode && !isPageLayoutInEditMode; - - const showHeader = - layoutMode !== PageLayoutTabLayoutMode.CANVAS && !hideHeaderInViewMode; - - const handleClick = () => { - openWidgetSettingsInSidePanel({ - widgetId: widget.id, - widgetType: widget.type, - }); - }; - - const handleRemove = (e?: MouseEvent) => { - e?.stopPropagation(); - deletePageLayoutWidget(widget.id); - }; - - const setWidgetCardHovered = useSetAtomComponentFamilyState( - widgetCardHoveredComponentFamilyState, - widget.id, - ); - - const handleMouseEnter = () => { - setWidgetCardHovered(true); - }; - - const handleMouseLeave = () => { - setWidgetCardHovered(false); - }; - - const variant = getWidgetCardVariant({ - layoutMode, - isInPinnedTab, - pageLayoutType: currentPageLayout.type, - isMobile, - isInSidePanel, - }); - - const actions = useWidgetActions({ widget }); - - // TODO: remove once all record page layouts widgets use the editable contain in edit mode - const shouldWrapWithEditingWrapper = - isWidgetEditable && - variant === 'side-column' && - !isRecordPageGlobalEditionEnabled; - - const isCanvasVariant = variant === 'canvas'; - - const widgetCard = ( - - {showHeader && ( - - ) - } - /> - )} - - - {hasAccess ? ( - - - - ) : ( - - - - )} - - - ); - - return ( - - {shouldWrapWithEditingWrapper ? ( - {widgetCard} - ) : ( - widgetCard - )} - - ); + return ; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/hooks/useWidgetRendererState.ts b/packages/twenty-front/src/modules/page-layout/widgets/hooks/useWidgetRendererState.ts new file mode 100644 index 0000000000..8d853f90b8 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/hooks/useWidgetRendererState.ts @@ -0,0 +1,121 @@ +import { usePageLayoutContentContext } from '@/page-layout/contexts/PageLayoutContentContext'; +import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow'; +import { useDeletePageLayoutWidget } from '@/page-layout/hooks/useDeletePageLayoutWidget'; +import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode'; +import { pageLayoutDraggingWidgetIdComponentState } from '@/page-layout/states/pageLayoutDraggingWidgetIdComponentState'; +import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState'; +import { pageLayoutResizingWidgetIdComponentState } from '@/page-layout/states/pageLayoutResizingWidgetIdComponentState'; +import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; +import { useIsCurrentWidgetLastOfTab } from '@/page-layout/widgets/hooks/useIsCurrentWidgetLastOfTab'; +import { useIsInPinnedTab } from '@/page-layout/widgets/hooks/useIsInPinnedTab'; +import { useWidgetPermissions } from '@/page-layout/widgets/hooks/useWidgetPermissions'; +import { widgetCardHoveredComponentFamilyState } from '@/page-layout/widgets/states/widgetCardHoveredComponentFamilyState'; +import { getWidgetCardVariant } from '@/page-layout/widgets/utils/getWidgetCardVariant'; +import { useOpenWidgetSettingsInSidePanel } from '@/side-panel/hooks/useOpenWidgetSettingsInSidePanel'; +import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; +import { useSetAtomComponentFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentFamilyState'; +import { type MouseEvent } from 'react'; +import { + PageLayoutTabLayoutMode, + WidgetType, +} from '~/generated-metadata/graphql'; + +export const useWidgetRendererState = (widget: PageLayoutWidget) => { + const { deletePageLayoutWidget } = useDeletePageLayoutWidget(); + const { openWidgetSettingsInSidePanel } = useOpenWidgetSettingsInSidePanel(); + + const isPageLayoutInEditMode = useIsPageLayoutInEditMode(); + + const pageLayoutDraggingWidgetId = useAtomComponentStateValue( + pageLayoutDraggingWidgetIdComponentState, + ); + + const pageLayoutResizingWidgetId = useAtomComponentStateValue( + pageLayoutResizingWidgetIdComponentState, + ); + + const pageLayoutEditingWidgetId = useAtomComponentStateValue( + pageLayoutEditingWidgetIdComponentState, + ); + + const isEditing = pageLayoutEditingWidgetId === widget.id; + const isDragging = pageLayoutDraggingWidgetId === widget.id; + const isResizing = pageLayoutResizingWidgetId === widget.id; + + const { hasAccess, restriction } = useWidgetPermissions(widget); + + const { layoutMode } = usePageLayoutContentContext(); + const { isInPinnedTab } = useIsInPinnedTab(); + const { isInSidePanel } = useLayoutRenderingContext(); + const isMobile = useIsMobile(); + + const { currentPageLayout } = useCurrentPageLayoutOrThrow(); + + const isLastWidget = useIsCurrentWidgetLastOfTab(widget.id); + + const isHeaderHiddenInViewMode = + widget.type === WidgetType.STANDALONE_RICH_TEXT || + widget.type === WidgetType.EMAIL_THREAD; + const hideHeaderInViewMode = + isHeaderHiddenInViewMode && !isPageLayoutInEditMode; + + const showHeader = + layoutMode !== PageLayoutTabLayoutMode.CANVAS && !hideHeaderInViewMode; + + const handleClick = () => { + openWidgetSettingsInSidePanel({ + widgetId: widget.id, + widgetType: widget.type, + }); + }; + + const handleRemove = (e?: MouseEvent) => { + e?.stopPropagation(); + deletePageLayoutWidget(widget.id); + }; + + const setWidgetCardHovered = useSetAtomComponentFamilyState( + widgetCardHoveredComponentFamilyState, + widget.id, + ); + + const handleMouseEnter = () => { + setWidgetCardHovered(true); + }; + + const handleMouseLeave = () => { + setWidgetCardHovered(false); + }; + + const variant = getWidgetCardVariant({ + layoutMode, + isInPinnedTab, + pageLayoutType: currentPageLayout.type, + isMobile, + isInSidePanel, + }); + + const isInVerticalListTab = + layoutMode === PageLayoutTabLayoutMode.VERTICAL_LIST; + + return { + isPageLayoutInEditMode, + isEditing, + isDragging, + isResizing, + hasAccess, + restriction, + currentPageLayout, + isLastWidget, + showHeader, + variant, + isMobile, + isInVerticalListTab, + handleClick, + handleRemove, + handleMouseEnter, + handleMouseLeave, + }; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/types/WidgetCardVariant.ts b/packages/twenty-front/src/modules/page-layout/widgets/types/WidgetCardVariant.ts index e57d847716..bbaeecf204 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/types/WidgetCardVariant.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/types/WidgetCardVariant.ts @@ -2,4 +2,5 @@ export type WidgetCardVariant = | 'canvas' | 'side-column' | 'dashboard' + | 'standalone' | 'record-page'; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/utils/__tests__/getWidgetCardVariant.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/utils/__tests__/getWidgetCardVariant.test.ts index 98f4dbbeab..bb589e7c26 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/utils/__tests__/getWidgetCardVariant.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/utils/__tests__/getWidgetCardVariant.test.ts @@ -17,6 +17,30 @@ describe('getWidgetCardVariant', () => { ).toBe('dashboard'); }); + it('should return standalone for STANDALONE_PAGE page layout type', () => { + expect( + getWidgetCardVariant({ + layoutMode: PageLayoutTabLayoutMode.GRID, + isInPinnedTab: false, + pageLayoutType: PageLayoutType.STANDALONE_PAGE, + isMobile: false, + isInSidePanel: false, + }), + ).toBe('standalone'); + }); + + it('should prioritize standalone over canvas', () => { + expect( + getWidgetCardVariant({ + layoutMode: PageLayoutTabLayoutMode.CANVAS, + isInPinnedTab: false, + pageLayoutType: PageLayoutType.STANDALONE_PAGE, + isMobile: false, + isInSidePanel: false, + }), + ).toBe('standalone'); + }); + it('should return canvas for CANVAS layout mode', () => { expect( getWidgetCardVariant({ diff --git a/packages/twenty-front/src/modules/page-layout/widgets/utils/getWidgetCardVariant.ts b/packages/twenty-front/src/modules/page-layout/widgets/utils/getWidgetCardVariant.ts index 3ee8a5c3ec..7d909bcc94 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/utils/getWidgetCardVariant.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/utils/getWidgetCardVariant.ts @@ -23,6 +23,10 @@ export const getWidgetCardVariant = ({ return 'dashboard'; } + if (pageLayoutType === PageLayoutType.STANDALONE_PAGE) { + return 'standalone'; + } + if (layoutMode === PageLayoutTabLayoutMode.CANVAS) { return 'canvas'; } diff --git a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx index bf14303b7c..957006d92e 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx @@ -23,7 +23,7 @@ const computeBorderColor = ( if (props.isEditable && (props.isEditing || props.isDragging)) { return themeCssVariables.color.blue; } - if (props.variant === 'dashboard') { + if (props.variant === 'dashboard' || props.variant === 'standalone') { return themeCssVariables.border.color.light; } return 'transparent'; @@ -37,6 +37,7 @@ const StyledWidgetCard = styled.div` } if ( props.variant === 'dashboard' || + props.variant === 'standalone' || (props.variant === 'side-column' && props.isEditable) ) { return themeCssVariables.background.secondary; @@ -49,6 +50,7 @@ const StyledWidgetCard = styled.div` border: ${(props) => props.variant === 'dashboard' || + props.variant === 'standalone' || props.variant === 'record-page' || props.isEditable ? `1px solid ${computeBorderColor(props)}` @@ -66,7 +68,10 @@ const StyledWidgetCard = styled.div` return `1px solid ${computeBorderColor(props)}`; }}; border-radius: ${({ variant, isEditable }) => - variant === 'dashboard' || variant === 'record-page' || isEditable + variant === 'dashboard' || + variant === 'standalone' || + variant === 'record-page' || + isEditable ? themeCssVariables.border.radius.md : '0'}; @@ -90,8 +95,13 @@ const StyledWidgetCard = styled.div` height: 100%; padding: ${({ variant, isEditable, headerLess }) => { - if (variant === 'dashboard' && headerLess === true) return '0'; - if (variant === 'dashboard') return themeCssVariables.spacing[2]; + if ( + (variant === 'dashboard' || variant === 'standalone') && + headerLess === true + ) + return '0'; + if (variant === 'dashboard' || variant === 'standalone') + return themeCssVariables.spacing[2]; if (variant === 'side-column' && !isEditable) return themeCssVariables.spacing[3]; if (variant === 'record-page' || isEditable) diff --git a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardContent.tsx b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardContent.tsx index d57f36149a..547811841a 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardContent.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardContent.tsx @@ -35,7 +35,8 @@ const StyledWidgetCardContent = styled.div` overflow: hidden; padding: ${({ variant, isEditable }) => { - if (variant === 'dashboard') return themeCssVariables.spacing[2]; + if (variant === 'dashboard' || variant === 'standalone') + return themeCssVariables.spacing[2]; if ( variant === 'record-page' || (variant === 'side-column' && isEditable) diff --git a/packages/twenty-front/src/modules/side-panel/hooks/__tests__/useOpenRecordInSidePanel.test.tsx b/packages/twenty-front/src/modules/side-panel/hooks/__tests__/useOpenRecordInSidePanel.test.tsx index 2e15ec1b59..007535d85c 100644 --- a/packages/twenty-front/src/modules/side-panel/hooks/__tests__/useOpenRecordInSidePanel.test.tsx +++ b/packages/twenty-front/src/modules/side-panel/hooks/__tests__/useOpenRecordInSidePanel.test.tsx @@ -1,20 +1,20 @@ import { renderHook } from '@testing-library/react'; import { act } from 'react'; +import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; +import { contextStoreCurrentPageTypeComponentState } from '@/context-store/states/contextStoreCurrentPageTypeComponentState'; +import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; +import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; +import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; +import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem'; import { SIDE_PANEL_COMPONENT_INSTANCE_ID } from '@/side-panel/constants/SidePanelComponentInstanceId'; import { useOpenRecordInSidePanel } from '@/side-panel/hooks/useOpenRecordInSidePanel'; import { viewableRecordIdComponentState } from '@/side-panel/pages/record-page/states/viewableRecordIdComponentState'; import { viewableRecordNameSingularComponentState } from '@/side-panel/pages/record-page/states/viewableRecordNameSingularComponentState'; import { sidePanelNavigationMorphItemsByPageState } from '@/side-panel/states/sidePanelNavigationMorphItemsByPageState'; -import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; -import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; -import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; -import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; -import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; -import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { SidePanelPages } from 'twenty-shared/types'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { ContextStorePageType, SidePanelPages } from 'twenty-shared/types'; import { useIcons } from 'twenty-ui/display'; import { getJestMetadataAndApolloMocksAndCommandMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndCommandMenuWrapper'; import { getTestEnrichedObjectMetadataItemsMock } from '~/testing/utils/getTestEnrichedObjectMetadataItemsMock'; @@ -85,8 +85,8 @@ const renderHooks = () => { contextStoreNumberOfSelectedRecordsComponentState, 'mocked-uuid', ); - const contextStoreCurrentViewType = useAtomComponentStateValue( - contextStoreCurrentViewTypeComponentState, + const contextStoreCurrentPageType = useAtomComponentStateValue( + contextStoreCurrentPageTypeComponentState, 'mocked-uuid', ); const { getIcon } = useIcons(); @@ -98,7 +98,7 @@ const renderHooks = () => { contextStoreCurrentObjectMetadataItemId, contextStoreTargetedRecordsRule, contextStoreNumberOfSelectedRecords, - contextStoreCurrentViewType, + contextStoreCurrentPageType, getIcon, }; }, @@ -137,8 +137,8 @@ describe('useOpenRecordInSidePanel', () => { selectedRecordIds: [recordId], }); expect(result.current.contextStoreNumberOfSelectedRecords).toBe(1); - expect(result.current.contextStoreCurrentViewType).toBe( - ContextStoreViewType.ShowPage, + expect(result.current.contextStoreCurrentPageType).toBe( + ContextStorePageType.Record, ); const sidePanelNavigationMorphItemsByPage = jotaiStore.get( diff --git a/packages/twenty-front/src/modules/side-panel/hooks/useOpenRecordInSidePanel.ts b/packages/twenty-front/src/modules/side-panel/hooks/useOpenRecordInSidePanel.ts index 4dd533e6b7..fa6e51cde0 100644 --- a/packages/twenty-front/src/modules/side-panel/hooks/useOpenRecordInSidePanel.ts +++ b/packages/twenty-front/src/modules/side-panel/hooks/useOpenRecordInSidePanel.ts @@ -6,17 +6,20 @@ import { sidePanelNavigationStackState } from '@/side-panel/states/sidePanelNavi import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; -import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; +import { contextStoreCurrentPageTypeComponentState } from '@/context-store/states/contextStoreCurrentPageTypeComponentState'; import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState'; import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; import { getIconColorForObjectType } from '@/object-metadata/utils/getIconColorForObjectType'; import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem'; import { viewableRecordIdState } from '@/object-record/record-side-panel/states/viewableRecordIdState'; import { useOpenNewRecordTitleCell } from '@/object-record/record-title-cell/hooks/useOpenNewRecordTitleCell'; -import { CoreObjectNameSingular, SidePanelPages } from 'twenty-shared/types'; +import { + ContextStorePageType, + CoreObjectNameSingular, + SidePanelPages, +} from 'twenty-shared/types'; import { useRunWorkflowRunOpeningInSidePanelEffects } from '@/workflow/hooks/useRunWorkflowRunOpeningInSidePanelEffects'; import { t } from '@lingui/core/macro'; @@ -117,10 +120,10 @@ export const useOpenRecordInSidePanel = () => { ); store.set( - contextStoreCurrentViewTypeComponentState.atomFamily({ + contextStoreCurrentPageTypeComponentState.atomFamily({ instanceId: pageComponentInstanceId, }), - ContextStoreViewType.ShowPage, + ContextStorePageType.Record, ); store.set( diff --git a/packages/twenty-front/src/modules/side-panel/pages/root/components/SidePanelRootPage.tsx b/packages/twenty-front/src/modules/side-panel/pages/root/components/SidePanelRootPage.tsx deleted file mode 100644 index 8375406197..0000000000 --- a/packages/twenty-front/src/modules/side-panel/pages/root/components/SidePanelRootPage.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext'; -import { CommandMenuItemRenderer } from '@/command-menu-item/display/components/CommandMenuItemRenderer'; -import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; -import { SidePanelList } from '@/side-panel/components/SidePanelList'; -import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState'; -import { useFilterCommandMenuItemsWithSidePanelSearch } from '@/side-panel/pages/root/hooks/useFilterCommandMenuItemsWithSidePanelSearch'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useLingui } from '@lingui/react/macro'; -import { useContext, useMemo } from 'react'; -import { CommandMenuItemAvailabilityType } from '~/generated-metadata/graphql'; - -export const SidePanelRootPage = () => { - const { t } = useLingui(); - - const sidePanelSearch = useAtomStateValue(sidePanelSearchState); - const { commandMenuItems, commandMenuContextApi } = - useContext(CommandMenuContext); - - const { filterCommandMenuItemsWithSidePanelSearch } = - useFilterCommandMenuItemsWithSidePanelSearch({ - sidePanelSearch, - commandMenuContextApi, - }); - - const recordSelectionItems = useMemo( - () => - commandMenuItems.filter( - (item) => - item.availabilityType === - CommandMenuItemAvailabilityType.RECORD_SELECTION, - ), - [commandMenuItems], - ); - - const globalItems = useMemo( - () => - commandMenuItems.filter( - (item) => - item.availabilityType === CommandMenuItemAvailabilityType.GLOBAL, - ), - [commandMenuItems], - ); - - const fallbackItems = useMemo( - () => - commandMenuItems.filter( - (item) => - item.availabilityType === CommandMenuItemAvailabilityType.FALLBACK, - ), - [commandMenuItems], - ); - - const matchingRecordSelectionItems = - filterCommandMenuItemsWithSidePanelSearch(recordSelectionItems); - const matchingGlobalItems = - filterCommandMenuItemsWithSidePanelSearch(globalItems); - - const noResults = - !matchingRecordSelectionItems.length && !matchingGlobalItems.length; - - const selectableItemIds = [ - ...matchingRecordSelectionItems, - ...matchingGlobalItems, - ...(noResults ? fallbackItems : []), - ].map((item) => item.id); - - return ( - - {matchingRecordSelectionItems.length > 0 && ( - - {matchingRecordSelectionItems.map((item) => ( - - ))} - - )} - {matchingGlobalItems.length > 0 && ( - - {matchingGlobalItems.map((item) => ( - - ))} - - )} - {noResults && fallbackItems.length > 0 && ( - - {fallbackItems.map((item) => ( - - ))} - - )} - - ); -}; diff --git a/packages/twenty-front/src/pages/page-layout/StandalonePageHeader.tsx b/packages/twenty-front/src/pages/page-layout/StandalonePageHeader.tsx new file mode 100644 index 0000000000..0071de81b7 --- /dev/null +++ b/packages/twenty-front/src/pages/page-layout/StandalonePageHeader.tsx @@ -0,0 +1,38 @@ +import { StandalonePageCommandMenu } from '@/command-menu-item/components/StandalonePageCommandMenu'; +import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState'; +import { navigationMenuItemsSelector } from '@/navigation-menu-item/common/states/navigationMenuItemsSelector'; +import { SidePanelToggleButton } from '@/side-panel/components/SidePanelToggleButton'; +import { PageHeader } from '@/ui/layout/page/components/PageHeader'; +import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { isDefined } from 'twenty-shared/utils'; +import { useIcons } from 'twenty-ui/display'; + +type StandalonePageHeaderProps = { + pageLayoutId: string; +}; + +export const StandalonePageHeader = ({ + pageLayoutId, +}: StandalonePageHeaderProps) => { + const { getIcon } = useIcons(); + const navigationMenuItems = useAtomStateValue(navigationMenuItemsSelector); + const isLayoutCustomizationModeEnabled = useAtomStateValue( + isLayoutCustomizationModeEnabledState, + ); + + const navigationMenuItem = navigationMenuItems.find( + (item) => item.pageLayoutId === pageLayoutId, + ); + + const title = navigationMenuItem?.name ?? ''; + const Icon = isDefined(navigationMenuItem?.icon) + ? getIcon(navigationMenuItem.icon) + : undefined; + + return ( + + + {!isLayoutCustomizationModeEnabled && } + + ); +}; diff --git a/packages/twenty-front/src/pages/page-layout/StandalonePageLayoutPage.tsx b/packages/twenty-front/src/pages/page-layout/StandalonePageLayoutPage.tsx new file mode 100644 index 0000000000..42a5e60cae --- /dev/null +++ b/packages/twenty-front/src/pages/page-layout/StandalonePageLayoutPage.tsx @@ -0,0 +1,45 @@ +import { useParams } from 'react-router-dom'; + +import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext'; +import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; +import { MainContainerLayoutWithSidePanel } from '@/object-record/components/MainContainerLayoutWithSidePanel'; +import { PageLayoutRenderer } from '@/page-layout/components/PageLayoutRenderer'; +import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { PageContainer } from '@/ui/layout/page/components/PageContainer'; +import { isDefined } from 'twenty-shared/utils'; +import { PageLayoutType } from '~/generated-metadata/graphql'; +import { StandalonePageHeader } from '~/pages/page-layout/StandalonePageHeader'; + +export const StandalonePageLayoutPage = () => { + const { pageLayoutId } = useParams<{ pageLayoutId: string }>(); + + if (!isDefined(pageLayoutId)) { + return null; + } + + return ( + + + + + + + + + + + + + ); +}; diff --git a/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx b/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx index c4ae490a6f..f1f2d333da 100644 --- a/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx +++ b/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx @@ -1,6 +1,7 @@ import { type PropsWithChildren, useContext, useEffect, useState } from 'react'; import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; +import { contextStoreCurrentPageTypeComponentState } from '@/context-store/states/contextStoreCurrentPageTypeComponentState'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; import { contextStoreFilterGroupsComponentState } from '@/context-store/states/contextStoreFilterGroupsComponentState'; @@ -12,6 +13,7 @@ import { type ContextStoreTargetedRecordsRule, contextStoreTargetedRecordsRuleComponentState, } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; +import { type ContextStorePageType } from 'twenty-shared/types'; import { type ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { type RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; @@ -26,6 +28,7 @@ export type JestContextStoreSetterMocks = { contextStoreCurrentObjectMetadataNameSingular?: string; contextStoreCurrentViewId?: string; contextStoreCurrentViewType?: ContextStoreViewType; + contextStoreCurrentPageType?: ContextStorePageType; }; type JestContextStoreSetterProps = @@ -41,6 +44,7 @@ export const JestContextStoreSetter = ({ contextStoreFilters = [], contextStoreFilterGroups = [], contextStoreCurrentViewType, + contextStoreCurrentPageType, children, }: JestContextStoreSetterProps) => { const contextStoreInstanceContext = useContext( @@ -84,6 +88,11 @@ export const JestContextStoreSetter = ({ instanceId, ); + const setContextStoreCurrentPageType = useSetAtomComponentState( + contextStoreCurrentPageTypeComponentState, + instanceId, + ); + const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular: contextStoreCurrentObjectMetadataNameSingular, }); @@ -99,6 +108,7 @@ export const JestContextStoreSetter = ({ setContextStoreFilters(contextStoreFilters); setContextStoreFilterGroups(contextStoreFilterGroups); setContextStoreCurrentViewType(contextStoreCurrentViewType ?? null); + setContextStoreCurrentPageType(contextStoreCurrentPageType ?? null); setIsLoaded(true); }, [ setContextStoreTargetedRecordsRule, @@ -114,6 +124,8 @@ export const JestContextStoreSetter = ({ contextStoreCurrentViewId, setContextStoreCurrentViewType, contextStoreCurrentViewType, + setContextStoreCurrentPageType, + contextStoreCurrentPageType, setContextStoreFilterGroups, contextStoreFilterGroups, ]); diff --git a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndCommandMenuWrapper.tsx b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndCommandMenuWrapper.tsx index 813f294eba..c01f8d0873 100644 --- a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndCommandMenuWrapper.tsx +++ b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndCommandMenuWrapper.tsx @@ -26,6 +26,7 @@ export const getJestMetadataAndApolloMocksAndCommandMenuWrapper = ({ contextStoreTargetedRecordsRule, contextStoreCurrentViewId, contextStoreCurrentViewType, + contextStoreCurrentPageType, contextStoreNumberOfSelectedRecords, contextStoreCurrentObjectMetadataNameSingular, contextStoreFilters, @@ -70,6 +71,7 @@ export const getJestMetadataAndApolloMocksAndCommandMenuWrapper = ({ contextStoreCurrentObjectMetadataNameSingular } contextStoreCurrentViewType={contextStoreCurrentViewType} + contextStoreCurrentPageType={contextStoreCurrentPageType} > {children} diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/conditional-availability/__tests__/transform-conditional-availability-expressions.test.ts b/packages/twenty-sdk/src/cli/utilities/build/common/conditional-availability/__tests__/transform-conditional-availability-expressions.test.ts index 44c0e61a6d..73a4adddef 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/common/conditional-availability/__tests__/transform-conditional-availability-expressions.test.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/common/conditional-availability/__tests__/transform-conditional-availability-expressions.test.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { transformConditionalAvailabilityExpressionsForEsBuildPlugin } from '@/cli/utilities/build/common/conditional-availability/utils/transform-conditional-availability-expressions'; import { - CommandMenuContextApiPageType, + ContextStorePageType, type CommandMenuContextApi, } from 'twenty-shared/types'; import { evaluateConditionalAvailabilityExpression } from 'twenty-shared/utils'; @@ -16,7 +16,7 @@ const readMock = (filename: string): string => const buildMockCommandMenuContextApi = ( overrides: Partial = {}, ): CommandMenuContextApi => ({ - pageType: CommandMenuContextApiPageType.INDEX_PAGE, + pageType: ContextStorePageType.Index, isInSidePanel: false, isPageInEditMode: false, favoriteRecordIds: [], @@ -190,7 +190,7 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => { describe('simple-boolean-front-component', () => { it('should evaluate pageType when RECORD_PAGE', () => { const context = buildMockCommandMenuContextApi({ - pageType: CommandMenuContextApiPageType.RECORD_PAGE, + pageType: ContextStorePageType.Record, }); expect( @@ -203,7 +203,7 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => { it('should evaluate pageType when INDEX_PAGE', () => { const context = buildMockCommandMenuContextApi({ - pageType: CommandMenuContextApiPageType.INDEX_PAGE, + pageType: ContextStorePageType.Index, }); expect( @@ -348,7 +348,7 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => { describe('parenthesized-expression-front-component', () => { it('should allow when favorite and not remote', () => { const context = buildMockCommandMenuContextApi({ - pageType: CommandMenuContextApiPageType.INDEX_PAGE, + pageType: ContextStorePageType.Index, favoriteRecordIds: ['rec-1'], objectMetadataItem: { isRemote: false }, }); @@ -363,7 +363,7 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => { it('should deny when remote even if record page', () => { const context = buildMockCommandMenuContextApi({ - pageType: CommandMenuContextApiPageType.RECORD_PAGE, + pageType: ContextStorePageType.Record, favoriteRecordIds: [], objectMetadataItem: { isRemote: true }, }); @@ -422,7 +422,7 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => { describe('string-comparison-front-component', () => { it('should match when on record page and company name matches', () => { const context = buildMockCommandMenuContextApi({ - pageType: CommandMenuContextApiPageType.RECORD_PAGE, + pageType: ContextStorePageType.Record, selectedRecords: [ { id: 'rec-1', @@ -444,7 +444,7 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => { it('should not match when company name differs', () => { const context = buildMockCommandMenuContextApi({ - pageType: CommandMenuContextApiPageType.RECORD_PAGE, + pageType: ContextStorePageType.Record, selectedRecords: [ { id: 'rec-1', @@ -468,7 +468,7 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => { describe('target-permissions-front-component', () => { it('should allow when on record page with write permission', () => { const context = buildMockCommandMenuContextApi({ - pageType: CommandMenuContextApiPageType.RECORD_PAGE, + pageType: ContextStorePageType.Record, targetObjectWritePermissions: { person: true }, }); @@ -482,7 +482,7 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => { it('should deny when not on record page', () => { const context = buildMockCommandMenuContextApi({ - pageType: CommandMenuContextApiPageType.INDEX_PAGE, + pageType: ContextStorePageType.Index, targetObjectWritePermissions: { person: true }, }); diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-instance-command-fast-1775752781995-add-standalone-page.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-instance-command-fast-1775752781995-add-standalone-page.ts new file mode 100644 index 0000000000..39d947e3dc --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-instance-command-fast-1775752781995-add-standalone-page.ts @@ -0,0 +1,105 @@ +import { QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface'; + +@RegisteredInstanceCommand('1.23.0', 1775752781995) +export class AddStandalonePageFastInstanceCommand + implements FastInstanceCommand +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "core"."navigationMenuItem" ADD "pageLayoutId" uuid', + ); + await queryRunner.query( + 'ALTER TYPE "core"."pageLayout_type_enum" RENAME TO "pageLayout_type_enum_old"', + ); + await queryRunner.query( + "CREATE TYPE \"core\".\"pageLayout_type_enum\" AS ENUM('RECORD_INDEX', 'RECORD_PAGE', 'DASHBOARD', 'STANDALONE_PAGE')", + ); + await queryRunner.query( + 'ALTER TABLE "core"."pageLayout" ALTER COLUMN "type" DROP DEFAULT', + ); + await queryRunner.query( + 'ALTER TABLE "core"."pageLayout" ALTER COLUMN "type" TYPE "core"."pageLayout_type_enum" USING "type"::"text"::"core"."pageLayout_type_enum"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."pageLayout" ALTER COLUMN "type" SET DEFAULT \'RECORD_PAGE\'', + ); + await queryRunner.query('DROP TYPE "core"."pageLayout_type_enum_old"'); + await queryRunner.query( + 'ALTER TABLE "core"."navigationMenuItem" DROP CONSTRAINT IF EXISTS "CHK_navigation_menu_item_type_fields"', + ); + await queryRunner.query( + 'ALTER TYPE "core"."navigationMenuItem_type_enum" RENAME TO "navigationMenuItem_type_enum_old"', + ); + await queryRunner.query( + "CREATE TYPE \"core\".\"navigationMenuItem_type_enum\" AS ENUM('VIEW', 'FOLDER', 'LINK', 'OBJECT', 'RECORD', 'PAGE_LAYOUT')", + ); + await queryRunner.query( + 'ALTER TABLE "core"."navigationMenuItem" ALTER COLUMN "type" TYPE "core"."navigationMenuItem_type_enum" USING "type"::"text"::"core"."navigationMenuItem_type_enum"', + ); + await queryRunner.query( + 'DROP TYPE "core"."navigationMenuItem_type_enum_old"', + ); + await queryRunner.query( + `ALTER TABLE "core"."navigationMenuItem" ADD CONSTRAINT "CHK_navigation_menu_item_type_fields" CHECK (("type" = 'FOLDER') OR ("type" = 'OBJECT' AND "targetObjectMetadataId" IS NOT NULL) OR ("type" = 'VIEW' AND "viewId" IS NOT NULL) OR ("type" = 'RECORD' AND "targetRecordId" IS NOT NULL AND "targetObjectMetadataId" IS NOT NULL) OR ("type" = 'LINK' AND "link" IS NOT NULL) OR ("type" = 'PAGE_LAYOUT' AND "pageLayoutId" IS NOT NULL))`, + ); + await queryRunner.query( + 'CREATE INDEX "IDX_NAVIGATION_MENU_ITEM_PAGE_LAYOUT_ID_WORKSPACE_ID" ON "core"."navigationMenuItem" ("pageLayoutId", "workspaceId") ', + ); + await queryRunner.query( + 'ALTER TABLE "core"."navigationMenuItem" ADD CONSTRAINT "FK_4ba3e5e988c4c5f159ec8753ee3" FOREIGN KEY ("pageLayoutId") REFERENCES "core"."pageLayout"("id") ON DELETE CASCADE ON UPDATE NO ACTION', + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "core"."navigationMenuItem" DROP CONSTRAINT "FK_4ba3e5e988c4c5f159ec8753ee3"', + ); + await queryRunner.query( + 'DROP INDEX "core"."IDX_NAVIGATION_MENU_ITEM_PAGE_LAYOUT_ID_WORKSPACE_ID"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."navigationMenuItem" DROP CONSTRAINT IF EXISTS "CHK_navigation_menu_item_type_fields"', + ); + await queryRunner.query( + 'DELETE FROM "core"."navigationMenuItem" WHERE "type" = \'PAGE_LAYOUT\'', + ); + await queryRunner.query( + "CREATE TYPE \"core\".\"navigationMenuItem_type_enum_old\" AS ENUM('FOLDER', 'LINK', 'OBJECT', 'RECORD', 'VIEW')", + ); + await queryRunner.query( + 'ALTER TABLE "core"."navigationMenuItem" ALTER COLUMN "type" TYPE "core"."navigationMenuItem_type_enum_old" USING "type"::"text"::"core"."navigationMenuItem_type_enum_old"', + ); + await queryRunner.query('DROP TYPE "core"."navigationMenuItem_type_enum"'); + await queryRunner.query( + 'ALTER TYPE "core"."navigationMenuItem_type_enum_old" RENAME TO "navigationMenuItem_type_enum"', + ); + await queryRunner.query( + `ALTER TABLE "core"."navigationMenuItem" ADD CONSTRAINT "CHK_navigation_menu_item_type_fields" CHECK (("type" = 'FOLDER') OR ("type" = 'OBJECT' AND "targetObjectMetadataId" IS NOT NULL) OR ("type" = 'VIEW' AND "viewId" IS NOT NULL) OR ("type" = 'RECORD' AND "targetRecordId" IS NOT NULL AND "targetObjectMetadataId" IS NOT NULL) OR ("type" = 'LINK' AND "link" IS NOT NULL))`, + ); + await queryRunner.query( + 'DELETE FROM "core"."pageLayout" WHERE "type" = \'STANDALONE_PAGE\'', + ); + await queryRunner.query( + "CREATE TYPE \"core\".\"pageLayout_type_enum_old\" AS ENUM('DASHBOARD', 'RECORD_INDEX', 'RECORD_PAGE')", + ); + await queryRunner.query( + 'ALTER TABLE "core"."pageLayout" ALTER COLUMN "type" DROP DEFAULT', + ); + await queryRunner.query( + 'ALTER TABLE "core"."pageLayout" ALTER COLUMN "type" TYPE "core"."pageLayout_type_enum_old" USING "type"::"text"::"core"."pageLayout_type_enum_old"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."pageLayout" ALTER COLUMN "type" SET DEFAULT \'RECORD_PAGE\'', + ); + await queryRunner.query('DROP TYPE "core"."pageLayout_type_enum"'); + await queryRunner.query( + 'ALTER TYPE "core"."pageLayout_type_enum_old" RENAME TO "pageLayout_type_enum"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."navigationMenuItem" DROP COLUMN "pageLayoutId"', + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-instance-command-fast-1776090711153-add-global-object-context-to-command-menu-item-availability-type.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-instance-command-fast-1776090711153-add-global-object-context-to-command-menu-item-availability-type.ts new file mode 100644 index 0000000000..2bb0785716 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-instance-command-fast-1776090711153-add-global-object-context-to-command-menu-item-availability-type.ts @@ -0,0 +1,54 @@ +import { QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface'; + +@RegisteredInstanceCommand('1.23.0', 1776090711153) +export class AddGlobalObjectContextToCommandMenuItemAvailabilityTypeFastInstanceCommand + implements FastInstanceCommand +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TYPE "core"."commandMenuItem_availabilitytype_enum" RENAME TO "commandMenuItem_availabilitytype_enum_old"', + ); + await queryRunner.query( + "CREATE TYPE \"core\".\"commandMenuItem_availabilitytype_enum\" AS ENUM('GLOBAL', 'GLOBAL_OBJECT_CONTEXT', 'RECORD_SELECTION', 'FALLBACK')", + ); + await queryRunner.query( + 'ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "availabilityType" DROP DEFAULT', + ); + await queryRunner.query( + 'ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "availabilityType" TYPE "core"."commandMenuItem_availabilitytype_enum" USING "availabilityType"::"text"::"core"."commandMenuItem_availabilitytype_enum"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "availabilityType" SET DEFAULT \'GLOBAL\'', + ); + await queryRunner.query( + 'DROP TYPE "core"."commandMenuItem_availabilitytype_enum_old"', + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + "CREATE TYPE \"core\".\"commandMenuItem_availabilitytype_enum_old\" AS ENUM('FALLBACK', 'GLOBAL', 'RECORD_SELECTION')", + ); + await queryRunner.query( + 'ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "availabilityType" DROP DEFAULT', + ); + await queryRunner.query( + `UPDATE "core"."commandMenuItem" SET "availabilityType" = 'GLOBAL' WHERE "availabilityType" = 'GLOBAL_OBJECT_CONTEXT'`, + ); + await queryRunner.query( + 'ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "availabilityType" TYPE "core"."commandMenuItem_availabilitytype_enum_old" USING "availabilityType"::"text"::"core"."commandMenuItem_availabilitytype_enum_old"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "availabilityType" SET DEFAULT \'GLOBAL\'', + ); + await queryRunner.query( + 'DROP TYPE "core"."commandMenuItem_availabilitytype_enum"', + ); + await queryRunner.query( + 'ALTER TYPE "core"."commandMenuItem_availabilitytype_enum_old" RENAME TO "commandMenuItem_availabilitytype_enum"', + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-upgrade-version-command.module.ts index 7d35bff49f..381b36934e 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-upgrade-version-command.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module'; import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-23/1-23-workspace-command-1780000001000-backfill-page-layouts-and-fields-widget-view-fields.command'; +import { UpdateGlobalObjectContextCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-23/1-23-workspace-command-1780000005000-update-global-object-context-command-menu-items.command'; import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; @@ -15,6 +16,9 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace WorkspaceIteratorModule, WorkspaceMigrationModule, ], - providers: [BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand], + providers: [ + BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand, + UpdateGlobalObjectContextCommandMenuItemsCommand, + ], }) export class V1_23_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-workspace-command-1780000005000-update-global-object-context-command-menu-items.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-workspace-command-1780000005000-update-global-object-context-command-menu-items.command.ts new file mode 100644 index 0000000000..97475f5f39 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-workspace-command-1780000005000-update-global-object-context-command-menu-items.command.ts @@ -0,0 +1,143 @@ +import { Command } from 'nest-commander'; +import { isDefined } from 'twenty-shared/utils'; + +import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner'; +import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service'; +import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; +import { STANDARD_COMMAND_MENU_ITEMS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant'; +import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; + +const UNIVERSAL_IDENTIFIERS_TO_FIX = new Set([ + STANDARD_COMMAND_MENU_ITEMS.createNewRecord.universalIdentifier, + STANDARD_COMMAND_MENU_ITEMS.importRecords.universalIdentifier, + STANDARD_COMMAND_MENU_ITEMS.exportView.universalIdentifier, + STANDARD_COMMAND_MENU_ITEMS.seeDeletedRecords.universalIdentifier, + STANDARD_COMMAND_MENU_ITEMS.createNewView.universalIdentifier, + STANDARD_COMMAND_MENU_ITEMS.hideDeletedRecords.universalIdentifier, +]); + +@RegisteredWorkspaceCommand('1.23.0', 1780000005000) +@Command({ + name: 'upgrade:1-23:update-global-object-context-command-menu-items', + description: + 'Update command menu items that require object context from GLOBAL to GLOBAL_OBJECT_CONTEXT', +}) +export class UpdateGlobalObjectContextCommandMenuItemsCommand extends ActiveOrSuspendedWorkspaceCommandRunner { + constructor( + protected readonly workspaceIteratorService: WorkspaceIteratorService, + private readonly applicationService: ApplicationService, + private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, + private readonly workspaceCacheService: WorkspaceCacheService, + ) { + super(workspaceIteratorService); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + const isDryRun = options.dryRun ?? false; + + this.logger.log( + `${isDryRun ? '[DRY RUN] ' : ''}Starting GLOBAL_OBJECT_CONTEXT availability type update for workspace ${workspaceId}`, + ); + + const { twentyStandardFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ); + + const { flatCommandMenuItemMaps: existingFlatCommandMenuItemMaps } = + await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatCommandMenuItemMaps', + ]); + + const { allFlatEntityMaps: standardAllFlatEntityMaps } = + computeTwentyStandardApplicationAllFlatEntityMaps({ + shouldIncludeRecordPageLayouts: true, + now: new Date().toISOString(), + workspaceId, + twentyStandardApplicationId: twentyStandardFlatApplication.id, + }); + + const itemsToUpdate = [...UNIVERSAL_IDENTIFIERS_TO_FIX] + .map((universalIdentifier) => { + const standardItem = + standardAllFlatEntityMaps.flatCommandMenuItemMaps + .byUniversalIdentifier[universalIdentifier]; + const existingItem = + existingFlatCommandMenuItemMaps.byUniversalIdentifier[ + universalIdentifier + ]; + + if ( + !isDefined(standardItem) || + !isDefined(existingItem) || + existingItem.availabilityType === standardItem.availabilityType + ) { + return undefined; + } + + return { + ...existingItem, + availabilityType: standardItem.availabilityType, + updatedAt: new Date().toISOString(), + }; + }) + .filter(isDefined); + + if (itemsToUpdate.length === 0) { + this.logger.log( + `Command menu item availability types already up to date for workspace ${workspaceId}`, + ); + + return; + } + + this.logger.log( + `Found ${itemsToUpdate.length} command menu item(s) to update for workspace ${workspaceId}`, + ); + + if (isDryRun) { + this.logger.log( + `[DRY RUN] Would update ${itemsToUpdate.length} command menu item availability type(s) for workspace ${workspaceId}`, + ); + + return; + } + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + allFlatEntityOperationByMetadataName: { + commandMenuItem: { + flatEntityToCreate: [], + flatEntityToDelete: [], + flatEntityToUpdate: itemsToUpdate, + }, + }, + workspaceId, + applicationUniversalIdentifier: + twentyStandardFlatApplication.universalIdentifier, + }, + ); + + if (validateAndBuildResult.status === 'fail') { + this.logger.error( + `Failed to update command menu item availability types:\n${JSON.stringify(validateAndBuildResult, null, 2)}`, + ); + + throw new Error( + `Failed to update command menu item availability types for workspace ${workspaceId}`, + ); + } + + this.logger.log( + `Successfully updated ${itemsToUpdate.length} command menu item availability type(s) for workspace ${workspaceId}`, + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts index 2f63cd90e8..0664772428 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts @@ -3,6 +3,7 @@ import { AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-instance-command-fast-1775129420309-add-view-field-group-id-index-on-view-field'; import { MigrateMessagingCalendarToCoreFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-instance-command-fast-1775165049548-migrate-messaging-calendar-to-core'; import { AddEmailThreadWidgetTypeFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-instance-command-fast-1775200000000-add-email-thread-widget-type'; +import { AddStandalonePageFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-23/1-23-instance-command-fast-1775752781995-add-standalone-page'; import { AddPermissionFlagRoleIdIndexFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775749486425-add-permission-flag-role-id-index'; import { AddWorkspaceIdToIndirectEntitiesFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775758621017-add-workspace-id-to-indirect-entities'; import { AddWorkspaceIdIndexesAndFksFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775761294897-add-workspace-id-indexes-and-fks-to-indirect-entities'; @@ -10,11 +11,13 @@ import { DropObjectMetadataDataSourceFkFastInstanceCommand } from 'src/database/ import { AddCreditBalanceToBillingCustomerFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1776078919203-add-credit-balance-to-billing-customer'; import { BackfillWorkspaceIdOnIndirectEntitiesSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/1-22/1-22-instance-command-slow-1775758621018-backfill-workspace-id-on-indirect-entities'; import { DropWorkspaceVersionColumnFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-23/1-23-instance-command-fast-1785000000000-drop-workspace-version-column'; +import { AddGlobalObjectContextToCommandMenuItemAvailabilityTypeFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-23/1-23-instance-command-fast-1776090711153-add-global-object-context-to-command-menu-item-availability-type'; export const INSTANCE_COMMANDS = [ AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand, MigrateMessagingCalendarToCoreFastInstanceCommand, AddEmailThreadWidgetTypeFastInstanceCommand, + AddStandalonePageFastInstanceCommand, AddPermissionFlagRoleIdIndexFastInstanceCommand, AddWorkspaceIdToIndirectEntitiesFastInstanceCommand, BackfillWorkspaceIdOnIndirectEntitiesSlowInstanceCommand, @@ -22,4 +25,5 @@ export const INSTANCE_COMMANDS = [ DropObjectMetadataDataSourceFkFastInstanceCommand, AddCreditBalanceToBillingCustomerFastInstanceCommand, DropWorkspaceVersionColumnFastInstanceCommand, + AddGlobalObjectContextToCommandMenuItemAvailabilityTypeFastInstanceCommand, ]; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util.ts index a819dda7bd..595739dde2 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util.ts @@ -9,6 +9,7 @@ const AVAILABILITY_TYPE_MAP: Record< CommandMenuItemAvailabilityType > = { GLOBAL: CommandMenuItemAvailabilityType.GLOBAL, + GLOBAL_OBJECT_CONTEXT: CommandMenuItemAvailabilityType.GLOBAL_OBJECT_CONTEXT, RECORD_SELECTION: CommandMenuItemAvailabilityType.RECORD_SELECTION, FALLBACK: CommandMenuItemAvailabilityType.FALLBACK, }; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-navigation-menu-item-manifest-to-universal-flat-navigation-menu-item.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-navigation-menu-item-manifest-to-universal-flat-navigation-menu-item.util.ts index bd67d5f608..53d235de11 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-navigation-menu-item-manifest-to-universal-flat-navigation-menu-item.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-navigation-menu-item-manifest-to-universal-flat-navigation-menu-item.util.ts @@ -27,6 +27,8 @@ export const fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem = navigationMenuItemManifest.folderUniversalIdentifier ?? null, targetObjectMetadataUniversalIdentifier: navigationMenuItemManifest.targetObjectUniversalIdentifier ?? null, + pageLayoutUniversalIdentifier: + navigationMenuItemManifest.pageLayoutUniversalIdentifier ?? null, targetRecordId: null, userWorkspaceId: null, createdAt: now, diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts index 01bbe95b60..af9cea1502 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts @@ -1100,7 +1100,12 @@ export const computeMetadataSchemaComponents = ( name: { type: 'string' }, type: { type: 'string', - enum: ['RECORD_INDEX', 'RECORD_PAGE', 'DASHBOARD'], + enum: [ + 'RECORD_INDEX', + 'RECORD_PAGE', + 'DASHBOARD', + 'STANDALONE_PAGE', + ], default: 'RECORD_PAGE', }, objectMetadataId: { type: 'string', format: 'uuid' }, @@ -1121,7 +1126,12 @@ export const computeMetadataSchemaComponents = ( name: { type: 'string' }, type: { type: 'string', - enum: ['RECORD_INDEX', 'RECORD_PAGE', 'DASHBOARD'], + enum: [ + 'RECORD_INDEX', + 'RECORD_PAGE', + 'DASHBOARD', + 'STANDALONE_PAGE', + ], }, objectMetadataId: { type: 'string', format: 'uuid' }, }, @@ -1134,7 +1144,12 @@ export const computeMetadataSchemaComponents = ( name: { type: 'string' }, type: { type: 'string', - enum: ['RECORD_INDEX', 'RECORD_PAGE', 'DASHBOARD'], + enum: [ + 'RECORD_INDEX', + 'RECORD_PAGE', + 'DASHBOARD', + 'STANDALONE_PAGE', + ], }, objectMetadataId: { type: 'string', format: 'uuid' }, tabs: { diff --git a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum.ts b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum.ts index 7b63549bd3..1ae57104b0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum.ts +++ b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum.ts @@ -2,6 +2,7 @@ import { registerEnumType } from '@nestjs/graphql'; export enum CommandMenuItemAvailabilityType { GLOBAL = 'GLOBAL', + GLOBAL_OBJECT_CONTEXT = 'GLOBAL_OBJECT_CONTEXT', RECORD_SELECTION = 'RECORD_SELECTION', FALLBACK = 'FALLBACK', } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts index 5d04c18e84..beee31f485 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts @@ -1169,6 +1169,11 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = { toStringify: false, universalProperty: 'targetObjectMetadataUniversalIdentifier', }, + pageLayoutId: { + toCompare: false, + toStringify: false, + universalProperty: 'pageLayoutUniversalIdentifier', + }, }, permissionFlag: { flag: { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-foreign-key.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-foreign-key.constant.ts index ca4a67b7cf..f53c1c9d1b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-foreign-key.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-foreign-key.constant.ts @@ -64,6 +64,9 @@ export const ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY = { view: { foreignKey: 'viewId', }, + pageLayout: { + foreignKey: 'pageLayoutId', + }, }, fieldMetadata: { object: { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant.ts index a9387777cb..558dd41d6c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant.ts @@ -112,6 +112,13 @@ export const ALL_MANY_TO_ONE_METADATA_RELATIONS = { isNullable: true, universalForeignKey: 'viewUniversalIdentifier', }, + pageLayout: { + metadataName: 'pageLayout', + foreignKey: 'pageLayoutId', + inverseOneToManyProperty: null, + isNullable: true, + universalForeignKey: 'pageLayoutUniversalIdentifier', + }, }, fieldMetadata: { object: { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts index 50add7d279..80fc5f3be0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts @@ -73,6 +73,7 @@ export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = { navigationMenuItem: { objectMetadata: true, view: true, + pageLayout: true, }, permissionFlag: { role: true, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/get-metadata-related-metadata-names.util.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/get-metadata-related-metadata-names.util.spec.ts.snap index ae6a57a325..1c00211e5c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/get-metadata-related-metadata-names.util.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/get-metadata-related-metadata-names.util.spec.ts.snap @@ -44,6 +44,7 @@ exports[`getMetadataRelatedMetadataNames should return related metadata names fo "objectMetadata", "navigationMenuItem", "view", + "pageLayout", ] `; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/sort-metadata-names-children-first.util.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/sort-metadata-names-children-first.util.spec.ts.snap index 437509a5a9..4f7c9edc08 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/sort-metadata-names-children-first.util.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/sort-metadata-names-children-first.util.spec.ts.snap @@ -3,8 +3,8 @@ exports[`sortMetadataNamesChildrenFirst should return metadata names sorted with children first (most manyToOne relations first) 1`] = ` [ "rowLevelPermissionPredicate", - "fieldPermission", "navigationMenuItem", + "fieldPermission", "viewField", "viewFilter", "commandMenuItem", diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/constants/flat-navigation-menu-item-editable-properties.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/constants/flat-navigation-menu-item-editable-properties.constant.ts index 27c3d47d7e..21e9f887e3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/constants/flat-navigation-menu-item-editable-properties.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/constants/flat-navigation-menu-item-editable-properties.constant.ts @@ -7,4 +7,5 @@ export const FLAT_NAVIGATION_MENU_ITEM_EDITABLE_PROPERTIES = [ 'link', 'icon', 'color', + 'pageLayoutId', ] as const satisfies MetadataEntityPropertyName<'navigationMenuItem'>[]; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/flat-navigation-menu-item.module.ts b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/flat-navigation-menu-item.module.ts index c5e2a6a357..7048d56901 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/flat-navigation-menu-item.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/flat-navigation-menu-item.module.ts @@ -6,6 +6,7 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata import { WorkspaceFlatNavigationMenuItemMapCacheService } from 'src/engine/metadata-modules/flat-navigation-menu-item/services/workspace-flat-navigation-menu-item-map-cache.service'; import { NavigationMenuItemEntity } from 'src/engine/metadata-modules/navigation-menu-item/entities/navigation-menu-item.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity'; import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; @Module({ @@ -14,6 +15,7 @@ import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entit NavigationMenuItemEntity, ApplicationEntity, ObjectMetadataEntity, + PageLayoutEntity, ViewEntity, ]), WorkspaceManyOrAllFlatEntityMapsCacheModule, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/services/workspace-flat-navigation-menu-item-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/services/workspace-flat-navigation-menu-item-map-cache.service.ts index 1a923b0613..7967c3ccb3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/services/workspace-flat-navigation-menu-item-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/services/workspace-flat-navigation-menu-item-map-cache.service.ts @@ -12,6 +12,7 @@ import { addFlatNavigationMenuItemToMapsAndUpdateIndex } from 'src/engine/metada import { fromNavigationMenuItemEntityToFlatNavigationMenuItem } from 'src/engine/metadata-modules/flat-navigation-menu-item/utils/from-navigation-menu-item-entity-to-flat-navigation-menu-item.util'; import { NavigationMenuItemEntity } from 'src/engine/metadata-modules/navigation-menu-item/entities/navigation-menu-item.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity'; import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; import { createIdToUniversalIdentifierMap } from 'src/engine/workspace-cache/utils/create-id-to-universal-identifier-map.util'; @@ -28,6 +29,8 @@ export class WorkspaceFlatNavigationMenuItemMapCacheService extends WorkspaceCac private readonly objectMetadataRepository: Repository, @InjectRepository(ViewEntity) private readonly viewRepository: Repository, + @InjectRepository(PageLayoutEntity) + private readonly pageLayoutRepository: Repository, ) { super(); } @@ -35,28 +38,38 @@ export class WorkspaceFlatNavigationMenuItemMapCacheService extends WorkspaceCac async computeForCache( workspaceId: string, ): Promise { - const [navigationMenuItems, applications, objectMetadatas, views] = - await Promise.all([ - this.navigationMenuItemRepository.find({ - where: { workspaceId }, - withDeleted: true, - }), - this.applicationRepository.find({ - where: { workspaceId }, - select: ['id', 'universalIdentifier'], - withDeleted: true, - }), - this.objectMetadataRepository.find({ - where: { workspaceId }, - select: ['id', 'universalIdentifier'], - withDeleted: true, - }), - this.viewRepository.find({ - where: { workspaceId }, - select: ['id', 'universalIdentifier'], - withDeleted: true, - }), - ]); + const [ + navigationMenuItems, + applications, + objectMetadatas, + views, + pageLayouts, + ] = await Promise.all([ + this.navigationMenuItemRepository.find({ + where: { workspaceId }, + withDeleted: true, + }), + this.applicationRepository.find({ + where: { workspaceId }, + select: ['id', 'universalIdentifier'], + withDeleted: true, + }), + this.objectMetadataRepository.find({ + where: { workspaceId }, + select: ['id', 'universalIdentifier'], + withDeleted: true, + }), + this.viewRepository.find({ + where: { workspaceId }, + select: ['id', 'universalIdentifier'], + withDeleted: true, + }), + this.pageLayoutRepository.find({ + where: { workspaceId }, + select: ['id', 'universalIdentifier'], + withDeleted: true, + }), + ]); const applicationIdToUniversalIdentifierMap = createIdToUniversalIdentifierMap(applications); @@ -66,6 +79,8 @@ export class WorkspaceFlatNavigationMenuItemMapCacheService extends WorkspaceCac createIdToUniversalIdentifierMap(navigationMenuItems); const viewIdToUniversalIdentifierMap = createIdToUniversalIdentifierMap(views); + const pageLayoutIdToUniversalIdentifierMap = + createIdToUniversalIdentifierMap(pageLayouts); const flatNavigationMenuItemMaps = { ...createEmptyFlatEntityMaps(), @@ -80,6 +95,7 @@ export class WorkspaceFlatNavigationMenuItemMapCacheService extends WorkspaceCac objectMetadataIdToUniversalIdentifierMap, navigationMenuItemIdToUniversalIdentifierMap, viewIdToUniversalIdentifierMap, + pageLayoutIdToUniversalIdentifierMap, }); addFlatNavigationMenuItemToMapsAndUpdateIndex({ diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-create-navigation-menu-item-input-to-flat-navigation-menu-item-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-create-navigation-menu-item-input-to-flat-navigation-menu-item-to-create.util.ts index 97395b11a7..6d327efe61 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-create-navigation-menu-item-input-to-flat-navigation-menu-item-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-create-navigation-menu-item-input-to-flat-navigation-menu-item-to-create.util.ts @@ -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, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-flat-navigation-menu-item-to-navigation-menu-item-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-flat-navigation-menu-item-to-navigation-menu-item-dto.util.ts index e66401c452..b1112c8b7c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-flat-navigation-menu-item-to-navigation-menu-item-dto.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-flat-navigation-menu-item-to-navigation-menu-item-dto.util.ts @@ -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, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-navigation-menu-item-entity-to-flat-navigation-menu-item.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-navigation-menu-item-entity-to-flat-navigation-menu-item.util.ts index 8d66fb1a05..a4588391c7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-navigation-menu-item-entity-to-flat-navigation-menu-item.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-navigation-menu-item-entity-to-flat-navigation-menu-item.util.ts @@ -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, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-update-navigation-menu-item-input-to-flat-navigation-menu-item-to-update-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-update-navigation-menu-item-input-to-flat-navigation-menu-item-to-update-or-throw.util.ts index 83e23a73a1..c036cc59ba 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-update-navigation-menu-item-input-to-flat-navigation-menu-item-to-update-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-update-navigation-menu-item-input-to-flat-navigation-menu-item-to-update-or-throw.util.ts @@ -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; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/dtos/create-navigation-menu-item.input.ts b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/dtos/create-navigation-menu-item.input.ts index b073157c3a..238b2ebd5a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/dtos/create-navigation-menu-item.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/dtos/create-navigation-menu-item.input.ts @@ -67,6 +67,11 @@ export class CreateNavigationMenuItemInput { @Field(() => UUIDScalarType, { nullable: true }) folderId?: string; + @IsUUID() + @IsOptional() + @Field(() => UUIDScalarType, { nullable: true }) + pageLayoutId?: string | null; + @IsNumber() @IsOptional() @Field({ nullable: true }) diff --git a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/dtos/navigation-menu-item.dto.ts b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/dtos/navigation-menu-item.dto.ts index ada49605c7..8a9913038d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/dtos/navigation-menu-item.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/dtos/navigation-menu-item.dto.ts @@ -66,6 +66,11 @@ export class NavigationMenuItemDTO { @Field(() => UUIDScalarType, { nullable: true }) folderId?: string; + @IsUUID() + @IsOptional() + @Field(() => UUIDScalarType, { nullable: true }) + pageLayoutId?: string | null; + @IsNumber() @IsNotEmpty() @Field() diff --git a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/dtos/update-navigation-menu-item.input.ts b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/dtos/update-navigation-menu-item.input.ts index 3967a1e4b9..0fce9f4278 100644 --- a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/dtos/update-navigation-menu-item.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/dtos/update-navigation-menu-item.input.ts @@ -43,6 +43,11 @@ export class UpdateNavigationMenuItemInput { @IsString() @Field(() => String, { nullable: true }) color?: string | null; + + @IsUUID() + @IsOptional() + @Field(() => UUIDScalarType, { nullable: true }) + pageLayoutId?: string | null; } @InputType() diff --git a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/entities/navigation-menu-item.entity.ts b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/entities/navigation-menu-item.entity.ts index de9996137a..56cb93fdfc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/entities/navigation-menu-item.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/entities/navigation-menu-item.entity.ts @@ -13,6 +13,7 @@ import { import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity'; import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; import { NavigationMenuItemType } from 'src/engine/metadata-modules/navigation-menu-item/enums/navigation-menu-item-type.enum'; import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; @@ -35,13 +36,18 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti 'viewId', 'workspaceId', ]) +@Index('IDX_NAVIGATION_MENU_ITEM_PAGE_LAYOUT_ID_WORKSPACE_ID', [ + 'pageLayoutId', + 'workspaceId', +]) @Check( 'CHK_navigation_menu_item_type_fields', `("type" = 'FOLDER') OR ("type" = 'OBJECT' AND "targetObjectMetadataId" IS NOT NULL) OR ("type" = 'VIEW' AND "viewId" IS NOT NULL) OR ("type" = 'RECORD' AND "targetRecordId" IS NOT NULL AND "targetObjectMetadataId" IS NOT NULL) - OR ("type" = 'LINK' AND "link" IS NOT NULL)`, + OR ("type" = 'LINK' AND "link" IS NOT NULL) + OR ("type" = 'PAGE_LAYOUT' AND "pageLayoutId" IS NOT NULL)`, ) export class NavigationMenuItemEntity extends SyncableEntity @@ -112,6 +118,16 @@ export class NavigationMenuItemEntity @Column({ nullable: true, type: 'uuid' }) folderId: string | null; + @ManyToOne(() => PageLayoutEntity, { + onDelete: 'CASCADE', + nullable: true, + }) + @JoinColumn({ name: 'pageLayoutId' }) + pageLayout: Relation | null; + + @Column({ nullable: true, type: 'uuid' }) + pageLayoutId: string | null; + @Column({ nullable: false, type: 'double precision' }) position: number; diff --git a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts index 9d6126d993..445ce6d194 100644 --- a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts @@ -186,6 +186,7 @@ export class NavigationMenuItemService { flatNavigationMenuItemMaps: existingFlatNavigationMenuItemMaps, flatObjectMetadataMaps, flatViewMaps, + flatPageLayoutMaps, } = await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, @@ -193,6 +194,7 @@ export class NavigationMenuItemService { 'flatNavigationMenuItemMaps', 'flatObjectMetadataMaps', 'flatViewMaps', + 'flatPageLayoutMaps', ], }, ); @@ -230,6 +232,7 @@ export class NavigationMenuItemService { flatNavigationMenuItemMaps: optimisticFlatNavigationMenuItemMaps, flatObjectMetadataMaps, flatViewMaps, + flatPageLayoutMaps, }); addFlatNavigationMenuItemToMapsAndUpdateIndex({ @@ -341,11 +344,14 @@ export class NavigationMenuItemService { { workspaceId }, ); - const { flatNavigationMenuItemMaps: existingFlatNavigationMenuItemMaps } = + const { + flatNavigationMenuItemMaps: existingFlatNavigationMenuItemMaps, + flatPageLayoutMaps, + } = await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, - flatMapsKeys: ['flatNavigationMenuItemMaps'], + flatMapsKeys: ['flatNavigationMenuItemMaps', 'flatPageLayoutMaps'], }, ); @@ -378,6 +384,7 @@ export class NavigationMenuItemService { fromUpdateNavigationMenuItemInputToFlatNavigationMenuItemToUpdateOrThrow( { flatNavigationMenuItemMaps: existingFlatNavigationMenuItemMaps, + flatPageLayoutMaps, updateNavigationMenuItemInput: updateInput, }, ), diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts index 74d6e9ba6a..8b619d4bb3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts @@ -835,6 +835,8 @@ export class ObjectMetadataService extends TypeOrmQueryService { + const now = new Date().toISOString(); + + return [ + { + id: generateSeedId( + workspaceId, + NAVIGATION_MENU_ITEM_SEEDS.DOCUMENTATION_PAGE, + ), + universalIdentifier: generateSeedId( + workspaceId, + NAVIGATION_MENU_ITEM_SEEDS.DOCUMENTATION_PAGE, + ), + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, + workspaceId, + type: NavigationMenuItemType.PAGE_LAYOUT, + name: 'Star History', + icon: 'IconStar', + color: 'yellow', + position: 9999, + link: null, + userWorkspaceId: null, + targetRecordId: null, + targetObjectMetadataId: null, + targetObjectMetadataUniversalIdentifier: null, + viewId: null, + viewUniversalIdentifier: null, + folderId: null, + folderUniversalIdentifier: null, + pageLayoutId: generateSeedId( + workspaceId, + PAGE_LAYOUT_SEEDS.DOCUMENTATION_STANDALONE_PAGE, + ), + pageLayoutUniversalIdentifier: generateSeedId( + workspaceId, + PAGE_LAYOUT_SEEDS.DOCUMENTATION_STANDALONE_PAGE, + ), + createdAt: now, + updatedAt: now, + }, + ]; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-data-seeds.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-data-seeds.util.ts index 7ccfd48fca..93e50f3dcb 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-data-seeds.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-data-seeds.util.ts @@ -1,48 +1,105 @@ -import { v4 } from 'uuid'; - +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type'; import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; import { PAGE_LAYOUT_SEEDS } from 'src/engine/workspace-manager/dev-seeder/core/constants/page-layout-seeds.constant'; import { generateSeedId } from 'src/engine/workspace-manager/dev-seeder/core/utils/generate-seed-id.util'; -type PageLayoutSeed = { - id: string; - name: string; - type: PageLayoutType; - objectMetadataId: string | null; +export const getPageLayoutFlatEntitySeeds = ({ + workspaceId, + flatApplication, +}: { workspaceId: string; - universalIdentifier: string; - applicationId: string; -}; + flatApplication: FlatApplication; +}): FlatPageLayout[] => { + const now = new Date().toISOString(); -export const getPageLayoutDataSeeds = ( - workspaceId: string, - applicationId: string, -): PageLayoutSeed[] => [ - { - id: generateSeedId(workspaceId, PAGE_LAYOUT_SEEDS.SALES_DASHBOARD), - name: 'Sales Dashboard Layout', - type: PageLayoutType.DASHBOARD, - objectMetadataId: null, - workspaceId, - universalIdentifier: v4(), - applicationId, - }, - { - id: generateSeedId(workspaceId, PAGE_LAYOUT_SEEDS.CUSTOMER_DASHBOARD), - name: 'Customer Dashboard Layout', - type: PageLayoutType.DASHBOARD, - objectMetadataId: null, - workspaceId, - universalIdentifier: v4(), - applicationId, - }, - { - id: generateSeedId(workspaceId, PAGE_LAYOUT_SEEDS.TEAM_DASHBOARD), - name: 'Team Dashboard Layout', - type: PageLayoutType.DASHBOARD, - objectMetadataId: null, - workspaceId, - universalIdentifier: v4(), - applicationId, - }, -]; + return [ + { + id: generateSeedId(workspaceId, PAGE_LAYOUT_SEEDS.SALES_DASHBOARD), + universalIdentifier: generateSeedId( + workspaceId, + PAGE_LAYOUT_SEEDS.SALES_DASHBOARD, + ), + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, + workspaceId, + name: 'Sales Dashboard Layout', + type: PageLayoutType.DASHBOARD, + objectMetadataId: null, + objectMetadataUniversalIdentifier: null, + tabIds: [], + tabUniversalIdentifiers: [], + defaultTabToFocusOnMobileAndSidePanelId: null, + defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier: null, + createdAt: now, + updatedAt: now, + deletedAt: null, + }, + { + id: generateSeedId(workspaceId, PAGE_LAYOUT_SEEDS.CUSTOMER_DASHBOARD), + universalIdentifier: generateSeedId( + workspaceId, + PAGE_LAYOUT_SEEDS.CUSTOMER_DASHBOARD, + ), + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, + workspaceId, + name: 'Customer Dashboard Layout', + type: PageLayoutType.DASHBOARD, + objectMetadataId: null, + objectMetadataUniversalIdentifier: null, + tabIds: [], + tabUniversalIdentifiers: [], + defaultTabToFocusOnMobileAndSidePanelId: null, + defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier: null, + createdAt: now, + updatedAt: now, + deletedAt: null, + }, + { + id: generateSeedId(workspaceId, PAGE_LAYOUT_SEEDS.TEAM_DASHBOARD), + universalIdentifier: generateSeedId( + workspaceId, + PAGE_LAYOUT_SEEDS.TEAM_DASHBOARD, + ), + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, + workspaceId, + name: 'Team Dashboard Layout', + type: PageLayoutType.DASHBOARD, + objectMetadataId: null, + objectMetadataUniversalIdentifier: null, + tabIds: [], + tabUniversalIdentifiers: [], + defaultTabToFocusOnMobileAndSidePanelId: null, + defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier: null, + createdAt: now, + updatedAt: now, + deletedAt: null, + }, + { + id: generateSeedId( + workspaceId, + PAGE_LAYOUT_SEEDS.DOCUMENTATION_STANDALONE_PAGE, + ), + universalIdentifier: generateSeedId( + workspaceId, + PAGE_LAYOUT_SEEDS.DOCUMENTATION_STANDALONE_PAGE, + ), + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, + workspaceId, + name: 'Documentation', + type: PageLayoutType.STANDALONE_PAGE, + objectMetadataId: null, + objectMetadataUniversalIdentifier: null, + tabIds: [], + tabUniversalIdentifiers: [], + defaultTabToFocusOnMobileAndSidePanelId: null, + defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier: null, + createdAt: now, + updatedAt: now, + deletedAt: null, + }, + ]; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-tab-data-seeds.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-tab-data-seeds.util.ts index 8b6a9e47a1..e0e3637c61 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-tab-data-seeds.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-tab-data-seeds.util.ts @@ -1,98 +1,88 @@ -import { v4 } from 'uuid'; +import { PageLayoutTabLayoutMode } from 'twenty-shared/types'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type'; import { PAGE_LAYOUT_SEEDS } from 'src/engine/workspace-manager/dev-seeder/core/constants/page-layout-seeds.constant'; import { PAGE_LAYOUT_TAB_SEEDS } from 'src/engine/workspace-manager/dev-seeder/core/constants/page-layout-tab-seeds.constant'; import { generateSeedId } from 'src/engine/workspace-manager/dev-seeder/core/utils/generate-seed-id.util'; -type PageLayoutTabSeed = { - id: string; - title: string; - position: number; - pageLayoutId: string; - workspaceId: string; -} & Pick< - FlatPageLayoutTab, - 'applicationId' | 'universalIdentifier' | 'overrides' ->; - -export const getPageLayoutTabDataSeeds = ({ - applicationId, +export const getPageLayoutTabFlatEntitySeeds = ({ workspaceId, + flatApplication, }: { workspaceId: string; - applicationId: string; -}): PageLayoutTabSeed[] => [ - { - id: generateSeedId(workspaceId, PAGE_LAYOUT_TAB_SEEDS.SALES_OVERVIEW), - title: 'Overview', - position: 0, - pageLayoutId: generateSeedId( - workspaceId, + flatApplication: FlatApplication; +}): FlatPageLayoutTab[] => { + const now = new Date().toISOString(); + + const buildTab = ( + tabSeed: string, + title: string, + position: number, + pageLayoutSeed: string, + ): FlatPageLayoutTab => ({ + id: generateSeedId(workspaceId, tabSeed), + universalIdentifier: generateSeedId(workspaceId, tabSeed), + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, + workspaceId, + title, + position, + pageLayoutId: generateSeedId(workspaceId, pageLayoutSeed), + pageLayoutUniversalIdentifier: generateSeedId(workspaceId, pageLayoutSeed), + widgetIds: [], + widgetUniversalIdentifiers: [], + isActive: true, + icon: null, + layoutMode: PageLayoutTabLayoutMode.GRID, + overrides: null, + createdAt: now, + updatedAt: now, + deletedAt: null, + }); + + return [ + buildTab( + PAGE_LAYOUT_TAB_SEEDS.SALES_OVERVIEW, + 'Overview', + 0, PAGE_LAYOUT_SEEDS.SALES_DASHBOARD, ), - workspaceId, - universalIdentifier: v4(), - applicationId, - overrides: null, - }, - { - id: generateSeedId(workspaceId, PAGE_LAYOUT_TAB_SEEDS.SALES_DETAILS), - title: 'Details', - position: 1, - pageLayoutId: generateSeedId( - workspaceId, + buildTab( + PAGE_LAYOUT_TAB_SEEDS.SALES_DETAILS, + 'Details', + 1, PAGE_LAYOUT_SEEDS.SALES_DASHBOARD, ), - workspaceId, - universalIdentifier: v4(), - applicationId, - overrides: null, - }, - { - id: generateSeedId(workspaceId, PAGE_LAYOUT_TAB_SEEDS.CUSTOMER_OVERVIEW), - title: 'Overview', - position: 0, - pageLayoutId: generateSeedId( - workspaceId, + buildTab( + PAGE_LAYOUT_TAB_SEEDS.CUSTOMER_OVERVIEW, + 'Overview', + 0, PAGE_LAYOUT_SEEDS.CUSTOMER_DASHBOARD, ), - workspaceId, - universalIdentifier: v4(), - applicationId, - overrides: null, - }, - { - id: generateSeedId(workspaceId, PAGE_LAYOUT_TAB_SEEDS.CUSTOMER_ANALYTICS), - title: 'Analytics', - position: 1, - pageLayoutId: generateSeedId( - workspaceId, + buildTab( + PAGE_LAYOUT_TAB_SEEDS.CUSTOMER_ANALYTICS, + 'Analytics', + 1, PAGE_LAYOUT_SEEDS.CUSTOMER_DASHBOARD, ), - workspaceId, - universalIdentifier: v4(), - applicationId, - overrides: null, - }, - { - id: generateSeedId(workspaceId, PAGE_LAYOUT_TAB_SEEDS.TEAM_OVERVIEW), - title: 'Team & People', - position: 0, - pageLayoutId: generateSeedId(workspaceId, PAGE_LAYOUT_SEEDS.TEAM_DASHBOARD), - workspaceId, - universalIdentifier: v4(), - applicationId, - overrides: null, - }, - { - id: generateSeedId(workspaceId, PAGE_LAYOUT_TAB_SEEDS.TEAM_METRICS), - title: 'Tasks & Activity', - position: 1, - pageLayoutId: generateSeedId(workspaceId, PAGE_LAYOUT_SEEDS.TEAM_DASHBOARD), - workspaceId, - universalIdentifier: v4(), - applicationId, - overrides: null, - }, -]; + buildTab( + PAGE_LAYOUT_TAB_SEEDS.TEAM_OVERVIEW, + 'Team & People', + 0, + PAGE_LAYOUT_SEEDS.TEAM_DASHBOARD, + ), + buildTab( + PAGE_LAYOUT_TAB_SEEDS.TEAM_METRICS, + 'Tasks & Activity', + 1, + PAGE_LAYOUT_SEEDS.TEAM_DASHBOARD, + ), + buildTab( + PAGE_LAYOUT_TAB_SEEDS.DOCUMENTATION, + 'Documentation', + 0, + PAGE_LAYOUT_SEEDS.DOCUMENTATION_STANDALONE_PAGE, + ), + ]; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-widget-data-seeds.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-widget-data-seeds.util.ts index 59f5b86283..77a5164777 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-widget-data-seeds.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-widget-data-seeds.util.ts @@ -6,6 +6,9 @@ import { } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; +import { fromPageLayoutWidgetConfigurationToUniversalConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { AxisNameDisplay } from 'src/engine/metadata-modules/page-layout-widget/enums/axis-name-display.enum'; import { BarChartLayout } from 'src/engine/metadata-modules/page-layout-widget/enums/bar-chart-layout.enum'; @@ -18,6 +21,77 @@ import { PAGE_LAYOUT_WIDGET_SEEDS } from 'src/engine/workspace-manager/dev-seede import { type SeederFlatPageLayoutWidget } from 'src/engine/workspace-manager/dev-seeder/core/types/seeder-flat-page-layout-widget.type'; import { generateSeedId } from 'src/engine/workspace-manager/dev-seeder/core/utils/generate-seed-id.util'; import { getPageLayoutWidgetDataSeedsV2 } from 'src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-widget-data-seeds-v2.util'; +import { + getSeedFrontComponentDefinitions, + getSeedFrontComponentIds, +} from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-front-component-definitions.util'; + +export const getPageLayoutWidgetFlatEntitySeeds = ({ + workspaceId, + flatApplication, + objectMetadataItems, +}: { + workspaceId: string; + flatApplication: FlatApplication; + objectMetadataItems: ObjectMetadataEntity[]; +}): FlatPageLayoutWidget[] => { + const seeds = getPageLayoutWidgetDataSeeds(workspaceId, objectMetadataItems); + const now = new Date().toISOString(); + + const fieldMetadataUniversalIdentifierById: Record = {}; + + for (const objectMetadata of objectMetadataItems) { + if (!isDefined(objectMetadata.fields)) { + continue; + } + for (const field of objectMetadata.fields) { + fieldMetadataUniversalIdentifierById[field.id] = + field.universalIdentifier; + } + } + + const frontComponentUniversalIdentifierById: Record = {}; + + for (const definition of getSeedFrontComponentDefinitions(workspaceId)) { + frontComponentUniversalIdentifierById[definition.id] = + definition.universalIdentifier; + } + + return seeds.map((seed) => { + const objectMetadata = isDefined(seed.objectMetadataId) + ? objectMetadataItems.find( + (objectMetadataItem) => + objectMetadataItem.id === seed.objectMetadataId, + ) + : undefined; + + const universalConfiguration = + fromPageLayoutWidgetConfigurationToUniversalConfiguration({ + configuration: seed.configuration, + fieldMetadataUniversalIdentifierById, + frontComponentUniversalIdentifierById, + }); + + return { + ...seed, + universalIdentifier: seed.id, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, + workspaceId, + pageLayoutTabUniversalIdentifier: seed.pageLayoutTabId, + objectMetadataUniversalIdentifier: + objectMetadata?.universalIdentifier ?? null, + universalConfiguration, + conditionalDisplay: null, + conditionalAvailabilityExpression: null, + isActive: true, + createdAt: now, + updatedAt: now, + deletedAt: null, + universalOverrides: null, + } as FlatPageLayoutWidget; + }); +}; const getFieldId = ( object: ObjectMetadataEntity | undefined, @@ -299,13 +373,13 @@ export const getPageLayoutWidgetDataSeeds = ( ), title: 'Companies by Size (Stacked by City)', type: WidgetType.GRAPH, - gridPosition: { row: 0, column: 8, rowSpan: 10, columnSpan: 8 }, + gridPosition: { row: 0, column: 6, rowSpan: 10, columnSpan: 6 }, position: { layoutMode: PageLayoutTabLayoutMode.GRID, row: 0, - column: 8, + column: 6, rowSpan: 10, - columnSpan: 8, + columnSpan: 6, }, configuration: { configurationType: WidgetConfigurationType.BAR_CHART, @@ -516,11 +590,38 @@ export const getPageLayoutWidgetDataSeeds = ( }, configuration: { configurationType: WidgetConfigurationType.FRONT_COMPONENT, - frontComponentId: '6cdf2607-4b28-40e6-8c53-cc06799ddc88', + frontComponentId: getSeedFrontComponentIds(workspaceId).helloWorldId, } as const, objectMetadataId: null, overrides: null, } satisfies SeederFlatPageLayoutWidget, + + { + id: generateSeedId( + workspaceId, + PAGE_LAYOUT_WIDGET_SEEDS.DOCUMENTATION_IFRAME, + ), + pageLayoutTabId: generateSeedId( + workspaceId, + PAGE_LAYOUT_TAB_SEEDS.DOCUMENTATION, + ), + title: 'Twenty Star History', + type: WidgetType.IFRAME, + gridPosition: { row: 0, column: 0, rowSpan: 12, columnSpan: 12 }, + position: { + layoutMode: PageLayoutTabLayoutMode.GRID, + row: 0, + column: 0, + rowSpan: 12, + columnSpan: 12, + }, + configuration: { + configurationType: WidgetConfigurationType.IFRAME, + url: 'https://www.star-history.com/?repos=twentyhq%2Ftwenty&type=date&legend=top-left', + }, + objectMetadataId: null, + overrides: null, + } satisfies SeederFlatPageLayoutWidget, ].filter(isDefined); const v2Widgets = getPageLayoutWidgetDataSeedsV2( diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-tabs.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-tabs.util.ts deleted file mode 100644 index 600d819a96..0000000000 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-tabs.util.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { type DataSource } from 'typeorm'; - -import { getPageLayoutTabDataSeeds } from 'src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-tab-data-seeds.util'; - -export const seedPageLayoutTabs = async ({ - applicationId, - dataSource, - schemaName, - workspaceId, -}: { - dataSource: DataSource; - schemaName: string; - applicationId: string; - workspaceId: string; -}) => { - const pageLayoutTabs = getPageLayoutTabDataSeeds({ - workspaceId, - applicationId, - }); - - if (pageLayoutTabs.length > 0) { - await dataSource - .createQueryBuilder() - .insert() - .into(`${schemaName}.pageLayoutTab`, [ - 'id', - 'title', - 'position', - 'pageLayoutId', - 'workspaceId', - 'universalIdentifier', - 'applicationId', - 'overrides', - ]) - .values(pageLayoutTabs) - .orIgnore() - .execute(); - } -}; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-widgets.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-widgets.util.ts deleted file mode 100644 index ff47fb973a..0000000000 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-widgets.util.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { isDefined } from 'class-validator'; -import { type DataSource } from 'typeorm'; -import { v4 } from 'uuid'; - -import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { validateWidgetConfigurationInput } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-configuration-input.util'; -import { getPageLayoutWidgetDataSeeds } from 'src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-widget-data-seeds.util'; - -export const seedPageLayoutWidgets = async ({ - dataSource, - schemaName, - workspaceId, - objectMetadataItems, - applicationId, -}: { - dataSource: DataSource; - schemaName: string; - workspaceId: string; - objectMetadataItems: ObjectMetadataEntity[]; - applicationId: string; -}) => { - const widgetSeeds = getPageLayoutWidgetDataSeeds( - workspaceId, - objectMetadataItems, - ); - - const pageLayoutWidgets = widgetSeeds.map((widget) => { - if (isDefined(widget.configuration)) { - validateWidgetConfigurationInput({ - configuration: widget.configuration, - }); - } - - return { - ...widget, - workspaceId, - gridPosition: widget.gridPosition, - position: widget.position, - configuration: widget.configuration, - universalIdentifier: v4(), - applicationId, - overrides: widget.overrides ?? null, - }; - }); - - if (pageLayoutWidgets.length > 0) { - await dataSource - .createQueryBuilder() - .insert() - .into(`${schemaName}.pageLayoutWidget`, [ - 'id', - 'pageLayoutTabId', - 'title', - 'type', - 'gridPosition', - 'position', - 'configuration', - 'objectMetadataId', - 'workspaceId', - 'universalIdentifier', - 'applicationId', - 'overrides', - ]) - .values(pageLayoutWidgets) - .orIgnore() - .execute(); - } -}; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layouts.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layouts.util.ts index 26fd69b551..bac1eb2f43 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layouts.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layouts.util.ts @@ -1,30 +1,78 @@ -import { type DataSource } from 'typeorm'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { getNavigationMenuItemFlatEntitySeeds } from 'src/engine/workspace-manager/dev-seeder/core/utils/get-navigation-menu-item-data-seeds.util'; +import { getPageLayoutFlatEntitySeeds } from 'src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-data-seeds.util'; +import { getPageLayoutTabFlatEntitySeeds } from 'src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-tab-data-seeds.util'; +import { getPageLayoutWidgetFlatEntitySeeds } from 'src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-widget-data-seeds.util'; +import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception'; +import { type WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; -import { getPageLayoutDataSeeds } from 'src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-data-seeds.util'; +export const seedPageLayouts = async ({ + workspaceId, + flatApplication, + objectMetadataItems, + workspaceMigrationValidateBuildAndRunService, +}: { + workspaceId: string; + flatApplication: FlatApplication; + objectMetadataItems: ObjectMetadataEntity[]; + workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService; +}): Promise => { + const pageLayouts = getPageLayoutFlatEntitySeeds({ + workspaceId, + flatApplication, + }); -export const seedPageLayouts = async ( - dataSource: DataSource, - schemaName: string, - workspaceId: string, - applicationId: string, -) => { - const pageLayouts = getPageLayoutDataSeeds(workspaceId, applicationId); + const pageLayoutTabs = getPageLayoutTabFlatEntitySeeds({ + workspaceId, + flatApplication, + }); - if (pageLayouts.length > 0) { - await dataSource - .createQueryBuilder() - .insert() - .into(`${schemaName}.pageLayout`, [ - 'id', - 'name', - 'type', - 'objectMetadataId', - 'workspaceId', - 'universalIdentifier', - 'applicationId', - ]) - .values(pageLayouts) - .orIgnore() - .execute(); + const pageLayoutWidgets = getPageLayoutWidgetFlatEntitySeeds({ + workspaceId, + flatApplication, + objectMetadataItems, + }); + + const navigationMenuItems = getNavigationMenuItemFlatEntitySeeds({ + workspaceId, + flatApplication, + }); + + const result = + await workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + allFlatEntityOperationByMetadataName: { + pageLayout: { + flatEntityToCreate: pageLayouts, + flatEntityToDelete: [], + flatEntityToUpdate: [], + }, + pageLayoutTab: { + flatEntityToCreate: pageLayoutTabs, + flatEntityToDelete: [], + flatEntityToUpdate: [], + }, + pageLayoutWidget: { + flatEntityToCreate: pageLayoutWidgets, + flatEntityToDelete: [], + flatEntityToUpdate: [], + }, + navigationMenuItem: { + flatEntityToCreate: navigationMenuItems, + flatEntityToDelete: [], + flatEntityToUpdate: [], + }, + }, + workspaceId, + applicationUniversalIdentifier: flatApplication.universalIdentifier, + }, + ); + + if (result.status === 'fail') { + throw new WorkspaceMigrationBuilderException( + result, + 'Failed to seed page layouts, tabs, widgets, and navigation menu items', + ); } }; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service.ts index c7b118c40c..7a26cb80f3 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service.ts @@ -114,11 +114,7 @@ import { WORKSPACE_MEMBER_DATA_SEED_COLUMNS, } from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant'; import { TimelineActivitySeederService } from 'src/engine/workspace-manager/dev-seeder/data/services/timeline-activity-seeder.service'; -import { PrefillFrontComponentService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-front-component.service'; -import { PrefillLogicFunctionService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service'; import { prefillFrontComponentCommandMenuItems } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-front-component-command-menu-items.util'; -import { getSeedFrontComponentDefinitions } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-front-component-definitions.util'; -import { getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionDefinitions } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-code-step-logic-functions.util'; import { prefillWorkflowCommandMenuItems } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-command-menu-items.util'; import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflows.util'; import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; @@ -293,8 +289,6 @@ export class DevSeederDataService { private readonly timelineActivitySeederService: TimelineActivitySeederService, private readonly fileStorageService: FileStorageService, private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, - private readonly prefillLogicFunctionService: PrefillLogicFunctionService, - private readonly prefillFrontComponentService: PrefillFrontComponentService, private readonly applicationService: ApplicationService, private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, ) {} @@ -324,19 +318,6 @@ export class DevSeederDataService { const { seeds: attachmentSeeds, fileSeedMetadata: attachmentFileMeta } = generateAttachmentSeedsForWorkspace(workspaceId); - await this.prefillLogicFunctionService.ensureSeeded({ - workspaceId, - definitions: - getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionDefinitions( - workspaceId, - ), - }); - - await this.prefillFrontComponentService.ensureSeeded({ - workspaceId, - definitions: getSeedFrontComponentDefinitions(workspaceId), - }); - await this.coreDataSource.transaction( async (entityManager: WorkspaceEntityManager) => { await this.seedRecordsInBatches({ diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/services/dev-seeder.service.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/services/dev-seeder.service.ts index d284fe1b03..640a308c7d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/services/dev-seeder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/services/dev-seeder.service.ts @@ -1,7 +1,6 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; -import { ALL_METADATA_NAME } from 'twenty-shared/metadata'; import { DataSource, Repository } from 'typeorm'; import { v4 } from 'uuid'; @@ -12,38 +11,37 @@ import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twent import { UpgradeMigrationService } from 'src/engine/core-modules/upgrade/services/upgrade-migration.service'; import { UpgradeSequenceReaderService } from 'src/engine/core-modules/upgrade/services/upgrade-sequence-reader.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util'; -import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; +import { seedBillingCustomers } from 'src/engine/workspace-manager/dev-seeder/core/billing/utils/seed-billing-customers.util'; +import { seedBillingSubscriptions } from 'src/engine/workspace-manager/dev-seeder/core/billing/utils/seed-billing-subscriptions.util'; import { type SeededWorkspacesIds, SEEDER_CREATE_WORKSPACE_INPUT, } from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant'; -import { seedBillingCustomers } from 'src/engine/workspace-manager/dev-seeder/core/billing/utils/seed-billing-customers.util'; -import { seedBillingSubscriptions } from 'src/engine/workspace-manager/dev-seeder/core/billing/utils/seed-billing-subscriptions.util'; import { DevSeederPermissionsService } from 'src/engine/workspace-manager/dev-seeder/core/services/dev-seeder-permissions.service'; import { seedAgents } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-agents.util'; import { seedApiKeys } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-api-keys.util'; import { seedFeatureFlags } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util'; import { seedMetadataEntities } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-metadata-entities.util'; +import { seedPageLayouts } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layouts.util'; import { seedServerId } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-server-id.util'; import { seedUserWorkspaces } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-user-workspaces.util'; import { seedUsers } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-users.util'; import { createWorkspace } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspace.util'; -import { seedPageLayoutTabs } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-tabs.util'; -import { seedPageLayoutWidgets } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-widgets.util'; -import { seedPageLayouts } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layouts.util'; import { DevSeederDataService } from 'src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service'; import { DevSeederMetadataService } from 'src/engine/workspace-manager/dev-seeder/metadata/services/dev-seeder-metadata.service'; +import { PrefillFrontComponentService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-front-component.service'; +import { PrefillLogicFunctionService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service'; +import { getSeedFrontComponentDefinitions } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-front-component-definitions.util'; +import { getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionDefinitions } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-code-step-logic-functions.util'; import { TwentyStandardApplicationService } from 'src/engine/workspace-manager/twenty-standard-application/services/twenty-standard-application.service'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; @Injectable() export class DevSeederService { - private readonly logger = new Logger(DevSeederService.name); - constructor( private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, private readonly twentyConfigService: TwentyConfigService, @@ -58,6 +56,9 @@ export class DevSeederService { private readonly sdkClientGenerationService: SdkClientGenerationService, private readonly upgradeMigrationService: UpgradeMigrationService, private readonly upgradeSequenceReaderService: UpgradeSequenceReaderService, + private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, + private readonly prefillFrontComponentService: PrefillFrontComponentService, + private readonly prefillLogicFunctionService: PrefillLogicFunctionService, @InjectDataSource() private readonly coreDataSource: DataSource, @InjectRepository(WorkspaceEntity) @@ -142,19 +143,6 @@ export class DevSeederService { light, }); - await seedPageLayouts( - this.coreDataSource, - 'core', - workspaceId, - twentyStandardFlatApplication.id, - ); - await seedPageLayoutTabs({ - applicationId: twentyStandardFlatApplication.id, - workspaceId, - dataSource: this.coreDataSource, - schemaName: 'core', - }); - const objectMetadataRepository = this.coreDataSource.getRepository(ObjectMetadataEntity); const objectMetadataItems = await objectMetadataRepository.find({ @@ -162,24 +150,26 @@ export class DevSeederService { relations: { fields: true }, }); - await seedPageLayoutWidgets({ - dataSource: this.coreDataSource, - schemaName: 'core', + await this.prefillLogicFunctionService.ensureSeeded({ workspaceId, - objectMetadataItems, - applicationId: twentyStandardFlatApplication.id, + definitions: + getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionDefinitions( + workspaceId, + ), }); - const relatedPageLayoutCacheKeysToInvalidate = [ - ...getMetadataRelatedMetadataNames(ALL_METADATA_NAME.pageLayout), - ...getMetadataRelatedMetadataNames(ALL_METADATA_NAME.pageLayoutTab), - ...getMetadataRelatedMetadataNames(ALL_METADATA_NAME.pageLayoutWidget), - ].map(getMetadataFlatEntityMapsKey); - - await this.workspaceCacheService.invalidateAndRecompute( + await this.prefillFrontComponentService.ensureSeeded({ workspaceId, - relatedPageLayoutCacheKeysToInvalidate, - ); + definitions: getSeedFrontComponentDefinitions(workspaceId), + }); + + await seedPageLayouts({ + workspaceId, + flatApplication: twentyStandardFlatApplication, + objectMetadataItems, + workspaceMigrationValidateBuildAndRunService: + this.workspaceMigrationValidateBuildAndRunService, + }); await this.devSeederDataService.seed({ schemaName, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts index 00dea0788c..01e2de2710 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts @@ -42,7 +42,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = { isPinned: true, position: 3, shortLabel: 'New ${capitalize(objectMetadataItem.labelSingular)}', - availabilityType: CommandMenuItemAvailabilityType.GLOBAL, + availabilityType: CommandMenuItemAvailabilityType.GLOBAL_OBJECT_CONTEXT, conditionalAvailabilityExpression: 'pageType == "INDEX_PAGE" and objectPermissions.canUpdateObjectRecords and not hasAnySoftDeleteFilterOnView', availabilityObjectMetadataUniversalIdentifier: null, @@ -191,7 +191,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = { isPinned: false, position: 13, shortLabel: 'Import', - availabilityType: CommandMenuItemAvailabilityType.GLOBAL, + availabilityType: CommandMenuItemAvailabilityType.GLOBAL_OBJECT_CONTEXT, conditionalAvailabilityExpression: 'not hasAnySoftDeleteFilterOnView', availabilityObjectMetadataUniversalIdentifier: null, frontComponentUniversalIdentifier: null, @@ -205,7 +205,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = { isPinned: false, position: 14, shortLabel: 'Export', - availabilityType: CommandMenuItemAvailabilityType.GLOBAL, + availabilityType: CommandMenuItemAvailabilityType.GLOBAL_OBJECT_CONTEXT, conditionalAvailabilityExpression: null, availabilityObjectMetadataUniversalIdentifier: null, frontComponentUniversalIdentifier: null, @@ -219,7 +219,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = { isPinned: false, position: 15, shortLabel: 'Deleted ${capitalize(objectMetadataItem.labelPlural)}', - availabilityType: CommandMenuItemAvailabilityType.GLOBAL, + availabilityType: CommandMenuItemAvailabilityType.GLOBAL_OBJECT_CONTEXT, conditionalAvailabilityExpression: 'not hasAnySoftDeleteFilterOnView', availabilityObjectMetadataUniversalIdentifier: null, frontComponentUniversalIdentifier: null, @@ -233,7 +233,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = { isPinned: false, position: 16, shortLabel: 'Create View', - availabilityType: CommandMenuItemAvailabilityType.GLOBAL, + availabilityType: CommandMenuItemAvailabilityType.GLOBAL_OBJECT_CONTEXT, conditionalAvailabilityExpression: 'not hasAnySoftDeleteFilterOnView', availabilityObjectMetadataUniversalIdentifier: null, frontComponentUniversalIdentifier: null, @@ -247,7 +247,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = { isPinned: false, position: 17, shortLabel: 'Hide deleted', - availabilityType: CommandMenuItemAvailabilityType.GLOBAL, + availabilityType: CommandMenuItemAvailabilityType.GLOBAL_OBJECT_CONTEXT, conditionalAvailabilityExpression: 'hasAnySoftDeleteFilterOnView', availabilityObjectMetadataUniversalIdentifier: null, frontComponentUniversalIdentifier: null, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/navigation-menu-item/create-standard-navigation-menu-item-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/navigation-menu-item/create-standard-navigation-menu-item-flat-metadata.util.ts index b9526d6180..7fa027c171 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/navigation-menu-item/create-standard-navigation-menu-item-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/navigation-menu-item/create-standard-navigation-menu-item-flat-metadata.util.ts @@ -73,6 +73,8 @@ export const createStandardNavigationMenuItemFlatMetadata = ({ viewUniversalIdentifier: isObjectType ? null : flatView.universalIdentifier, folderId: null, folderUniversalIdentifier: null, + pageLayoutId: null, + pageLayoutUniversalIdentifier: null, name: null, link: null, icon: null, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/navigation-menu-item/create-standard-navigation-menu-item-folder-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/navigation-menu-item/create-standard-navigation-menu-item-folder-flat-metadata.util.ts index ec973da840..2e2759e821 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/navigation-menu-item/create-standard-navigation-menu-item-folder-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/navigation-menu-item/create-standard-navigation-menu-item-folder-flat-metadata.util.ts @@ -39,6 +39,8 @@ export const createStandardNavigationMenuItemFolderFlatMetadata = ({ viewUniversalIdentifier: null, folderId: null, folderUniversalIdentifier: null, + pageLayoutId: null, + pageLayoutUniversalIdentifier: null, name, link: null, icon: icon ?? null, @@ -101,6 +103,8 @@ export const createStandardNavigationMenuItemFolderItemFlatMetadata = ({ viewUniversalIdentifier: null, folderId, folderUniversalIdentifier, + pageLayoutId: null, + pageLayoutUniversalIdentifier: null, name: null, link: null, icon: null, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts index 579cabc4ca..fbeba76af4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts @@ -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, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-navigation-menu-item-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-navigation-menu-item-validator.service.ts index 56717965d5..5972799eae 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-navigation-menu-item-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-navigation-menu-item-validator.service.ts @@ -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[] { const errors: FlatEntityValidationError[] = @@ -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, }); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/navigation-menu-item/services/create-navigation-menu-item-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/navigation-menu-item/services/create-navigation-menu-item-action-handler.service.ts index cc2563c7e7..5e0476d936 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/navigation-menu-item/services/create-navigation-menu-item-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/navigation-menu-item/services/create-navigation-menu-item-action-handler.service.ts @@ -30,7 +30,7 @@ export class CreateNavigationMenuItemActionHandlerService extends WorkspaceMigra flatApplication, workspaceId, }: WorkspaceMigrationActionRunnerArgs): Promise { - 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, diff --git a/packages/twenty-shared/src/application/frontComponentManifestType.ts b/packages/twenty-shared/src/application/frontComponentManifestType.ts index 6cb75ca357..a92a0d4311 100644 --- a/packages/twenty-shared/src/application/frontComponentManifestType.ts +++ b/packages/twenty-shared/src/application/frontComponentManifestType.ts @@ -5,7 +5,11 @@ export type CommandMenuItemManifest = SyncableEntityOptions & { shortLabel?: string; icon?: string; isPinned?: boolean; - availabilityType?: 'GLOBAL' | 'RECORD_SELECTION' | 'FALLBACK'; + availabilityType?: + | 'GLOBAL' + | 'GLOBAL_OBJECT_CONTEXT' + | 'RECORD_SELECTION' + | 'FALLBACK'; availabilityObjectUniversalIdentifier?: string; frontComponentUniversalIdentifier: string; conditionalAvailabilityExpression?: string; diff --git a/packages/twenty-shared/src/application/navigationMenuItemManifestType.ts b/packages/twenty-shared/src/application/navigationMenuItemManifestType.ts index e5c66025a5..b89b8e0592 100644 --- a/packages/twenty-shared/src/application/navigationMenuItemManifestType.ts +++ b/packages/twenty-shared/src/application/navigationMenuItemManifestType.ts @@ -11,4 +11,5 @@ export type NavigationMenuItemManifest = SyncableEntityOptions & { link?: string; folderUniversalIdentifier?: string; targetObjectUniversalIdentifier?: string; + pageLayoutUniversalIdentifier?: string; }; diff --git a/packages/twenty-shared/src/types/AppPath.ts b/packages/twenty-shared/src/types/AppPath.ts index 09cad691b2..83366d2e12 100644 --- a/packages/twenty-shared/src/types/AppPath.ts +++ b/packages/twenty-shared/src/types/AppPath.ts @@ -23,6 +23,7 @@ export enum AppPath { RecordIndexPage = '/objects/:objectNamePlural', RecordShowPage = '/object/:objectNameSingular/:objectRecordId', + PageLayoutPage = '/page/:pageLayoutId', Settings = `settings`, SettingsCatchAll = `/${Settings}/*`, diff --git a/packages/twenty-shared/src/types/CommandMenuContextApi.ts b/packages/twenty-shared/src/types/CommandMenuContextApi.ts index d4b2ad6454..6c4d37bf43 100644 --- a/packages/twenty-shared/src/types/CommandMenuContextApi.ts +++ b/packages/twenty-shared/src/types/CommandMenuContextApi.ts @@ -1,9 +1,9 @@ -import { type CommandMenuContextApiPageType } from './CommandMenuContextApiPageType'; +import { type ContextStorePageType } from './ContextStorePageType'; import { type ObjectPermissions } from './ObjectPermissions'; import { type ObjectRecord } from './ObjectRecord'; export type CommandMenuContextApi = { - pageType: CommandMenuContextApiPageType; + pageType: ContextStorePageType; isInSidePanel: boolean; isPageInEditMode: boolean; favoriteRecordIds: string[]; diff --git a/packages/twenty-shared/src/types/CommandMenuContextApiPageType.ts b/packages/twenty-shared/src/types/CommandMenuContextApiPageType.ts deleted file mode 100644 index 98ba90aad6..0000000000 --- a/packages/twenty-shared/src/types/CommandMenuContextApiPageType.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum CommandMenuContextApiPageType { - INDEX_PAGE = 'INDEX_PAGE', - RECORD_PAGE = 'RECORD_PAGE', -} diff --git a/packages/twenty-shared/src/types/ContextStorePageType.ts b/packages/twenty-shared/src/types/ContextStorePageType.ts new file mode 100644 index 0000000000..41b52a61ed --- /dev/null +++ b/packages/twenty-shared/src/types/ContextStorePageType.ts @@ -0,0 +1,6 @@ +export enum ContextStorePageType { + Index = 'INDEX_PAGE', + Record = 'RECORD_PAGE', + Standalone = 'STANDALONE_PAGE', + Settings = 'SETTINGS_PAGE', +} diff --git a/packages/twenty-shared/src/types/NavigationMenuItemType.ts b/packages/twenty-shared/src/types/NavigationMenuItemType.ts index b3d18a3718..e4d82222fd 100644 --- a/packages/twenty-shared/src/types/NavigationMenuItemType.ts +++ b/packages/twenty-shared/src/types/NavigationMenuItemType.ts @@ -4,4 +4,5 @@ export enum NavigationMenuItemType { LINK = 'LINK', OBJECT = 'OBJECT', RECORD = 'RECORD', + PAGE_LAYOUT = 'PAGE_LAYOUT', } diff --git a/packages/twenty-shared/src/types/index.ts b/packages/twenty-shared/src/types/index.ts index 6a16064738..fdec892fed 100644 --- a/packages/twenty-shared/src/types/index.ts +++ b/packages/twenty-shared/src/types/index.ts @@ -19,7 +19,6 @@ export { CalendarChannelSyncStage } from './CalendarChannelSyncStage'; export { CalendarChannelSyncStatus } from './CalendarChannelSyncStatus'; export { CalendarChannelVisibility } from './CalendarChannelVisibility'; export type { CommandMenuContextApi } from './CommandMenuContextApi'; -export { CommandMenuContextApiPageType } from './CommandMenuContextApiPageType'; export { CommandMenuItemViewType } from './CommandMenuItemViewType'; export type { ActorMetadata } from './composite-types/actor.composite-type'; export { @@ -59,6 +58,7 @@ export type { CompositeFieldSubFieldName } from './CompositeFieldSubFieldNameTyp export type { ConfigVariableValue } from './ConfigVariableValue'; export type { ConfirmationModalCaller } from './ConfirmationModalCaller'; export { ConnectedAccountProvider } from './ConnectedAccountProvider'; +export { ContextStorePageType } from './ContextStorePageType'; export { CoreObjectNameSingular } from './CoreObjectNameSingular'; export { CrudOperationType } from './CrudOperationType'; export type { EmailAttachment } from './EmailAttachment'; diff --git a/packages/twenty-shared/src/utils/command-menu-items/__tests__/evaluateConditionalAvailabilityExpression.test.ts b/packages/twenty-shared/src/utils/command-menu-items/__tests__/evaluateConditionalAvailabilityExpression.test.ts index a34b6a556f..cdc25fa591 100644 --- a/packages/twenty-shared/src/utils/command-menu-items/__tests__/evaluateConditionalAvailabilityExpression.test.ts +++ b/packages/twenty-shared/src/utils/command-menu-items/__tests__/evaluateConditionalAvailabilityExpression.test.ts @@ -1,13 +1,10 @@ -import { - CommandMenuContextApiPageType, - type CommandMenuContextApi, -} from '@/types'; +import { ContextStorePageType, type CommandMenuContextApi } from '@/types'; import { evaluateConditionalAvailabilityExpression } from '../evaluateConditionalAvailabilityExpression'; const buildContext = ( overrides: Partial = {}, ): CommandMenuContextApi => ({ - pageType: CommandMenuContextApiPageType.INDEX_PAGE, + pageType: ContextStorePageType.Index, isInSidePanel: false, isPageInEditMode: false, favoriteRecordIds: [], diff --git a/packages/twenty-shared/src/utils/command-menu-items/__tests__/interpolateCommandMenuItemTemplate.test.ts b/packages/twenty-shared/src/utils/command-menu-items/__tests__/interpolateCommandMenuItemTemplate.test.ts index 161e829b86..77be0e952c 100644 --- a/packages/twenty-shared/src/utils/command-menu-items/__tests__/interpolateCommandMenuItemTemplate.test.ts +++ b/packages/twenty-shared/src/utils/command-menu-items/__tests__/interpolateCommandMenuItemTemplate.test.ts @@ -1,13 +1,10 @@ -import { - CommandMenuContextApiPageType, - type CommandMenuContextApi, -} from '@/types'; +import { ContextStorePageType, type CommandMenuContextApi } from '@/types'; import { interpolateCommandMenuItemTemplate } from '../interpolateCommandMenuItemTemplate'; const buildContext = ( overrides: Partial = {}, ): CommandMenuContextApi => ({ - pageType: CommandMenuContextApiPageType.INDEX_PAGE, + pageType: ContextStorePageType.Index, isInSidePanel: false, isPageInEditMode: false, favoriteRecordIds: [],