Separate code pathways for IS_COMMAND_MENU_ITEM_ENABLED flag (#18542)
This commit is contained in:
+61
@@ -0,0 +1,61 @@
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { useCommandMenuContextApi } from '@/command-menu-item/server-items/hooks/useCommandMenuContextApi';
|
||||
import { type CommandMenuContextType } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
|
||||
import { CommandMenuContextProviderServerItemsContent } from './CommandMenuContextProviderServerItemsContent';
|
||||
import { CommandMenuContextProviderServerItemsWithWorkflowEnrichment } from './CommandMenuContextProviderServerItemsWithWorkflowEnrichment';
|
||||
|
||||
type CommandMenuContextProviderServerItemsProps = {
|
||||
isInSidePanel: CommandMenuContextType['isInSidePanel'];
|
||||
displayType: CommandMenuContextType['displayType'];
|
||||
containerType: CommandMenuContextType['containerType'];
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const CommandMenuContextProviderServerItems = ({
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
children,
|
||||
}: CommandMenuContextProviderServerItemsProps) => {
|
||||
const commandMenuContextApi = useCommandMenuContextApi();
|
||||
|
||||
const currentObjectNameSingular =
|
||||
commandMenuContextApi.objectMetadataItem.nameSingular;
|
||||
|
||||
const isWorkflow =
|
||||
currentObjectNameSingular === CoreObjectNameSingular.Workflow;
|
||||
|
||||
const selectedWorkflowRecordIds = isWorkflow
|
||||
? commandMenuContextApi.selectedRecords
|
||||
.map((record) => record.id)
|
||||
.filter(isDefined)
|
||||
: [];
|
||||
|
||||
if (selectedWorkflowRecordIds.length > 0) {
|
||||
return (
|
||||
<CommandMenuContextProviderServerItemsWithWorkflowEnrichment
|
||||
isInSidePanel={isInSidePanel}
|
||||
displayType={displayType}
|
||||
containerType={containerType}
|
||||
commandMenuContextApi={commandMenuContextApi}
|
||||
selectedWorkflowRecordIds={selectedWorkflowRecordIds}
|
||||
>
|
||||
{children}
|
||||
</CommandMenuContextProviderServerItemsWithWorkflowEnrichment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandMenuContextProviderServerItemsContent
|
||||
isInSidePanel={isInSidePanel}
|
||||
displayType={displayType}
|
||||
containerType={containerType}
|
||||
commandMenuContextApi={commandMenuContextApi}
|
||||
>
|
||||
{children}
|
||||
</CommandMenuContextProviderServerItemsContent>
|
||||
);
|
||||
};
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
CommandMenuContext,
|
||||
type CommandMenuContextType,
|
||||
} from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { useCommandMenuItemFrontComponentCommands } from '@/command-menu-item/server-items/hooks/useCommandMenuItemFrontComponentCommands';
|
||||
import { type CommandMenuContextApi } from 'twenty-shared/types';
|
||||
|
||||
type CommandMenuContextProviderServerItemsContentProps = {
|
||||
isInSidePanel: CommandMenuContextType['isInSidePanel'];
|
||||
displayType: CommandMenuContextType['displayType'];
|
||||
containerType: CommandMenuContextType['containerType'];
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const CommandMenuContextProviderServerItemsContent = ({
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
children,
|
||||
commandMenuContextApi,
|
||||
}: CommandMenuContextProviderServerItemsContentProps & {
|
||||
commandMenuContextApi: CommandMenuContextApi;
|
||||
}) => {
|
||||
const commandMenuItemFrontComponentActions =
|
||||
useCommandMenuItemFrontComponentCommands(commandMenuContextApi);
|
||||
|
||||
return (
|
||||
<CommandMenuContext.Provider
|
||||
value={{
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
commandMenuItems: commandMenuItemFrontComponentActions,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</CommandMenuContext.Provider>
|
||||
);
|
||||
};
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import { type CommandMenuContextApi } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type CommandMenuContextType } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { useWorkflowsWithCurrentVersions } from '@/command-menu-item/server-items/hooks/useWorkflowsWithCurrentVersions';
|
||||
|
||||
import { CommandMenuContextProviderServerItemsContent } from './CommandMenuContextProviderServerItemsContent';
|
||||
|
||||
type CommandMenuContextProviderServerItemsWithWorkflowEnrichmentProps = {
|
||||
isInSidePanel: CommandMenuContextType['isInSidePanel'];
|
||||
displayType: CommandMenuContextType['displayType'];
|
||||
containerType: CommandMenuContextType['containerType'];
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const CommandMenuContextProviderServerItemsWithWorkflowEnrichment = ({
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
children,
|
||||
commandMenuContextApi,
|
||||
selectedWorkflowRecordIds,
|
||||
}: CommandMenuContextProviderServerItemsWithWorkflowEnrichmentProps & {
|
||||
commandMenuContextApi: CommandMenuContextApi;
|
||||
selectedWorkflowRecordIds: string[];
|
||||
}) => {
|
||||
const workflowsWithCurrentVersions = useWorkflowsWithCurrentVersions(
|
||||
selectedWorkflowRecordIds,
|
||||
);
|
||||
|
||||
const enrichedSelectedRecords = commandMenuContextApi.selectedRecords.map(
|
||||
(record) => {
|
||||
const workflowWithCurrentVersion = workflowsWithCurrentVersions.find(
|
||||
(workflow) => workflow.id === record.id,
|
||||
);
|
||||
|
||||
if (!isDefined(workflowWithCurrentVersion)) {
|
||||
return record;
|
||||
}
|
||||
|
||||
return {
|
||||
...record,
|
||||
currentVersion: workflowWithCurrentVersion.currentVersion,
|
||||
versions: workflowWithCurrentVersion.versions,
|
||||
statuses: workflowWithCurrentVersion.statuses,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const enrichedCommandMenuContextApi = {
|
||||
...commandMenuContextApi,
|
||||
selectedRecords: enrichedSelectedRecords,
|
||||
};
|
||||
|
||||
return (
|
||||
<CommandMenuContextProviderServerItemsContent
|
||||
isInSidePanel={isInSidePanel}
|
||||
displayType={displayType}
|
||||
containerType={containerType}
|
||||
commandMenuContextApi={enrichedCommandMenuContextApi}
|
||||
>
|
||||
{children}
|
||||
</CommandMenuContextProviderServerItemsContent>
|
||||
);
|
||||
};
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { objectPermissionsFamilySelector } from '@/auth/states/objectPermissionsFamilySelector';
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
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 { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { usePrefetchedNavigationMenuItemsData } from '@/navigation-menu-item/hooks/usePrefetchedNavigationMenuItemsData';
|
||||
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 { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { useStore } from 'jotai';
|
||||
import { useContext } from 'react';
|
||||
import {
|
||||
CommandMenuContextApiPageType,
|
||||
type CommandMenuContextApi,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useCommandMenuContextApi = (): CommandMenuContextApi => {
|
||||
const store = useStore();
|
||||
|
||||
const { isInSidePanel } = useContext(CommandMenuContext);
|
||||
|
||||
const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
);
|
||||
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED,
|
||||
);
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === contextStoreCurrentObjectMetadataItemId,
|
||||
);
|
||||
|
||||
const { sortedFavorites: favorites } = useFavorites();
|
||||
const { navigationMenuItems } = usePrefetchedNavigationMenuItemsData();
|
||||
|
||||
const recordIds =
|
||||
contextStoreTargetedRecordsRule.mode === 'selection'
|
||||
? contextStoreTargetedRecordsRule.selectedRecordIds
|
||||
: undefined;
|
||||
|
||||
const favoriteRecordIds = (() => {
|
||||
if (!isNonEmptyArray(recordIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (isNavigationMenuItemEditingEnabled && isDefined(objectMetadataItem)) {
|
||||
return recordIds.filter((recordId) =>
|
||||
navigationMenuItems?.some(
|
||||
(item) =>
|
||||
item.targetRecordId === recordId &&
|
||||
item.targetObjectMetadataId === objectMetadataItem.id,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return recordIds.filter((recordId) =>
|
||||
favorites?.some((favorite) => favorite.recordId === recordId),
|
||||
);
|
||||
})();
|
||||
|
||||
const selectedRecords = useAtomFamilySelectorValue(
|
||||
recordStoreRecordsSelector,
|
||||
{ recordIds: recordIds ?? [] },
|
||||
);
|
||||
|
||||
const objectPermissionsFromHook = useObjectPermissionsForObject(
|
||||
objectMetadataItem?.id ?? '',
|
||||
);
|
||||
const objectPermissions = isDefined(objectMetadataItem)
|
||||
? objectPermissionsFromHook
|
||||
: {
|
||||
canReadObjectRecords: false,
|
||||
canUpdateObjectRecords: false,
|
||||
canSoftDeleteObjectRecords: false,
|
||||
canDestroyObjectRecords: false,
|
||||
restrictedFields: {},
|
||||
objectMetadataId: '',
|
||||
rowLevelPermissionPredicates: [],
|
||||
rowLevelPermissionPredicateGroups: [],
|
||||
};
|
||||
|
||||
const { recordIndexId } = useRecordIndexIdFromCurrentContextStore();
|
||||
|
||||
const hasAnySoftDeleteFilterOnView = useAtomComponentSelectorValue(
|
||||
hasAnySoftDeleteFilterOnViewComponentSelector,
|
||||
recordIndexId,
|
||||
);
|
||||
|
||||
const contextStoreCurrentViewType = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewTypeComponentState,
|
||||
);
|
||||
|
||||
const contextStoreIsPageInEditMode = useAtomComponentStateValue(
|
||||
contextStoreIsPageInEditModeComponentState,
|
||||
);
|
||||
|
||||
const pageType =
|
||||
contextStoreCurrentViewType === ContextStoreViewType.ShowPage
|
||||
? CommandMenuContextApiPageType.RECORD_PAGE
|
||||
: CommandMenuContextApiPageType.INDEX_PAGE;
|
||||
|
||||
const contextStoreNumberOfSelectedRecords = useAtomComponentStateValue(
|
||||
contextStoreNumberOfSelectedRecordsComponentState,
|
||||
);
|
||||
|
||||
const isSelectAll = contextStoreTargetedRecordsRule.mode === 'exclusion';
|
||||
|
||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||
|
||||
const featureFlags: Record<string, boolean> = {};
|
||||
|
||||
for (const flag of currentWorkspace?.featureFlags ?? []) {
|
||||
featureFlags[flag.key] = flag.value === true;
|
||||
}
|
||||
|
||||
const targetObjectReadPermissions: Record<string, boolean> = {};
|
||||
const targetObjectWritePermissions: Record<string, boolean> = {};
|
||||
|
||||
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,
|
||||
isInSidePanel,
|
||||
isPageInEditMode: contextStoreIsPageInEditMode,
|
||||
favoriteRecordIds,
|
||||
isSelectAll,
|
||||
hasAnySoftDeleteFilterOnView,
|
||||
numberOfSelectedRecords: contextStoreNumberOfSelectedRecords,
|
||||
objectPermissions,
|
||||
selectedRecords,
|
||||
featureFlags,
|
||||
targetObjectReadPermissions,
|
||||
targetObjectWritePermissions,
|
||||
objectMetadataItem: objectMetadataItem ?? {},
|
||||
};
|
||||
};
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { HeadlessFrontComponentCommandMenuItem } from '@/command-menu-item/display/components/HeadlessFrontComponentCommandMenuItem';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useMountHeadlessFrontComponent } from '@/front-components/hooks/useMountHeadlessFrontComponent';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useOpenFrontComponentInSidePanel } from '@/side-panel/hooks/useOpenFrontComponentInSidePanel';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { type CommandMenuContextApi } from 'twenty-shared/types';
|
||||
import {
|
||||
evaluateConditionalAvailabilityExpression,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
import { type IconComponent, useIcons } from 'twenty-ui/display';
|
||||
|
||||
import { type HeadlessFrontComponentMountContext } from '@/front-components/states/mountedHeadlessFrontComponentMapsState';
|
||||
import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
|
||||
import {
|
||||
CommandMenuItemAvailabilityType,
|
||||
type CommandMenuItemFieldsFragment,
|
||||
useFindManyCommandMenuItemsQuery,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
type CommandMenuItemWithFrontComponent = CommandMenuItemFieldsFragment & {
|
||||
frontComponentId: string;
|
||||
conditionalAvailabilityExpression?: string | null;
|
||||
};
|
||||
|
||||
type BuildCommandMenuItemFromFrontComponentParams = {
|
||||
item: CommandMenuItemWithFrontComponent;
|
||||
type?: CommandMenuItemType;
|
||||
scope: CommandMenuItemScope;
|
||||
isPinned: boolean;
|
||||
getIcon: ReturnType<typeof useIcons>['getIcon'];
|
||||
openFrontComponentInSidePanel: (params: {
|
||||
frontComponentId: string;
|
||||
pageTitle: string;
|
||||
pageIcon: IconComponent;
|
||||
recordContext?: {
|
||||
recordId: string;
|
||||
objectNameSingular: string;
|
||||
};
|
||||
}) => void;
|
||||
mountHeadlessFrontComponent: (
|
||||
frontComponentId: string,
|
||||
context?: HeadlessFrontComponentMountContext,
|
||||
) => void;
|
||||
mountContext?: HeadlessFrontComponentMountContext;
|
||||
commandMenuContextApi: CommandMenuContextApi;
|
||||
};
|
||||
|
||||
// TODO: we should remove this backward compatibility logic in the future
|
||||
// once we have migrated all command menu items
|
||||
const buildCommandMenuItemFromFrontComponent = ({
|
||||
item,
|
||||
type = CommandMenuItemType.FrontComponent,
|
||||
scope,
|
||||
isPinned,
|
||||
getIcon,
|
||||
openFrontComponentInSidePanel,
|
||||
mountHeadlessFrontComponent,
|
||||
mountContext,
|
||||
commandMenuContextApi,
|
||||
}: BuildCommandMenuItemFromFrontComponentParams) => {
|
||||
const displayLabel = item.label;
|
||||
|
||||
const Icon = getIcon(item.icon, COMMAND_MENU_DEFAULT_ICON);
|
||||
|
||||
const isHeadless = item.frontComponent?.isHeadless === true;
|
||||
|
||||
const handleClick = () => {
|
||||
if (isHeadless) {
|
||||
mountHeadlessFrontComponent(item.frontComponentId, mountContext);
|
||||
} else {
|
||||
openFrontComponentInSidePanel({
|
||||
frontComponentId: item.frontComponentId,
|
||||
pageTitle: displayLabel,
|
||||
pageIcon: Icon,
|
||||
recordContext: isDefined(mountContext)
|
||||
? {
|
||||
recordId: mountContext.recordId,
|
||||
objectNameSingular: mountContext.objectNameSingular,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
type,
|
||||
key: `command-menu-item-front-component-${item.id}`,
|
||||
scope,
|
||||
label: displayLabel,
|
||||
shortLabel: item.shortLabel ?? undefined,
|
||||
position: item.position,
|
||||
isPinned,
|
||||
Icon,
|
||||
shouldBeRegistered: () =>
|
||||
evaluateConditionalAvailabilityExpression(
|
||||
item.conditionalAvailabilityExpression,
|
||||
commandMenuContextApi,
|
||||
),
|
||||
component: isHeadless ? (
|
||||
<HeadlessFrontComponentCommandMenuItem
|
||||
frontComponentId={item.frontComponentId}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
) : (
|
||||
<Command onClick={handleClick} />
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export const useCommandMenuItemFrontComponentCommands = (
|
||||
commandMenuContextApi: CommandMenuContextApi,
|
||||
) => {
|
||||
const { getIcon } = useIcons();
|
||||
const { openFrontComponentInSidePanel } = useOpenFrontComponentInSidePanel();
|
||||
const mountHeadlessFrontComponent = useMountHeadlessFrontComponent();
|
||||
|
||||
const contextStoreIsPageInEditMode = useAtomComponentStateValue(
|
||||
contextStoreIsPageInEditModeComponentState,
|
||||
);
|
||||
|
||||
const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
);
|
||||
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const objectMetadataItems = useAtomStateValue(objectMetadataItemsState);
|
||||
|
||||
const currentObjectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === contextStoreCurrentObjectMetadataItemId,
|
||||
);
|
||||
|
||||
const selectedRecordIds =
|
||||
contextStoreTargetedRecordsRule.mode === 'selection'
|
||||
? contextStoreTargetedRecordsRule.selectedRecordIds
|
||||
: [];
|
||||
|
||||
const hasRecordSelection =
|
||||
selectedRecordIds.length >= 1 ||
|
||||
contextStoreTargetedRecordsRule.mode === 'exclusion';
|
||||
|
||||
const mountContext: HeadlessFrontComponentMountContext | undefined =
|
||||
selectedRecordIds.length === 1 && isDefined(currentObjectMetadataItem)
|
||||
? {
|
||||
recordId: selectedRecordIds[0],
|
||||
objectNameSingular: currentObjectMetadataItem.nameSingular,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const { data } = useFindManyCommandMenuItemsQuery();
|
||||
|
||||
const frontComponentItems =
|
||||
data?.commandMenuItems?.filter(
|
||||
(item): item is CommandMenuItemWithFrontComponent =>
|
||||
isDefined(item.frontComponentId),
|
||||
) ?? [];
|
||||
|
||||
const objectMatches = (item: CommandMenuItemWithFrontComponent) =>
|
||||
!isDefined(item.availabilityObjectMetadataId) ||
|
||||
item.availabilityObjectMetadataId ===
|
||||
contextStoreCurrentObjectMetadataItemId;
|
||||
|
||||
const frontComponentItemsWithObjectMatches =
|
||||
frontComponentItems.filter(objectMatches);
|
||||
|
||||
const globalItems = frontComponentItemsWithObjectMatches.filter(
|
||||
(item) => item.availabilityType === CommandMenuItemAvailabilityType.GLOBAL,
|
||||
);
|
||||
|
||||
const recordScopedItems = frontComponentItemsWithObjectMatches.filter(
|
||||
(item) =>
|
||||
item.availabilityType ===
|
||||
CommandMenuItemAvailabilityType.RECORD_SELECTION,
|
||||
);
|
||||
|
||||
const fallbackItems = frontComponentItemsWithObjectMatches.filter(
|
||||
(item) =>
|
||||
item.availabilityType === CommandMenuItemAvailabilityType.FALLBACK,
|
||||
);
|
||||
|
||||
const globalCommandMenuItems = globalItems.map((item) =>
|
||||
buildCommandMenuItemFromFrontComponent({
|
||||
item,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
isPinned: !contextStoreIsPageInEditMode && item.isPinned,
|
||||
getIcon,
|
||||
openFrontComponentInSidePanel,
|
||||
mountHeadlessFrontComponent,
|
||||
commandMenuContextApi,
|
||||
}),
|
||||
);
|
||||
|
||||
const recordScopedCommandMenuItems = hasRecordSelection
|
||||
? recordScopedItems.map((item) =>
|
||||
buildCommandMenuItemFromFrontComponent({
|
||||
item,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
isPinned: !contextStoreIsPageInEditMode && item.isPinned,
|
||||
getIcon,
|
||||
openFrontComponentInSidePanel,
|
||||
mountHeadlessFrontComponent,
|
||||
commandMenuContextApi,
|
||||
mountContext,
|
||||
}),
|
||||
)
|
||||
: [];
|
||||
|
||||
const fallbackCommandMenuItems = fallbackItems.map((item) =>
|
||||
buildCommandMenuItemFromFrontComponent({
|
||||
item,
|
||||
type: CommandMenuItemType.Fallback,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
isPinned: false,
|
||||
getIcon,
|
||||
openFrontComponentInSidePanel,
|
||||
mountHeadlessFrontComponent,
|
||||
commandMenuContextApi,
|
||||
}),
|
||||
);
|
||||
|
||||
return [
|
||||
...globalCommandMenuItems,
|
||||
...recordScopedCommandMenuItems,
|
||||
...fallbackCommandMenuItems,
|
||||
]
|
||||
.filter((item) => item.shouldBeRegistered())
|
||||
.sort((a, b) => a.position - b.position);
|
||||
};
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import {
|
||||
type Workflow,
|
||||
type WorkflowVersion,
|
||||
type WorkflowWithCurrentVersion,
|
||||
} from '@/workflow/types/Workflow';
|
||||
|
||||
const getCurrentVersionId = (workflow: Workflow): string | undefined => {
|
||||
const draftVersion = workflow.versions.find(
|
||||
(version) => version.status === 'DRAFT',
|
||||
);
|
||||
|
||||
const sortedVersions = workflow.versions.toSorted((a, b) =>
|
||||
a.createdAt > b.createdAt ? -1 : 1,
|
||||
);
|
||||
|
||||
const latestVersion = sortedVersions[0];
|
||||
|
||||
return (draftVersion ?? latestVersion)?.id;
|
||||
};
|
||||
|
||||
export const useWorkflowsWithCurrentVersions = (
|
||||
workflowIds: string[],
|
||||
): WorkflowWithCurrentVersion[] => {
|
||||
const { records: workflows } = useFindManyRecords<Workflow>({
|
||||
objectNameSingular: CoreObjectNameSingular.Workflow,
|
||||
filter: { id: { in: workflowIds } },
|
||||
recordGqlFields: {
|
||||
id: true,
|
||||
name: true,
|
||||
statuses: true,
|
||||
lastPublishedVersionId: true,
|
||||
versions: {
|
||||
id: true,
|
||||
status: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
},
|
||||
},
|
||||
skip: workflowIds.length === 0,
|
||||
});
|
||||
|
||||
const currentVersionIds = workflows
|
||||
.map(getCurrentVersionId)
|
||||
.filter(isDefined);
|
||||
|
||||
const { records: currentVersions } = useFindManyRecords<WorkflowVersion>({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
|
||||
filter: { id: { in: currentVersionIds } },
|
||||
skip: currentVersionIds.length === 0,
|
||||
});
|
||||
|
||||
return workflows
|
||||
.map((workflow) => {
|
||||
const currentVersionId = getCurrentVersionId(workflow);
|
||||
const currentVersion = currentVersions.find(
|
||||
(version) => version.id === currentVersionId,
|
||||
);
|
||||
|
||||
if (!isDefined(currentVersion)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
...workflow,
|
||||
currentVersion,
|
||||
};
|
||||
})
|
||||
.filter(isDefined);
|
||||
};
|
||||
Reference in New Issue
Block a user