Replace hotkey scopes by focus stack (Part 5 - Form field Inputs, Pages, Dialog ...) (#13106)
# Replace hotkey scopes by focus stack (Part 5 - Form field Inputs, Pages, Dialog ...) This PR is the 5th 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 In this part, all the last components using hotkey scopes were refactored. In the 6th and final part of this refactoring we will be able to completely remove the hotkey scopes from the codebase.
This commit is contained in:
@ -1,26 +0,0 @@
|
|||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated This hook uses useEffect
|
|
||||||
* Use event handlers and imperative code to manage hotkey scope changes.
|
|
||||||
*/
|
|
||||||
export const useHotkeyScopeOnMount = (hotkeyScope: string) => {
|
|
||||||
const {
|
|
||||||
goBackToPreviousHotkeyScope,
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
|
||||||
} = usePreviousHotkeyScope();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope({
|
|
||||||
scope: hotkeyScope,
|
|
||||||
});
|
|
||||||
return () => {
|
|
||||||
goBackToPreviousHotkeyScope();
|
|
||||||
};
|
|
||||||
}, [
|
|
||||||
hotkeyScope,
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
|
||||||
goBackToPreviousHotkeyScope,
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilCallback, useRecoilState } from 'recoil';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
|
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
|
||||||
@ -11,7 +11,6 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
|||||||
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
|
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
|
||||||
import { isFieldValueReadOnly } from '@/object-record/record-field/utils/isFieldValueReadOnly';
|
import { isFieldValueReadOnly } from '@/object-record/record-field/utils/isFieldValueReadOnly';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
|
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
@ -24,9 +23,6 @@ import { Task } from '@/activities/types/Task';
|
|||||||
import { filterAttachmentsToRestore } from '@/activities/utils/filterAttachmentsToRestore';
|
import { filterAttachmentsToRestore } from '@/activities/utils/filterAttachmentsToRestore';
|
||||||
import { getActivityAttachmentIdsToDelete } from '@/activities/utils/getActivityAttachmentIdsToDelete';
|
import { getActivityAttachmentIdsToDelete } from '@/activities/utils/getActivityAttachmentIdsToDelete';
|
||||||
import { getActivityAttachmentPathsToRestore } from '@/activities/utils/getActivityAttachmentPathsToRestore';
|
import { getActivityAttachmentPathsToRestore } from '@/activities/utils/getActivityAttachmentPathsToRestore';
|
||||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
|
||||||
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
|
|
||||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
|
||||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||||
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
|
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
|
||||||
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
|
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
|
||||||
@ -39,6 +35,7 @@ import { BlockEditor } from '@/ui/input/editor/components/BlockEditor';
|
|||||||
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
|
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
|
||||||
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
|
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
|
||||||
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||||
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||||
import type { PartialBlock } from '@blocknote/core';
|
import type { PartialBlock } from '@blocknote/core';
|
||||||
import '@blocknote/core/fonts/inter.css';
|
import '@blocknote/core/fonts/inter.css';
|
||||||
import '@blocknote/mantine/style.css';
|
import '@blocknote/mantine/style.css';
|
||||||
@ -304,60 +301,56 @@ export const ActivityRichTextEditor = ({
|
|||||||
uploadFile: handleEditorBuiltInUploadFile,
|
uploadFile: handleEditorBuiltInUploadFile,
|
||||||
});
|
});
|
||||||
|
|
||||||
const commandMenuPage = useRecoilValue(commandMenuPageState);
|
useHotkeysOnFocusedElement({
|
||||||
|
keys: Key.Escape,
|
||||||
useScopedHotkeys(
|
callback: () => {
|
||||||
Key.Escape,
|
|
||||||
() => {
|
|
||||||
editor.domElement?.blur();
|
editor.domElement?.blur();
|
||||||
},
|
},
|
||||||
ActivityEditorHotkeyScope.ActivityBody,
|
focusId: activityId,
|
||||||
);
|
scope: ActivityEditorHotkeyScope.ActivityBody,
|
||||||
|
dependencies: [editor],
|
||||||
|
});
|
||||||
|
|
||||||
useScopedHotkeys(
|
const handleAllKeys = (keyboardEvent: KeyboardEvent) => {
|
||||||
'*',
|
if (keyboardEvent.key === Key.Escape) {
|
||||||
(keyboardEvent) => {
|
return;
|
||||||
// TODO: remove once stacked hotkeys / focusKeys are in place
|
}
|
||||||
if (commandMenuPage !== CommandMenuPages.EditRichText) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyboardEvent.key === Key.Escape) {
|
const isWritingText =
|
||||||
return;
|
!isNonTextWritingKey(keyboardEvent.key) &&
|
||||||
}
|
!keyboardEvent.ctrlKey &&
|
||||||
|
!keyboardEvent.metaKey;
|
||||||
|
|
||||||
const isWritingText =
|
if (!isWritingText) {
|
||||||
!isNonTextWritingKey(keyboardEvent.key) &&
|
return;
|
||||||
!keyboardEvent.ctrlKey &&
|
}
|
||||||
!keyboardEvent.metaKey;
|
|
||||||
|
|
||||||
if (!isWritingText) {
|
keyboardEvent.preventDefault();
|
||||||
return;
|
keyboardEvent.stopPropagation();
|
||||||
}
|
keyboardEvent.stopImmediatePropagation();
|
||||||
|
|
||||||
keyboardEvent.preventDefault();
|
const newBlockId = v4();
|
||||||
keyboardEvent.stopPropagation();
|
const newBlock = {
|
||||||
keyboardEvent.stopImmediatePropagation();
|
id: newBlockId,
|
||||||
|
type: 'paragraph' as const,
|
||||||
|
content: keyboardEvent.key,
|
||||||
|
};
|
||||||
|
|
||||||
const newBlockId = v4();
|
const lastBlock = editor.document[editor.document.length - 1];
|
||||||
const newBlock = {
|
editor.insertBlocks([newBlock], lastBlock);
|
||||||
id: newBlockId,
|
|
||||||
type: 'paragraph' as const,
|
|
||||||
content: keyboardEvent.key,
|
|
||||||
};
|
|
||||||
|
|
||||||
const lastBlock = editor.document[editor.document.length - 1];
|
editor.setTextCursorPosition(newBlockId, 'end');
|
||||||
editor.insertBlocks([newBlock], lastBlock);
|
editor.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
useHotkeysOnFocusedElement({
|
||||||
|
keys: '*',
|
||||||
|
callback: handleAllKeys,
|
||||||
|
focusId: activityId,
|
||||||
|
scope: ActivityEditorHotkeyScope.ActivityBody,
|
||||||
|
dependencies: [handleAllKeys],
|
||||||
|
});
|
||||||
|
|
||||||
editor.setTextCursorPosition(newBlockId, 'end');
|
|
||||||
editor.focus();
|
|
||||||
},
|
|
||||||
CommandMenuHotkeyScope.CommandMenuFocused,
|
|
||||||
[],
|
|
||||||
{
|
|
||||||
preventDefault: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const { labelIdentifierFieldMetadataItem } = useRecordShowContainerData({
|
const { labelIdentifierFieldMetadataItem } = useRecordShowContainerData({
|
||||||
objectNameSingular: activityObjectNameSingular,
|
objectNameSingular: activityObjectNameSingular,
|
||||||
objectRecordId: activityId,
|
objectRecordId: activityId,
|
||||||
|
|||||||
@ -31,8 +31,10 @@ import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/use
|
|||||||
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
|
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
|
||||||
import { AppBasePath } from '@/types/AppBasePath';
|
import { AppBasePath } from '@/types/AppBasePath';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
|
import { PageFocusId } from '@/types/PageFocusId';
|
||||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
import { useResetFocusStackToFocusItem } from '@/ui/utilities/focus/hooks/useResetFocusStackToFocusItem';
|
||||||
|
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { AnalyticsType } from '~/generated/graphql';
|
import { AnalyticsType } from '~/generated/graphql';
|
||||||
@ -48,8 +50,6 @@ export const PageChangeEffect = () => {
|
|||||||
|
|
||||||
const [previousLocation, setPreviousLocation] = useState('');
|
const [previousLocation, setPreviousLocation] = useState('');
|
||||||
|
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
const pageChangeEffectNavigateLocation =
|
const pageChangeEffectNavigateLocation =
|
||||||
@ -92,6 +92,8 @@ export const PageChangeEffect = () => {
|
|||||||
|
|
||||||
const { closeCommandMenu } = useCommandMenu();
|
const { closeCommandMenu } = useCommandMenu();
|
||||||
|
|
||||||
|
const { resetFocusStackToFocusItem } = useResetFocusStackToFocusItem();
|
||||||
|
|
||||||
const { resetFocusStackToRecordIndex } = useResetFocusStackToRecordIndex();
|
const { resetFocusStackToRecordIndex } = useResetFocusStackToRecordIndex();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -140,55 +142,200 @@ export const PageChangeEffect = () => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case isMatchingLocation(location, AppPath.RecordShowPage): {
|
case isMatchingLocation(location, AppPath.RecordShowPage): {
|
||||||
setHotkeyScope(PageHotkeyScope.RecordShowPage, {
|
resetFocusStackToFocusItem({
|
||||||
goto: true,
|
focusStackItem: {
|
||||||
keyboardShortcutMenu: true,
|
focusId: PageFocusId.RecordShowPage,
|
||||||
searchRecords: true,
|
componentInstance: {
|
||||||
|
componentType: FocusComponentType.PAGE,
|
||||||
|
componentInstanceId: PageFocusId.RecordShowPage,
|
||||||
|
},
|
||||||
|
globalHotkeysConfig: {
|
||||||
|
enableGlobalHotkeysWithModifiers: true,
|
||||||
|
enableGlobalHotkeysConflictingWithKeyboard: true,
|
||||||
|
},
|
||||||
|
memoizeKey: 'global',
|
||||||
|
},
|
||||||
|
hotkeyScope: {
|
||||||
|
scope: PageHotkeyScope.RecordShowPage,
|
||||||
|
customScopes: {
|
||||||
|
goto: true,
|
||||||
|
keyboardShortcutMenu: true,
|
||||||
|
searchRecords: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case isMatchingLocation(location, AppPath.SignInUp): {
|
case isMatchingLocation(location, AppPath.SignInUp): {
|
||||||
setHotkeyScope(PageHotkeyScope.SignInUp);
|
resetFocusStackToFocusItem({
|
||||||
|
focusStackItem: {
|
||||||
|
focusId: PageFocusId.SignInUp,
|
||||||
|
componentInstance: {
|
||||||
|
componentType: FocusComponentType.PAGE,
|
||||||
|
componentInstanceId: PageFocusId.SignInUp,
|
||||||
|
},
|
||||||
|
globalHotkeysConfig: {
|
||||||
|
enableGlobalHotkeysWithModifiers: false,
|
||||||
|
enableGlobalHotkeysConflictingWithKeyboard: false,
|
||||||
|
},
|
||||||
|
memoizeKey: 'global',
|
||||||
|
},
|
||||||
|
hotkeyScope: {
|
||||||
|
scope: PageHotkeyScope.SignInUp,
|
||||||
|
},
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case isMatchingLocation(location, AppPath.Invite): {
|
case isMatchingLocation(location, AppPath.Invite): {
|
||||||
setHotkeyScope(PageHotkeyScope.SignInUp);
|
resetFocusStackToFocusItem({
|
||||||
|
focusStackItem: {
|
||||||
|
focusId: PageFocusId.InviteTeam,
|
||||||
|
componentInstance: {
|
||||||
|
componentType: FocusComponentType.PAGE,
|
||||||
|
componentInstanceId: PageFocusId.InviteTeam,
|
||||||
|
},
|
||||||
|
globalHotkeysConfig: {
|
||||||
|
enableGlobalHotkeysWithModifiers: false,
|
||||||
|
enableGlobalHotkeysConflictingWithKeyboard: false,
|
||||||
|
},
|
||||||
|
memoizeKey: 'global',
|
||||||
|
},
|
||||||
|
hotkeyScope: {
|
||||||
|
scope: PageHotkeyScope.InviteTeam,
|
||||||
|
},
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case isMatchingLocation(location, AppPath.CreateProfile): {
|
case isMatchingLocation(location, AppPath.CreateProfile): {
|
||||||
setHotkeyScope(PageHotkeyScope.CreateProfile);
|
resetFocusStackToFocusItem({
|
||||||
|
focusStackItem: {
|
||||||
|
focusId: PageFocusId.CreateProfile,
|
||||||
|
componentInstance: {
|
||||||
|
componentType: FocusComponentType.PAGE,
|
||||||
|
componentInstanceId: PageFocusId.CreateProfile,
|
||||||
|
},
|
||||||
|
globalHotkeysConfig: {
|
||||||
|
enableGlobalHotkeysWithModifiers: false,
|
||||||
|
enableGlobalHotkeysConflictingWithKeyboard: false,
|
||||||
|
},
|
||||||
|
memoizeKey: 'global',
|
||||||
|
},
|
||||||
|
hotkeyScope: {
|
||||||
|
scope: PageHotkeyScope.CreateProfile,
|
||||||
|
},
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case isMatchingLocation(location, AppPath.CreateWorkspace): {
|
case isMatchingLocation(location, AppPath.CreateWorkspace): {
|
||||||
setHotkeyScope(PageHotkeyScope.CreateWorkspace);
|
resetFocusStackToFocusItem({
|
||||||
|
focusStackItem: {
|
||||||
|
focusId: PageFocusId.CreateWorkspace,
|
||||||
|
componentInstance: {
|
||||||
|
componentType: FocusComponentType.PAGE,
|
||||||
|
componentInstanceId: PageFocusId.CreateWorkspace,
|
||||||
|
},
|
||||||
|
globalHotkeysConfig: {
|
||||||
|
enableGlobalHotkeysWithModifiers: false,
|
||||||
|
enableGlobalHotkeysConflictingWithKeyboard: false,
|
||||||
|
},
|
||||||
|
memoizeKey: 'global',
|
||||||
|
},
|
||||||
|
hotkeyScope: {
|
||||||
|
scope: PageHotkeyScope.CreateWorkspace,
|
||||||
|
},
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case isMatchingLocation(location, AppPath.SyncEmails): {
|
case isMatchingLocation(location, AppPath.SyncEmails): {
|
||||||
setHotkeyScope(PageHotkeyScope.SyncEmail);
|
resetFocusStackToFocusItem({
|
||||||
|
focusStackItem: {
|
||||||
|
focusId: PageFocusId.SyncEmail,
|
||||||
|
componentInstance: {
|
||||||
|
componentType: FocusComponentType.PAGE,
|
||||||
|
componentInstanceId: PageFocusId.SyncEmail,
|
||||||
|
},
|
||||||
|
globalHotkeysConfig: {
|
||||||
|
enableGlobalHotkeysWithModifiers: false,
|
||||||
|
enableGlobalHotkeysConflictingWithKeyboard: false,
|
||||||
|
},
|
||||||
|
memoizeKey: 'global',
|
||||||
|
},
|
||||||
|
hotkeyScope: {
|
||||||
|
scope: PageHotkeyScope.SyncEmail,
|
||||||
|
},
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case isMatchingLocation(location, AppPath.InviteTeam): {
|
case isMatchingLocation(location, AppPath.InviteTeam): {
|
||||||
setHotkeyScope(PageHotkeyScope.InviteTeam);
|
resetFocusStackToFocusItem({
|
||||||
|
focusStackItem: {
|
||||||
|
focusId: PageFocusId.InviteTeam,
|
||||||
|
componentInstance: {
|
||||||
|
componentType: FocusComponentType.PAGE,
|
||||||
|
componentInstanceId: PageFocusId.InviteTeam,
|
||||||
|
},
|
||||||
|
globalHotkeysConfig: {
|
||||||
|
enableGlobalHotkeysWithModifiers: false,
|
||||||
|
enableGlobalHotkeysConflictingWithKeyboard: false,
|
||||||
|
},
|
||||||
|
memoizeKey: 'global',
|
||||||
|
},
|
||||||
|
hotkeyScope: {
|
||||||
|
scope: PageHotkeyScope.InviteTeam,
|
||||||
|
},
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case isMatchingLocation(location, AppPath.PlanRequired): {
|
case isMatchingLocation(location, AppPath.PlanRequired): {
|
||||||
setHotkeyScope(PageHotkeyScope.PlanRequired);
|
resetFocusStackToFocusItem({
|
||||||
|
focusStackItem: {
|
||||||
|
focusId: PageFocusId.PlanRequired,
|
||||||
|
componentInstance: {
|
||||||
|
componentType: FocusComponentType.PAGE,
|
||||||
|
componentInstanceId: PageFocusId.PlanRequired,
|
||||||
|
},
|
||||||
|
globalHotkeysConfig: {
|
||||||
|
enableGlobalHotkeysWithModifiers: false,
|
||||||
|
enableGlobalHotkeysConflictingWithKeyboard: false,
|
||||||
|
},
|
||||||
|
memoizeKey: 'global',
|
||||||
|
},
|
||||||
|
hotkeyScope: {
|
||||||
|
scope: PageHotkeyScope.PlanRequired,
|
||||||
|
},
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case location.pathname.startsWith(AppBasePath.Settings): {
|
case location.pathname.startsWith(AppBasePath.Settings): {
|
||||||
setHotkeyScope(PageHotkeyScope.Settings, {
|
resetFocusStackToFocusItem({
|
||||||
goto: false,
|
focusStackItem: {
|
||||||
keyboardShortcutMenu: false,
|
focusId: PageFocusId.Settings,
|
||||||
commandMenu: false,
|
componentInstance: {
|
||||||
commandMenuOpen: false,
|
componentType: FocusComponentType.PAGE,
|
||||||
searchRecords: false,
|
componentInstanceId: PageFocusId.Settings,
|
||||||
|
},
|
||||||
|
globalHotkeysConfig: {
|
||||||
|
enableGlobalHotkeysWithModifiers: false,
|
||||||
|
enableGlobalHotkeysConflictingWithKeyboard: false,
|
||||||
|
},
|
||||||
|
memoizeKey: 'global',
|
||||||
|
},
|
||||||
|
hotkeyScope: {
|
||||||
|
scope: PageHotkeyScope.Settings,
|
||||||
|
customScopes: {
|
||||||
|
goto: false,
|
||||||
|
keyboardShortcutMenu: false,
|
||||||
|
commandMenu: false,
|
||||||
|
commandMenuOpen: false,
|
||||||
|
searchRecords: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
location,
|
location,
|
||||||
setHotkeyScope,
|
|
||||||
previousLocation,
|
previousLocation,
|
||||||
contextStoreCurrentViewType,
|
contextStoreCurrentViewType,
|
||||||
resetTableSelections,
|
resetTableSelections,
|
||||||
@ -198,6 +345,7 @@ export const PageChangeEffect = () => {
|
|||||||
deactivateBoardCard,
|
deactivateBoardCard,
|
||||||
unfocusBoardCard,
|
unfocusBoardCard,
|
||||||
resetFocusStackToRecordIndex,
|
resetFocusStackToRecordIndex,
|
||||||
|
resetFocusStackToFocusItem,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -4,9 +4,12 @@ import { KEYBOARD_SHORTCUTS_GENERAL } from '@/keyboard-shortcut-menu/constants/K
|
|||||||
import { KEYBOARD_SHORTCUTS_TABLE } from '@/keyboard-shortcut-menu/constants/KeyboardShortcutsTable';
|
import { KEYBOARD_SHORTCUTS_TABLE } from '@/keyboard-shortcut-menu/constants/KeyboardShortcutsTable';
|
||||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||||
|
|
||||||
import { useKeyboardShortcutMenu } from '../hooks/useKeyboardShortcutMenu';
|
import {
|
||||||
|
KEYBOARD_SHORTCUT_MENU_INSTANCE_ID,
|
||||||
|
useKeyboardShortcutMenu,
|
||||||
|
} from '../hooks/useKeyboardShortcutMenu';
|
||||||
|
|
||||||
import { useGlobalHotkeys } from '@/ui/utilities/hotkey/hooks/useGlobalHotkeys';
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||||
import { KeyboardMenuDialog } from './KeyboardShortcutMenuDialog';
|
import { KeyboardMenuDialog } from './KeyboardShortcutMenuDialog';
|
||||||
import { KeyboardMenuGroup } from './KeyboardShortcutMenuGroup';
|
import { KeyboardMenuGroup } from './KeyboardShortcutMenuGroup';
|
||||||
import { KeyboardMenuItem } from './KeyboardShortcutMenuItem';
|
import { KeyboardMenuItem } from './KeyboardShortcutMenuItem';
|
||||||
@ -15,15 +18,15 @@ export const KeyboardShortcutMenuOpenContent = () => {
|
|||||||
const { toggleKeyboardShortcutMenu, closeKeyboardShortcutMenu } =
|
const { toggleKeyboardShortcutMenu, closeKeyboardShortcutMenu } =
|
||||||
useKeyboardShortcutMenu();
|
useKeyboardShortcutMenu();
|
||||||
|
|
||||||
useGlobalHotkeys(
|
useHotkeysOnFocusedElement({
|
||||||
[Key.Escape],
|
keys: [Key.Escape],
|
||||||
() => {
|
callback: () => {
|
||||||
closeKeyboardShortcutMenu();
|
closeKeyboardShortcutMenu();
|
||||||
},
|
},
|
||||||
false,
|
focusId: KEYBOARD_SHORTCUT_MENU_INSTANCE_ID,
|
||||||
AppHotkeyScope.KeyboardShortcutMenuOpen,
|
scope: AppHotkeyScope.KeyboardShortcutMenuOpen,
|
||||||
[closeKeyboardShortcutMenu],
|
dependencies: [closeKeyboardShortcutMenu],
|
||||||
);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -8,18 +8,24 @@ import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
|||||||
|
|
||||||
import { useKeyboardShortcutMenu } from '../useKeyboardShortcutMenu';
|
import { useKeyboardShortcutMenu } from '../useKeyboardShortcutMenu';
|
||||||
|
|
||||||
const mockSetHotkeyScopeAndMemorizePreviousScope = jest.fn();
|
const mockPushFocusItemToFocusStack = jest.fn();
|
||||||
|
const mockRemoveFocusItemFromFocusStackById = jest.fn();
|
||||||
|
|
||||||
const mockGoBackToPreviousHotkeyScope = jest.fn();
|
jest.mock('@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack', () => ({
|
||||||
|
usePushFocusItemToFocusStack: () => ({
|
||||||
jest.mock('@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope', () => ({
|
pushFocusItemToFocusStack: mockPushFocusItemToFocusStack,
|
||||||
usePreviousHotkeyScope: () => ({
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope:
|
|
||||||
mockSetHotkeyScopeAndMemorizePreviousScope,
|
|
||||||
goBackToPreviousHotkeyScope: mockGoBackToPreviousHotkeyScope,
|
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock(
|
||||||
|
'@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById',
|
||||||
|
() => ({
|
||||||
|
useRemoveFocusItemFromFocusStackById: () => ({
|
||||||
|
removeFocusItemFromFocusStackById: mockRemoveFocusItemFromFocusStackById,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const renderHookConfig = () => {
|
const renderHookConfig = () => {
|
||||||
const { result } = renderHook(
|
const { result } = renderHook(
|
||||||
() => {
|
() => {
|
||||||
@ -39,6 +45,10 @@ const renderHookConfig = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('useKeyboardShortcutMenu', () => {
|
describe('useKeyboardShortcutMenu', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
it('should toggle keyboard shortcut menu correctly', async () => {
|
it('should toggle keyboard shortcut menu correctly', async () => {
|
||||||
const { result } = renderHookConfig();
|
const { result } = renderHookConfig();
|
||||||
expect(result.current.toggleKeyboardShortcutMenu).toBeDefined();
|
expect(result.current.toggleKeyboardShortcutMenu).toBeDefined();
|
||||||
@ -48,8 +58,19 @@ describe('useKeyboardShortcutMenu', () => {
|
|||||||
result.current.toggleKeyboardShortcutMenu();
|
result.current.toggleKeyboardShortcutMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockSetHotkeyScopeAndMemorizePreviousScope).toHaveBeenCalledWith({
|
expect(mockPushFocusItemToFocusStack).toHaveBeenCalledWith({
|
||||||
scope: AppHotkeyScope.KeyboardShortcutMenu,
|
focusId: 'keyboard-shortcut-menu',
|
||||||
|
component: {
|
||||||
|
type: 'keyboard-shortcut-menu',
|
||||||
|
instanceId: 'keyboard-shortcut-menu',
|
||||||
|
},
|
||||||
|
globalHotkeysConfig: {
|
||||||
|
enableGlobalHotkeysConflictingWithKeyboard: false,
|
||||||
|
enableGlobalHotkeysWithModifiers: false,
|
||||||
|
},
|
||||||
|
hotkeyScope: {
|
||||||
|
scope: AppHotkeyScope.KeyboardShortcutMenuOpen,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
expect(result.current.isKeyboardShortcutMenuOpened).toBe(true);
|
expect(result.current.isKeyboardShortcutMenuOpened).toBe(true);
|
||||||
|
|
||||||
@ -57,8 +78,8 @@ describe('useKeyboardShortcutMenu', () => {
|
|||||||
result.current.toggleKeyboardShortcutMenu();
|
result.current.toggleKeyboardShortcutMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockSetHotkeyScopeAndMemorizePreviousScope).toHaveBeenCalledWith({
|
expect(mockRemoveFocusItemFromFocusStackById).toHaveBeenCalledWith({
|
||||||
scope: AppHotkeyScope.KeyboardShortcutMenu,
|
focusId: 'keyboard-shortcut-menu',
|
||||||
});
|
});
|
||||||
expect(result.current.isKeyboardShortcutMenuOpened).toBe(false);
|
expect(result.current.isKeyboardShortcutMenuOpened).toBe(false);
|
||||||
});
|
});
|
||||||
@ -69,8 +90,19 @@ describe('useKeyboardShortcutMenu', () => {
|
|||||||
result.current.openKeyboardShortcutMenu();
|
result.current.openKeyboardShortcutMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockSetHotkeyScopeAndMemorizePreviousScope).toHaveBeenCalledWith({
|
expect(mockPushFocusItemToFocusStack).toHaveBeenCalledWith({
|
||||||
scope: AppHotkeyScope.KeyboardShortcutMenu,
|
focusId: 'keyboard-shortcut-menu',
|
||||||
|
component: {
|
||||||
|
type: 'keyboard-shortcut-menu',
|
||||||
|
instanceId: 'keyboard-shortcut-menu',
|
||||||
|
},
|
||||||
|
globalHotkeysConfig: {
|
||||||
|
enableGlobalHotkeysConflictingWithKeyboard: false,
|
||||||
|
enableGlobalHotkeysWithModifiers: false,
|
||||||
|
},
|
||||||
|
hotkeyScope: {
|
||||||
|
scope: AppHotkeyScope.KeyboardShortcutMenuOpen,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
expect(result.current.isKeyboardShortcutMenuOpened).toBe(true);
|
expect(result.current.isKeyboardShortcutMenuOpened).toBe(true);
|
||||||
|
|
||||||
@ -78,7 +110,9 @@ describe('useKeyboardShortcutMenu', () => {
|
|||||||
result.current.closeKeyboardShortcutMenu();
|
result.current.closeKeyboardShortcutMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockGoBackToPreviousHotkeyScope).toHaveBeenCalled();
|
expect(mockRemoveFocusItemFromFocusStackById).toHaveBeenCalledWith({
|
||||||
|
focusId: 'keyboard-shortcut-menu',
|
||||||
|
});
|
||||||
expect(result.current.isKeyboardShortcutMenuOpened).toBe(false);
|
expect(result.current.isKeyboardShortcutMenuOpened).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,25 +1,39 @@
|
|||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
|
||||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||||
|
|
||||||
|
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
|
||||||
|
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
|
||||||
|
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||||
import { isKeyboardShortcutMenuOpenedState } from '../states/isKeyboardShortcutMenuOpenedState';
|
import { isKeyboardShortcutMenuOpenedState } from '../states/isKeyboardShortcutMenuOpenedState';
|
||||||
|
|
||||||
|
export const KEYBOARD_SHORTCUT_MENU_INSTANCE_ID = 'keyboard-shortcut-menu';
|
||||||
|
|
||||||
export const useKeyboardShortcutMenu = () => {
|
export const useKeyboardShortcutMenu = () => {
|
||||||
const {
|
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
const { removeFocusItemFromFocusStackById } =
|
||||||
goBackToPreviousHotkeyScope,
|
useRemoveFocusItemFromFocusStackById();
|
||||||
} = usePreviousHotkeyScope();
|
|
||||||
|
|
||||||
const openKeyboardShortcutMenu = useRecoilCallback(
|
const openKeyboardShortcutMenu = useRecoilCallback(
|
||||||
({ set }) =>
|
({ set }) =>
|
||||||
() => {
|
() => {
|
||||||
set(isKeyboardShortcutMenuOpenedState, true);
|
set(isKeyboardShortcutMenuOpenedState, true);
|
||||||
setHotkeyScopeAndMemorizePreviousScope({
|
pushFocusItemToFocusStack({
|
||||||
scope: AppHotkeyScope.KeyboardShortcutMenu,
|
focusId: KEYBOARD_SHORTCUT_MENU_INSTANCE_ID,
|
||||||
|
component: {
|
||||||
|
type: FocusComponentType.KEYBOARD_SHORTCUT_MENU,
|
||||||
|
instanceId: KEYBOARD_SHORTCUT_MENU_INSTANCE_ID,
|
||||||
|
},
|
||||||
|
globalHotkeysConfig: {
|
||||||
|
enableGlobalHotkeysConflictingWithKeyboard: false,
|
||||||
|
enableGlobalHotkeysWithModifiers: false,
|
||||||
|
},
|
||||||
|
hotkeyScope: {
|
||||||
|
scope: AppHotkeyScope.KeyboardShortcutMenuOpen,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[setHotkeyScopeAndMemorizePreviousScope],
|
[pushFocusItemToFocusStack],
|
||||||
);
|
);
|
||||||
|
|
||||||
const closeKeyboardShortcutMenu = useRecoilCallback(
|
const closeKeyboardShortcutMenu = useRecoilCallback(
|
||||||
@ -31,10 +45,12 @@ export const useKeyboardShortcutMenu = () => {
|
|||||||
|
|
||||||
if (isKeyboardShortcutMenuOpened) {
|
if (isKeyboardShortcutMenuOpened) {
|
||||||
set(isKeyboardShortcutMenuOpenedState, false);
|
set(isKeyboardShortcutMenuOpenedState, false);
|
||||||
goBackToPreviousHotkeyScope();
|
removeFocusItemFromFocusStackById({
|
||||||
|
focusId: KEYBOARD_SHORTCUT_MENU_INSTANCE_ID,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[goBackToPreviousHotkeyScope],
|
[removeFocusItemFromFocusStackById],
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleKeyboardShortcutMenu = useRecoilCallback(
|
const toggleKeyboardShortcutMenu = useRecoilCallback(
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import { Key } from 'ts-key-enum';
|
|||||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||||
import { useRecordBoardCardNavigation } from '@/object-record/record-board/hooks/useRecordBoardCardNavigation';
|
import { useRecordBoardCardNavigation } from '@/object-record/record-board/hooks/useRecordBoardCardNavigation';
|
||||||
import { useRecordBoardSelectAllHotkeys } from '@/object-record/record-board/hooks/useRecordBoardSelectAllHotkeys';
|
import { useRecordBoardSelectAllHotkeys } from '@/object-record/record-board/hooks/useRecordBoardSelectAllHotkeys';
|
||||||
import { RECORD_INDEX_FOCUS_ID } from '@/object-record/record-index/constants/RecordIndexFocusId';
|
|
||||||
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
|
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
|
||||||
|
import { PageFocusId } from '@/types/PageFocusId';
|
||||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||||
|
|
||||||
export const RecordBoardHotkeyEffect = () => {
|
export const RecordBoardHotkeyEffect = () => {
|
||||||
@ -18,7 +18,7 @@ export const RecordBoardHotkeyEffect = () => {
|
|||||||
callback: () => {
|
callback: () => {
|
||||||
move('down');
|
move('down');
|
||||||
},
|
},
|
||||||
focusId: RECORD_INDEX_FOCUS_ID,
|
focusId: PageFocusId.RecordIndex,
|
||||||
scope: RecordIndexHotkeyScope.RecordIndex,
|
scope: RecordIndexHotkeyScope.RecordIndex,
|
||||||
dependencies: [move],
|
dependencies: [move],
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,65 +1,28 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
import { useCallback, useRef } from 'react';
|
|
||||||
|
|
||||||
import { useRecordGroupActions } from '@/object-record/record-group/hooks/useRecordGroupActions';
|
import { useRecordGroupActions } from '@/object-record/record-group/hooks/useRecordGroupActions';
|
||||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
|
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
|
||||||
import { ViewType } from '@/views/types/ViewType';
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
import { MenuItem } from 'twenty-ui/navigation';
|
import { MenuItem } from 'twenty-ui/navigation';
|
||||||
|
|
||||||
const StyledMenuContainer = styled.div`
|
export const RecordBoardColumnDropdownMenu = () => {
|
||||||
position: absolute;
|
|
||||||
top: ${({ theme }) => theme.spacing(10)};
|
|
||||||
width: 200px;
|
|
||||||
z-index: 1;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type RecordBoardColumnDropdownMenuProps = {
|
|
||||||
onClose: () => void;
|
|
||||||
onDelete?: (id: string) => void;
|
|
||||||
stageId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RecordBoardColumnDropdownMenu = ({
|
|
||||||
onClose,
|
|
||||||
}: RecordBoardColumnDropdownMenuProps) => {
|
|
||||||
const boardColumnMenuRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const recordGroupActions = useRecordGroupActions({
|
const recordGroupActions = useRecordGroupActions({
|
||||||
viewType: ViewType.Kanban,
|
viewType: ViewType.Kanban,
|
||||||
});
|
});
|
||||||
|
|
||||||
const closeMenu = useCallback(() => {
|
|
||||||
onClose();
|
|
||||||
}, [onClose]);
|
|
||||||
|
|
||||||
useListenClickOutside({
|
|
||||||
refs: [boardColumnMenuRef],
|
|
||||||
callback: closeMenu,
|
|
||||||
listenerId: 'record-board-column-dropdown-menu',
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledMenuContainer ref={boardColumnMenuRef}>
|
<DropdownContent selectDisabled>
|
||||||
<OverlayContainer>
|
<DropdownMenuItemsContainer>
|
||||||
<DropdownContent selectDisabled>
|
{recordGroupActions.map((action) => (
|
||||||
<DropdownMenuItemsContainer>
|
<MenuItem
|
||||||
{recordGroupActions.map((action) => (
|
key={action.id}
|
||||||
<MenuItem
|
onClick={() => {
|
||||||
key={action.id}
|
action.callback();
|
||||||
onClick={() => {
|
}}
|
||||||
action.callback();
|
LeftIcon={action.icon}
|
||||||
closeMenu();
|
text={action.label}
|
||||||
}}
|
/>
|
||||||
LeftIcon={action.icon}
|
))}
|
||||||
text={action.label}
|
</DropdownMenuItemsContainer>
|
||||||
/>
|
</DropdownContent>
|
||||||
))}
|
|
||||||
</DropdownMenuItemsContainer>
|
|
||||||
</DropdownContent>
|
|
||||||
</OverlayContainer>
|
|
||||||
</StyledMenuContainer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,10 +7,10 @@ import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/reco
|
|||||||
import { RecordBoardColumnHeaderAggregateDropdown } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdown';
|
import { RecordBoardColumnHeaderAggregateDropdown } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdown';
|
||||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||||
import { useAggregateRecordsForRecordBoardColumn } from '@/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn';
|
import { useAggregateRecordsForRecordBoardColumn } from '@/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn';
|
||||||
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
|
|
||||||
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
|
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
|
import { useToggleDropdown } from '@/ui/layout/dropdown/hooks/useToggleDropdown';
|
||||||
import { Tag } from 'twenty-ui/components';
|
import { Tag } from 'twenty-ui/components';
|
||||||
import { IconDotsVertical, IconPlus } from 'twenty-ui/display';
|
import { IconDotsVertical, IconPlus } from 'twenty-ui/display';
|
||||||
import { LightIconButton } from 'twenty-ui/input';
|
import { LightIconButton } from 'twenty-ui/input';
|
||||||
@ -66,32 +66,11 @@ const StyledTag = styled(Tag)`
|
|||||||
|
|
||||||
export const RecordBoardColumnHeader = () => {
|
export const RecordBoardColumnHeader = () => {
|
||||||
const { columnDefinition } = useContext(RecordBoardColumnContext);
|
const { columnDefinition } = useContext(RecordBoardColumnContext);
|
||||||
const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false);
|
|
||||||
const [isHeaderHovered, setIsHeaderHovered] = useState(false);
|
const [isHeaderHovered, setIsHeaderHovered] = useState(false);
|
||||||
|
|
||||||
const { objectMetadataItem, selectFieldMetadataItem } =
|
const { objectMetadataItem, selectFieldMetadataItem } =
|
||||||
useContext(RecordBoardContext);
|
useContext(RecordBoardContext);
|
||||||
|
|
||||||
const {
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
|
||||||
goBackToPreviousHotkeyScope,
|
|
||||||
} = usePreviousHotkeyScope();
|
|
||||||
|
|
||||||
const handleBoardColumnMenuOpen = () => {
|
|
||||||
setIsBoardColumnMenuOpen(true);
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope({
|
|
||||||
scope: RecordBoardColumnHotkeyScope.BoardColumn,
|
|
||||||
customScopes: {
|
|
||||||
goto: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBoardColumnMenuClose = () => {
|
|
||||||
goBackToPreviousHotkeyScope();
|
|
||||||
setIsBoardColumnMenuOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const { aggregateValue, aggregateLabel } =
|
const { aggregateValue, aggregateLabel } =
|
||||||
useAggregateRecordsForRecordBoardColumn();
|
useAggregateRecordsForRecordBoardColumn();
|
||||||
|
|
||||||
@ -105,6 +84,10 @@ export const RecordBoardColumnHeader = () => {
|
|||||||
objectMetadataItem: objectMetadataItem,
|
objectMetadataItem: objectMetadataItem,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { toggleDropdown } = useToggleDropdown();
|
||||||
|
|
||||||
|
const dropdownId = `record-board-column-dropdown-${columnDefinition.id}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledColumn>
|
<StyledColumn>
|
||||||
<StyledHeader
|
<StyledHeader
|
||||||
@ -113,25 +96,36 @@ export const RecordBoardColumnHeader = () => {
|
|||||||
>
|
>
|
||||||
<StyledHeaderContainer>
|
<StyledHeaderContainer>
|
||||||
<StyledLeftContainer>
|
<StyledLeftContainer>
|
||||||
<StyledTag
|
<Dropdown
|
||||||
onClick={handleBoardColumnMenuOpen}
|
dropdownId={dropdownId}
|
||||||
variant={
|
dropdownPlacement="bottom-start"
|
||||||
columnDefinition.type === RecordGroupDefinitionType.Value
|
dropdownOffset={{
|
||||||
? 'solid'
|
x: 0,
|
||||||
: 'outline'
|
y: 10,
|
||||||
}
|
}}
|
||||||
color={
|
clickableComponent={
|
||||||
columnDefinition.type === RecordGroupDefinitionType.Value
|
<StyledTag
|
||||||
? columnDefinition.color
|
variant={
|
||||||
: 'transparent'
|
columnDefinition.type === RecordGroupDefinitionType.Value
|
||||||
}
|
? 'solid'
|
||||||
text={columnDefinition.title}
|
: 'outline'
|
||||||
weight={
|
}
|
||||||
columnDefinition.type === RecordGroupDefinitionType.Value
|
color={
|
||||||
? 'regular'
|
columnDefinition.type === RecordGroupDefinitionType.Value
|
||||||
: 'medium'
|
? columnDefinition.color
|
||||||
|
: 'transparent'
|
||||||
|
}
|
||||||
|
text={columnDefinition.title}
|
||||||
|
weight={
|
||||||
|
columnDefinition.type === RecordGroupDefinitionType.Value
|
||||||
|
? 'regular'
|
||||||
|
: 'medium'
|
||||||
|
}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
|
dropdownComponents={<RecordBoardColumnDropdownMenu />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RecordBoardColumnHeaderAggregateDropdown
|
<RecordBoardColumnHeaderAggregateDropdown
|
||||||
aggregateValue={aggregateValue}
|
aggregateValue={aggregateValue}
|
||||||
dropdownId={`record-board-column-aggregate-dropdown-${columnDefinition.id}`}
|
dropdownId={`record-board-column-aggregate-dropdown-${columnDefinition.id}`}
|
||||||
@ -145,7 +139,11 @@ export const RecordBoardColumnHeader = () => {
|
|||||||
<LightIconButton
|
<LightIconButton
|
||||||
accent="tertiary"
|
accent="tertiary"
|
||||||
Icon={IconDotsVertical}
|
Icon={IconDotsVertical}
|
||||||
onClick={handleBoardColumnMenuOpen}
|
onClick={() => {
|
||||||
|
toggleDropdown({
|
||||||
|
dropdownComponentInstanceIdFromProps: dropdownId,
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{hasObjectUpdatePermissions && (
|
{hasObjectUpdatePermissions && (
|
||||||
<LightIconButton
|
<LightIconButton
|
||||||
@ -164,12 +162,6 @@ export const RecordBoardColumnHeader = () => {
|
|||||||
</StyledRightContainer>
|
</StyledRightContainer>
|
||||||
</StyledHeaderContainer>
|
</StyledHeaderContainer>
|
||||||
</StyledHeader>
|
</StyledHeader>
|
||||||
{isBoardColumnMenuOpen && (
|
|
||||||
<RecordBoardColumnDropdownMenu
|
|
||||||
onClose={handleBoardColumnMenuClose}
|
|
||||||
stageId={columnDefinition.id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</StyledColumn>
|
</StyledColumn>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,36 +1,24 @@
|
|||||||
import { Key } from 'ts-key-enum';
|
|
||||||
|
|
||||||
import { useDropdownContextStateManagement } from '@/dropdown-context-state-management/hooks/useDropdownContextStateManagement';
|
import { useDropdownContextStateManagement } from '@/dropdown-context-state-management/hooks/useDropdownContextStateManagement';
|
||||||
import {
|
import {
|
||||||
RecordBoardColumnHeaderAggregateDropdownContext,
|
RecordBoardColumnHeaderAggregateDropdownContext,
|
||||||
RecordBoardColumnHeaderAggregateDropdownContextValue,
|
RecordBoardColumnHeaderAggregateDropdownContextValue,
|
||||||
} from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
|
} from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
|
||||||
|
|
||||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
|
||||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { MenuItem } from 'twenty-ui/navigation';
|
import { MenuItem } from 'twenty-ui/navigation';
|
||||||
|
|
||||||
export const RecordBoardColumnHeaderAggregateDropdownMenuContent = () => {
|
export const RecordBoardColumnHeaderAggregateDropdownMenuContent = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const { onContentChange, closeDropdown } =
|
const { onContentChange } =
|
||||||
useDropdownContextStateManagement<RecordBoardColumnHeaderAggregateDropdownContextValue>(
|
useDropdownContextStateManagement<RecordBoardColumnHeaderAggregateDropdownContextValue>(
|
||||||
{
|
{
|
||||||
context: RecordBoardColumnHeaderAggregateDropdownContext,
|
context: RecordBoardColumnHeaderAggregateDropdownContext,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
[Key.Escape],
|
|
||||||
() => {
|
|
||||||
closeDropdown();
|
|
||||||
},
|
|
||||||
TableOptionsHotkeyScope.Dropdown,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownContent>
|
<DropdownContent>
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
|
|||||||
@ -10,18 +10,15 @@ import { getAggregateOperationLabel } from '@/object-record/record-board/record-
|
|||||||
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
||||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||||
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
|
||||||
import { AvailableFieldsForAggregateOperation } from '@/object-record/types/AvailableFieldsForAggregateOperation';
|
import { AvailableFieldsForAggregateOperation } from '@/object-record/types/AvailableFieldsForAggregateOperation';
|
||||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import { useUpdateViewAggregate } from '@/views/hooks/useUpdateViewAggregate';
|
import { useUpdateViewAggregate } from '@/views/hooks/useUpdateViewAggregate';
|
||||||
import isEmpty from 'lodash.isempty';
|
import isEmpty from 'lodash.isempty';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { Key } from 'ts-key-enum';
|
|
||||||
import { IconCheck, IconChevronLeft } from 'twenty-ui/display';
|
import { IconCheck, IconChevronLeft } from 'twenty-ui/display';
|
||||||
|
|
||||||
export const RecordBoardColumnHeaderAggregateDropdownOptionsContent = ({
|
export const RecordBoardColumnHeaderAggregateDropdownOptionsContent = ({
|
||||||
@ -38,14 +35,6 @@ export const RecordBoardColumnHeaderAggregateDropdownOptionsContent = ({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
[Key.Escape],
|
|
||||||
() => {
|
|
||||||
closeDropdown();
|
|
||||||
},
|
|
||||||
TableOptionsHotkeyScope.Dropdown,
|
|
||||||
);
|
|
||||||
|
|
||||||
const setAggregateOperation = useSetRecoilComponentStateV2(
|
const setAggregateOperation = useSetRecoilComponentStateV2(
|
||||||
aggregateOperationComponentState,
|
aggregateOperationComponentState,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -85,6 +85,7 @@ export const FormBooleanFieldInput = ({
|
|||||||
|
|
||||||
<FormFieldInputRowContainer>
|
<FormFieldInputRowContainer>
|
||||||
<FormFieldInputInnerContainer
|
<FormFieldInputInnerContainer
|
||||||
|
formFieldInputInstanceId={instanceId}
|
||||||
hasRightElement={isDefined(VariablePicker) && !readonly}
|
hasRightElement={isDefined(VariablePicker) && !readonly}
|
||||||
>
|
>
|
||||||
{draftValue.type === 'static' ? (
|
{draftValue.type === 'static' ? (
|
||||||
|
|||||||
@ -296,6 +296,7 @@ export const FormDateTimeFieldInput = ({
|
|||||||
|
|
||||||
<FormFieldInputRowContainer>
|
<FormFieldInputRowContainer>
|
||||||
<StyledInputContainer
|
<StyledInputContainer
|
||||||
|
formFieldInputInstanceId={instanceId}
|
||||||
ref={datePickerWrapperRef}
|
ref={datePickerWrapperRef}
|
||||||
hasRightElement={isDefined(VariablePicker) && !readonly}
|
hasRightElement={isDefined(VariablePicker) && !readonly}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { FormFieldInputHotKeyScope } from '@/object-record/record-field/form-types/constants/FormFieldInputHotKeyScope';
|
import { FormFieldInputHotKeyScope } from '@/object-record/record-field/form-types/constants/FormFieldInputHotKeyScope';
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
|
||||||
|
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
|
||||||
|
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { forwardRef, HTMLAttributes, Ref } from 'react';
|
import { forwardRef, HTMLAttributes, Ref } from 'react';
|
||||||
@ -9,9 +11,12 @@ type FormFieldInputInnerContainerProps = {
|
|||||||
multiline?: boolean;
|
multiline?: boolean;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
preventSetHotkeyScope?: boolean;
|
preventSetHotkeyScope?: boolean;
|
||||||
|
formFieldInputInstanceId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledFormFieldInputInnerContainer = styled.div<FormFieldInputInnerContainerProps>`
|
const StyledFormFieldInputInnerContainer = styled.div<
|
||||||
|
Omit<FormFieldInputInnerContainerProps, 'formFieldInputInstanceId'>
|
||||||
|
>`
|
||||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||||
border-top-left-radius: ${({ theme }) => theme.border.radius.sm};
|
border-top-left-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
@ -48,20 +53,25 @@ export const FormFieldInputInnerContainer = forwardRef(
|
|||||||
readonly,
|
readonly,
|
||||||
preventSetHotkeyScope = false,
|
preventSetHotkeyScope = false,
|
||||||
onClick,
|
onClick,
|
||||||
|
formFieldInputInstanceId,
|
||||||
}: HTMLAttributes<HTMLDivElement> & FormFieldInputInnerContainerProps,
|
}: HTMLAttributes<HTMLDivElement> & FormFieldInputInnerContainerProps,
|
||||||
ref: Ref<HTMLDivElement>,
|
ref: Ref<HTMLDivElement>,
|
||||||
) => {
|
) => {
|
||||||
const {
|
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
|
||||||
goBackToPreviousHotkeyScope,
|
const { removeFocusItemFromFocusStackById } =
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
useRemoveFocusItemFromFocusStackById();
|
||||||
} = usePreviousHotkeyScope();
|
|
||||||
|
|
||||||
const handleFocus = (e: React.FocusEvent<HTMLDivElement>) => {
|
const handleFocus = (e: React.FocusEvent<HTMLDivElement>) => {
|
||||||
onFocus?.(e);
|
onFocus?.(e);
|
||||||
|
|
||||||
if (!preventSetHotkeyScope) {
|
if (!preventSetHotkeyScope) {
|
||||||
setHotkeyScopeAndMemorizePreviousScope({
|
pushFocusItemToFocusStack({
|
||||||
scope: FormFieldInputHotKeyScope.FormFieldInput,
|
focusId: formFieldInputInstanceId,
|
||||||
|
component: {
|
||||||
|
type: FocusComponentType.FORM_FIELD_INPUT,
|
||||||
|
instanceId: formFieldInputInstanceId,
|
||||||
|
},
|
||||||
|
hotkeyScope: { scope: FormFieldInputHotKeyScope.FormFieldInput },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -70,7 +80,9 @@ export const FormFieldInputInnerContainer = forwardRef(
|
|||||||
onBlur?.(e);
|
onBlur?.(e);
|
||||||
|
|
||||||
if (!preventSetHotkeyScope) {
|
if (!preventSetHotkeyScope) {
|
||||||
goBackToPreviousHotkeyScope();
|
removeFocusItemFromFocusStackById({
|
||||||
|
focusId: formFieldInputInstanceId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,9 @@ import { MultiSelectInput } from '@/ui/field/input/components/MultiSelectInput';
|
|||||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||||
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
|
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
|
||||||
|
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
|
||||||
|
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||||
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
|
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import { isArray } from '@sniptt/guards';
|
import { isArray } from '@sniptt/guards';
|
||||||
@ -88,10 +90,9 @@ export const FormMultiSelectFieldInput = ({
|
|||||||
const hotkeyScope =
|
const hotkeyScope =
|
||||||
FormMultiSelectFieldInputHotKeyScope.FormMultiSelectFieldInput;
|
FormMultiSelectFieldInputHotKeyScope.FormMultiSelectFieldInput;
|
||||||
|
|
||||||
const {
|
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
const { removeFocusItemFromFocusStackById } =
|
||||||
goBackToPreviousHotkeyScope,
|
useRemoveFocusItemFromFocusStackById();
|
||||||
} = usePreviousHotkeyScope();
|
|
||||||
|
|
||||||
const [draftValue, setDraftValue] = useState<
|
const [draftValue, setDraftValue] = useState<
|
||||||
| {
|
| {
|
||||||
@ -128,8 +129,13 @@ export const FormMultiSelectFieldInput = ({
|
|||||||
editingMode: 'edit',
|
editingMode: 'edit',
|
||||||
});
|
});
|
||||||
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope({
|
pushFocusItemToFocusStack({
|
||||||
scope: hotkeyScope,
|
focusId: instanceId,
|
||||||
|
component: {
|
||||||
|
type: FocusComponentType.FORM_FIELD_INPUT,
|
||||||
|
instanceId,
|
||||||
|
},
|
||||||
|
hotkeyScope: { scope: hotkeyScope },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -157,7 +163,7 @@ export const FormMultiSelectFieldInput = ({
|
|||||||
editingMode: 'view',
|
editingMode: 'view',
|
||||||
});
|
});
|
||||||
|
|
||||||
goBackToPreviousHotkeyScope();
|
removeFocusItemFromFocusStackById({ focusId: instanceId });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVariableTagInsert = (variableName: string) => {
|
const handleVariableTagInsert = (variableName: string) => {
|
||||||
@ -201,6 +207,7 @@ export const FormMultiSelectFieldInput = ({
|
|||||||
|
|
||||||
<FormFieldInputRowContainer>
|
<FormFieldInputRowContainer>
|
||||||
<FormFieldInputInnerContainer
|
<FormFieldInputInnerContainer
|
||||||
|
formFieldInputInstanceId={instanceId}
|
||||||
hasRightElement={isDefined(VariablePicker) && !readonly}
|
hasRightElement={isDefined(VariablePicker) && !readonly}
|
||||||
>
|
>
|
||||||
{draftValue.type === 'static' ? (
|
{draftValue.type === 'static' ? (
|
||||||
|
|||||||
@ -121,6 +121,7 @@ export const FormNumberFieldInput = ({
|
|||||||
|
|
||||||
<FormFieldInputRowContainer>
|
<FormFieldInputRowContainer>
|
||||||
<FormFieldInputInnerContainer
|
<FormFieldInputInnerContainer
|
||||||
|
formFieldInputInstanceId={instanceId}
|
||||||
hasRightElement={isDefined(VariablePicker) && !readonly}
|
hasRightElement={isDefined(VariablePicker) && !readonly}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -71,6 +71,7 @@ export const FormRawJsonFieldInput = ({
|
|||||||
|
|
||||||
<FormFieldInputRowContainer multiline>
|
<FormFieldInputRowContainer multiline>
|
||||||
<FormFieldInputInnerContainer
|
<FormFieldInputInnerContainer
|
||||||
|
formFieldInputInstanceId={instanceId}
|
||||||
hasRightElement={isDefined(VariablePicker) && !readonly}
|
hasRightElement={isDefined(VariablePicker) && !readonly}
|
||||||
multiline
|
multiline
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/
|
|||||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||||
import { Select } from '@/ui/input/components/Select';
|
import { Select } from '@/ui/input/components/Select';
|
||||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||||
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
|
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import { useId, useState } from 'react';
|
import { useId, useState } from 'react';
|
||||||
@ -40,7 +40,8 @@ export const FormSelectFieldInput = ({
|
|||||||
|
|
||||||
const hotkeyScope = InlineCellHotkeyScope.InlineCell;
|
const hotkeyScope = InlineCellHotkeyScope.InlineCell;
|
||||||
|
|
||||||
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
|
const { removeFocusItemFromFocusStackById } =
|
||||||
|
useRemoveFocusItemFromFocusStackById();
|
||||||
|
|
||||||
const [draftValue, setDraftValue] = useState<
|
const [draftValue, setDraftValue] = useState<
|
||||||
| {
|
| {
|
||||||
@ -72,7 +73,7 @@ export const FormSelectFieldInput = ({
|
|||||||
editingMode: 'view',
|
editingMode: 'view',
|
||||||
});
|
});
|
||||||
|
|
||||||
goBackToPreviousHotkeyScope();
|
removeFocusItemFromFocusStackById({ focusId: instanceId });
|
||||||
|
|
||||||
onChange(option);
|
onChange(option);
|
||||||
};
|
};
|
||||||
@ -87,7 +88,7 @@ export const FormSelectFieldInput = ({
|
|||||||
editingMode: 'view',
|
editingMode: 'view',
|
||||||
});
|
});
|
||||||
|
|
||||||
goBackToPreviousHotkeyScope();
|
removeFocusItemFromFocusStackById({ focusId: instanceId });
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedOption = options.find(
|
const selectedOption = options.find(
|
||||||
@ -119,14 +120,13 @@ export const FormSelectFieldInput = ({
|
|||||||
onChange(variableName);
|
onChange(variableName);
|
||||||
};
|
};
|
||||||
|
|
||||||
useScopedHotkeys(
|
useHotkeysOnFocusedElement({
|
||||||
Key.Escape,
|
keys: Key.Escape,
|
||||||
() => {
|
callback: onCancel,
|
||||||
onCancel();
|
focusId: instanceId,
|
||||||
},
|
scope: hotkeyScope,
|
||||||
hotkeyScope,
|
dependencies: [onCancel],
|
||||||
[onCancel],
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormFieldInputContainer>
|
<FormFieldInputContainer>
|
||||||
@ -149,6 +149,7 @@ export const FormSelectFieldInput = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FormFieldInputInnerContainer
|
<FormFieldInputInnerContainer
|
||||||
|
formFieldInputInstanceId={instanceId}
|
||||||
hasRightElement={isDefined(VariablePicker) && !readonly}
|
hasRightElement={isDefined(VariablePicker) && !readonly}
|
||||||
>
|
>
|
||||||
<VariableChipStandalone
|
<VariableChipStandalone
|
||||||
|
|||||||
@ -150,7 +150,11 @@ export const FormSingleRecordPicker = ({
|
|||||||
{label ? <InputLabel>{label}</InputLabel> : null}
|
{label ? <InputLabel>{label}</InputLabel> : null}
|
||||||
<FormFieldInputRowContainer>
|
<FormFieldInputRowContainer>
|
||||||
{disabled ? (
|
{disabled ? (
|
||||||
<StyledFormSelectContainer hasRightElement={false} readonly>
|
<StyledFormSelectContainer
|
||||||
|
formFieldInputInstanceId={componentId}
|
||||||
|
hasRightElement={false}
|
||||||
|
readonly
|
||||||
|
>
|
||||||
<FormSingleRecordFieldChip
|
<FormSingleRecordFieldChip
|
||||||
draftValue={draftValue}
|
draftValue={draftValue}
|
||||||
selectedRecord={selectedRecord}
|
selectedRecord={selectedRecord}
|
||||||
@ -169,6 +173,7 @@ export const FormSingleRecordPicker = ({
|
|||||||
dropdownOffset={{ y: parseInt(theme.spacing(1), 10) }}
|
dropdownOffset={{ y: parseInt(theme.spacing(1), 10) }}
|
||||||
clickableComponent={
|
clickableComponent={
|
||||||
<StyledFormSelectContainer
|
<StyledFormSelectContainer
|
||||||
|
formFieldInputInstanceId={componentId}
|
||||||
hasRightElement={isDefined(VariablePicker) && !disabled}
|
hasRightElement={isDefined(VariablePicker) && !disabled}
|
||||||
preventSetHotkeyScope={true}
|
preventSetHotkeyScope={true}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -71,6 +71,7 @@ export const FormTextFieldInput = ({
|
|||||||
|
|
||||||
<FormFieldInputRowContainer multiline={multiline}>
|
<FormFieldInputRowContainer multiline={multiline}>
|
||||||
<FormFieldInputInnerContainer
|
<FormFieldInputInnerContainer
|
||||||
|
formFieldInputInstanceId={instanceId}
|
||||||
hasRightElement={isDefined(VariablePicker) && !readonly}
|
hasRightElement={isDefined(VariablePicker) && !readonly}
|
||||||
multiline={multiline}
|
multiline={multiline}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
|
|||||||
@ -95,6 +95,7 @@ export const FormUuidFieldInput = ({
|
|||||||
|
|
||||||
<FormFieldInputRowContainer>
|
<FormFieldInputRowContainer>
|
||||||
<FormFieldInputInnerContainer
|
<FormFieldInputInnerContainer
|
||||||
|
formFieldInputInstanceId={instanceId}
|
||||||
hasRightElement={isDefined(VariablePicker) && !readonly}
|
hasRightElement={isDefined(VariablePicker) && !readonly}
|
||||||
>
|
>
|
||||||
{draftValue.type === 'static' ? (
|
{draftValue.type === 'static' ? (
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
export const RECORD_INDEX_FOCUS_ID = 'record-index';
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { RECORD_INDEX_FOCUS_ID } from '@/object-record/record-index/constants/RecordIndexFocusId';
|
|
||||||
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
|
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
|
||||||
|
import { PageFocusId } from '@/types/PageFocusId';
|
||||||
import { useResetFocusStackToFocusItem } from '@/ui/utilities/focus/hooks/useResetFocusStackToFocusItem';
|
import { useResetFocusStackToFocusItem } from '@/ui/utilities/focus/hooks/useResetFocusStackToFocusItem';
|
||||||
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||||
|
|
||||||
@ -9,10 +9,10 @@ export const useResetFocusStackToRecordIndex = () => {
|
|||||||
const resetFocusStackToRecordIndex = () => {
|
const resetFocusStackToRecordIndex = () => {
|
||||||
resetFocusStackToFocusItem({
|
resetFocusStackToFocusItem({
|
||||||
focusStackItem: {
|
focusStackItem: {
|
||||||
focusId: RECORD_INDEX_FOCUS_ID,
|
focusId: PageFocusId.RecordIndex,
|
||||||
componentInstance: {
|
componentInstance: {
|
||||||
componentType: FocusComponentType.PAGE,
|
componentType: FocusComponentType.PAGE,
|
||||||
componentInstanceId: RECORD_INDEX_FOCUS_ID,
|
componentInstanceId: PageFocusId.RecordIndex,
|
||||||
},
|
},
|
||||||
globalHotkeysConfig: {
|
globalHotkeysConfig: {
|
||||||
enableGlobalHotkeysWithModifiers: true,
|
enableGlobalHotkeysWithModifiers: true,
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/Recor
|
|||||||
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
|
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
|
||||||
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
|
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
|
||||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext';
|
import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext';
|
||||||
import { useRecordTable } from '../hooks/useRecordTable';
|
import { useRecordTable } from '../hooks/useRecordTable';
|
||||||
|
|
||||||
@ -48,16 +47,6 @@ export const RecordTableWithWrappers = ({
|
|||||||
selectAllRows();
|
selectAllRows();
|
||||||
};
|
};
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
'ctrl+a,meta+a',
|
|
||||||
handleSelectAllRows,
|
|
||||||
RecordIndexHotkeyScope.RecordIndex,
|
|
||||||
[],
|
|
||||||
{
|
|
||||||
enableOnFormTags: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
useHotkeysOnFocusedElement({
|
useHotkeysOnFocusedElement({
|
||||||
keys: ['ctrl+a,meta+a'],
|
keys: ['ctrl+a,meta+a'],
|
||||||
callback: handleSelectAllRows,
|
callback: handleSelectAllRows,
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { RECORD_INDEX_FOCUS_ID } from '@/object-record/record-index/constants/RecordIndexFocusId';
|
|
||||||
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
|
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
import { isAtLeastOneTableRowSelectedSelector } from '@/object-record/record-table/record-table-row/states/isAtLeastOneTableRowSelectedSelector';
|
import { isAtLeastOneTableRowSelectedSelector } from '@/object-record/record-table/record-table-row/states/isAtLeastOneTableRowSelectedSelector';
|
||||||
|
import { PageFocusId } from '@/types/PageFocusId';
|
||||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ export const RecordTableBodyEscapeHotkeyEffect = () => {
|
|||||||
useHotkeysOnFocusedElement({
|
useHotkeysOnFocusedElement({
|
||||||
keys: [Key.Escape],
|
keys: [Key.Escape],
|
||||||
callback: handleEscape,
|
callback: handleEscape,
|
||||||
focusId: RECORD_INDEX_FOCUS_ID,
|
focusId: PageFocusId.RecordIndex,
|
||||||
scope: RecordIndexHotkeyScope.RecordIndex,
|
scope: RecordIndexHotkeyScope.RecordIndex,
|
||||||
dependencies: [handleEscape],
|
dependencies: [handleEscape],
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { RECORD_INDEX_FOCUS_ID } from '@/object-record/record-index/constants/RecordIndexFocusId';
|
|
||||||
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
|
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
|
||||||
import { useRecordTableRowFocusHotkeys } from '@/object-record/record-table/hooks/useRecordTableRowFocusHotkeys';
|
import { useRecordTableRowFocusHotkeys } from '@/object-record/record-table/hooks/useRecordTableRowFocusHotkeys';
|
||||||
|
import { PageFocusId } from '@/types/PageFocusId';
|
||||||
|
|
||||||
export const RecordTableBodyFocusKeyboardEffect = () => {
|
export const RecordTableBodyFocusKeyboardEffect = () => {
|
||||||
useRecordTableRowFocusHotkeys({
|
useRecordTableRowFocusHotkeys({
|
||||||
focusId: RECORD_INDEX_FOCUS_ID,
|
focusId: PageFocusId.RecordIndex,
|
||||||
hotkeyScope: RecordIndexHotkeyScope.RecordIndex,
|
hotkeyScope: RecordIndexHotkeyScope.RecordIndex,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,11 @@
|
|||||||
import { RecordTableColumnAggregateFooterAggregateOperationMenuItems } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterAggregateOperationMenuItems';
|
import { RecordTableColumnAggregateFooterAggregateOperationMenuItems } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterAggregateOperationMenuItems';
|
||||||
import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext';
|
import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext';
|
||||||
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
|
||||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { Key } from 'ts-key-enum';
|
|
||||||
import { IconChevronLeft } from 'twenty-ui/display';
|
import { IconChevronLeft } from 'twenty-ui/display';
|
||||||
|
|
||||||
export const RecordTableColumnAggregateFooterDropdownSubmenuContent = ({
|
export const RecordTableColumnAggregateFooterDropdownSubmenuContent = ({
|
||||||
@ -19,19 +15,10 @@ export const RecordTableColumnAggregateFooterDropdownSubmenuContent = ({
|
|||||||
aggregateOperations: ExtendedAggregateOperations[];
|
aggregateOperations: ExtendedAggregateOperations[];
|
||||||
title: string;
|
title: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { dropdownId, resetContent } = useContext(
|
const { resetContent } = useContext(
|
||||||
RecordTableColumnAggregateFooterDropdownContext,
|
RecordTableColumnAggregateFooterDropdownContext,
|
||||||
);
|
);
|
||||||
const { closeDropdown } = useCloseDropdown();
|
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
[Key.Escape],
|
|
||||||
() => {
|
|
||||||
resetContent();
|
|
||||||
closeDropdown(dropdownId);
|
|
||||||
},
|
|
||||||
TableOptionsHotkeyScope.Dropdown,
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<DropdownContent>
|
<DropdownContent>
|
||||||
<DropdownMenuHeader
|
<DropdownMenuHeader
|
||||||
|
|||||||
@ -4,14 +4,11 @@ import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record
|
|||||||
import { NON_STANDARD_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/nonStandardAggregateOperationsOptions';
|
import { NON_STANDARD_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/nonStandardAggregateOperationsOptions';
|
||||||
import { useViewFieldAggregateOperation } from '@/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation';
|
import { useViewFieldAggregateOperation } from '@/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation';
|
||||||
import { getAvailableAggregateOperationsForFieldMetadataType } from '@/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType';
|
import { getAvailableAggregateOperationsForFieldMetadataType } from '@/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType';
|
||||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
|
||||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { useContext, useMemo } from 'react';
|
import { useContext, useMemo } from 'react';
|
||||||
import { Key } from 'ts-key-enum';
|
|
||||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||||
import { IconCheck } from 'twenty-ui/display';
|
import { IconCheck } from 'twenty-ui/display';
|
||||||
import { MenuItem } from 'twenty-ui/navigation';
|
import { MenuItem } from 'twenty-ui/navigation';
|
||||||
@ -28,14 +25,6 @@ export const RecordTableColumnAggregateFooterMenuContent = () => {
|
|||||||
const { closeDropdown } = useCloseDropdown();
|
const { closeDropdown } = useCloseDropdown();
|
||||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
[Key.Escape],
|
|
||||||
() => {
|
|
||||||
closeDropdown(dropdownId);
|
|
||||||
},
|
|
||||||
TableOptionsHotkeyScope.Dropdown,
|
|
||||||
);
|
|
||||||
|
|
||||||
const availableAggregateOperation = useMemo(
|
const availableAggregateOperation = useMemo(
|
||||||
() =>
|
() =>
|
||||||
getAvailableAggregateOperationsForFieldMetadataType({
|
getAvailableAggregateOperationsForFieldMetadataType({
|
||||||
|
|||||||
@ -3,14 +3,10 @@ import {
|
|||||||
SettingsServerlessFunctionCodeEditor,
|
SettingsServerlessFunctionCodeEditor,
|
||||||
} from '@/settings/serverless-functions/components/SettingsServerlessFunctionCodeEditor';
|
} from '@/settings/serverless-functions/components/SettingsServerlessFunctionCodeEditor';
|
||||||
import { SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/settings/serverless-functions/constants/SettingsServerlessFunctionTabListComponentId';
|
import { SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/settings/serverless-functions/constants/SettingsServerlessFunctionTabListComponentId';
|
||||||
import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope';
|
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
|
||||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Key } from 'ts-key-enum';
|
|
||||||
import {
|
import {
|
||||||
H2Title,
|
H2Title,
|
||||||
IconGitCommit,
|
IconGitCommit,
|
||||||
@ -19,8 +15,6 @@ import {
|
|||||||
} from 'twenty-ui/display';
|
} from 'twenty-ui/display';
|
||||||
import { Button, CoreEditorHeader } from 'twenty-ui/input';
|
import { Button, CoreEditorHeader } from 'twenty-ui/input';
|
||||||
import { Section } from 'twenty-ui/layout';
|
import { Section } from 'twenty-ui/layout';
|
||||||
import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount';
|
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
|
||||||
|
|
||||||
const StyledTabList = styled(TabList)`
|
const StyledTabList = styled(TabList)`
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
@ -91,19 +85,6 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const navigate = useNavigateSettings();
|
|
||||||
useHotkeyScopeOnMount(
|
|
||||||
SettingsServerlessFunctionHotkeyScope.ServerlessFunctionEditorTab,
|
|
||||||
);
|
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
[Key.Escape],
|
|
||||||
() => {
|
|
||||||
navigate(SettingsPath.ServerlessFunctions);
|
|
||||||
},
|
|
||||||
SettingsServerlessFunctionHotkeyScope.ServerlessFunctionEditorTab,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section>
|
<Section>
|
||||||
<H2Title
|
<H2Title
|
||||||
|
|||||||
@ -2,16 +2,12 @@ import { SettingsServerlessFunctionNewForm } from '@/settings/serverless-functio
|
|||||||
import { SettingsServerlessFunctionTabEnvironmentVariablesSection } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTabEnvironmentVariablesSection';
|
import { SettingsServerlessFunctionTabEnvironmentVariablesSection } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTabEnvironmentVariablesSection';
|
||||||
import { useDeleteOneServerlessFunction } from '@/settings/serverless-functions/hooks/useDeleteOneServerlessFunction';
|
import { useDeleteOneServerlessFunction } from '@/settings/serverless-functions/hooks/useDeleteOneServerlessFunction';
|
||||||
import { ServerlessFunctionFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
|
import { ServerlessFunctionFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
|
||||||
import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope';
|
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { Key } from 'ts-key-enum';
|
|
||||||
import { H2Title } from 'twenty-ui/display';
|
import { H2Title } from 'twenty-ui/display';
|
||||||
import { Button } from 'twenty-ui/input';
|
import { Button } from 'twenty-ui/input';
|
||||||
import { Section } from 'twenty-ui/layout';
|
import { Section } from 'twenty-ui/layout';
|
||||||
import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount';
|
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
|
|
||||||
const DELETE_FUNCTION_MODAL_ID = 'delete-function-modal';
|
const DELETE_FUNCTION_MODAL_ID = 'delete-function-modal';
|
||||||
@ -36,25 +32,6 @@ export const SettingsServerlessFunctionSettingsTab = ({
|
|||||||
navigate(SettingsPath.ServerlessFunctions);
|
navigate(SettingsPath.ServerlessFunctions);
|
||||||
};
|
};
|
||||||
|
|
||||||
useHotkeyScopeOnMount(
|
|
||||||
SettingsServerlessFunctionHotkeyScope.ServerlessFunctionSettingsTab,
|
|
||||||
);
|
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
[Key.Delete],
|
|
||||||
() => {
|
|
||||||
openModal(DELETE_FUNCTION_MODAL_ID);
|
|
||||||
},
|
|
||||||
SettingsServerlessFunctionHotkeyScope.ServerlessFunctionSettingsTab,
|
|
||||||
);
|
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
[Key.Escape],
|
|
||||||
() => {
|
|
||||||
navigate(SettingsPath.ServerlessFunctions);
|
|
||||||
},
|
|
||||||
SettingsServerlessFunctionHotkeyScope.ServerlessFunctionSettingsTab,
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SettingsServerlessFunctionNewForm
|
<SettingsServerlessFunctionNewForm
|
||||||
|
|||||||
@ -1,15 +1,9 @@
|
|||||||
import { ServerlessFunctionExecutionResult } from '@/serverless-functions/components/ServerlessFunctionExecutionResult';
|
import { ServerlessFunctionExecutionResult } from '@/serverless-functions/components/ServerlessFunctionExecutionResult';
|
||||||
import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope';
|
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState';
|
import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { Key } from 'ts-key-enum';
|
|
||||||
import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount';
|
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
|
||||||
import { Button, CodeEditor, CoreEditorHeader } from 'twenty-ui/input';
|
|
||||||
import { H2Title, IconPlayerPlay } from 'twenty-ui/display';
|
import { H2Title, IconPlayerPlay } from 'twenty-ui/display';
|
||||||
|
import { Button, CodeEditor, CoreEditorHeader } from 'twenty-ui/input';
|
||||||
import { Section } from 'twenty-ui/layout';
|
import { Section } from 'twenty-ui/layout';
|
||||||
|
|
||||||
const StyledInputsContainer = styled.div`
|
const StyledInputsContainer = styled.div`
|
||||||
@ -40,19 +34,6 @@ export const SettingsServerlessFunctionTestTab = ({
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigate = useNavigateSettings();
|
|
||||||
useHotkeyScopeOnMount(
|
|
||||||
SettingsServerlessFunctionHotkeyScope.ServerlessFunctionTestTab,
|
|
||||||
);
|
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
[Key.Escape],
|
|
||||||
() => {
|
|
||||||
navigate(SettingsPath.ServerlessFunctions);
|
|
||||||
},
|
|
||||||
SettingsServerlessFunctionHotkeyScope.ServerlessFunctionTestTab,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section>
|
<Section>
|
||||||
<H2Title
|
<H2Title
|
||||||
|
|||||||
11
packages/twenty-front/src/modules/types/PageFocusId.ts
Normal file
11
packages/twenty-front/src/modules/types/PageFocusId.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export enum PageFocusId {
|
||||||
|
Settings = 'settings',
|
||||||
|
CreateWorkspace = 'create-workspace',
|
||||||
|
SignInUp = 'sign-in-up',
|
||||||
|
CreateProfile = 'create-profile',
|
||||||
|
InviteTeam = 'invite-team',
|
||||||
|
SyncEmail = 'sync-email',
|
||||||
|
PlanRequired = 'plan-required',
|
||||||
|
RecordShowPage = 'record-show-page',
|
||||||
|
RecordIndex = 'record-index',
|
||||||
|
}
|
||||||
@ -2,11 +2,11 @@ import styled from '@emotion/styled';
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
|
|
||||||
import { DIALOG_CLICK_OUTSIDE_ID } from '@/ui/feedback/dialog-manager/constants/DialogClickOutsideId';
|
import { DIALOG_CLICK_OUTSIDE_ID } from '@/ui/feedback/dialog-manager/constants/DialogClickOutsideId';
|
||||||
|
import { DIALOG_FOCUS_ID } from '@/ui/feedback/dialog-manager/constants/DialogFocusId';
|
||||||
import { DIALOG_LISTENER_ID } from '@/ui/feedback/dialog-manager/constants/DialogListenerId';
|
import { DIALOG_LISTENER_ID } from '@/ui/feedback/dialog-manager/constants/DialogListenerId';
|
||||||
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
|
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
|
||||||
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
@ -96,31 +96,37 @@ export const Dialog = ({
|
|||||||
closed: { y: '50vh' },
|
closed: { y: '50vh' },
|
||||||
};
|
};
|
||||||
|
|
||||||
useScopedHotkeys(
|
const handleEnter = (event: KeyboardEvent) => {
|
||||||
Key.Enter,
|
const confirmButton = buttons.find((button) => button.role === 'confirm');
|
||||||
(event: KeyboardEvent) => {
|
|
||||||
const confirmButton = buttons.find((button) => button.role === 'confirm');
|
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (isDefined(confirmButton)) {
|
if (isDefined(confirmButton)) {
|
||||||
confirmButton?.onClick?.(event);
|
confirmButton?.onClick?.(event);
|
||||||
onClose?.();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DialogHotkeyScope.Dialog,
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
Key.Escape,
|
|
||||||
(event: KeyboardEvent) => {
|
|
||||||
event.preventDefault();
|
|
||||||
onClose?.();
|
onClose?.();
|
||||||
},
|
}
|
||||||
DialogHotkeyScope.Dialog,
|
};
|
||||||
[],
|
|
||||||
);
|
const handleEscape = (event: KeyboardEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
onClose?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
useHotkeysOnFocusedElement({
|
||||||
|
keys: [Key.Enter],
|
||||||
|
callback: handleEnter,
|
||||||
|
focusId: DIALOG_FOCUS_ID,
|
||||||
|
scope: DialogHotkeyScope.Dialog,
|
||||||
|
dependencies: [buttons],
|
||||||
|
});
|
||||||
|
|
||||||
|
useHotkeysOnFocusedElement({
|
||||||
|
keys: [Key.Escape],
|
||||||
|
callback: handleEscape,
|
||||||
|
focusId: DIALOG_FOCUS_ID,
|
||||||
|
scope: DialogHotkeyScope.Dialog,
|
||||||
|
dependencies: [handleEscape],
|
||||||
|
});
|
||||||
|
|
||||||
const dialogRef = useRef<HTMLDivElement>(null);
|
const dialogRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
|||||||
@ -1,26 +1,34 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { DIALOG_FOCUS_ID } from '@/ui/feedback/dialog-manager/constants/DialogFocusId';
|
||||||
|
|
||||||
import { DIALOG_MANAGER_HOTKEY_SCOPE_MEMOIZE_KEY } from '@/ui/feedback/dialog-manager/constants/DialogManagerHotkeyScopeMemoizeKey';
|
import { DIALOG_MANAGER_HOTKEY_SCOPE_MEMOIZE_KEY } from '@/ui/feedback/dialog-manager/constants/DialogManagerHotkeyScopeMemoizeKey';
|
||||||
|
import { DialogHotkeyScope } from '@/ui/feedback/dialog-manager/types/DialogHotkeyScope';
|
||||||
|
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
|
||||||
|
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||||
import { useDialogManagerScopedStates } from '../hooks/internal/useDialogManagerScopedStates';
|
import { useDialogManagerScopedStates } from '../hooks/internal/useDialogManagerScopedStates';
|
||||||
import { DialogHotkeyScope } from '../types/DialogHotkeyScope';
|
|
||||||
|
|
||||||
export const DialogManagerEffect = () => {
|
export const DialogManagerEffect = () => {
|
||||||
const { dialogInternal } = useDialogManagerScopedStates();
|
const { dialogInternal } = useDialogManagerScopedStates();
|
||||||
|
|
||||||
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
|
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dialogInternal.queue.length === 0) {
|
if (dialogInternal.queue.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope({
|
pushFocusItemToFocusStack({
|
||||||
scope: DialogHotkeyScope.Dialog,
|
focusId: DIALOG_FOCUS_ID,
|
||||||
|
component: {
|
||||||
|
type: FocusComponentType.DIALOG,
|
||||||
|
instanceId: DIALOG_FOCUS_ID,
|
||||||
|
},
|
||||||
|
hotkeyScope: {
|
||||||
|
scope: DialogHotkeyScope.Dialog,
|
||||||
|
},
|
||||||
memoizeKey: DIALOG_MANAGER_HOTKEY_SCOPE_MEMOIZE_KEY,
|
memoizeKey: DIALOG_MANAGER_HOTKEY_SCOPE_MEMOIZE_KEY,
|
||||||
});
|
});
|
||||||
}, [dialogInternal.queue, setHotkeyScopeAndMemorizePreviousScope]);
|
}, [dialogInternal.queue, pushFocusItemToFocusStack]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export const DIALOG_FOCUS_ID = 'dialog';
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
|
||||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||||
|
|
||||||
import { DIALOG_MANAGER_HOTKEY_SCOPE_MEMOIZE_KEY } from '@/ui/feedback/dialog-manager/constants/DialogManagerHotkeyScopeMemoizeKey';
|
import { DIALOG_FOCUS_ID } from '@/ui/feedback/dialog-manager/constants/DialogFocusId';
|
||||||
|
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
|
||||||
import { DialogManagerScopeInternalContext } from '../scopes/scope-internal-context/DialogManagerScopeInternalContext';
|
import { DialogManagerScopeInternalContext } from '../scopes/scope-internal-context/DialogManagerScopeInternalContext';
|
||||||
import { dialogInternalScopedState } from '../states/dialogInternalScopedState';
|
import { dialogInternalScopedState } from '../states/dialogInternalScopedState';
|
||||||
import { DialogOptions } from '../types/DialogOptions';
|
import { DialogOptions } from '../types/DialogOptions';
|
||||||
@ -19,7 +19,8 @@ export const useDialogManager = (props?: useDialogManagerProps) => {
|
|||||||
props?.dialogManagerScopeId,
|
props?.dialogManagerScopeId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
|
const { removeFocusItemFromFocusStackById } =
|
||||||
|
useRemoveFocusItemFromFocusStackById();
|
||||||
|
|
||||||
const closeDialog = useRecoilCallback(
|
const closeDialog = useRecoilCallback(
|
||||||
({ set }) =>
|
({ set }) =>
|
||||||
@ -29,9 +30,9 @@ export const useDialogManager = (props?: useDialogManagerProps) => {
|
|||||||
queue: prevState.queue.filter((dialog) => dialog.id !== id),
|
queue: prevState.queue.filter((dialog) => dialog.id !== id),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
goBackToPreviousHotkeyScope(DIALOG_MANAGER_HOTKEY_SCOPE_MEMOIZE_KEY);
|
removeFocusItemFromFocusStackById({ focusId: DIALOG_FOCUS_ID });
|
||||||
},
|
},
|
||||||
[goBackToPreviousHotkeyScope, scopeId],
|
[removeFocusItemFromFocusStackById, scopeId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const setDialogQueue = useRecoilCallback(
|
const setDialogQueue = useRecoilCallback(
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
|
||||||
|
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
|
||||||
|
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||||
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
import { FocusEvent, useRef } from 'react';
|
import { FocusEvent, useRef } from 'react';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { IconComponent, TablerIconsProps } from 'twenty-ui/display';
|
import { IconComponent, TablerIconsProps } from 'twenty-ui/display';
|
||||||
import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount';
|
|
||||||
|
|
||||||
type NavigationDrawerInputProps = {
|
type NavigationDrawerInputProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -19,6 +21,8 @@ type NavigationDrawerInputProps = {
|
|||||||
hotkeyScope: string;
|
hotkeyScope: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const NAVIGATION_DRAWER_INPUT_FOCUS_ID = 'navigation-drawer-input';
|
||||||
|
|
||||||
export const NavigationDrawerInput = ({
|
export const NavigationDrawerInput = ({
|
||||||
className,
|
className,
|
||||||
placeholder,
|
placeholder,
|
||||||
@ -32,37 +36,64 @@ export const NavigationDrawerInput = ({
|
|||||||
}: NavigationDrawerInputProps) => {
|
}: NavigationDrawerInputProps) => {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
useHotkeyScopeOnMount(hotkeyScope);
|
useHotkeysOnFocusedElement({
|
||||||
|
keys: Key.Escape,
|
||||||
useScopedHotkeys(
|
callback: () => {
|
||||||
[Key.Escape],
|
|
||||||
() => {
|
|
||||||
onCancel(value);
|
onCancel(value);
|
||||||
|
removeFocusItemFromFocusStackById({
|
||||||
|
focusId: NAVIGATION_DRAWER_INPUT_FOCUS_ID,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
hotkeyScope,
|
focusId: NAVIGATION_DRAWER_INPUT_FOCUS_ID,
|
||||||
);
|
scope: hotkeyScope,
|
||||||
|
});
|
||||||
|
|
||||||
useScopedHotkeys(
|
useHotkeysOnFocusedElement({
|
||||||
[Key.Enter],
|
keys: Key.Enter,
|
||||||
() => {
|
callback: () => {
|
||||||
onSubmit(value);
|
onSubmit(value);
|
||||||
|
removeFocusItemFromFocusStackById({
|
||||||
|
focusId: NAVIGATION_DRAWER_INPUT_FOCUS_ID,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
hotkeyScope,
|
focusId: NAVIGATION_DRAWER_INPUT_FOCUS_ID,
|
||||||
);
|
scope: hotkeyScope,
|
||||||
|
});
|
||||||
|
|
||||||
useListenClickOutside({
|
useListenClickOutside({
|
||||||
refs: [inputRef],
|
refs: [inputRef],
|
||||||
callback: (event) => {
|
callback: (event) => {
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
onClickOutside(event, value);
|
onClickOutside(event, value);
|
||||||
|
removeFocusItemFromFocusStackById({
|
||||||
|
focusId: NAVIGATION_DRAWER_INPUT_FOCUS_ID,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
listenerId: 'navigation-drawer-input',
|
listenerId: 'navigation-drawer-input',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
|
||||||
|
const { removeFocusItemFromFocusStackById } =
|
||||||
|
useRemoveFocusItemFromFocusStackById();
|
||||||
|
|
||||||
const handleFocus = (event: FocusEvent<HTMLInputElement>) => {
|
const handleFocus = (event: FocusEvent<HTMLInputElement>) => {
|
||||||
if (isDefined(value)) {
|
if (isDefined(value)) {
|
||||||
event.target.select();
|
event.target.select();
|
||||||
}
|
}
|
||||||
|
pushFocusItemToFocusStack({
|
||||||
|
focusId: NAVIGATION_DRAWER_INPUT_FOCUS_ID,
|
||||||
|
component: {
|
||||||
|
type: FocusComponentType.TEXT_INPUT,
|
||||||
|
instanceId: NAVIGATION_DRAWER_INPUT_FOCUS_ID,
|
||||||
|
},
|
||||||
|
hotkeyScope: { scope: hotkeyScope },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
removeFocusItemFromFocusStackById({
|
||||||
|
focusId: NAVIGATION_DRAWER_INPUT_FOCUS_ID,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -74,6 +105,7 @@ export const NavigationDrawerInput = ({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
|
onBlur={handleBlur}
|
||||||
sizeVariant="md"
|
sizeVariant="md"
|
||||||
fullWidth
|
fullWidth
|
||||||
autoFocus
|
autoFocus
|
||||||
|
|||||||
@ -9,6 +9,9 @@ export enum FocusComponentType {
|
|||||||
RECORD_TABLE_CELL = 'record-table-cell',
|
RECORD_TABLE_CELL = 'record-table-cell',
|
||||||
TEXT_AREA = 'text-area',
|
TEXT_AREA = 'text-area',
|
||||||
TEXT_INPUT = 'text-input',
|
TEXT_INPUT = 'text-input',
|
||||||
|
FORM_FIELD_INPUT = 'form-field-input',
|
||||||
RECORD_BOARD_CARD = 'record-board-card',
|
RECORD_BOARD_CARD = 'record-board-card',
|
||||||
ACTIVITY_RICH_TEXT_EDITOR = 'activity-rich-text-editor',
|
ACTIVITY_RICH_TEXT_EDITOR = 'activity-rich-text-editor',
|
||||||
|
KEYBOARD_SHORTCUT_MENU = 'keyboard-shortcut-menu',
|
||||||
|
DIALOG = 'dialog',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export const DEBUG_HOTKEY_SCOPE = true;
|
export const DEBUG_HOTKEY_SCOPE = false;
|
||||||
|
|||||||
@ -1,32 +0,0 @@
|
|||||||
import { act } from 'react-dom/test-utils';
|
|
||||||
import { fireEvent, renderHook } from '@testing-library/react';
|
|
||||||
import { RecoilRoot } from 'recoil';
|
|
||||||
|
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
|
||||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
|
||||||
|
|
||||||
const hotKeyCallback = jest.fn();
|
|
||||||
|
|
||||||
describe('useScopedHotkeys', () => {
|
|
||||||
it('should work as expected', () => {
|
|
||||||
renderHook(
|
|
||||||
() => {
|
|
||||||
useScopedHotkeys('ctrl+k', hotKeyCallback, AppHotkeyScope.App);
|
|
||||||
|
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
|
||||||
|
|
||||||
setHotkeyScope(AppHotkeyScope.App);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
wrapper: RecoilRoot,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
fireEvent.keyDown(document, { key: 'k', code: 'KeyK', ctrlKey: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(hotKeyCallback).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
import {
|
|
||||||
Hotkey,
|
|
||||||
OptionsOrDependencyArray,
|
|
||||||
} from 'react-hotkeys-hook/dist/types';
|
|
||||||
import { useRecoilCallback } from 'recoil';
|
|
||||||
|
|
||||||
import { logDebug } from '~/utils/logDebug';
|
|
||||||
|
|
||||||
import { DEBUG_HOTKEY_SCOPE } from '../constants/DebugHotkeyScope';
|
|
||||||
import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState';
|
|
||||||
|
|
||||||
export const useScopedHotkeyCallback = (
|
|
||||||
dependencies?: OptionsOrDependencyArray,
|
|
||||||
) => {
|
|
||||||
const dependencyArray = Array.isArray(dependencies) ? dependencies : [];
|
|
||||||
|
|
||||||
return useRecoilCallback(
|
|
||||||
({ snapshot }) =>
|
|
||||||
({
|
|
||||||
callback,
|
|
||||||
hotkeysEvent,
|
|
||||||
keyboardEvent,
|
|
||||||
scope,
|
|
||||||
preventDefault,
|
|
||||||
}: {
|
|
||||||
keyboardEvent: KeyboardEvent;
|
|
||||||
hotkeysEvent: Hotkey;
|
|
||||||
callback: (keyboardEvent: KeyboardEvent, hotkeysEvent: Hotkey) => void;
|
|
||||||
scope: string;
|
|
||||||
preventDefault?: boolean;
|
|
||||||
}) => {
|
|
||||||
const currentHotkeyScopes = snapshot
|
|
||||||
.getLoadable(internalHotkeysEnabledScopesState)
|
|
||||||
.getValue();
|
|
||||||
|
|
||||||
if (!currentHotkeyScopes.includes(scope)) {
|
|
||||||
if (DEBUG_HOTKEY_SCOPE) {
|
|
||||||
logDebug(
|
|
||||||
`DEBUG: %cI can't call hotkey (${
|
|
||||||
hotkeysEvent.keys
|
|
||||||
}) because I'm in scope [${scope}] and the active scopes are : [${currentHotkeyScopes.join(
|
|
||||||
', ',
|
|
||||||
)}]`,
|
|
||||||
'color: gray; ',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DEBUG_HOTKEY_SCOPE) {
|
|
||||||
logDebug(
|
|
||||||
`DEBUG: %cI can call hotkey (${
|
|
||||||
hotkeysEvent.keys
|
|
||||||
}) because I'm in scope [${scope}] and the active scopes are : [${currentHotkeyScopes.join(
|
|
||||||
', ',
|
|
||||||
)}]`,
|
|
||||||
'color: green;',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preventDefault === true) {
|
|
||||||
if (DEBUG_HOTKEY_SCOPE) {
|
|
||||||
logDebug(
|
|
||||||
`DEBUG: %cI prevent default for hotkey (${hotkeysEvent.keys})`,
|
|
||||||
'color: gray;',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
keyboardEvent.stopPropagation();
|
|
||||||
keyboardEvent.preventDefault();
|
|
||||||
keyboardEvent.stopImmediatePropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(keyboardEvent, hotkeysEvent);
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
dependencyArray,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
|
||||||
import { HotkeyCallback, Keys, Options } from 'react-hotkeys-hook/dist/types';
|
|
||||||
import { useRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
import { pendingHotkeyState } from '../states/internal/pendingHotkeysState';
|
|
||||||
import { useScopedHotkeyCallback } from './useScopedHotkeyCallback';
|
|
||||||
|
|
||||||
type UseHotkeysOptionsWithoutBuggyOptions = Omit<Options, 'enabled'>;
|
|
||||||
|
|
||||||
export const useScopedHotkeys = (
|
|
||||||
keys: Keys,
|
|
||||||
callback: HotkeyCallback,
|
|
||||||
scope: string,
|
|
||||||
dependencies?: unknown[],
|
|
||||||
options?: UseHotkeysOptionsWithoutBuggyOptions,
|
|
||||||
) => {
|
|
||||||
const [pendingHotkey, setPendingHotkey] = useRecoilState(pendingHotkeyState);
|
|
||||||
|
|
||||||
const callScopedHotkeyCallback = useScopedHotkeyCallback(dependencies);
|
|
||||||
|
|
||||||
const enableOnContentEditable = isDefined(options?.enableOnContentEditable)
|
|
||||||
? options.enableOnContentEditable
|
|
||||||
: true;
|
|
||||||
|
|
||||||
const enableOnFormTags = isDefined(options?.enableOnFormTags)
|
|
||||||
? options.enableOnFormTags
|
|
||||||
: true;
|
|
||||||
|
|
||||||
const preventDefault = isDefined(options?.preventDefault)
|
|
||||||
? options.preventDefault === true
|
|
||||||
: true;
|
|
||||||
|
|
||||||
const ignoreModifiers = isDefined(options?.ignoreModifiers)
|
|
||||||
? options.ignoreModifiers === true
|
|
||||||
: false;
|
|
||||||
|
|
||||||
return useHotkeys(
|
|
||||||
keys,
|
|
||||||
(keyboardEvent, hotkeysEvent) => {
|
|
||||||
callScopedHotkeyCallback({
|
|
||||||
keyboardEvent,
|
|
||||||
hotkeysEvent,
|
|
||||||
callback: () => {
|
|
||||||
if (!pendingHotkey) {
|
|
||||||
callback(keyboardEvent, hotkeysEvent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setPendingHotkey(null);
|
|
||||||
},
|
|
||||||
scope,
|
|
||||||
preventDefault,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enableOnContentEditable,
|
|
||||||
enableOnFormTags,
|
|
||||||
ignoreModifiers,
|
|
||||||
},
|
|
||||||
dependencies,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import { InputHotkeyScope } from '@/ui/input/types/InputHotkeyScope';
|
import { InputHotkeyScope } from '@/ui/input/types/InputHotkeyScope';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||||
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||||
import { AgentChatMessageRole } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/agent-chat-message-role';
|
import { AgentChatMessageRole } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/agent-chat-message-role';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
@ -117,17 +117,21 @@ export const useAgentChat = (agentId: string) => {
|
|||||||
await sendChatMessage(content);
|
await sendChatMessage(content);
|
||||||
};
|
};
|
||||||
|
|
||||||
useScopedHotkeys(
|
useHotkeysOnFocusedElement({
|
||||||
[Key.Enter],
|
keys: [Key.Enter],
|
||||||
(event) => {
|
callback: (event: KeyboardEvent) => {
|
||||||
if (!event.ctrlKey && !event.metaKey) {
|
if (!event.ctrlKey && !event.metaKey) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
handleSendMessage();
|
handleSendMessage();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
InputHotkeyScope.TextInput,
|
focusId: `${agentId}-chat-input`,
|
||||||
[agentChatInput, isLoading],
|
scope: InputHotkeyScope.TextInput,
|
||||||
);
|
dependencies: [agentChatInput, isLoading],
|
||||||
|
options: {
|
||||||
|
enableOnFormTags: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleInputChange: (value: string) => setAgentChatInput(value),
|
handleInputChange: (value: string) => setAgentChatInput(value),
|
||||||
|
|||||||
@ -285,6 +285,7 @@ export const WorkflowEditActionFormBuilder = ({
|
|||||||
|
|
||||||
<FormFieldInputRowContainer>
|
<FormFieldInputRowContainer>
|
||||||
<FormFieldInputInnerContainer
|
<FormFieldInputInnerContainer
|
||||||
|
formFieldInputInstanceId={field.id}
|
||||||
hasRightElement={false}
|
hasRightElement={false}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleFieldClick(field.id);
|
handleFieldClick(field.id);
|
||||||
@ -358,6 +359,7 @@ export const WorkflowEditActionFormBuilder = ({
|
|||||||
<FormFieldInputContainer>
|
<FormFieldInputContainer>
|
||||||
<FormFieldInputRowContainer>
|
<FormFieldInputRowContainer>
|
||||||
<FormFieldInputInnerContainer
|
<FormFieldInputInnerContainer
|
||||||
|
formFieldInputInstanceId="add-field-button"
|
||||||
hasRightElement={false}
|
hasRightElement={false}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const { label, name } = getDefaultFormFieldSettings(
|
const { label, name } = getDefaultFormFieldSettings(
|
||||||
|
|||||||
@ -47,7 +47,10 @@ export const WorkflowFormEmptyMessage = () => {
|
|||||||
<StyledMessageContainer>
|
<StyledMessageContainer>
|
||||||
<FormFieldInputContainer>
|
<FormFieldInputContainer>
|
||||||
<FormFieldInputRowContainer multiline maxHeight={124}>
|
<FormFieldInputRowContainer multiline maxHeight={124}>
|
||||||
<FormFieldInputInnerContainer hasRightElement={false}>
|
<FormFieldInputInnerContainer
|
||||||
|
formFieldInputInstanceId="empty-form-message"
|
||||||
|
hasRightElement={false}
|
||||||
|
>
|
||||||
<StyledFieldContainer>
|
<StyledFieldContainer>
|
||||||
<StyledMessageContentContainer>
|
<StyledMessageContentContainer>
|
||||||
<StyledMessageTitle data-testid="empty-form-message-title">
|
<StyledMessageTitle data-testid="empty-form-message-title">
|
||||||
|
|||||||
@ -14,11 +14,12 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
|||||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
|
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
|
||||||
import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader';
|
import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader';
|
||||||
|
import { PageFocusId } from '@/types/PageFocusId';
|
||||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||||
import { ApolloError } from '@apollo/client';
|
import { ApolloError } from '@apollo/client';
|
||||||
import { Trans, useLingui } from '@lingui/react/macro';
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
@ -148,15 +149,23 @@ export const CreateProfile = () => {
|
|||||||
|
|
||||||
const [isEditingMode, setIsEditingMode] = useState(false);
|
const [isEditingMode, setIsEditingMode] = useState(false);
|
||||||
|
|
||||||
useScopedHotkeys(
|
const handleEnter = () => {
|
||||||
Key.Enter,
|
if (isEditingMode) {
|
||||||
() => {
|
onSubmit(getValues());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useHotkeysOnFocusedElement({
|
||||||
|
keys: Key.Enter,
|
||||||
|
callback: () => {
|
||||||
if (isEditingMode) {
|
if (isEditingMode) {
|
||||||
onSubmit(getValues());
|
onSubmit(getValues());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PageHotkeyScope.CreateProfile,
|
focusId: PageFocusId.CreateProfile,
|
||||||
);
|
scope: PageHotkeyScope.CreateProfile,
|
||||||
|
dependencies: [handleEnter],
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal.Content isVerticalCentered isHorizontalCentered>
|
<Modal.Content isVerticalCentered isHorizontalCentered>
|
||||||
|
|||||||
@ -3,11 +3,12 @@ import { Title } from '@/auth/components/Title';
|
|||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
|
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
|
||||||
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
|
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
|
||||||
|
import { PageFocusId } from '@/types/PageFocusId';
|
||||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
@ -161,14 +162,15 @@ export const InviteTeam = () => {
|
|||||||
await onSubmit({ emails: [] });
|
await onSubmit({ emails: [] });
|
||||||
};
|
};
|
||||||
|
|
||||||
useScopedHotkeys(
|
useHotkeysOnFocusedElement({
|
||||||
[Key.Enter],
|
keys: Key.Enter,
|
||||||
() => {
|
callback: () => {
|
||||||
handleSubmit(onSubmit)();
|
handleSubmit(onSubmit)();
|
||||||
},
|
},
|
||||||
PageHotkeyScope.InviteTeam,
|
focusId: PageFocusId.InviteTeam,
|
||||||
[handleSubmit],
|
scope: PageHotkeyScope.InviteTeam,
|
||||||
);
|
dependencies: [handleSubmit, onSubmit],
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal.Content isVerticalCentered isHorizontalCentered>
|
<Modal.Content isVerticalCentered isHorizontalCentered>
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import { Title } from '@/auth/components/Title';
|
|||||||
import { OnboardingSyncEmailsSettingsCard } from '@/onboarding/components/OnboardingSyncEmailsSettingsCard';
|
import { OnboardingSyncEmailsSettingsCard } from '@/onboarding/components/OnboardingSyncEmailsSettingsCard';
|
||||||
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
|
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
|
||||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
|
|
||||||
import { isGoogleCalendarEnabledState } from '@/client-config/states/isGoogleCalendarEnabledState';
|
import { isGoogleCalendarEnabledState } from '@/client-config/states/isGoogleCalendarEnabledState';
|
||||||
import { isGoogleMessagingEnabledState } from '@/client-config/states/isGoogleMessagingEnabledState';
|
import { isGoogleMessagingEnabledState } from '@/client-config/states/isGoogleMessagingEnabledState';
|
||||||
@ -17,7 +16,9 @@ import { isMicrosoftCalendarEnabledState } from '@/client-config/states/isMicros
|
|||||||
import { isMicrosoftMessagingEnabledState } from '@/client-config/states/isMicrosoftMessagingEnabledState';
|
import { isMicrosoftMessagingEnabledState } from '@/client-config/states/isMicrosoftMessagingEnabledState';
|
||||||
import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth';
|
import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
|
import { PageFocusId } from '@/types/PageFocusId';
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||||
import { IconGoogle, IconMicrosoft } from 'twenty-ui/display';
|
import { IconGoogle, IconMicrosoft } from 'twenty-ui/display';
|
||||||
import { MainButton } from 'twenty-ui/input';
|
import { MainButton } from 'twenty-ui/input';
|
||||||
@ -95,14 +96,15 @@ export const SyncEmails = () => {
|
|||||||
const isMicrosoftProviderEnabled =
|
const isMicrosoftProviderEnabled =
|
||||||
isMicrosoftMessagingEnabled || isMicrosoftCalendarEnabled;
|
isMicrosoftMessagingEnabled || isMicrosoftCalendarEnabled;
|
||||||
|
|
||||||
useScopedHotkeys(
|
useHotkeysOnFocusedElement({
|
||||||
[Key.Enter],
|
keys: Key.Enter,
|
||||||
async () => {
|
callback: async () => {
|
||||||
await continueWithoutSync();
|
await continueWithoutSync();
|
||||||
},
|
},
|
||||||
PageHotkeyScope.SyncEmail,
|
focusId: PageFocusId.SyncEmail,
|
||||||
[continueWithoutSync],
|
scope: PageHotkeyScope.SyncEmail,
|
||||||
);
|
dependencies: [continueWithoutSync],
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal.Content isVerticalCentered isHorizontalCentered>
|
<Modal.Content isVerticalCentered isHorizontalCentered>
|
||||||
|
|||||||
@ -5,15 +5,11 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBa
|
|||||||
import { SettingsServerlessFunctionNewForm } from '@/settings/serverless-functions/components/SettingsServerlessFunctionNewForm';
|
import { SettingsServerlessFunctionNewForm } from '@/settings/serverless-functions/components/SettingsServerlessFunctionNewForm';
|
||||||
import { useCreateOneServerlessFunction } from '@/settings/serverless-functions/hooks/useCreateOneServerlessFunction';
|
import { useCreateOneServerlessFunction } from '@/settings/serverless-functions/hooks/useCreateOneServerlessFunction';
|
||||||
import { ServerlessFunctionNewFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
|
import { ServerlessFunctionNewFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
|
||||||
import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope';
|
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Key } from 'ts-key-enum';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount';
|
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
export const SettingsServerlessFunctionsNew = () => {
|
export const SettingsServerlessFunctionsNew = () => {
|
||||||
const navigate = useNavigateSettings();
|
const navigate = useNavigateSettings();
|
||||||
@ -50,28 +46,6 @@ export const SettingsServerlessFunctionsNew = () => {
|
|||||||
|
|
||||||
const canSave = !!formValues.name && createOneServerlessFunction;
|
const canSave = !!formValues.name && createOneServerlessFunction;
|
||||||
|
|
||||||
useHotkeyScopeOnMount(
|
|
||||||
SettingsServerlessFunctionHotkeyScope.ServerlessFunctionNew,
|
|
||||||
);
|
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
[Key.Enter],
|
|
||||||
() => {
|
|
||||||
if (canSave !== false) {
|
|
||||||
handleSave();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
SettingsServerlessFunctionHotkeyScope.ServerlessFunctionNew,
|
|
||||||
[canSave],
|
|
||||||
);
|
|
||||||
useScopedHotkeys(
|
|
||||||
[Key.Escape],
|
|
||||||
() => {
|
|
||||||
navigate(SettingsPath.ServerlessFunctions);
|
|
||||||
},
|
|
||||||
SettingsServerlessFunctionHotkeyScope.ServerlessFunctionNew,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer
|
<SubMenuTopBarContainer
|
||||||
title="New Function"
|
title="New Function"
|
||||||
|
|||||||
@ -4,21 +4,15 @@ import { RecoilRoot } from 'recoil';
|
|||||||
|
|
||||||
import { ApolloCoreClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloCoreClientMockedProvider';
|
import { ApolloCoreClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloCoreClientMockedProvider';
|
||||||
|
|
||||||
import { InitializeHotkeyStorybookHookEffect } from '../InitializeHotkeyStorybookHook';
|
|
||||||
import { mockedApolloClient } from '../mockedApolloClient';
|
import { mockedApolloClient } from '../mockedApolloClient';
|
||||||
|
|
||||||
export const RootDecorator: Decorator = (Story, context) => {
|
export const RootDecorator: Decorator = (Story, context) => {
|
||||||
const { parameters } = context;
|
const { parameters } = context;
|
||||||
|
|
||||||
const disableHotkeyInitialization = parameters.disableHotkeyInitialization;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecoilRoot initializeState={parameters.initializeState}>
|
<RecoilRoot initializeState={parameters.initializeState}>
|
||||||
<ApolloProvider client={mockedApolloClient}>
|
<ApolloProvider client={mockedApolloClient}>
|
||||||
<ApolloCoreClientMockedProvider>
|
<ApolloCoreClientMockedProvider>
|
||||||
{!disableHotkeyInitialization && (
|
|
||||||
<InitializeHotkeyStorybookHookEffect />
|
|
||||||
)}
|
|
||||||
<Story />
|
<Story />
|
||||||
</ApolloCoreClientMockedProvider>
|
</ApolloCoreClientMockedProvider>
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
|
|||||||
Reference in New Issue
Block a user