improve command menu actions ordering

This commit is contained in:
bosiraphael
2024-11-20 16:16:20 +01:00
parent 8c05830dcf
commit 1ba43bbff8
14 changed files with 233 additions and 82 deletions

View File

@@ -1,4 +1,8 @@
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import {
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useAllActiveWorkflowVersions } from '@/workflow/hooks/useAllActiveWorkflowVersions'; import { useAllActiveWorkflowVersions } from '@/workflow/hooks/useAllActiveWorkflowVersions';
@@ -28,9 +32,9 @@ export const WorkflowRunActionEffect = () => {
activeWorkflowVersion, activeWorkflowVersion,
] of activeWorkflowVersions.entries()) { ] of activeWorkflowVersions.entries()) {
addActionMenuEntry({ addActionMenuEntry({
type: 'workflow-run', type: ActionMenuEntryType.WorkflowRun,
key: `workflow-run-${activeWorkflowVersion.id}`, key: `workflow-run-${activeWorkflowVersion.id}`,
scope: 'global', scope: ActionMenuEntryScope.Global,
label: capitalize(activeWorkflowVersion.workflow.name), label: capitalize(activeWorkflowVersion.workflow.name),
position: index, position: index,
Icon: IconSettingsAutomation, Icon: IconSettingsAutomation,

View File

@@ -1,5 +1,9 @@
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import {
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState'; import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
@@ -105,8 +109,8 @@ export const DeleteRecordsActionEffect = ({
useEffect(() => { useEffect(() => {
if (canDelete) { if (canDelete) {
addActionMenuEntry({ addActionMenuEntry({
type: 'standard', type: ActionMenuEntryType.Standard,
scope: 'record-selection', scope: ActionMenuEntryScope.RecordSelection,
key: 'delete', key: 'delete',
label: 'Delete', label: 'Delete',
position, position,

View File

@@ -4,6 +4,10 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { IconDatabaseExport } from 'twenty-ui'; import { IconDatabaseExport } from 'twenty-ui';
import {
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
import { import {
displayedExportProgress, displayedExportProgress,
useExportRecords, useExportRecords,
@@ -31,8 +35,11 @@ export const ExportRecordsActionEffect = ({
useEffect(() => { useEffect(() => {
addActionMenuEntry({ addActionMenuEntry({
type: 'standard', type: ActionMenuEntryType.Standard,
scope: 'record-selection', scope:
contextStoreNumberOfSelectedRecords > 0
? ActionMenuEntryScope.RecordSelection
: ActionMenuEntryScope.Global,
key: 'export', key: 'export',
position, position,
label: displayedExportProgress( label: displayedExportProgress(

View File

@@ -1,4 +1,8 @@
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import {
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite'; import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite';
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite'; import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
@@ -50,8 +54,8 @@ export const ManageFavoritesActionEffect = ({
} }
addActionMenuEntry({ addActionMenuEntry({
type: 'standard', type: ActionMenuEntryType.Standard,
scope: 'record-selection', scope: ActionMenuEntryScope.RecordSelection,
key: 'manage-favorites', key: 'manage-favorites',
label: isFavorite ? 'Remove from favorites' : 'Add to favorites', label: isFavorite ? 'Remove from favorites' : 'Add to favorites',
position, position,

View File

@@ -1,4 +1,8 @@
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import {
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
@@ -55,9 +59,9 @@ export const WorkflowRunRecordActionEffect = ({
activeWorkflowVersion, activeWorkflowVersion,
] of activeWorkflowVersions.entries()) { ] of activeWorkflowVersions.entries()) {
addActionMenuEntry({ addActionMenuEntry({
type: 'workflow-run', type: ActionMenuEntryType.WorkflowRun,
key: `workflow-run-${activeWorkflowVersion.id}`, key: `workflow-run-${activeWorkflowVersion.id}`,
scope: 'record-selection', scope: ActionMenuEntryScope.RecordSelection,
label: capitalize(activeWorkflowVersion.workflow.name), label: capitalize(activeWorkflowVersion.workflow.name),
position: index, position: index,
Icon: IconSettingsAutomation, Icon: IconSettingsAutomation,

View File

@@ -1,5 +1,6 @@
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector'; import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ActionMenuEntryScope } from '@/action-menu/types/ActionMenuEntry';
import { RightDrawerActionMenuDropdownHotkeyScope } from '@/action-menu/types/RightDrawerActionMenuDropdownHotkeyScope'; import { RightDrawerActionMenuDropdownHotkeyScope } from '@/action-menu/types/RightDrawerActionMenuDropdownHotkeyScope';
import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getRightDrawerActionMenuDropdownIdFromActionMenuId'; import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getRightDrawerActionMenuDropdownIdFromActionMenuId';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
@@ -67,7 +68,8 @@ export const RightDrawerActionMenuDropdown = () => {
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
{actionMenuEntries {actionMenuEntries
.filter( .filter(
(actionMenuEntry) => actionMenuEntry.scope === 'record-selection', (actionMenuEntry) =>
actionMenuEntry.scope === ActionMenuEntryScope.RecordSelection,
) )
.map((actionMenuEntry, index) => ( .map((actionMenuEntry, index) => (
<MenuItem <MenuItem

View File

@@ -5,7 +5,11 @@ import { RecoilRoot } from 'recoil';
import { RecordIndexActionMenuBar } from '@/action-menu/components/RecordIndexActionMenuBar'; import { RecordIndexActionMenuBar } from '@/action-menu/components/RecordIndexActionMenuBar';
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; import {
ActionMenuEntry,
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId'; import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
@@ -48,8 +52,8 @@ const meta: Meta<typeof RecordIndexActionMenuBar> = {
map.set('delete', { map.set('delete', {
isPinned: true, isPinned: true,
scope: 'record-selection', scope: ActionMenuEntryScope.RecordSelection,
type: 'standard', type: ActionMenuEntryType.Standard,
key: 'delete', key: 'delete',
label: 'Delete', label: 'Delete',
position: 0, position: 0,

View File

@@ -1,4 +1,8 @@
import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIndexActionMenuBarEntry'; import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIndexActionMenuBarEntry';
import {
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
import { expect, jest } from '@storybook/jest'; import { expect, jest } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library'; import { userEvent, within } from '@storybook/testing-library';
@@ -21,8 +25,8 @@ const markAsDoneMock = jest.fn();
export const Default: Story = { export const Default: Story = {
args: { args: {
entry: { entry: {
type: 'standard', type: ActionMenuEntryType.Standard,
scope: 'record-selection', scope: ActionMenuEntryScope.RecordSelection,
key: 'delete', key: 'delete',
label: 'Delete', label: 'Delete',
position: 0, position: 0,
@@ -35,8 +39,8 @@ export const Default: Story = {
export const WithDangerAccent: Story = { export const WithDangerAccent: Story = {
args: { args: {
entry: { entry: {
type: 'standard', type: ActionMenuEntryType.Standard,
scope: 'record-selection', scope: ActionMenuEntryScope.RecordSelection,
key: 'delete', key: 'delete',
label: 'Delete', label: 'Delete',
position: 0, position: 0,
@@ -50,8 +54,8 @@ export const WithDangerAccent: Story = {
export const WithInteraction: Story = { export const WithInteraction: Story = {
args: { args: {
entry: { entry: {
type: 'standard', type: ActionMenuEntryType.Standard,
scope: 'record-selection', scope: ActionMenuEntryScope.RecordSelection,
key: 'markAsDone', key: 'markAsDone',
label: 'Mark as done', label: 'Mark as done',
position: 0, position: 0,

View File

@@ -7,7 +7,11 @@ import { RecordIndexActionMenuDropdown } from '@/action-menu/components/RecordIn
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState'; import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; import {
ActionMenuEntry,
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import { IconCheckbox, IconHeart, IconTrash } from 'twenty-ui'; import { IconCheckbox, IconHeart, IconTrash } from 'twenty-ui';
@@ -41,8 +45,8 @@ const meta: Meta<typeof RecordIndexActionMenuDropdown> = {
); );
map.set('delete', { map.set('delete', {
type: 'standard', type: ActionMenuEntryType.Standard,
scope: 'record-selection', scope: ActionMenuEntryScope.RecordSelection,
key: 'delete', key: 'delete',
label: 'Delete', label: 'Delete',
position: 0, position: 0,
@@ -51,8 +55,8 @@ const meta: Meta<typeof RecordIndexActionMenuDropdown> = {
}); });
map.set('markAsDone', { map.set('markAsDone', {
type: 'standard', type: ActionMenuEntryType.Standard,
scope: 'record-selection', scope: ActionMenuEntryScope.RecordSelection,
key: 'markAsDone', key: 'markAsDone',
label: 'Mark as done', label: 'Mark as done',
position: 1, position: 1,
@@ -61,8 +65,8 @@ const meta: Meta<typeof RecordIndexActionMenuDropdown> = {
}); });
map.set('addToFavorites', { map.set('addToFavorites', {
type: 'standard', type: ActionMenuEntryType.Standard,
scope: 'record-selection', scope: ActionMenuEntryScope.RecordSelection,
key: 'addToFavorites', key: 'addToFavorites',
label: 'Add to favorites', label: 'Add to favorites',
position: 2, position: 2,

View File

@@ -5,7 +5,11 @@ import { RecoilRoot } from 'recoil';
import { RightDrawerActionMenuDropdown } from '@/action-menu/components/RightDrawerActionMenuDropdown'; import { RightDrawerActionMenuDropdown } from '@/action-menu/components/RightDrawerActionMenuDropdown';
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; import {
ActionMenuEntry,
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { userEvent, waitFor, within } from '@storybook/test'; import { userEvent, waitFor, within } from '@storybook/test';
@@ -54,8 +58,8 @@ const meta: Meta<typeof RightDrawerActionMenuDropdown> = {
); );
map.set('addToFavorites', { map.set('addToFavorites', {
type: 'standard', type: ActionMenuEntryType.Standard,
scope: 'record-selection', scope: ActionMenuEntryScope.RecordSelection,
key: 'addToFavorites', key: 'addToFavorites',
label: 'Add to favorites', label: 'Add to favorites',
position: 0, position: 0,
@@ -64,8 +68,8 @@ const meta: Meta<typeof RightDrawerActionMenuDropdown> = {
}); });
map.set('export', { map.set('export', {
type: 'standard', type: ActionMenuEntryType.Standard,
scope: 'record-selection', scope: ActionMenuEntryScope.RecordSelection,
key: 'export', key: 'export',
label: 'Export', label: 'Export',
position: 1, position: 1,
@@ -74,8 +78,8 @@ const meta: Meta<typeof RightDrawerActionMenuDropdown> = {
}); });
map.set('delete', { map.set('delete', {
type: 'standard', type: ActionMenuEntryType.Standard,
scope: 'record-selection', scope: ActionMenuEntryScope.RecordSelection,
key: 'delete', key: 'delete',
label: 'Delete', label: 'Delete',
position: 2, position: 2,

View File

@@ -1,9 +1,19 @@
import { MouseEvent, ReactNode } from 'react'; import { MouseEvent, ReactNode } from 'react';
import { IconComponent, MenuItemAccent } from 'twenty-ui'; import { IconComponent, MenuItemAccent } from 'twenty-ui';
export enum ActionMenuEntryType {
Standard = 'Standard',
WorkflowRun = 'WorkflowRun',
}
export enum ActionMenuEntryScope {
Global = 'Global',
RecordSelection = 'RecordSelection',
}
export type ActionMenuEntry = { export type ActionMenuEntry = {
type: 'standard' | 'workflow-run'; type: ActionMenuEntryType;
scope: 'global' | 'record-selection'; scope: ActionMenuEntryScope;
key: string; key: string;
label: string; label: string;
position: number; position: number;

View File

@@ -12,7 +12,11 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { commandMenuCommandsState } from '@/command-menu/states/commandMenuCommandsState'; import { commandMenuCommandsState } from '@/command-menu/states/commandMenuCommandsState';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState'; import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { Command, CommandType } from '@/command-menu/types/Command'; import {
Command,
CommandScope,
CommandType,
} from '@/command-menu/types/Command';
import { Company } from '@/companies/types/Company'; import { Company } from '@/companies/types/Company';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
@@ -363,20 +367,45 @@ export const CommandMenu = () => {
: true) && cmd.type === CommandType.Create, : true) && cmd.type === CommandType.Create,
); );
const matchingStandardActionCommands = commandMenuCommands.filter( const matchingStandardActionRecordSelectionCommands =
commandMenuCommands.filter(
(cmd) =>
(deferredCommandMenuSearch.length > 0
? checkInShortcuts(cmd, deferredCommandMenuSearch) ||
checkInLabels(cmd, deferredCommandMenuSearch)
: true) &&
cmd.type === CommandType.StandardAction &&
cmd.scope === CommandScope.RecordSelection,
);
const matchingStandardActionGlobalCommands = commandMenuCommands.filter(
(cmd) => (cmd) =>
(deferredCommandMenuSearch.length > 0 (deferredCommandMenuSearch.length > 0
? checkInShortcuts(cmd, deferredCommandMenuSearch) || ? checkInShortcuts(cmd, deferredCommandMenuSearch) ||
checkInLabels(cmd, deferredCommandMenuSearch) checkInLabels(cmd, deferredCommandMenuSearch)
: true) && cmd.type === CommandType.StandardAction, : true) &&
cmd.type === CommandType.StandardAction &&
cmd.scope === CommandScope.Global,
); );
const matchingWorkflowRunCommands = commandMenuCommands.filter( const matchingWorkflowRunRecordSelectionCommands = commandMenuCommands.filter(
(cmd) => (cmd) =>
(deferredCommandMenuSearch.length > 0 (deferredCommandMenuSearch.length > 0
? checkInShortcuts(cmd, deferredCommandMenuSearch) || ? checkInShortcuts(cmd, deferredCommandMenuSearch) ||
checkInLabels(cmd, deferredCommandMenuSearch) checkInLabels(cmd, deferredCommandMenuSearch)
: true) && cmd.type === CommandType.WorkflowRun, : true) &&
cmd.type === CommandType.WorkflowRun &&
cmd.scope === CommandScope.RecordSelection,
);
const matchingWorkflowRunGlobalCommands = commandMenuCommands.filter(
(cmd) =>
(deferredCommandMenuSearch.length > 0
? checkInShortcuts(cmd, deferredCommandMenuSearch) ||
checkInLabels(cmd, deferredCommandMenuSearch)
: true) &&
cmd.type === CommandType.WorkflowRun &&
cmd.scope === CommandScope.Global,
); );
useListenClickOutside({ useListenClickOutside({
@@ -404,8 +433,10 @@ export const CommandMenu = () => {
const selectableItemIds = copilotCommands const selectableItemIds = copilotCommands
.map((cmd) => cmd.id) .map((cmd) => cmd.id)
.concat(matchingStandardActionCommands.map((cmd) => cmd.id)) .concat(matchingStandardActionRecordSelectionCommands.map((cmd) => cmd.id))
.concat(matchingWorkflowRunCommands.map((cmd) => cmd.id)) .concat(matchingWorkflowRunRecordSelectionCommands.map((cmd) => cmd.id))
.concat(matchingStandardActionGlobalCommands.map((cmd) => cmd.id))
.concat(matchingWorkflowRunGlobalCommands.map((cmd) => cmd.id))
.concat(matchingCreateCommand.map((cmd) => cmd.id)) .concat(matchingCreateCommand.map((cmd) => cmd.id))
.concat(matchingNavigateCommand.map((cmd) => cmd.id)) .concat(matchingNavigateCommand.map((cmd) => cmd.id))
.concat(people?.map((person) => person.id)) .concat(people?.map((person) => person.id))
@@ -422,8 +453,10 @@ export const CommandMenu = () => {
); );
const isNoResults = const isNoResults =
!matchingStandardActionCommands.length && !matchingStandardActionRecordSelectionCommands.length &&
!matchingWorkflowRunCommands.length && !matchingWorkflowRunRecordSelectionCommands.length &&
!matchingStandardActionGlobalCommands.length &&
!matchingWorkflowRunGlobalCommands.length &&
!matchingCreateCommand.length && !matchingCreateCommand.length &&
!matchingNavigateCommand.length && !matchingNavigateCommand.length &&
!people?.length && !people?.length &&
@@ -599,38 +632,82 @@ export const CommandMenu = () => {
</SelectableItem> </SelectableItem>
</CommandGroup> </CommandGroup>
)} )}
<CommandGroup heading="Standard Actions"> <CommandGroup heading="Record Selection">
{matchingStandardActionCommands?.map( {matchingStandardActionRecordSelectionCommands?.map(
(standardActionCommand) => ( (standardActionrecordSelectionCommand) => (
<SelectableItem <SelectableItem
itemId={standardActionCommand.id} itemId={standardActionrecordSelectionCommand.id}
key={standardActionCommand.id} key={standardActionrecordSelectionCommand.id}
> >
<CommandMenuItem <CommandMenuItem
id={standardActionCommand.id} id={standardActionrecordSelectionCommand.id}
label={standardActionCommand.label} label={standardActionrecordSelectionCommand.label}
Icon={standardActionCommand.Icon} Icon={standardActionrecordSelectionCommand.Icon}
onClick={standardActionCommand.onCommandClick} onClick={
standardActionrecordSelectionCommand.onCommandClick
}
/>
</SelectableItem>
),
)}
{matchingWorkflowRunRecordSelectionCommands?.map(
(workflowRunRecordSelectionCommand) => (
<SelectableItem
itemId={workflowRunRecordSelectionCommand.id}
key={workflowRunRecordSelectionCommand.id}
>
<CommandMenuItem
id={workflowRunRecordSelectionCommand.id}
label={workflowRunRecordSelectionCommand.label}
Icon={workflowRunRecordSelectionCommand.Icon}
onClick={
workflowRunRecordSelectionCommand.onCommandClick
}
/> />
</SelectableItem> </SelectableItem>
), ),
)} )}
</CommandGroup> </CommandGroup>
<CommandGroup heading="Workflows"> {matchingStandardActionGlobalCommands?.length > 0 && (
{matchingWorkflowRunCommands?.map((workflowRunCommand) => ( <CommandGroup heading="View">
<SelectableItem {matchingStandardActionGlobalCommands?.map(
itemId={workflowRunCommand.id} (standardActionGlobalCommand) => (
key={workflowRunCommand.id} <SelectableItem
> itemId={standardActionGlobalCommand.id}
<CommandMenuItem key={standardActionGlobalCommand.id}
id={workflowRunCommand.id} >
label={workflowRunCommand.label} <CommandMenuItem
Icon={workflowRunCommand.Icon} id={standardActionGlobalCommand.id}
onClick={workflowRunCommand.onCommandClick} label={standardActionGlobalCommand.label}
/> Icon={standardActionGlobalCommand.Icon}
</SelectableItem> onClick={
))} standardActionGlobalCommand.onCommandClick
</CommandGroup> }
/>
</SelectableItem>
),
)}
</CommandGroup>
)}
{matchingWorkflowRunGlobalCommands?.length > 0 && (
<CommandGroup heading="Workflows">
{matchingWorkflowRunGlobalCommands?.map(
(workflowRunGlobalCommand) => (
<SelectableItem
itemId={workflowRunGlobalCommand.id}
key={workflowRunGlobalCommand.id}
>
<CommandMenuItem
id={workflowRunGlobalCommand.id}
label={workflowRunGlobalCommand.label}
Icon={workflowRunGlobalCommand.Icon}
onClick={workflowRunGlobalCommand.onCommandClick}
/>
</SelectableItem>
),
)}
</CommandGroup>
)}
{commandGroups.map(({ heading, items, renderItem }) => {commandGroups.map(({ heading, items, renderItem }) =>
items?.length ? ( items?.length ? (

View File

@@ -1,5 +1,4 @@
import { IconComponent } from 'twenty-ui'; import { IconComponent } from 'twenty-ui';
export enum CommandType { export enum CommandType {
Navigate = 'Navigate', Navigate = 'Navigate',
Create = 'Create', Create = 'Create',
@@ -7,15 +6,17 @@ export enum CommandType {
WorkflowRun = 'WorkflowRun', WorkflowRun = 'WorkflowRun',
} }
export enum CommandScope {
Global = 'Global',
RecordSelection = 'RecordSelection',
}
export type Command = { export type Command = {
id: string; id: string;
to?: string; to?: string;
label: string; label: string;
type?: type?: CommandType;
| CommandType.Navigate scope?: CommandScope;
| CommandType.Create
| CommandType.StandardAction
| CommandType.WorkflowRun;
Icon?: IconComponent; Icon?: IconComponent;
firstHotKey?: string; firstHotKey?: string;
secondHotKey?: string; secondHotKey?: string;

View File

@@ -1,30 +1,52 @@
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; import {
ActionMenuEntry,
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
import { COMMAND_MENU_COMMANDS } from '@/command-menu/constants/CommandMenuCommands'; import { COMMAND_MENU_COMMANDS } from '@/command-menu/constants/CommandMenuCommands';
import { CommandType } from '@/command-menu/types/Command'; import {
Command,
CommandScope,
CommandType,
} from '@/command-menu/types/Command';
export const computeCommandMenuCommands = ( export const computeCommandMenuCommands = (
actionMenuEntries: ActionMenuEntry[], actionMenuEntries: ActionMenuEntry[],
) => { ): Command[] => {
const commands = Object.values(COMMAND_MENU_COMMANDS); const commands = Object.values(COMMAND_MENU_COMMANDS);
const actionCommands = actionMenuEntries const actionCommands: Command[] = actionMenuEntries
?.filter((actionMenuEntry) => actionMenuEntry.type === 'standard') ?.filter(
(actionMenuEntry) =>
actionMenuEntry.type === ActionMenuEntryType.Standard,
)
?.map((actionMenuEntry) => ({ ?.map((actionMenuEntry) => ({
id: actionMenuEntry.key, id: actionMenuEntry.key,
label: actionMenuEntry.label, label: actionMenuEntry.label,
Icon: actionMenuEntry.Icon, Icon: actionMenuEntry.Icon,
onCommandClick: actionMenuEntry.onClick, onCommandClick: actionMenuEntry.onClick,
type: CommandType.StandardAction, type: CommandType.StandardAction,
scope:
actionMenuEntry.scope === ActionMenuEntryScope.RecordSelection
? CommandScope.RecordSelection
: CommandScope.Global,
})); }));
const workflowRunCommands = actionMenuEntries const workflowRunCommands: Command[] = actionMenuEntries
?.filter((actionMenuEntry) => actionMenuEntry.type === 'workflow-run') ?.filter(
(actionMenuEntry) =>
actionMenuEntry.type === ActionMenuEntryType.WorkflowRun,
)
?.map((actionMenuEntry) => ({ ?.map((actionMenuEntry) => ({
id: actionMenuEntry.key, id: actionMenuEntry.key,
label: actionMenuEntry.label, label: actionMenuEntry.label,
Icon: actionMenuEntry.Icon, Icon: actionMenuEntry.Icon,
onCommandClick: actionMenuEntry.onClick, onCommandClick: actionMenuEntry.onClick,
type: CommandType.WorkflowRun, type: CommandType.WorkflowRun,
scope:
actionMenuEntry.scope === ActionMenuEntryScope.RecordSelection
? CommandScope.RecordSelection
: CommandScope.Global,
})); }));
return [...commands, ...actionCommands, ...workflowRunCommands]; return [...commands, ...actionCommands, ...workflowRunCommands];