From 2ac17893127402e9a38cd0c459f00efb1fe1862b Mon Sep 17 00:00:00 2001 From: Sorawit Kongnurat Date: Mon, 6 May 2024 16:34:06 +0700 Subject: [PATCH 1/3] Use ctrl and remove alt usage with certain hotkeys for mac shortcuts --- src/apps/chat/AppChat.tsx | 9 +++++---- src/apps/settings-modal/ShortcutsModal.tsx | 9 +++++---- src/common/components/KeyStroke.tsx | 2 +- src/common/components/useGlobalShortcut.ts | 3 +-- src/common/layout/optima/useOptimaLayout.tsx | 3 ++- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/apps/chat/AppChat.tsx b/src/apps/chat/AppChat.tsx index d1315ea5d..ec9ecde2b 100644 --- a/src/apps/chat/AppChat.tsx +++ b/src/apps/chat/AppChat.tsx @@ -43,6 +43,7 @@ import { Composer } from './components/composer/Composer'; import { usePanesManager } from './components/panes/usePanesManager'; import { _handleExecute } from './editors/_handleExecute'; +import { isMacUser } from '~/common/util/pwaUtils'; // what to say when a chat is new and has no title @@ -385,12 +386,12 @@ export function AppChat() { // focused conversation ['b', true, true, false, handleMessageBeamLastInFocusedPane], ['r', true, true, false, handleMessageRegenerateLastInFocusedPane], - ['n', true, false, true, handleConversationNewInFocusedPane], + ['n', true, false, isMacUser ? false : true, handleConversationNewInFocusedPane], ['o', true, false, false, handleFileOpenConversation], ['s', true, false, false, () => handleFileSaveConversation(focusedPaneConversationId)], - ['b', true, false, true, () => isFocusedChatEmpty || (focusedPaneConversationId && handleConversationBranch(focusedPaneConversationId, null))], - ['x', true, false, true, () => isFocusedChatEmpty || (focusedPaneConversationId && handleConversationClear(focusedPaneConversationId))], - ['d', true, false, true, () => focusedPaneConversationId && handleDeleteConversations([focusedPaneConversationId], false)], + ['b', true, false, isMacUser ? false : true, () => isFocusedChatEmpty || (focusedPaneConversationId && handleConversationBranch(focusedPaneConversationId, null))], + ['x', true, false, isMacUser ? false : true, () => isFocusedChatEmpty || (focusedPaneConversationId && handleConversationClear(focusedPaneConversationId))], + ['d', true, false, isMacUser ? false : true, () => focusedPaneConversationId && handleDeleteConversations([focusedPaneConversationId], false)], [ShortcutKeyName.Left, true, false, true, () => handleNavigateHistoryInFocusedPane('back')], [ShortcutKeyName.Right, true, false, true, () => handleNavigateHistoryInFocusedPane('forward')], // global diff --git a/src/apps/settings-modal/ShortcutsModal.tsx b/src/apps/settings-modal/ShortcutsModal.tsx index c95134ad2..efd632892 100644 --- a/src/apps/settings-modal/ShortcutsModal.tsx +++ b/src/apps/settings-modal/ShortcutsModal.tsx @@ -5,6 +5,7 @@ import { BlocksRenderer } from '~/modules/blocks/BlocksRenderer'; import { GoodModal } from '~/common/components/GoodModal'; import { platformAwareKeystrokes } from '~/common/components/KeyStroke'; import { useIsMobile } from '~/common/components/useMatchMedia'; +import { isMacUser } from '~/common/util/pwaUtils'; const shortcutsMd = platformAwareKeystrokes(` @@ -21,10 +22,10 @@ const shortcutsMd = platformAwareKeystrokes(` | **Chats** | | | Ctrl + O | Open Chat ... | | Ctrl + S | Save Chat ... | -| Ctrl + Alt + N | **New** chat | -| Ctrl + Alt + X | **Reset** chat | -| Ctrl + Alt + D | **Delete** chat | -| Ctrl + Alt + B | **Branch** chat | +| Ctrl + ${isMacUser ? '' : 'Alt +'} N | **New** chat | +| Ctrl + ${isMacUser ? '' : 'Alt +'} X | **Reset** chat | +| Ctrl + ${isMacUser ? '' : 'Alt +'} D | **Delete** chat | +| Ctrl + ${isMacUser ? '' : 'Alt +'} B | **Branch** chat | | Ctrl + Alt + Left | **Previous** chat (in history) | | Ctrl + Alt + Right | **Next** chat (in history) | | **Settings** | | diff --git a/src/common/components/KeyStroke.tsx b/src/common/components/KeyStroke.tsx index 517692141..009da5f68 100644 --- a/src/common/components/KeyStroke.tsx +++ b/src/common/components/KeyStroke.tsx @@ -10,7 +10,7 @@ import { isMacUser } from '~/common/util/pwaUtils'; export function platformAwareKeystrokes(text: string) { return isMacUser ? text - .replaceAll('Ctrl', '⌘' /* Command */) + .replaceAll('Ctrl', '⌃' /* Command */) .replaceAll('Alt', '⌥' /* Option */) .replaceAll('Shift', '⇧') // Optional: Replace "Enter" with "Return" if you want to align with Mac keyboard labeling diff --git a/src/common/components/useGlobalShortcut.ts b/src/common/components/useGlobalShortcut.ts index b31641df5..12241a283 100644 --- a/src/common/components/useGlobalShortcut.ts +++ b/src/common/components/useGlobalShortcut.ts @@ -51,10 +51,9 @@ export const useGlobalShortcuts = (shortcuts: GlobalShortcutItem[]) => { React.useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { for (const [key, useCtrl, useShift, useAlt, action] of shortcuts) { - const isCtrlOrCmd = (event.ctrlKey && !event.metaKey) || (event.metaKey && !event.ctrlKey); if ( key && - (useCtrl === isCtrlOrCmd) && + (useCtrl === event.ctrlKey) && (useShift === event.shiftKey) && (useAlt === event.altKey) && event.key.toLowerCase() === key.toLowerCase() diff --git a/src/common/layout/optima/useOptimaLayout.tsx b/src/common/layout/optima/useOptimaLayout.tsx index 89414394d..b166cc960 100644 --- a/src/common/layout/optima/useOptimaLayout.tsx +++ b/src/common/layout/optima/useOptimaLayout.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import type { DLLMId } from '~/modules/llms/store-llms'; import { GlobalShortcutItem, useGlobalShortcuts } from '~/common/components/useGlobalShortcut'; +import { isMacUser } from '~/common/util/pwaUtils'; const DEBUG_OPTIMA_LAYOUT_PLUGGING = false; @@ -115,7 +116,7 @@ export function OptimaLayoutProvider(props: { children: React.ReactNode }) { // global shortcuts for Optima const shortcuts = React.useMemo((): GlobalShortcutItem[] => [ - ['?', true, true, false, actions.openShortcuts], + [isMacUser ? '/' : '?', true, true, false, actions.openShortcuts], ['m', true, true, false, actions.openModelsSetup], ['p', true, true, false, actions.openPreferencesTab], ], [actions]); From f38be4aff3e4ec4dd8b674682959da738e7308f4 Mon Sep 17 00:00:00 2001 From: Sorawit Kongnurat Date: Sat, 6 Jul 2024 15:41:45 +0700 Subject: [PATCH 2/3] feat: replace useGlobalShortcut with useGlobalShortcuts Ignore alt key for mac users. --- src/apps/chat/AppChat.tsx | 10 +-- src/apps/chat/components/ChatBarAltBeam.tsx | 4 +- src/apps/chat/components/ChatMessageList.tsx | 6 +- .../chat/components/composer/Composer.tsx | 6 +- src/apps/settings-modal/ShortcutsModal.tsx | 51 ++++++------- src/common/components/ExplainerCarousel.tsx | 2 +- src/common/components/useGlobalShortcut.ts | 71 ------------------- src/common/components/useGlobalShortcuts.ts | 38 ++++++++++ src/common/layout/optima/useOptimaLayout.tsx | 2 +- 9 files changed, 79 insertions(+), 111 deletions(-) delete mode 100644 src/common/components/useGlobalShortcut.ts create mode 100644 src/common/components/useGlobalShortcuts.ts diff --git a/src/apps/chat/AppChat.tsx b/src/apps/chat/AppChat.tsx index bf2c9883c..7a989b281 100644 --- a/src/apps/chat/AppChat.tsx +++ b/src/apps/chat/AppChat.tsx @@ -17,7 +17,7 @@ import { useCapabilityTextToImage } from '~/modules/t2i/t2i.client'; import { ConfirmationModal } from '~/common/components/ConfirmationModal'; import { ConversationsManager } from '~/common/chats/ConversationsManager'; -import { GlobalShortcutItem, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcut'; +import { GlobalShortcutItem, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts'; import { PanelResizeInset } from '~/common/components/panes/GoodPanelResizeHandler'; import { PreferencesTab, useOptimaLayout, usePluggableOptimaLayout } from '~/common/layout/optima/useOptimaLayout'; import { ScrollToBottom } from '~/common/scroll-to-bottom/ScrollToBottom'; @@ -402,12 +402,12 @@ export function AppChat() { // focused conversation ['b', true, true, false, handleMessageBeamLastInFocusedPane], ['r', true, true, false, handleMessageRegenerateLastInFocusedPane], - ['n', true, false, isMacUser ? false : true, handleConversationNewInFocusedPane], + ['n', true, false, true, handleConversationNewInFocusedPane], ['o', true, false, false, handleFileOpenConversation], ['s', true, false, false, () => handleFileSaveConversation(focusedPaneConversationId)], - ['b', true, false, isMacUser ? false : true, () => isFocusedChatEmpty || (focusedPaneConversationId && handleConversationBranch(focusedPaneConversationId, null))], - ['x', true, false, isMacUser ? false : true, () => isFocusedChatEmpty || (focusedPaneConversationId && handleConversationClear(focusedPaneConversationId))], - ['d', true, false, isMacUser ? false : true, () => focusedPaneConversationId && handleDeleteConversations([focusedPaneConversationId], false)], + ['b', true, false, true, () => isFocusedChatEmpty || (focusedPaneConversationId && handleConversationBranch(focusedPaneConversationId, null))], + ['x', true, false, true, () => isFocusedChatEmpty || (focusedPaneConversationId && handleConversationClear(focusedPaneConversationId))], + ['d', true, false, true, () => focusedPaneConversationId && handleDeleteConversations([focusedPaneConversationId], false)], [ShortcutKeyName.Left, true, false, true, () => handleNavigateHistoryInFocusedPane('back')], [ShortcutKeyName.Right, true, false, true, () => handleNavigateHistoryInFocusedPane('forward')], // global diff --git a/src/apps/chat/components/ChatBarAltBeam.tsx b/src/apps/chat/components/ChatBarAltBeam.tsx index 03cf3039f..f0c066487 100644 --- a/src/apps/chat/components/ChatBarAltBeam.tsx +++ b/src/apps/chat/components/ChatBarAltBeam.tsx @@ -10,7 +10,7 @@ import { BeamStoreApi, useBeamStore } from '~/modules/beam/store-beam.hooks'; import { ConfirmationModal } from '~/common/components/ConfirmationModal'; import { GoodTooltip } from '~/common/components/GoodTooltip'; import { KeyStroke } from '~/common/components/KeyStroke'; -import { ShortcutKeyName, useGlobalShortcut } from '~/common/components/useGlobalShortcut'; +import { ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts'; import { animationBackgroundBeamGather, animationColorBeamScatterINV, animationEnterBelow } from '~/common/util/animUtils'; @@ -59,7 +59,7 @@ export function ChatBarAltBeam(props: { // intercept esc this beam is focused - useGlobalShortcut(ShortcutKeyName.Esc, false, false, false, handleCloseBeam); + useGlobalShortcuts([[ShortcutKeyName.Esc, false, false, false, handleCloseBeam]]); return ( diff --git a/src/apps/chat/components/ChatMessageList.tsx b/src/apps/chat/components/ChatMessageList.tsx index 407b39d23..e53397fe9 100644 --- a/src/apps/chat/components/ChatMessageList.tsx +++ b/src/apps/chat/components/ChatMessageList.tsx @@ -9,7 +9,7 @@ import type { DiagramConfig } from '~/modules/aifn/digrams/DiagramsModal'; import type { ConversationHandler } from '~/common/chats/ConversationHandler'; import { InlineError } from '~/common/components/InlineError'; import { PreferencesTab, useOptimaLayout } from '~/common/layout/optima/useOptimaLayout'; -import { ShortcutKeyName, useGlobalShortcut } from '~/common/components/useGlobalShortcut'; +import { ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts'; import { createDMessage, DConversationId, DMessage, DMessageUserFlag, getConversation, messageToggleUserFlag, useChatStore } from '~/common/state/store-chats'; import { useBrowserTranslationWarning } from '~/common/components/useIsBrowserTranslating'; import { useCapabilityElevenLabs } from '~/common/components/useCapabilities'; @@ -186,9 +186,9 @@ export function ChatMessageList(props: { setSelectedMessages(new Set()); }; - useGlobalShortcut(props.isMessageSelectionMode && ShortcutKeyName.Esc, false, false, false, () => { + useGlobalShortcuts([[props.isMessageSelectionMode && ShortcutKeyName.Esc, false, false, false, () => { props.setIsMessageSelectionMode(false); - }); + }]]); // text-diff functionality: only diff the last message and when it's complete (not typing), and they're similar in size diff --git a/src/apps/chat/components/composer/Composer.tsx b/src/apps/chat/components/composer/Composer.tsx index fa519c137..1fb49a293 100644 --- a/src/apps/chat/components/composer/Composer.tsx +++ b/src/apps/chat/components/composer/Composer.tsx @@ -39,7 +39,7 @@ import { supportsScreenCapture } from '~/common/util/screenCaptureUtils'; import { useAppStateStore } from '~/common/state/store-appstate'; import { useChatOverlayStore } from '~/common/chats/store-chat-overlay-vanilla'; import { useDebouncer } from '~/common/components/useDebouncer'; -import { useGlobalShortcut } from '~/common/components/useGlobalShortcut'; +import { useGlobalShortcuts } from '~/common/components/useGlobalShortcuts'; import { useUICounter, useUIPreferencesStore } from '~/common/state/store-ui'; import { useUXLabsStore } from '~/common/state/store-ux-labs'; @@ -399,7 +399,7 @@ export function Composer(props: { const { isSpeechEnabled, isSpeechError, isRecordingAudio, isRecordingSpeech, toggleRecording } = useSpeechRecognition(onSpeechResultCallback, chatMicTimeoutMs || 2000); - useGlobalShortcut('m', true, false, false, toggleRecording); + useGlobalShortcuts([['m', true, false, false, toggleRecording]]); const micIsRunning = !!speechInterimResult; const micContinuationTrigger = micContinuation && !micIsRunning && !assistantAbortible && !isSpeechError; @@ -451,7 +451,7 @@ export function Composer(props: { } }, [attachAppendFile]); - useGlobalShortcut(supportsClipboardRead ? 'v' : false, true, true, false, attachAppendClipboardItems); + useGlobalShortcuts([[supportsClipboardRead ? 'v' : false, true, true, false, attachAppendClipboardItems]]); const handleAttachmentInlineText = React.useCallback((attachmentId: AttachmentId) => { setComposeText(currentText => { diff --git a/src/apps/settings-modal/ShortcutsModal.tsx b/src/apps/settings-modal/ShortcutsModal.tsx index 4d234b06c..262ea7066 100644 --- a/src/apps/settings-modal/ShortcutsModal.tsx +++ b/src/apps/settings-modal/ShortcutsModal.tsx @@ -10,31 +10,31 @@ import { isMacUser } from '~/common/util/pwaUtils'; const shortcutsMd = platformAwareKeystrokes(` -| Shortcut | Description | -|---------------------|-------------------------------------------------| -| **Edit** | | -| Shift + Enter | Newline | -| Alt + Enter | Append (no response) | -| Ctrl + Shift + B | **Beam** last message | -| Ctrl + Shift + R | **Regenerate** last message | -| Ctrl + Shift + V | Attach clipboard (better than Ctrl + V) | -| Ctrl + M | Microphone (voice typing) | -| **Chats** | | -| Ctrl + O | Open Chat ... | -| Ctrl + S | Save Chat ... | -| Ctrl + ${isMacUser ? '' : 'Alt +'} N | **New** chat | -| Ctrl + ${isMacUser ? '' : 'Alt +'} X | **Reset** chat | -| Ctrl + ${isMacUser ? '' : 'Alt +'} D | **Delete** chat | -| Ctrl + ${isMacUser ? '' : 'Alt +'} B | **Branch** chat | -| Ctrl + Alt + Left | **Previous** chat (in history) | -| Ctrl + Alt + Right | **Next** chat (in history) | -| **Settings** | | -| Ctrl + Shift + P | ⚙️ Preferences | -| Ctrl + Shift + M | 🧠 Models | -| Ctrl + Shift + O | 💬 Options (current Chat Model) | -| Ctrl + Shift + + | Increase Text Size | -| Ctrl + Shift + - | Decrease Text Size | -| Ctrl + Shift + ? | Shortcuts | +| Shortcut | Description | +|---------------------------------------|-------------------------------------------------| +| **Edit** | | +| Shift + Enter | Newline | +| Alt + Enter | Append (no response) | +| Ctrl + Shift + B | **Beam** last message | +| Ctrl + Shift + R | **Regenerate** last message | +| Ctrl + Shift + V | Attach clipboard (better than Ctrl + V) | +| Ctrl + M | Microphone (voice typing) | +| **Chats** | | +| Ctrl + O | Open Chat ... | +| Ctrl + S | Save Chat ... | +| Ctrl + ${isMacUser ? '' : 'Alt +'} N | **New** chat | +| Ctrl + ${isMacUser ? '' : 'Alt +'} X | **Reset** chat | +| Ctrl + ${isMacUser ? '' : 'Alt +'} D | **Delete** chat | +| Ctrl + ${isMacUser ? '' : 'Alt +'} B | **Branch** chat | +| Ctrl + Alt + Left | **Previous** chat (in history) | +| Ctrl + Alt + Right | **Next** chat (in history) | +| **Settings** | | +| Ctrl + Shift + P | ⚙️ Preferences | +| Ctrl + Shift + M | 🧠 Models | +| Ctrl + Shift + O | 💬 Options (current Chat Model) | +| Ctrl + Shift + + | Increase Text Size | +| Ctrl + Shift + - | Decrease Text Size | +| Ctrl + Shift + ? | Shortcuts | `).trim(); @@ -56,3 +56,4 @@ export function ShortcutsModal(props: { onClose: () => void }) { /> ); +} diff --git a/src/common/components/ExplainerCarousel.tsx b/src/common/components/ExplainerCarousel.tsx index 9e1db675f..b43d4c641 100644 --- a/src/common/components/ExplainerCarousel.tsx +++ b/src/common/components/ExplainerCarousel.tsx @@ -12,7 +12,7 @@ import { BlocksRenderer } from '~/modules/blocks/BlocksRenderer'; import { AgiSquircleIcon } from '~/common/components/icons/AgiSquircleIcon'; import { ChatBeamIcon } from '~/common/components/icons/ChatBeamIcon'; -import { GlobalShortcutItem, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcut'; +import { GlobalShortcutItem, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts'; import { hasGoogleAnalytics } from '~/common/components/GoogleAnalytics'; import { useIsMobile } from '~/common/components/useMatchMedia'; import { animationTextShadowLimey } from '~/common/util/animUtils'; diff --git a/src/common/components/useGlobalShortcut.ts b/src/common/components/useGlobalShortcut.ts deleted file mode 100644 index 12241a283..000000000 --- a/src/common/components/useGlobalShortcut.ts +++ /dev/null @@ -1,71 +0,0 @@ -import * as React from 'react'; - -export const ShortcutKeyName = { - Esc: 'Escape', - Left: 'ArrowLeft', - Right: 'ArrowRight', -}; - - -/** - * Registers a global keyboard shortcut (if not undefined) to activate a callback. - * - * @param shortcutKey If undefined, the shortcut will not be registered. - * @param useCtrl If true, the Ctrl key must be pressed for the shortcut to be activated. - * @param useShift If true, the Shift key must be pressed for the shortcut to be activated. - * @param useAlt If true, the Alt key must be pressed for the shortcut to be activated. - * @param callback Make sure this is a memoized callback, otherwise the effect will be re-registered every time. - */ -export const useGlobalShortcut = (shortcutKey: string | false, useCtrl: boolean, useShift: boolean, useAlt: boolean, callback: () => void) => { - React.useEffect(() => { - if (!shortcutKey) return; - const lcShortcut = shortcutKey.toLowerCase(); - const handleKeyDown = (event: KeyboardEvent) => { - const isCtrlOrCmd = (event.ctrlKey && !event.metaKey) || (event.metaKey && !event.ctrlKey); - if ( - (useCtrl === isCtrlOrCmd) && - (useShift === event.shiftKey) && - (useAlt === event.altKey) && - event.key.toLowerCase() === lcShortcut - ) { - event.preventDefault(); - event.stopPropagation(); - callback(); - } - }; - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [callback, shortcutKey, useAlt, useCtrl, useShift]); -}; - - -export type GlobalShortcutItem = [key: string, ctrl: boolean, shift: boolean, alt: boolean, action: () => void]; - - -/** - * Registers multiple global keyboard shortcuts to activate callbacks. - * - * @param shortcuts An array of shortcut objects. - */ -export const useGlobalShortcuts = (shortcuts: GlobalShortcutItem[]) => { - React.useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - for (const [key, useCtrl, useShift, useAlt, action] of shortcuts) { - if ( - key && - (useCtrl === event.ctrlKey) && - (useShift === event.shiftKey) && - (useAlt === event.altKey) && - event.key.toLowerCase() === key.toLowerCase() - ) { - event.preventDefault(); - event.stopPropagation(); - action(); - break; - } - } - }; - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [shortcuts]); -}; \ No newline at end of file diff --git a/src/common/components/useGlobalShortcuts.ts b/src/common/components/useGlobalShortcuts.ts new file mode 100644 index 000000000..712c97598 --- /dev/null +++ b/src/common/components/useGlobalShortcuts.ts @@ -0,0 +1,38 @@ +import * as React from 'react'; +import { isMacUser } from '../util/pwaUtils'; + +export const ShortcutKeyName = { + Esc: 'Escape', + Left: 'ArrowLeft', + Right: 'ArrowRight', +}; + +export type GlobalShortcutItem = [key: string | false, ctrl: boolean, shift: boolean, alt: boolean, action: () => void]; + +/** + * Registers multiple global keyboard shortcuts to activate callbacks. + * + * @param shortcuts An array of shortcut objects. + */ +export const useGlobalShortcuts = (shortcuts: GlobalShortcutItem[]) => { + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + for (const [key, useCtrl, useShift, useAlt, action] of shortcuts) { + if ( + key && + (useCtrl === event.ctrlKey) && + (useShift === event.shiftKey) && + (isMacUser ? true : useAlt === event.altKey) && + event.key.toLowerCase() === key.toLowerCase() + ) { + event.preventDefault(); + event.stopPropagation(); + action(); + break; + } + } + }; + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [shortcuts]); +}; diff --git a/src/common/layout/optima/useOptimaLayout.tsx b/src/common/layout/optima/useOptimaLayout.tsx index b166cc960..d94a7085b 100644 --- a/src/common/layout/optima/useOptimaLayout.tsx +++ b/src/common/layout/optima/useOptimaLayout.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import type { DLLMId } from '~/modules/llms/store-llms'; -import { GlobalShortcutItem, useGlobalShortcuts } from '~/common/components/useGlobalShortcut'; +import { GlobalShortcutItem, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts'; import { isMacUser } from '~/common/util/pwaUtils'; From b12637267b5f2718c442b70579d9fba43317a711 Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Sun, 7 Jul 2024 04:20:23 -0700 Subject: [PATCH 3/3] Small cleanups with shortcut fixes --- src/apps/chat/AppChat.tsx | 5 +- src/apps/settings-modal/ShortcutsModal.tsx | 52 ++++++++++---------- src/common/components/ExplainerCarousel.tsx | 4 +- src/common/components/KeyStroke.tsx | 2 +- src/common/components/useGlobalShortcuts.ts | 18 ++++--- src/common/layout/optima/useOptimaLayout.tsx | 4 +- 6 files changed, 45 insertions(+), 40 deletions(-) diff --git a/src/apps/chat/AppChat.tsx b/src/apps/chat/AppChat.tsx index 7a989b281..e2a038cf9 100644 --- a/src/apps/chat/AppChat.tsx +++ b/src/apps/chat/AppChat.tsx @@ -17,7 +17,7 @@ import { useCapabilityTextToImage } from '~/modules/t2i/t2i.client'; import { ConfirmationModal } from '~/common/components/ConfirmationModal'; import { ConversationsManager } from '~/common/chats/ConversationsManager'; -import { GlobalShortcutItem, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts'; +import { GlobalShortcutDefinition, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts'; import { PanelResizeInset } from '~/common/components/panes/GoodPanelResizeHandler'; import { PreferencesTab, useOptimaLayout, usePluggableOptimaLayout } from '~/common/layout/optima/useOptimaLayout'; import { ScrollToBottom } from '~/common/scroll-to-bottom/ScrollToBottom'; @@ -43,7 +43,6 @@ import { Composer } from './components/composer/Composer'; import { usePanesManager } from './components/panes/usePanesManager'; import { _handleExecute } from './editors/_handleExecute'; -import { isMacUser } from '~/common/util/pwaUtils'; // what to say when a chat is new and has no title @@ -398,7 +397,7 @@ export function AppChat() { openLlmOptions(chatLLMId); }, [openLlmOptions]); - const shortcuts = React.useMemo((): GlobalShortcutItem[] => [ + const shortcuts = React.useMemo((): GlobalShortcutDefinition[] => [ // focused conversation ['b', true, true, false, handleMessageBeamLastInFocusedPane], ['r', true, true, false, handleMessageRegenerateLastInFocusedPane], diff --git a/src/apps/settings-modal/ShortcutsModal.tsx b/src/apps/settings-modal/ShortcutsModal.tsx index 262ea7066..6f39ce434 100644 --- a/src/apps/settings-modal/ShortcutsModal.tsx +++ b/src/apps/settings-modal/ShortcutsModal.tsx @@ -3,38 +3,38 @@ import * as React from 'react'; import { BlocksRenderer } from '~/modules/blocks/BlocksRenderer'; import { GoodModal } from '~/common/components/GoodModal'; +import { isMacUser } from '~/common/util/pwaUtils'; import { platformAwareKeystrokes } from '~/common/components/KeyStroke'; import { useIsMobile } from '~/common/components/useMatchMedia'; -import { isMacUser } from '~/common/util/pwaUtils'; const shortcutsMd = platformAwareKeystrokes(` -| Shortcut | Description | -|---------------------------------------|-------------------------------------------------| -| **Edit** | | -| Shift + Enter | Newline | -| Alt + Enter | Append (no response) | -| Ctrl + Shift + B | **Beam** last message | -| Ctrl + Shift + R | **Regenerate** last message | -| Ctrl + Shift + V | Attach clipboard (better than Ctrl + V) | -| Ctrl + M | Microphone (voice typing) | -| **Chats** | | -| Ctrl + O | Open Chat ... | -| Ctrl + S | Save Chat ... | -| Ctrl + ${isMacUser ? '' : 'Alt +'} N | **New** chat | -| Ctrl + ${isMacUser ? '' : 'Alt +'} X | **Reset** chat | -| Ctrl + ${isMacUser ? '' : 'Alt +'} D | **Delete** chat | -| Ctrl + ${isMacUser ? '' : 'Alt +'} B | **Branch** chat | -| Ctrl + Alt + Left | **Previous** chat (in history) | -| Ctrl + Alt + Right | **Next** chat (in history) | -| **Settings** | | -| Ctrl + Shift + P | ⚙️ Preferences | -| Ctrl + Shift + M | 🧠 Models | -| Ctrl + Shift + O | 💬 Options (current Chat Model) | -| Ctrl + Shift + + | Increase Text Size | -| Ctrl + Shift + - | Decrease Text Size | -| Ctrl + Shift + ? | Shortcuts | +| Shortcut | Description | +|-----------------------------------------|-------------------------------------------------| +| **Edit** | | +| Shift + Enter | Newline | +| Alt + Enter | Append (no response) | +| Ctrl + Shift + B | **Beam** last message | +| Ctrl + Shift + R | **Regenerate** last message | +| Ctrl + Shift + V | Attach clipboard (better than Ctrl + V) | +| Ctrl + M | Microphone (voice typing) | +| **Chats** | | +| Ctrl + O | Open Chat ... | +| Ctrl + S | Save Chat ... | +| Ctrl + ${isMacUser ? '' : 'Alt +'} N | **New** chat | +| Ctrl + ${isMacUser ? '' : 'Alt +'} X | **Reset** chat | +| Ctrl + ${isMacUser ? '' : 'Alt +'} D | **Delete** chat | +| Ctrl + ${isMacUser ? '' : 'Alt +'} B | **Branch** chat | +| Ctrl + Alt + Left | **Previous** chat (in history) | +| Ctrl + Alt + Right | **Next** chat (in history) | +| **Settings** | | +| Ctrl + Shift + P | ⚙️ Preferences | +| Ctrl + Shift + M | 🧠 Models | +| Ctrl + Shift + O | 💬 Options (current Chat Model) | +| Ctrl + Shift + + | Increase Text Size | +| Ctrl + Shift + - | Decrease Text Size | +| Ctrl + Shift + ${isMacUser ? '/' : '?'} | Shortcuts | `).trim(); diff --git a/src/common/components/ExplainerCarousel.tsx b/src/common/components/ExplainerCarousel.tsx index b43d4c641..1f0897805 100644 --- a/src/common/components/ExplainerCarousel.tsx +++ b/src/common/components/ExplainerCarousel.tsx @@ -12,7 +12,7 @@ import { BlocksRenderer } from '~/modules/blocks/BlocksRenderer'; import { AgiSquircleIcon } from '~/common/components/icons/AgiSquircleIcon'; import { ChatBeamIcon } from '~/common/components/icons/ChatBeamIcon'; -import { GlobalShortcutItem, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts'; +import { GlobalShortcutDefinition, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts'; import { hasGoogleAnalytics } from '~/common/components/GoogleAnalytics'; import { useIsMobile } from '~/common/components/useMatchMedia'; import { animationTextShadowLimey } from '~/common/util/animUtils'; @@ -159,7 +159,7 @@ export function ExplainerCarousel(props: { }, [props.explainerId]); - const shortcuts = React.useMemo((): GlobalShortcutItem[] => [ + const shortcuts = React.useMemo((): GlobalShortcutDefinition[] => [ [ShortcutKeyName.Left, false, false, false, handlePrevPage], [ShortcutKeyName.Right, false, false, false, handleNextPage], ], [handleNextPage, handlePrevPage]); diff --git a/src/common/components/KeyStroke.tsx b/src/common/components/KeyStroke.tsx index 009da5f68..5c2a13f4a 100644 --- a/src/common/components/KeyStroke.tsx +++ b/src/common/components/KeyStroke.tsx @@ -10,7 +10,7 @@ import { isMacUser } from '~/common/util/pwaUtils'; export function platformAwareKeystrokes(text: string) { return isMacUser ? text - .replaceAll('Ctrl', '⌃' /* Command */) + .replaceAll('Ctrl', '⌃' /* Control */) .replaceAll('Alt', '⌥' /* Option */) .replaceAll('Shift', '⇧') // Optional: Replace "Enter" with "Return" if you want to align with Mac keyboard labeling diff --git a/src/common/components/useGlobalShortcuts.ts b/src/common/components/useGlobalShortcuts.ts index 712c97598..b94b8ebb4 100644 --- a/src/common/components/useGlobalShortcuts.ts +++ b/src/common/components/useGlobalShortcuts.ts @@ -1,28 +1,34 @@ import * as React from 'react'; + import { isMacUser } from '../util/pwaUtils'; + export const ShortcutKeyName = { Esc: 'Escape', Left: 'ArrowLeft', Right: 'ArrowRight', }; -export type GlobalShortcutItem = [key: string | false, ctrl: boolean, shift: boolean, alt: boolean, action: () => void]; +export type GlobalShortcutDefinition = [key: string | false, useCtrl: boolean, useShift: boolean, useAltForNonMac: boolean, action: () => void]; /** - * Registers multiple global keyboard shortcuts to activate callbacks. + * Registers multiple global keyboard shortcuts -> function mappings. + * + * Important notes below: + * - [MAC only] the Alt key is ignored even if defined in the shortcut + * - [MAC only] are not using the command key at the moment, as it interfered with browser shortcuts + * - stabilize the shortcuts definition (e.g. React.useMemo()) to avoid re-registering the shortcuts at every render * - * @param shortcuts An array of shortcut objects. */ -export const useGlobalShortcuts = (shortcuts: GlobalShortcutItem[]) => { +export const useGlobalShortcuts = (shortcuts: GlobalShortcutDefinition[]) => { React.useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { - for (const [key, useCtrl, useShift, useAlt, action] of shortcuts) { + for (const [key, useCtrl, useShift, useAltForNonMac, action] of shortcuts) { if ( key && (useCtrl === event.ctrlKey) && (useShift === event.shiftKey) && - (isMacUser ? true : useAlt === event.altKey) && + (isMacUser /* Mac users won't need the Alt keys */ || useAltForNonMac === event.altKey) && event.key.toLowerCase() === key.toLowerCase() ) { event.preventDefault(); diff --git a/src/common/layout/optima/useOptimaLayout.tsx b/src/common/layout/optima/useOptimaLayout.tsx index d94a7085b..d2802a5f6 100644 --- a/src/common/layout/optima/useOptimaLayout.tsx +++ b/src/common/layout/optima/useOptimaLayout.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import type { DLLMId } from '~/modules/llms/store-llms'; -import { GlobalShortcutItem, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts'; +import { GlobalShortcutDefinition, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts'; import { isMacUser } from '~/common/util/pwaUtils'; @@ -115,7 +115,7 @@ export function OptimaLayoutProvider(props: { children: React.ReactNode }) { // global shortcuts for Optima - const shortcuts = React.useMemo((): GlobalShortcutItem[] => [ + const shortcuts = React.useMemo((): GlobalShortcutDefinition[] => [ [isMacUser ? '/' : '?', true, true, false, actions.openShortcuts], ['m', true, true, false, actions.openModelsSetup], ['p', true, true, false, actions.openPreferencesTab],