mirror of
https://github.com/lingble/twenty.git
synced 2025-10-29 11:52:28 +00:00
refactoring, bug fixes and create CommandMenuCommandsEffect
This commit is contained in:
@@ -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 && (
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
@@ -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 />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user