Merge branch 'fork/mapringg/mac-shortcuts'

This commit is contained in:
Enrico Ros
2024-07-07 04:21:24 -07:00
10 changed files with 88 additions and 114 deletions
+2 -2
View File
@@ -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 { 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';
@@ -397,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],
+2 -2
View File
@@ -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 (
+3 -3
View File
@@ -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
@@ -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 => {
+27 -26
View File
@@ -3,37 +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';
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 File ... |
| Ctrl + S | Save Chat File ... |
| Ctrl + Alt + N | **New** chat |
| Ctrl + Alt + X | **Reset** chat |
| Ctrl + Alt + D | **Delete** chat |
| Ctrl + 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();
@@ -55,4 +56,4 @@ export function ShortcutsModal(props: { onClose: () => void }) {
/>
</GoodModal>
);
}
}
+2 -2
View File
@@ -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 { 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]);
+1 -1
View File
@@ -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
@@ -1,72 +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) {
const isCtrlOrCmd = (event.ctrlKey && !event.metaKey) || (event.metaKey && !event.ctrlKey);
if (
key &&
(useCtrl === isCtrlOrCmd) &&
(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]);
};
@@ -0,0 +1,44 @@
import * as React from 'react';
import { isMacUser } from '../util/pwaUtils';
export const ShortcutKeyName = {
Esc: 'Escape',
Left: 'ArrowLeft',
Right: 'ArrowRight',
};
export type GlobalShortcutDefinition = [key: string | false, useCtrl: boolean, useShift: boolean, useAltForNonMac: boolean, action: () => void];
/**
* 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
*
*/
export const useGlobalShortcuts = (shortcuts: GlobalShortcutDefinition[]) => {
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
for (const [key, useCtrl, useShift, useAltForNonMac, action] of shortcuts) {
if (
key &&
(useCtrl === event.ctrlKey) &&
(useShift === event.shiftKey) &&
(isMacUser /* Mac users won't need the Alt keys */ || useAltForNonMac === event.altKey) &&
event.key.toLowerCase() === key.toLowerCase()
) {
event.preventDefault();
event.stopPropagation();
action();
break;
}
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [shortcuts]);
};
+4 -3
View File
@@ -2,7 +2,8 @@ import * as React from 'react';
import type { DLLMId } from '~/modules/llms/store-llms';
import { GlobalShortcutItem, useGlobalShortcuts } from '~/common/components/useGlobalShortcut';
import { GlobalShortcutDefinition, useGlobalShortcuts } from '~/common/components/useGlobalShortcuts';
import { isMacUser } from '~/common/util/pwaUtils';
const DEBUG_OPTIMA_LAYOUT_PLUGGING = false;
@@ -114,8 +115,8 @@ export function OptimaLayoutProvider(props: { children: React.ReactNode }) {
// global shortcuts for Optima
const shortcuts = React.useMemo((): GlobalShortcutItem[] => [
['?', true, true, false, actions.openShortcuts],
const shortcuts = React.useMemo((): GlobalShortcutDefinition[] => [
[isMacUser ? '/' : '?', true, true, false, actions.openShortcuts],
['m', true, true, false, actions.openModelsSetup],
['p', true, true, false, actions.openPreferencesTab],
], [actions]);