mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-25 04:36:46 +00:00
Rename files
This commit is contained in:
398
ts/hooks/useKeyboardShortcuts.dom.tsx
Normal file
398
ts/hooks/useKeyboardShortcuts.dom.tsx
Normal file
@@ -0,0 +1,398 @@
|
||||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import lodash from 'lodash';
|
||||
import { useSelector } from 'react-redux';
|
||||
import * as KeyboardLayout from '../services/keyboardLayout.dom.js';
|
||||
import { getHasPanelOpen } from '../state/selectors/conversations.dom.js';
|
||||
import { isInFullScreenCall } from '../state/selectors/calling.std.js';
|
||||
import { isShowingAnyModal } from '../state/selectors/globalModals.std.js';
|
||||
import type { ContextMenuTriggerType } from '../components/conversation/MessageContextMenu.dom.js';
|
||||
|
||||
const { get } = lodash;
|
||||
|
||||
type KeyboardShortcutHandlerType = (ev: KeyboardEvent) => boolean;
|
||||
|
||||
export function isCmdOrCtrl(ev: KeyboardEvent): boolean {
|
||||
const { ctrlKey, metaKey } = ev;
|
||||
const commandKey = get(window, 'platform') === 'darwin' && metaKey;
|
||||
const controlKey = get(window, 'platform') !== 'darwin' && ctrlKey;
|
||||
return commandKey || controlKey;
|
||||
}
|
||||
|
||||
function isCtrlOrAlt(ev: KeyboardEvent): boolean {
|
||||
const { altKey, ctrlKey } = ev;
|
||||
const controlKey = get(window, 'platform') === 'darwin' && ctrlKey;
|
||||
const theAltKey = get(window, 'platform') !== 'darwin' && altKey;
|
||||
return controlKey || theAltKey;
|
||||
}
|
||||
|
||||
type Mods = {
|
||||
// Mac: Meta (Command), Windows: Control
|
||||
controlOrMeta: boolean;
|
||||
// Shift
|
||||
shift: boolean;
|
||||
// Mac: Option, Windows: Alt
|
||||
alt: boolean;
|
||||
};
|
||||
|
||||
const defaultsMods: Mods = {
|
||||
controlOrMeta: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a keyboard event has the exact modifiers specified in the options,
|
||||
* and no others currently pressed.
|
||||
*/
|
||||
function hasExactModifiers(
|
||||
event: KeyboardEvent,
|
||||
options: Mods | 'none'
|
||||
): boolean {
|
||||
const mods = options === 'none' ? defaultsMods : options;
|
||||
const isApple = get(window, 'platform') === 'darwin';
|
||||
|
||||
if (isApple) {
|
||||
if (event.metaKey !== mods.controlOrMeta) {
|
||||
return false;
|
||||
}
|
||||
if (event.ctrlKey) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (event.ctrlKey !== mods.controlOrMeta) {
|
||||
return false;
|
||||
}
|
||||
if (event.metaKey) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.shiftKey !== mods.shift) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.altKey !== mods.alt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function useHasPanels(): boolean {
|
||||
return useSelector(getHasPanelOpen);
|
||||
}
|
||||
|
||||
function useHasGlobalModal(): boolean {
|
||||
return useSelector(isShowingAnyModal);
|
||||
}
|
||||
|
||||
function useHasCalling(): boolean {
|
||||
return useSelector(isInFullScreenCall);
|
||||
}
|
||||
|
||||
function useHasAnyOverlay(): boolean {
|
||||
const panels = useHasPanels();
|
||||
const globalModal = useHasGlobalModal();
|
||||
const calling = useHasCalling();
|
||||
|
||||
return panels || globalModal || calling;
|
||||
}
|
||||
|
||||
export function isKeyboardActivation(event: KeyboardEvent): boolean {
|
||||
if (
|
||||
hasExactModifiers(event, 'none') &&
|
||||
(event.key === 'Enter' || event.key === 'Space')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function useActiveCallShortcuts(
|
||||
hangUp: (reason: string) => unknown
|
||||
): KeyboardShortcutHandlerType {
|
||||
return useCallback(
|
||||
ev => {
|
||||
const { shiftKey } = ev;
|
||||
const key = KeyboardLayout.lookup(ev);
|
||||
|
||||
if (isCtrlOrAlt(ev) && shiftKey && (key === 'e' || key === 'E')) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
hangUp('Keyboard shortcut');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
[hangUp]
|
||||
);
|
||||
}
|
||||
|
||||
export function useIncomingCallShortcuts(
|
||||
acceptAudioCall: () => unknown,
|
||||
acceptVideoCall: () => unknown,
|
||||
declineCall: () => unknown
|
||||
): KeyboardShortcutHandlerType {
|
||||
return useCallback(
|
||||
ev => {
|
||||
const { shiftKey } = ev;
|
||||
const key = KeyboardLayout.lookup(ev);
|
||||
|
||||
if (isCtrlOrAlt(ev) && shiftKey && (key === 'v' || key === 'V')) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
acceptVideoCall();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isCtrlOrAlt(ev) && shiftKey && (key === 'a' || key === 'A')) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
acceptAudioCall();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isCtrlOrAlt(ev) && shiftKey && (key === 'd' || key === 'D')) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
declineCall();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
[acceptAudioCall, acceptVideoCall, declineCall]
|
||||
);
|
||||
}
|
||||
|
||||
export function useStartCallShortcuts(
|
||||
startAudioCall: () => unknown,
|
||||
startVideoCall: () => unknown
|
||||
): KeyboardShortcutHandlerType {
|
||||
return useCallback(
|
||||
ev => {
|
||||
const { shiftKey } = ev;
|
||||
const key = KeyboardLayout.lookup(ev);
|
||||
|
||||
if (isCtrlOrAlt(ev) && shiftKey && (key === 'c' || key === 'C')) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
startAudioCall();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isCtrlOrAlt(ev) && shiftKey && (key === 'y' || key === 'Y')) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
startVideoCall();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
[startAudioCall, startVideoCall]
|
||||
);
|
||||
}
|
||||
|
||||
export function useStartRecordingShortcut(
|
||||
startAudioRecording: () => unknown
|
||||
): KeyboardShortcutHandlerType {
|
||||
const hasOverlay = useHasAnyOverlay();
|
||||
|
||||
return useCallback(
|
||||
ev => {
|
||||
if (hasOverlay) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const key = KeyboardLayout.lookup(ev);
|
||||
|
||||
if (
|
||||
hasExactModifiers(ev, {
|
||||
controlOrMeta: true,
|
||||
shift: true,
|
||||
alt: false,
|
||||
}) &&
|
||||
(key === 'y' || key === 'Y')
|
||||
) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
startAudioRecording();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
[hasOverlay, startAudioRecording]
|
||||
);
|
||||
}
|
||||
|
||||
export function useAttachFileShortcut(
|
||||
attachFile: () => unknown
|
||||
): KeyboardShortcutHandlerType {
|
||||
const hasOverlay = useHasAnyOverlay();
|
||||
|
||||
return useCallback(
|
||||
ev => {
|
||||
if (hasOverlay) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { shiftKey } = ev;
|
||||
const key = KeyboardLayout.lookup(ev);
|
||||
|
||||
if (isCmdOrCtrl(ev) && !shiftKey && (key === 'u' || key === 'U')) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
attachFile();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
[attachFile, hasOverlay]
|
||||
);
|
||||
}
|
||||
|
||||
export function useToggleReactionPicker(
|
||||
handleReact: () => unknown
|
||||
): KeyboardShortcutHandlerType {
|
||||
const hasOverlay = useHasAnyOverlay();
|
||||
|
||||
return useCallback(
|
||||
ev => {
|
||||
if (hasOverlay) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { shiftKey } = ev;
|
||||
const key = KeyboardLayout.lookup(ev);
|
||||
|
||||
if (isCmdOrCtrl(ev) && shiftKey && (key === 'e' || key === 'E')) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
handleReact();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
[handleReact, hasOverlay]
|
||||
);
|
||||
}
|
||||
|
||||
export function useOpenContextMenu(
|
||||
openContextMenu: ContextMenuTriggerType['handleContextClick'] | undefined
|
||||
): KeyboardShortcutHandlerType {
|
||||
const hasOverlay = useHasAnyOverlay();
|
||||
|
||||
return useCallback(
|
||||
ev => {
|
||||
if (hasOverlay) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { shiftKey } = ev;
|
||||
const key = KeyboardLayout.lookup(ev);
|
||||
|
||||
const isMacOS = get(window, 'platform') === 'darwin';
|
||||
|
||||
if (
|
||||
(!isMacOS && shiftKey && key === 'F10') ||
|
||||
(isMacOS && isCmdOrCtrl(ev) && key === 'F12')
|
||||
) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
openContextMenu?.(new MouseEvent('click'));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
[hasOverlay, openContextMenu]
|
||||
);
|
||||
}
|
||||
|
||||
export function useEditLastMessageSent(
|
||||
maybeEditMessage: () => boolean
|
||||
): KeyboardShortcutHandlerType {
|
||||
const hasOverlay = useHasAnyOverlay();
|
||||
|
||||
return useCallback(
|
||||
ev => {
|
||||
if (hasOverlay) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const key = KeyboardLayout.lookup(ev);
|
||||
|
||||
// None of the modifiers should be pressed
|
||||
if (!hasExactModifiers(ev, 'none')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key === 'ArrowUp') {
|
||||
const value = maybeEditMessage();
|
||||
if (value) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
[hasOverlay, maybeEditMessage]
|
||||
);
|
||||
}
|
||||
|
||||
export function useKeyboardShortcuts(
|
||||
...eventHandlers: Array<KeyboardShortcutHandlerType>
|
||||
): void {
|
||||
useEffect(() => {
|
||||
function handleKeydown(ev: KeyboardEvent): void {
|
||||
eventHandlers.some(eventHandler => eventHandler(ev));
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeydown);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
};
|
||||
}, [eventHandlers]);
|
||||
}
|
||||
|
||||
export function useKeyboardShortcutsConditionally(
|
||||
condition: boolean,
|
||||
...eventHandlers: Array<KeyboardShortcutHandlerType>
|
||||
): void {
|
||||
useEffect(() => {
|
||||
if (!condition) {
|
||||
return;
|
||||
}
|
||||
|
||||
function handleKeydown(ev: KeyboardEvent): void {
|
||||
eventHandlers.some(eventHandler => eventHandler(ev));
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeydown);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
};
|
||||
}, [condition, eventHandlers]);
|
||||
}
|
||||
Reference in New Issue
Block a user