Replace hotkey scopes by focus stack (Part 6 - Remove Hotkey scopes 🫳🎤) (#13127)
# Replace hotkey scopes by focus stack (Part 6 - Remove Hotkey scopes) This PR is the last part of a refactoring aiming to deprecate the hotkey scopes api in favor of the new focus stack api which is more robust. Part 1: https://github.com/twentyhq/twenty/pull/12673 Part 2: https://github.com/twentyhq/twenty/pull/12798 Part 3: https://github.com/twentyhq/twenty/pull/12910 Part 4: https://github.com/twentyhq/twenty/pull/12933 Part 5: https://github.com/twentyhq/twenty/pull/13106 In this part, we completely remove the hotkey scopes.
This commit is contained in:
@ -7,7 +7,6 @@ import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/Comman
|
||||
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
|
||||
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import styled from '@emotion/styled';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
@ -78,7 +77,6 @@ export const CommandMenuList = ({
|
||||
selectableListInstanceId="command-menu-list"
|
||||
focusId={SIDE_PANEL_FOCUS_ID}
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={AppHotkeyScope.CommandMenuOpen}
|
||||
onSelect={() => {
|
||||
setHasUserSelectedCommand(true);
|
||||
}}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { COMMAND_MENU_ANIMATION_VARIANTS } from '@/command-menu/constants/CommandMenuAnimationVariants';
|
||||
import { COMMAND_MENU_CLICK_OUTSIDE_ID } from '@/command-menu/constants/CommandMenuClickOutsideId';
|
||||
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant';
|
||||
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
|
||||
import { RECORD_CHIP_CLICK_OUTSIDE_ID } from '@/object-record/record-table/constants/RecordChipClickOutsideId';
|
||||
import { SLASH_MENU_DROPDOWN_CLICK_OUTSIDE_ID } from '@/ui/input/constants/SlashMenuDropdownClickOutsideId';
|
||||
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
|
||||
import { PAGE_HEADER_COMMAND_MENU_BUTTON_CLICK_OUTSIDE_ID } from '@/ui/layout/page-header/constants/PageHeaderCommandMenuButtonClickOutsideId';
|
||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||
import { currentFocusIdSelector } from '@/ui/utilities/focus/states/currentFocusIdSelector';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { WORKFLOW_DIAGRAM_CREATE_STEP_NODE_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramCreateStepNodeClickOutsideId';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
@ -55,11 +55,11 @@ export const CommandMenuOpenContainer = ({
|
||||
const handleClickOutside = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
const hotkeyScope = snapshot
|
||||
.getLoadable(currentHotkeyScopeState)
|
||||
const currentFocusId = snapshot
|
||||
.getLoadable(currentFocusIdSelector)
|
||||
.getValue();
|
||||
|
||||
if (hotkeyScope?.scope === CommandMenuHotkeyScope.CommandMenuFocused) {
|
||||
if (currentFocusId === SIDE_PANEL_FOCUS_ID) {
|
||||
event.stopImmediatePropagation();
|
||||
event.preventDefault();
|
||||
closeCommandMenu();
|
||||
|
||||
@ -8,18 +8,6 @@ import { commandMenuNavigationStackState } from '@/command-menu/states/commandMe
|
||||
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
|
||||
|
||||
const mockGoBackToPreviousHotkeyScope = jest.fn();
|
||||
const mockSetHotkeyScopeAndMemorizePreviousScope = jest.fn();
|
||||
|
||||
jest.mock('@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope', () => ({
|
||||
usePreviousHotkeyScope: () => ({
|
||||
goBackToPreviousHotkeyScope: mockGoBackToPreviousHotkeyScope,
|
||||
setHotkeyScopeAndMemorizePreviousScope:
|
||||
mockSetHotkeyScopeAndMemorizePreviousScope,
|
||||
}),
|
||||
}));
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<RecoilRoot>
|
||||
@ -71,13 +59,6 @@ describe('useCommandMenu', () => {
|
||||
});
|
||||
|
||||
expect(result.current.isCommandMenuOpened).toBe(true);
|
||||
expect(mockSetHotkeyScopeAndMemorizePreviousScope).toHaveBeenCalledWith({
|
||||
scope: CommandMenuHotkeyScope.CommandMenuFocused,
|
||||
memoizeKey: 'command-menu',
|
||||
customScopes: {
|
||||
commandMenuOpen: true,
|
||||
},
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.commandMenu.closeCommandMenu();
|
||||
@ -96,13 +77,6 @@ describe('useCommandMenu', () => {
|
||||
});
|
||||
|
||||
expect(result.current.isCommandMenuOpened).toBe(true);
|
||||
expect(mockSetHotkeyScopeAndMemorizePreviousScope).toHaveBeenCalledWith({
|
||||
scope: CommandMenuHotkeyScope.CommandMenuFocused,
|
||||
memoizeKey: 'command-menu',
|
||||
customScopes: {
|
||||
commandMenuOpen: true,
|
||||
},
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.commandMenu.toggleCommandMenu();
|
||||
@ -110,21 +84,4 @@ describe('useCommandMenu', () => {
|
||||
|
||||
expect(result.current.isCommandMenuOpened).toBe(false);
|
||||
});
|
||||
|
||||
it('should call goBackToPreviousHotkeyScope when closing the command menu', () => {
|
||||
const { result } = renderHooks();
|
||||
|
||||
act(() => {
|
||||
result.current.commandMenu.openCommandMenu();
|
||||
});
|
||||
|
||||
expect(result.current.isCommandMenuOpened).toBe(true);
|
||||
expect(mockGoBackToPreviousHotkeyScope).not.toHaveBeenCalled();
|
||||
|
||||
act(() => {
|
||||
result.current.commandMenu.closeCommandMenu();
|
||||
});
|
||||
|
||||
expect(mockGoBackToPreviousHotkeyScope).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@ -6,15 +6,12 @@ import { commandMenuNavigationStackState } from '@/command-menu/states/commandMe
|
||||
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
|
||||
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
|
||||
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useCommandMenuHistory = () => {
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const goBackFromCommandMenu = useRecoilCallback(
|
||||
({ snapshot, set }) => {
|
||||
@ -69,78 +66,64 @@ export const useCommandMenuHistory = () => {
|
||||
}
|
||||
|
||||
set(hasUserSelectedCommandState, false);
|
||||
|
||||
setHotkeyScope(CommandMenuHotkeyScope.CommandMenuFocused, {
|
||||
commandMenuOpen: true,
|
||||
});
|
||||
};
|
||||
},
|
||||
[closeCommandMenu, setHotkeyScope],
|
||||
[closeCommandMenu],
|
||||
);
|
||||
|
||||
const navigateCommandMenuHistory = useRecoilCallback(
|
||||
({ snapshot, set }) => {
|
||||
return (pageIndex: number) => {
|
||||
const currentNavigationStack = snapshot
|
||||
.getLoadable(commandMenuNavigationStackState)
|
||||
.getValue();
|
||||
const navigateCommandMenuHistory = useRecoilCallback(({ snapshot, set }) => {
|
||||
return (pageIndex: number) => {
|
||||
const currentNavigationStack = snapshot
|
||||
.getLoadable(commandMenuNavigationStackState)
|
||||
.getValue();
|
||||
|
||||
const newNavigationStack = currentNavigationStack.slice(
|
||||
0,
|
||||
pageIndex + 1,
|
||||
const newNavigationStack = currentNavigationStack.slice(0, pageIndex + 1);
|
||||
|
||||
set(commandMenuNavigationStackState, newNavigationStack);
|
||||
|
||||
const newNavigationStackItem = newNavigationStack.at(-1);
|
||||
|
||||
if (!isDefined(newNavigationStackItem)) {
|
||||
throw new Error(
|
||||
`No command menu navigation stack item found for index ${pageIndex}`,
|
||||
);
|
||||
}
|
||||
|
||||
set(commandMenuNavigationStackState, newNavigationStack);
|
||||
set(commandMenuPageState, newNavigationStackItem.page);
|
||||
set(commandMenuPageInfoState, {
|
||||
title: newNavigationStackItem.pageTitle,
|
||||
Icon: newNavigationStackItem.pageIcon,
|
||||
instanceId: newNavigationStackItem.pageId,
|
||||
});
|
||||
const currentMorphItems = snapshot
|
||||
.getLoadable(commandMenuNavigationMorphItemByPageState)
|
||||
.getValue();
|
||||
|
||||
const newNavigationStackItem = newNavigationStack.at(-1);
|
||||
|
||||
if (!isDefined(newNavigationStackItem)) {
|
||||
throw new Error(
|
||||
`No command menu navigation stack item found for index ${pageIndex}`,
|
||||
for (const [pageId, morphItem] of currentMorphItems.entries()) {
|
||||
if (!newNavigationStack.some((item) => item.pageId === pageId)) {
|
||||
set(
|
||||
activeTabIdComponentState.atomFamily({
|
||||
instanceId: getShowPageTabListComponentId({
|
||||
pageId,
|
||||
targetObjectId: morphItem.recordId,
|
||||
}),
|
||||
}),
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
set(commandMenuPageState, newNavigationStackItem.page);
|
||||
set(commandMenuPageInfoState, {
|
||||
title: newNavigationStackItem.pageTitle,
|
||||
Icon: newNavigationStackItem.pageIcon,
|
||||
instanceId: newNavigationStackItem.pageId,
|
||||
});
|
||||
const currentMorphItems = snapshot
|
||||
.getLoadable(commandMenuNavigationMorphItemByPageState)
|
||||
.getValue();
|
||||
const newMorphItems = new Map(
|
||||
Array.from(currentMorphItems.entries()).filter(([pageId]) =>
|
||||
newNavigationStack.some((item) => item.pageId === pageId),
|
||||
),
|
||||
);
|
||||
|
||||
for (const [pageId, morphItem] of currentMorphItems.entries()) {
|
||||
if (!newNavigationStack.some((item) => item.pageId === pageId)) {
|
||||
set(
|
||||
activeTabIdComponentState.atomFamily({
|
||||
instanceId: getShowPageTabListComponentId({
|
||||
pageId,
|
||||
targetObjectId: morphItem.recordId,
|
||||
}),
|
||||
}),
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
set(commandMenuNavigationMorphItemByPageState, newMorphItems);
|
||||
|
||||
const newMorphItems = new Map(
|
||||
Array.from(currentMorphItems.entries()).filter(([pageId]) =>
|
||||
newNavigationStack.some((item) => item.pageId === pageId),
|
||||
),
|
||||
);
|
||||
|
||||
set(commandMenuNavigationMorphItemByPageState, newMorphItems);
|
||||
|
||||
set(hasUserSelectedCommandState, false);
|
||||
|
||||
setHotkeyScope(CommandMenuHotkeyScope.CommandMenuFocused, {
|
||||
commandMenuOpen: true,
|
||||
});
|
||||
};
|
||||
},
|
||||
[setHotkeyScope],
|
||||
);
|
||||
set(hasUserSelectedCommandState, false);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
goBackFromCommandMenu,
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
|
||||
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { useCommandMenuHistory } from '@/command-menu/hooks/useCommandMenuHistory';
|
||||
import { useOpenRecordsSearchPageInCommandMenu } from '@/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu';
|
||||
import { useSetGlobalCommandMenuContext } from '@/command-menu/hooks/useSetGlobalCommandMenuContext';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
|
||||
import { useGlobalHotkeys } from '@/ui/utilities/hotkey/hooks/useGlobalHotkeys';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
@ -43,7 +43,6 @@ export const useCommandMenuHotKeys = () => {
|
||||
toggleCommandMenu();
|
||||
},
|
||||
true,
|
||||
AppHotkeyScope.CommandMenu,
|
||||
[closeKeyboardShortcutMenu, toggleCommandMenu],
|
||||
);
|
||||
|
||||
@ -53,26 +52,24 @@ export const useCommandMenuHotKeys = () => {
|
||||
openRecordsSearchPage();
|
||||
},
|
||||
false,
|
||||
AppHotkeyScope.SearchRecords,
|
||||
[openRecordsSearchPage],
|
||||
{
|
||||
ignoreModifiers: true,
|
||||
},
|
||||
);
|
||||
|
||||
useGlobalHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [Key.Escape],
|
||||
callback: () => {
|
||||
goBackFromCommandMenu();
|
||||
},
|
||||
true,
|
||||
CommandMenuHotkeyScope.CommandMenuFocused,
|
||||
[goBackFromCommandMenu],
|
||||
);
|
||||
focusId: SIDE_PANEL_FOCUS_ID,
|
||||
dependencies: [goBackFromCommandMenu],
|
||||
});
|
||||
|
||||
useGlobalHotkeys(
|
||||
[Key.Backspace, Key.Delete],
|
||||
() => {
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [Key.Backspace, Key.Delete],
|
||||
callback: () => {
|
||||
if (isNonEmptyString(commandMenuSearch)) {
|
||||
return;
|
||||
}
|
||||
@ -91,17 +88,13 @@ export const useCommandMenuHotKeys = () => {
|
||||
goBackFromCommandMenu();
|
||||
}
|
||||
},
|
||||
true,
|
||||
CommandMenuHotkeyScope.CommandMenuFocused,
|
||||
[
|
||||
focusId: SIDE_PANEL_FOCUS_ID,
|
||||
dependencies: [
|
||||
commandMenuPage,
|
||||
commandMenuSearch,
|
||||
contextStoreTargetedRecordsRuleComponent,
|
||||
goBackFromCommandMenu,
|
||||
setGlobalCommandMenuContext,
|
||||
],
|
||||
{
|
||||
preventDefault: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@ -10,7 +10,6 @@ import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState
|
||||
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
|
||||
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
|
||||
@ -61,13 +60,9 @@ export const useNavigateCommandMenu = () => {
|
||||
type: FocusComponentType.SIDE_PANEL,
|
||||
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
||||
},
|
||||
hotkeyScope: {
|
||||
scope: CommandMenuHotkeyScope.CommandMenuFocused,
|
||||
customScopes: {
|
||||
commandMenuOpen: true,
|
||||
},
|
||||
globalHotkeysConfig: {
|
||||
enableGlobalHotkeysConflictingWithKeyboard: false,
|
||||
},
|
||||
memoizeKey: COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
||||
});
|
||||
|
||||
copyContextStoreStates({
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
export enum CommandMenuHotkeyScope {
|
||||
CommandMenuFocused = 'command-menu-focused',
|
||||
}
|
||||
Reference in New Issue
Block a user