Add standard command menu items (#18527)
## Add standard command menu items
### Summary
This PR introduces standard command menu items, migrating hardcoded
command menu actions to the backend command menu item architecture
powered by front components. It adds a new `twenty-standard-application`
package that defines, builds, and registers front components as standard
command menu items, gated behind the `IS_COMMAND_MENU_ITEM_ENABLED`
feature flag.
### Description
- **New `twenty-standard-application` package**: Contains front
component definitions with an esbuild-based build pipeline that
generates minified `.mjs` bundles and a manifest with checksums.
- **Server-side registration**: New constants register all items with
metadata (labels, icons, positions, availability types, conditional
expressions). A `StandardFrontComponentUploadService` uploads built
components to file storage.
- **`FALLBACK` availability type**: New enum value for command menu
items that appear as fallback options (e.g., "Search Records" fallback).
- **`CommandMenuContextApi` refactor**
- **Conditional availability enhancements**: New array-based helper
functions for evaluating multi-record conditions.
- **Frontend wiring** (twenty-front):
`useCommandMenuItemFrontComponentCommands`
## Next steps
Only simple commands have been implemented for now:
- **Navigation (9)** -- `CommandLink`: go-to-companies,
go-to-dashboards, go-to-notes, go-to-opportunities, go-to-people,
go-to-runs, go-to-settings, go-to-tasks, go-to-workflows
- **Side panel (4)** -- `CommandOpenSidePanelPage`: ask-ai,
search-records, search-records-fallback, view-previous-ai-chats
We still have to implement front components for all the following
commands:
All have placeholder `execute` logic (`async () => {}`) with a `// TODO:
implement execute logic` comment:
**Record (22)**
- `add-to-favorites`, `remove-from-favorites`
- `create-new-record`, `create-new-view`
- `delete-single-record`, `delete-multiple-records`
- `destroy-single-record`, `destroy-multiple-records`
- `restore-single-record`, `restore-multiple-records`
- `export-from-record-index`, `export-from-record-show`,
`export-multiple-records`, `export-note-to-pdf`, `export-view`
- `hide-deleted-records`, `see-deleted-records`
- `import-records`, `merge-multiple-records`, `update-multiple-records`
- `navigate-to-next-record`, `navigate-to-previous-record`
**Page layout (3)** -- `cancel-record-page-layout`,
`edit-record-page-layout`, `save-record-page-layout`
**Dashboard (4)** -- `cancel-dashboard-layout`, `duplicate-dashboard`,
`edit-dashboard-layout`, `save-dashboard-layout`
**Workflow (10)** -- `activate-workflow`, `add-node-workflow`,
`deactivate-workflow`, `discard-draft-workflow`, `duplicate-workflow`,
`see-active-version-workflow`, `see-runs-workflow`,
`see-versions-workflow`, `test-workflow`, `tidy-up-workflow`
**Workflow version (4)** -- `see-runs-workflow-version`,
`see-versions-workflow-version`, `see-workflow-workflow-version`,
`use-as-draft-workflow-version`
**Workflow run (3)** -- `see-version-workflow-run`,
`see-workflow-workflow-run`, `stop-workflow-run`
This commit is contained in:
+18
-11
@@ -1,16 +1,18 @@
|
||||
import { useRunWorkflowRecordCommands } from '@/command-menu-item/record/workflow/hooks/useRunWorkflowRecordCommands';
|
||||
import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
|
||||
import {
|
||||
CommandMenuContext,
|
||||
type CommandMenuContextType,
|
||||
} from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
|
||||
import { useCommandMenuItemFrontComponentCommands } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentCommands';
|
||||
import { useRegisteredCommandMenuItems } from '@/command-menu-item/hooks/useRegisteredCommandMenuItems';
|
||||
import { useShouldCommandMenuItemBeRegisteredParams } from '@/command-menu-item/hooks/useShouldCommandMenuItemBeRegisteredParams';
|
||||
import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
|
||||
import { useCommandMenuItemFrontComponentActions } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentActions';
|
||||
import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
|
||||
import { useRunWorkflowRecordCommands } from '@/command-menu-item/record/workflow/hooks/useRunWorkflowRecordCommands';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const CommandMenuContextProviderDefault = ({
|
||||
objectMetadataItem,
|
||||
@@ -56,7 +58,11 @@ export const CommandMenuContextProviderDefault = ({
|
||||
const commandMenuContextApi = useCommandMenuContextApi();
|
||||
|
||||
const commandMenuItemFrontComponentActions =
|
||||
useCommandMenuItemFrontComponentActions(commandMenuContextApi);
|
||||
useCommandMenuItemFrontComponentCommands(commandMenuContextApi);
|
||||
|
||||
const isCommandMenuItemEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
);
|
||||
|
||||
return (
|
||||
<CommandMenuContext.Provider
|
||||
@@ -64,12 +70,13 @@ export const CommandMenuContextProviderDefault = ({
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
commandMenuItems: [
|
||||
...commandMenuItems,
|
||||
...runWorkflowRecordCommands,
|
||||
...runWorkflowRecordAgnosticCommands,
|
||||
...commandMenuItemFrontComponentActions,
|
||||
],
|
||||
commandMenuItems: isCommandMenuItemEnabled
|
||||
? commandMenuItemFrontComponentActions
|
||||
: [
|
||||
...commandMenuItems,
|
||||
...runWorkflowRecordCommands,
|
||||
...runWorkflowRecordAgnosticCommands,
|
||||
],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
+39
-15
@@ -1,12 +1,12 @@
|
||||
import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
|
||||
import {
|
||||
CommandMenuContext,
|
||||
type CommandMenuContextType,
|
||||
} from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
|
||||
import { useCommandMenuItemFrontComponentCommands } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentCommands';
|
||||
import { useRegisteredCommandMenuItems } from '@/command-menu-item/hooks/useRegisteredCommandMenuItems';
|
||||
import { useShouldCommandMenuItemBeRegisteredParams } from '@/command-menu-item/hooks/useShouldCommandMenuItemBeRegisteredParams';
|
||||
import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
|
||||
import { useCommandMenuItemFrontComponentActions } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentActions';
|
||||
import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
@@ -14,7 +14,9 @@ import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/use
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { type WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
type CommandMenuContextProviderWorkflowObjectsProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
@@ -55,8 +57,30 @@ const CommandMenuContextProviderWorkflowObjectsContent = ({
|
||||
|
||||
const commandMenuContextApi = useCommandMenuContextApi();
|
||||
|
||||
const enrichedSelectedRecords = isDefined(workflowWithCurrentVersion)
|
||||
? commandMenuContextApi.selectedRecords.map((record) =>
|
||||
record.id === workflowWithCurrentVersion.id
|
||||
? {
|
||||
...record,
|
||||
currentVersion: workflowWithCurrentVersion.currentVersion,
|
||||
versions: workflowWithCurrentVersion.versions,
|
||||
statuses: workflowWithCurrentVersion.statuses,
|
||||
}
|
||||
: record,
|
||||
)
|
||||
: commandMenuContextApi.selectedRecords;
|
||||
|
||||
const enrichedCommandMenuContextApi = {
|
||||
...commandMenuContextApi,
|
||||
selectedRecords: enrichedSelectedRecords,
|
||||
};
|
||||
|
||||
const commandMenuItemFrontComponentActions =
|
||||
useCommandMenuItemFrontComponentActions(commandMenuContextApi);
|
||||
useCommandMenuItemFrontComponentCommands(enrichedCommandMenuContextApi);
|
||||
|
||||
const isCommandMenuItemEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
);
|
||||
|
||||
return (
|
||||
<CommandMenuContext.Provider
|
||||
@@ -64,11 +88,9 @@ const CommandMenuContextProviderWorkflowObjectsContent = ({
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
commandMenuItems: [
|
||||
...commandMenuItems,
|
||||
...runWorkflowRecordAgnosticCommands,
|
||||
...commandMenuItemFrontComponentActions,
|
||||
],
|
||||
commandMenuItems: isCommandMenuItemEnabled
|
||||
? commandMenuItemFrontComponentActions
|
||||
: [...commandMenuItems, ...runWorkflowRecordAgnosticCommands],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -104,7 +126,11 @@ const CommandMenuContextProviderWorkflowObjectsWithoutWorkflow = ({
|
||||
const commandMenuContextApi = useCommandMenuContextApi();
|
||||
|
||||
const commandMenuItemFrontComponentActions =
|
||||
useCommandMenuItemFrontComponentActions(commandMenuContextApi);
|
||||
useCommandMenuItemFrontComponentCommands(commandMenuContextApi);
|
||||
|
||||
const isCommandMenuItemEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
);
|
||||
|
||||
return (
|
||||
<CommandMenuContext.Provider
|
||||
@@ -112,11 +138,9 @@ const CommandMenuContextProviderWorkflowObjectsWithoutWorkflow = ({
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
commandMenuItems: [
|
||||
...commandMenuItems,
|
||||
...runWorkflowRecordAgnosticCommands,
|
||||
...commandMenuItemFrontComponentActions,
|
||||
],
|
||||
commandMenuItems: isCommandMenuItemEnabled
|
||||
? commandMenuItemFrontComponentActions
|
||||
: [...commandMenuItems, ...runWorkflowRecordAgnosticCommands],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
+42
-30
@@ -1,8 +1,9 @@
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
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';
|
||||
@@ -12,16 +13,17 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
|
||||
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 { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
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 { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
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 {
|
||||
CoreObjectNameSingular,
|
||||
CommandMenuContextApiPageType,
|
||||
type CommandMenuContextApi,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -53,28 +55,35 @@ export const useCommandMenuContextApi = (): CommandMenuContextApi => {
|
||||
const { sortedFavorites: favorites } = useFavorites();
|
||||
const { navigationMenuItems } = usePrefetchedNavigationMenuItemsData();
|
||||
|
||||
const recordId =
|
||||
const recordIds =
|
||||
contextStoreTargetedRecordsRule.mode === 'selection'
|
||||
? contextStoreTargetedRecordsRule.selectedRecordIds[0]
|
||||
? contextStoreTargetedRecordsRule.selectedRecordIds
|
||||
: undefined;
|
||||
|
||||
const isFavorite = (() => {
|
||||
if (!isDefined(recordId)) return false;
|
||||
const favoriteRecordIds = (() => {
|
||||
if (!isNonEmptyArray(recordIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (isNavigationMenuItemEditingEnabled && isDefined(objectMetadataItem)) {
|
||||
return !!navigationMenuItems?.find(
|
||||
(item) =>
|
||||
item.targetRecordId === recordId &&
|
||||
item.targetObjectMetadataId === objectMetadataItem.id,
|
||||
return recordIds.filter((recordId) =>
|
||||
navigationMenuItems?.some(
|
||||
(item) =>
|
||||
item.targetRecordId === recordId &&
|
||||
item.targetObjectMetadataId === objectMetadataItem.id,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return !!favorites?.find((favorite) => favorite.recordId === recordId);
|
||||
return recordIds.filter((recordId) =>
|
||||
favorites?.some((favorite) => favorite.recordId === recordId),
|
||||
);
|
||||
})();
|
||||
|
||||
const selectedRecord =
|
||||
useAtomFamilyStateValue(recordStoreFamilyState, recordId ?? '') ||
|
||||
undefined;
|
||||
const selectedRecords = useAtomFamilySelectorValue(
|
||||
recordStoreRecordsSelector,
|
||||
{ recordIds: recordIds ?? [] },
|
||||
);
|
||||
|
||||
const objectPermissionsFromHook = useObjectPermissionsForObject(
|
||||
objectMetadataItem?.id ?? '',
|
||||
@@ -92,12 +101,6 @@ export const useCommandMenuContextApi = (): CommandMenuContextApi => {
|
||||
rowLevelPermissionPredicateGroups: [],
|
||||
};
|
||||
|
||||
const isNoteOrTask =
|
||||
objectMetadataItem?.nameSingular === CoreObjectNameSingular.Note ||
|
||||
objectMetadataItem?.nameSingular === CoreObjectNameSingular.Task;
|
||||
|
||||
const isRemote = objectMetadataItem?.isRemote ?? false;
|
||||
|
||||
const { recordIndexId } = useRecordIndexIdFromCurrentContextStore();
|
||||
|
||||
const hasAnySoftDeleteFilterOnView = useAtomComponentSelectorValue(
|
||||
@@ -105,9 +108,18 @@ export const useCommandMenuContextApi = (): CommandMenuContextApi => {
|
||||
recordIndexId,
|
||||
);
|
||||
|
||||
const isShowPage =
|
||||
useAtomComponentStateValue(contextStoreCurrentViewTypeComponentState) ===
|
||||
ContextStoreViewType.ShowPage;
|
||||
const contextStoreCurrentViewType = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewTypeComponentState,
|
||||
);
|
||||
|
||||
const contextStoreIsPageInEditMode = useAtomComponentStateValue(
|
||||
contextStoreIsPageInEditModeComponentState,
|
||||
);
|
||||
|
||||
const pageType =
|
||||
contextStoreCurrentViewType === ContextStoreViewType.ShowPage
|
||||
? CommandMenuContextApiPageType.RECORD_PAGE
|
||||
: CommandMenuContextApiPageType.INDEX_PAGE;
|
||||
|
||||
const contextStoreNumberOfSelectedRecords = useAtomComponentStateValue(
|
||||
contextStoreNumberOfSelectedRecordsComponentState,
|
||||
@@ -139,18 +151,18 @@ export const useCommandMenuContextApi = (): CommandMenuContextApi => {
|
||||
}
|
||||
|
||||
return {
|
||||
isShowPage,
|
||||
pageType,
|
||||
isInSidePanel,
|
||||
isFavorite,
|
||||
isRemote,
|
||||
isNoteOrTask,
|
||||
isPageInEditMode: contextStoreIsPageInEditMode,
|
||||
favoriteRecordIds,
|
||||
isSelectAll,
|
||||
hasAnySoftDeleteFilterOnView,
|
||||
numberOfSelectedRecords: contextStoreNumberOfSelectedRecords,
|
||||
objectPermissions,
|
||||
selectedRecord: selectedRecord as CommandMenuContextApi['selectedRecord'],
|
||||
selectedRecords,
|
||||
featureFlags,
|
||||
targetObjectReadPermissions,
|
||||
targetObjectWritePermissions,
|
||||
objectMetadataItem: objectMetadataItem ?? {},
|
||||
};
|
||||
};
|
||||
|
||||
+52
-37
@@ -2,17 +2,15 @@ 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 { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { useOpenFrontComponentInSidePanel } from '@/side-panel/hooks/useOpenFrontComponentInSidePanel';
|
||||
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 { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useContext } from 'react';
|
||||
import { type CommandMenuContextApi } from 'twenty-shared/types';
|
||||
import {
|
||||
evaluateConditionalAvailabilityExpression,
|
||||
@@ -36,8 +34,8 @@ type CommandMenuItemWithFrontComponent = CommandMenuItemFieldsFragment & {
|
||||
|
||||
type BuildCommandMenuItemFromFrontComponentParams = {
|
||||
item: CommandMenuItemWithFrontComponent;
|
||||
type?: CommandMenuItemType;
|
||||
scope: CommandMenuItemScope;
|
||||
index: number;
|
||||
isPinned: boolean;
|
||||
getIcon: ReturnType<typeof useIcons>['getIcon'];
|
||||
openFrontComponentInSidePanel: (params: {
|
||||
@@ -61,8 +59,8 @@ type BuildCommandMenuItemFromFrontComponentParams = {
|
||||
// once we have migrated all command menu items
|
||||
const buildCommandMenuItemFromFrontComponent = ({
|
||||
item,
|
||||
type = CommandMenuItemType.FrontComponent,
|
||||
scope,
|
||||
index,
|
||||
isPinned,
|
||||
getIcon,
|
||||
openFrontComponentInSidePanel,
|
||||
@@ -95,12 +93,12 @@ const buildCommandMenuItemFromFrontComponent = ({
|
||||
};
|
||||
|
||||
return {
|
||||
type: CommandMenuItemType.FrontComponent,
|
||||
type,
|
||||
key: `command-menu-item-front-component-${item.id}`,
|
||||
scope,
|
||||
label: displayLabel,
|
||||
shortLabel: displayLabel,
|
||||
position: index,
|
||||
shortLabel: item.shortLabel ?? undefined,
|
||||
position: item.position,
|
||||
isPinned,
|
||||
Icon,
|
||||
shouldBeRegistered: () =>
|
||||
@@ -119,7 +117,7 @@ const buildCommandMenuItemFromFrontComponent = ({
|
||||
};
|
||||
};
|
||||
|
||||
export const useCommandMenuItemFrontComponentActions = (
|
||||
export const useCommandMenuItemFrontComponentCommands = (
|
||||
commandMenuContextApi: CommandMenuContextApi,
|
||||
) => {
|
||||
const { getIcon } = useIcons();
|
||||
@@ -130,8 +128,6 @@ export const useCommandMenuItemFrontComponentActions = (
|
||||
contextStoreIsPageInEditModeComponentState,
|
||||
);
|
||||
|
||||
const { containerType } = useContext(CommandMenuContext);
|
||||
|
||||
const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
);
|
||||
@@ -151,6 +147,10 @@ export const useCommandMenuItemFrontComponentActions = (
|
||||
? contextStoreTargetedRecordsRule.selectedRecordIds
|
||||
: [];
|
||||
|
||||
const hasRecordSelection =
|
||||
selectedRecordIds.length >= 1 ||
|
||||
contextStoreTargetedRecordsRule.mode === 'exclusion';
|
||||
|
||||
const mountContext: HeadlessFrontComponentMountContext | undefined =
|
||||
selectedRecordIds.length === 1 && isDefined(currentObjectMetadataItem)
|
||||
? {
|
||||
@@ -164,10 +164,7 @@ export const useCommandMenuItemFrontComponentActions = (
|
||||
);
|
||||
|
||||
const { data } = useFindManyCommandMenuItemsQuery({
|
||||
skip:
|
||||
!isCommandMenuItemEnabled ||
|
||||
(containerType !== 'command-menu-list' &&
|
||||
containerType !== 'command-menu-show-page-dropdown'),
|
||||
skip: !isCommandMenuItemEnabled,
|
||||
});
|
||||
|
||||
const frontComponentItems =
|
||||
@@ -176,35 +173,33 @@ export const useCommandMenuItemFrontComponentActions = (
|
||||
isDefined(item.frontComponentId),
|
||||
) ?? [];
|
||||
|
||||
const selectedRecordCount =
|
||||
contextStoreTargetedRecordsRule.mode === 'selection'
|
||||
? contextStoreTargetedRecordsRule.selectedRecordIds.length
|
||||
: 0;
|
||||
|
||||
const objectMatches = (item: CommandMenuItemWithFrontComponent) =>
|
||||
!isDefined(item.availabilityObjectMetadataId) ||
|
||||
item.availabilityObjectMetadataId ===
|
||||
contextStoreCurrentObjectMetadataItemId;
|
||||
|
||||
const globalItems = frontComponentItems.filter(
|
||||
const frontComponentItemsWithObjectMatches =
|
||||
frontComponentItems.filter(objectMatches);
|
||||
|
||||
const globalItems = frontComponentItemsWithObjectMatches.filter(
|
||||
(item) => item.availabilityType === CommandMenuItemAvailabilityType.GLOBAL,
|
||||
);
|
||||
|
||||
const recordScopedItems = frontComponentItems.filter((item) => {
|
||||
if (!objectMatches(item)) return false;
|
||||
|
||||
return (
|
||||
const recordScopedItems = frontComponentItemsWithObjectMatches.filter(
|
||||
(item) =>
|
||||
item.availabilityType ===
|
||||
CommandMenuItemAvailabilityType.RECORD_SELECTION &&
|
||||
selectedRecordCount >= 1
|
||||
);
|
||||
});
|
||||
CommandMenuItemAvailabilityType.RECORD_SELECTION,
|
||||
);
|
||||
|
||||
const globalCommandMenuItems = globalItems.map((item, index) =>
|
||||
const fallbackItems = frontComponentItemsWithObjectMatches.filter(
|
||||
(item) =>
|
||||
item.availabilityType === CommandMenuItemAvailabilityType.FALLBACK,
|
||||
);
|
||||
|
||||
const globalCommandMenuItems = globalItems.map((item) =>
|
||||
buildCommandMenuItemFromFrontComponent({
|
||||
item,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
index,
|
||||
isPinned: !contextStoreIsPageInEditMode && item.isPinned,
|
||||
getIcon,
|
||||
openFrontComponentInSidePanel,
|
||||
@@ -213,19 +208,39 @@ export const useCommandMenuItemFrontComponentActions = (
|
||||
}),
|
||||
);
|
||||
|
||||
const recordScopedCommandMenuItems = recordScopedItems.map((item, index) =>
|
||||
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,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
index,
|
||||
isPinned: !contextStoreIsPageInEditMode && item.isPinned,
|
||||
type: CommandMenuItemType.Fallback,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
isPinned: false,
|
||||
getIcon,
|
||||
openFrontComponentInSidePanel,
|
||||
mountHeadlessFrontComponent,
|
||||
commandMenuContextApi,
|
||||
mountContext,
|
||||
}),
|
||||
);
|
||||
|
||||
return [...globalCommandMenuItems, ...recordScopedCommandMenuItems];
|
||||
return [
|
||||
...globalCommandMenuItems,
|
||||
...recordScopedCommandMenuItems,
|
||||
...fallbackCommandMenuItems,
|
||||
]
|
||||
.filter((item) => item.shouldBeRegistered())
|
||||
.sort((a, b) => a.position - b.position);
|
||||
};
|
||||
Reference in New Issue
Block a user