refactoring, bug fixes and create CommandMenuCommandsEffect

This commit is contained in:
bosiraphael
2024-11-20 11:08:57 +01:00
parent efdb661b5c
commit cc460d51fe
8 changed files with 126 additions and 45 deletions

View File

@@ -13,6 +13,10 @@ import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchS
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { Command, CommandType } from '@/command-menu/types/Command';
import { Company } from '@/companies/types/Company';
import {
ContextStoreTargetedRecordsRule,
contextStoreTargetedRecordsRuleComponentState,
} from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
@@ -31,7 +35,12 @@ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { useMemo, useRef } from 'react';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import {
useRecoilCallback,
useRecoilState,
useRecoilValue,
useSetRecoilState,
} from 'recoil';
import { Key } from 'ts-key-enum';
import { Avatar, IconNotes, IconSparkles, isDefined } from 'twenty-ui';
import { useDebounce } from 'use-debounce';
@@ -100,6 +109,25 @@ export const CommandMenu = () => {
const commandMenuCommands = useRecoilValue(commandMenuCommandsState);
const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu();
const mainContextStoreComponentInstanceId = useRecoilValue(
mainContextStoreComponentInstanceIdState,
);
const setContextStoreTargetedRecordsRule = useRecoilCallback(
({ set }) =>
(rule: ContextStoreTargetedRecordsRule) => {
if (isDefined(mainContextStoreComponentInstanceId)) {
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: mainContextStoreComponentInstanceId,
}),
rule,
);
}
},
[mainContextStoreComponentInstanceId],
);
const isMobile = useIsMobile();
useScopedHotkeys(
@@ -121,6 +149,23 @@ export const CommandMenu = () => {
[closeCommandMenu],
);
useScopedHotkeys(
[Key.Backspace, Key.Delete],
() => {
if (!isNonEmptyString(commandMenuSearch)) {
setContextStoreTargetedRecordsRule({
mode: 'selection',
selectedRecordIds: [],
});
}
},
AppHotkeyScope.CommandMenu,
[closeCommandMenu],
{
preventDefault: false,
},
);
const { loading: isPeopleLoading, records: people } =
useSearchRecords<Person>({
skip: !isCommandMenuOpened,
@@ -304,10 +349,6 @@ export const CommandMenu = () => {
isOpportunitiesLoading ||
isCompaniesLoading;
const mainContextStoreComponentInstanceId = useRecoilValue(
mainContextStoreComponentInstanceIdState,
);
return (
<>
{isCommandMenuOpened && (

View File

@@ -0,0 +1,29 @@
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
import { commandMenuCommandsState } from '@/command-menu/states/commandMenuCommandsState';
import { computeCommandMenuCommands } from '@/command-menu/utils/computeCommandMenuCommands';
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useEffect } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-ui';
export const CommandMenuCommandsEffect = () => {
const mainContextStoreComponentInstanceId = useRecoilValue(
mainContextStoreComponentInstanceIdState,
);
const actionMenuEntries = useRecoilComponentValueV2(
actionMenuEntriesComponentSelector,
mainContextStoreComponentInstanceId,
);
const setCommands = useSetRecoilState(commandMenuCommandsState);
useEffect(() => {
if (isDefined(mainContextStoreComponentInstanceId)) {
setCommands(computeCommandMenuCommands(actionMenuEntries));
}
}, [mainContextStoreComponentInstanceId, actionMenuEntries, setCommands]);
return null;
};

View File

@@ -31,7 +31,7 @@ const StyledAvatarWrapper = styled.div`
border-radius: ${({ theme }) => theme.border.radius.sm};
padding: ${({ theme }) => theme.spacing(0.5)};
border: 1px solid ${({ theme }) => theme.border.color.medium};
&:not(:first-child) {
&:not(:first-of-type) {
margin-left: -${({ theme }) => theme.spacing(1)};
}
`;
@@ -75,7 +75,7 @@ export const CommandMenuContextRecordChip = () => {
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataIdComponentState,
mainContextStoreComponentInstanceId ?? undefined,
mainContextStoreComponentInstanceId,
);
const { objectMetadataItem } = useObjectMetadataItemById({
@@ -83,7 +83,7 @@ export const CommandMenuContextRecordChip = () => {
});
const { records, loading, totalCount } = useContextStoreSelectedRecords(
mainContextStoreComponentInstanceId ?? undefined,
mainContextStoreComponentInstanceId,
);
if (loading || !totalCount) {
@@ -96,6 +96,7 @@ export const CommandMenuContextRecordChip = () => {
{records.map((record) => (
<CommandMenuContextRecordChipAvatars
objectMetadataItem={objectMetadataItem}
key={record.id}
record={record}
/>
))}

View File

@@ -11,6 +11,7 @@ import { isDefined } from '~/utils/isDefined';
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
import { COMMAND_MENU_COMMANDS } from '@/command-menu/constants/CommandMenuCommands';
import { computeCommandMenuCommands } from '@/command-menu/utils/computeCommandMenuCommands';
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ALL_ICONS } from '@ui/display/icon/providers/internal/AllIcons';
@@ -37,39 +38,17 @@ export const useCommandMenu = () => {
({ snapshot }) =>
() => {
if (isDefined(mainContextStoreComponentInstanceId)) {
const actionMenuEntries = snapshot.getLoadable(
actionMenuEntriesComponentSelector.selectorFamily({
instanceId: mainContextStoreComponentInstanceId,
}),
);
const commands = Object.values(COMMAND_MENU_COMMANDS);
const actionCommands = actionMenuEntries
.getValue()
?.filter((actionMenuEntry) => actionMenuEntry.type === 'standard')
?.map((actionMenuEntry) => ({
id: actionMenuEntry.key,
label: actionMenuEntry.label,
Icon: actionMenuEntry.Icon,
onCommandClick: actionMenuEntry.onClick,
type: CommandType.StandardAction,
}));
const workflowRunCommands = actionMenuEntries
.getValue()
?.filter(
(actionMenuEntry) => actionMenuEntry.type === 'workflow-run',
const actionMenuEntries = snapshot
.getLoadable(
actionMenuEntriesComponentSelector.selectorFamily({
instanceId: mainContextStoreComponentInstanceId,
}),
)
?.map((actionMenuEntry) => ({
id: actionMenuEntry.key,
label: actionMenuEntry.label,
Icon: actionMenuEntry.Icon,
onCommandClick: actionMenuEntry.onClick,
type: CommandType.WorkflowRun,
}));
.getValue();
setCommands([...commands, ...actionCommands, ...workflowRunCommands]);
const commands = computeCommandMenuCommands(actionMenuEntries);
setCommands(commands);
}
setIsCommandMenuOpened(true);

View File

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

View File

@@ -11,10 +11,10 @@ export const MainContextStoreComponentInstanceIdSetterEffect = () => {
const context = useContext(ContextStoreComponentInstanceContext);
useEffect(() => {
setMainContextStoreComponentInstanceId(context?.instanceId ?? null);
setMainContextStoreComponentInstanceId(context?.instanceId ?? 'app');
return () => {
setMainContextStoreComponentInstanceId(null);
setMainContextStoreComponentInstanceId('app');
};
}, [context, setMainContextStoreComponentInstanceId]);

View File

@@ -1,8 +1,6 @@
import { createState } from 'twenty-ui';
export const mainContextStoreComponentInstanceIdState = createState<
string | null
>({
export const mainContextStoreComponentInstanceIdState = createState<string>({
key: 'mainContextStoreComponentInstanceIdState',
defaultValue: null,
defaultValue: 'app',
});

View File

@@ -1,5 +1,6 @@
import { AuthModal } from '@/auth/components/AuthModal';
import { CommandMenu } from '@/command-menu/components/CommandMenu';
import { CommandMenuCommandsEffect } from '@/command-menu/components/CommandMenuCommandsEffect';
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu';
import { AppNavigationDrawer } from '@/navigation/components/AppNavigationDrawer';
@@ -80,6 +81,7 @@ export const DefaultLayout = () => {
`}
/>
<StyledLayout>
<CommandMenuCommandsEffect />
<CommandMenu />
<KeyboardShortcutMenu />