Adds keyboard shortcuts for calling
This commit is contained in:
parent
1b052ad16b
commit
fa7b7fcd08
6 changed files with 311 additions and 90 deletions
|
@ -3133,6 +3133,30 @@
|
||||||
"message": "Toggle video on and off",
|
"message": "Toggle video on and off",
|
||||||
"description": "Shown in the shortcuts guide"
|
"description": "Shown in the shortcuts guide"
|
||||||
},
|
},
|
||||||
|
"Keyboard--accept-video-call": {
|
||||||
|
"message": "Accept call with video",
|
||||||
|
"description": "Shown in the calling keyboard shortcuts guide"
|
||||||
|
},
|
||||||
|
"Keyboard--accept-audio-call": {
|
||||||
|
"message": "Accept call with audio",
|
||||||
|
"description": "Shown in the calling keyboard shortcuts guide"
|
||||||
|
},
|
||||||
|
"Keyboard--start-audio-call": {
|
||||||
|
"message": "Start audio call",
|
||||||
|
"description": "Shown in the calling keyboard shortcuts guide"
|
||||||
|
},
|
||||||
|
"Keyboard--start-video-call": {
|
||||||
|
"message": "Start video call",
|
||||||
|
"description": "Shown in the calling keyboard shortcuts guide"
|
||||||
|
},
|
||||||
|
"Keyboard--decline-call": {
|
||||||
|
"message": "Decline call",
|
||||||
|
"description": "Shown in the calling keyboard shortcuts guide"
|
||||||
|
},
|
||||||
|
"Keyboard--hang-up": {
|
||||||
|
"message": "End call",
|
||||||
|
"description": "Shown in the calling keyboard shortcuts guide"
|
||||||
|
},
|
||||||
"close-popup": {
|
"close-popup": {
|
||||||
"message": "Close Popup",
|
"message": "Close Popup",
|
||||||
"description": "Used as alt text for any button closing a popup"
|
"description": "Used as alt text for any button closing a popup"
|
||||||
|
|
|
@ -39,6 +39,10 @@ import { missingCaseError } from '../util/missingCaseError';
|
||||||
import * as KeyboardLayout from '../services/keyboardLayout';
|
import * as KeyboardLayout from '../services/keyboardLayout';
|
||||||
import { useActivateSpeakerViewOnPresenting } from '../hooks/useActivateSpeakerViewOnPresenting';
|
import { useActivateSpeakerViewOnPresenting } from '../hooks/useActivateSpeakerViewOnPresenting';
|
||||||
import { CallingAudioIndicator } from './CallingAudioIndicator';
|
import { CallingAudioIndicator } from './CallingAudioIndicator';
|
||||||
|
import {
|
||||||
|
useActiveCallShortcuts,
|
||||||
|
useKeyboardShortcuts,
|
||||||
|
} from '../hooks/useKeyboardShortcuts';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
activeCall: ActiveCallType;
|
activeCall: ActiveCallType;
|
||||||
|
@ -140,6 +144,9 @@ export const CallScreen: React.FC<PropsType> = ({
|
||||||
toggleSpeakerView
|
toggleSpeakerView
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const activeCallShortcuts = useActiveCallShortcuts(hangUpActiveCall);
|
||||||
|
useKeyboardShortcuts(activeCallShortcuts);
|
||||||
|
|
||||||
const toggleAudio = useCallback(() => {
|
const toggleAudio = useCallback(() => {
|
||||||
setLocalAudio({
|
setLocalAudio({
|
||||||
enabled: !hasLocalAudio,
|
enabled: !hasLocalAudio,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { ReactChild } from 'react';
|
import type { ReactChild } from 'react';
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useCallback, useEffect, useRef } from 'react';
|
||||||
import { Avatar } from './Avatar';
|
import { Avatar } from './Avatar';
|
||||||
import { Tooltip } from './Tooltip';
|
import { Tooltip } from './Tooltip';
|
||||||
import { Intl } from './Intl';
|
import { Intl } from './Intl';
|
||||||
|
@ -16,6 +16,10 @@ import { CallMode } from '../types/Calling';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { AcceptCallType, DeclineCallType } from '../state/ducks/calling';
|
import type { AcceptCallType, DeclineCallType } from '../state/ducks/calling';
|
||||||
import { missingCaseError } from '../util/missingCaseError';
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
|
import {
|
||||||
|
useIncomingCallShortcuts,
|
||||||
|
useKeyboardShortcuts,
|
||||||
|
} from '../hooks/useKeyboardShortcuts';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
acceptCall: (_: AcceptCallType) => void;
|
acceptCall: (_: AcceptCallType) => void;
|
||||||
|
@ -222,6 +226,25 @@ export const IncomingCallBar = (props: PropsType): JSX.Element | null => {
|
||||||
};
|
};
|
||||||
}, [bounceAppIconStart, bounceAppIconStop]);
|
}, [bounceAppIconStart, bounceAppIconStop]);
|
||||||
|
|
||||||
|
const acceptVideoCall = useCallback(() => {
|
||||||
|
acceptCall({ conversationId, asVideoCall: true });
|
||||||
|
}, [acceptCall, conversationId]);
|
||||||
|
|
||||||
|
const acceptAudioCall = useCallback(() => {
|
||||||
|
acceptCall({ conversationId, asVideoCall: false });
|
||||||
|
}, [acceptCall, conversationId]);
|
||||||
|
|
||||||
|
const declineIncomingCall = useCallback(() => {
|
||||||
|
declineCall({ conversationId });
|
||||||
|
}, [conversationId, declineCall]);
|
||||||
|
|
||||||
|
const incomingCallShortcuts = useIncomingCallShortcuts(
|
||||||
|
acceptAudioCall,
|
||||||
|
acceptVideoCall,
|
||||||
|
declineIncomingCall
|
||||||
|
);
|
||||||
|
useKeyboardShortcuts(incomingCallShortcuts);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="IncomingCallBar__container">
|
<div className="IncomingCallBar__container">
|
||||||
<div className="IncomingCallBar__bar">
|
<div className="IncomingCallBar__bar">
|
||||||
|
@ -259,9 +282,7 @@ export const IncomingCallBar = (props: PropsType): JSX.Element | null => {
|
||||||
<div className="IncomingCallBar__actions">
|
<div className="IncomingCallBar__actions">
|
||||||
<CallButton
|
<CallButton
|
||||||
classSuffix="decline"
|
classSuffix="decline"
|
||||||
onClick={() => {
|
onClick={declineIncomingCall}
|
||||||
declineCall({ conversationId });
|
|
||||||
}}
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
tooltipContent={i18n('declineCall')}
|
tooltipContent={i18n('declineCall')}
|
||||||
/>
|
/>
|
||||||
|
@ -269,17 +290,13 @@ export const IncomingCallBar = (props: PropsType): JSX.Element | null => {
|
||||||
<>
|
<>
|
||||||
<CallButton
|
<CallButton
|
||||||
classSuffix="accept-video-as-audio"
|
classSuffix="accept-video-as-audio"
|
||||||
onClick={() => {
|
onClick={acceptAudioCall}
|
||||||
acceptCall({ conversationId, asVideoCall: false });
|
|
||||||
}}
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
tooltipContent={i18n('acceptCallWithoutVideo')}
|
tooltipContent={i18n('acceptCallWithoutVideo')}
|
||||||
/>
|
/>
|
||||||
<CallButton
|
<CallButton
|
||||||
classSuffix="accept-video"
|
classSuffix="accept-video"
|
||||||
onClick={() => {
|
onClick={acceptVideoCall}
|
||||||
acceptCall({ conversationId, asVideoCall: true });
|
|
||||||
}}
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
tooltipContent={i18n('acceptCall')}
|
tooltipContent={i18n('acceptCall')}
|
||||||
/>
|
/>
|
||||||
|
@ -287,9 +304,7 @@ export const IncomingCallBar = (props: PropsType): JSX.Element | null => {
|
||||||
) : (
|
) : (
|
||||||
<CallButton
|
<CallButton
|
||||||
classSuffix="accept-audio"
|
classSuffix="accept-audio"
|
||||||
onClick={() => {
|
onClick={acceptAudioCall}
|
||||||
acceptCall({ conversationId, asVideoCall: false });
|
|
||||||
}}
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
tooltipContent={i18n('acceptCall')}
|
tooltipContent={i18n('acceptCall')}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -15,6 +15,7 @@ export type Props = {
|
||||||
|
|
||||||
type KeyType =
|
type KeyType =
|
||||||
| 'commandOrCtrl'
|
| 'commandOrCtrl'
|
||||||
|
| 'ctrlOrAlt'
|
||||||
| 'optionOrAlt'
|
| 'optionOrAlt'
|
||||||
| 'shift'
|
| 'shift'
|
||||||
| 'enter'
|
| 'enter'
|
||||||
|
@ -40,6 +41,7 @@ type KeyType =
|
||||||
| 'U'
|
| 'U'
|
||||||
| 'V'
|
| 'V'
|
||||||
| 'X'
|
| 'X'
|
||||||
|
| 'Y'
|
||||||
| '1 to 9';
|
| '1 to 9';
|
||||||
type ShortcutType = {
|
type ShortcutType = {
|
||||||
description: string;
|
description: string;
|
||||||
|
@ -202,6 +204,30 @@ const CALLING_SHORTCUTS: Array<ShortcutType> = [
|
||||||
description: 'Keyboard--toggle-video',
|
description: 'Keyboard--toggle-video',
|
||||||
keys: [['shift', 'V']],
|
keys: [['shift', 'V']],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: 'Keyboard--accept-video-call',
|
||||||
|
keys: [['ctrlOrAlt', 'shift', 'V']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Keyboard--accept-audio-call',
|
||||||
|
keys: [['ctrlOrAlt', 'shift', 'A']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Keyboard--decline-call',
|
||||||
|
keys: [['ctrlOrAlt', 'shift', 'D']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Keyboard--start-audio-call',
|
||||||
|
keys: [['ctrlOrAlt', 'shift', 'C']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Keyboard--start-video-call',
|
||||||
|
keys: [['ctrlOrAlt', 'shift', 'Y']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Keyboard--hang-up',
|
||||||
|
keys: [['ctrlOrAlt', 'shift', 'E']],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ShortcutGuide = (props: Props): JSX.Element => {
|
export const ShortcutGuide = (props: Props): JSX.Element => {
|
||||||
|
@ -312,6 +338,14 @@ function renderShortcut(
|
||||||
label = i18n('Keyboard--Key--ctrl');
|
label = i18n('Keyboard--Key--ctrl');
|
||||||
isSquare = false;
|
isSquare = false;
|
||||||
}
|
}
|
||||||
|
if (key === 'ctrlOrAlt' && isMacOS) {
|
||||||
|
label = i18n('Keyboard--Key--ctrl');
|
||||||
|
isSquare = false;
|
||||||
|
}
|
||||||
|
if (key === 'ctrlOrAlt' && !isMacOS) {
|
||||||
|
label = i18n('Keyboard--Key--alt');
|
||||||
|
isSquare = false;
|
||||||
|
}
|
||||||
if (key === 'optionOrAlt' && isMacOS) {
|
if (key === 'optionOrAlt' && isMacOS) {
|
||||||
label = i18n('Keyboard--Key--option');
|
label = i18n('Keyboard--Key--option');
|
||||||
isSquare = false;
|
isSquare = false;
|
||||||
|
|
|
@ -24,6 +24,10 @@ import { getMuteOptions } from '../../util/getMuteOptions';
|
||||||
import * as expirationTimer from '../../util/expirationTimer';
|
import * as expirationTimer from '../../util/expirationTimer';
|
||||||
import { missingCaseError } from '../../util/missingCaseError';
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
import { isInSystemContacts } from '../../util/isInSystemContacts';
|
import { isInSystemContacts } from '../../util/isInSystemContacts';
|
||||||
|
import {
|
||||||
|
useStartCallShortcuts,
|
||||||
|
useKeyboardShortcuts,
|
||||||
|
} from '../../hooks/useKeyboardShortcuts';
|
||||||
|
|
||||||
export enum OutgoingCallButtonStyle {
|
export enum OutgoingCallButtonStyle {
|
||||||
None,
|
None,
|
||||||
|
@ -297,80 +301,6 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderOutgoingCallButtons(): ReactNode {
|
|
||||||
const {
|
|
||||||
announcementsOnly,
|
|
||||||
areWeAdmin,
|
|
||||||
i18n,
|
|
||||||
onOutgoingAudioCallInConversation,
|
|
||||||
onOutgoingVideoCallInConversation,
|
|
||||||
outgoingCallButtonStyle,
|
|
||||||
showBackButton,
|
|
||||||
} = this.props;
|
|
||||||
const { isNarrow } = this.state;
|
|
||||||
|
|
||||||
const videoButton = (
|
|
||||||
<button
|
|
||||||
aria-label={i18n('makeOutgoingVideoCall')}
|
|
||||||
className={classNames(
|
|
||||||
'module-ConversationHeader__button',
|
|
||||||
'module-ConversationHeader__button--video',
|
|
||||||
showBackButton ? null : 'module-ConversationHeader__button--show',
|
|
||||||
!showBackButton && announcementsOnly && !areWeAdmin
|
|
||||||
? 'module-ConversationHeader__button--show-disabled'
|
|
||||||
: undefined
|
|
||||||
)}
|
|
||||||
disabled={showBackButton}
|
|
||||||
onClick={onOutgoingVideoCallInConversation}
|
|
||||||
type="button"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
switch (outgoingCallButtonStyle) {
|
|
||||||
case OutgoingCallButtonStyle.None:
|
|
||||||
return null;
|
|
||||||
case OutgoingCallButtonStyle.JustVideo:
|
|
||||||
return videoButton;
|
|
||||||
case OutgoingCallButtonStyle.Both:
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{videoButton}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onOutgoingAudioCallInConversation}
|
|
||||||
className={classNames(
|
|
||||||
'module-ConversationHeader__button',
|
|
||||||
'module-ConversationHeader__button--audio',
|
|
||||||
showBackButton
|
|
||||||
? null
|
|
||||||
: 'module-ConversationHeader__button--show'
|
|
||||||
)}
|
|
||||||
disabled={showBackButton}
|
|
||||||
aria-label={i18n('makeOutgoingCall')}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
case OutgoingCallButtonStyle.Join:
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
aria-label={i18n('joinOngoingCall')}
|
|
||||||
className={classNames(
|
|
||||||
'module-ConversationHeader__button',
|
|
||||||
'module-ConversationHeader__button--join-call',
|
|
||||||
showBackButton ? null : 'module-ConversationHeader__button--show'
|
|
||||||
)}
|
|
||||||
disabled={showBackButton}
|
|
||||||
onClick={onOutgoingVideoCallInConversation}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{isNarrow ? null : i18n('joinOngoingCall')}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw missingCaseError(outgoingCallButtonStyle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private renderMenu(triggerId: string): ReactNode {
|
private renderMenu(triggerId: string): ReactNode {
|
||||||
const {
|
const {
|
||||||
acceptedMessageRequest,
|
acceptedMessageRequest,
|
||||||
|
@ -588,8 +518,19 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public override render(): ReactNode {
|
public override render(): ReactNode {
|
||||||
const { id, isSMSOnly, i18n, onSetDisappearingMessages, expireTimer } =
|
const {
|
||||||
this.props;
|
announcementsOnly,
|
||||||
|
areWeAdmin,
|
||||||
|
expireTimer,
|
||||||
|
i18n,
|
||||||
|
id,
|
||||||
|
isSMSOnly,
|
||||||
|
onOutgoingAudioCallInConversation,
|
||||||
|
onOutgoingVideoCallInConversation,
|
||||||
|
onSetDisappearingMessages,
|
||||||
|
outgoingCallButtonStyle,
|
||||||
|
showBackButton,
|
||||||
|
} = this.props;
|
||||||
const { isNarrow, modalState } = this.state;
|
const { isNarrow, modalState } = this.state;
|
||||||
const triggerId = `conversation-${id}`;
|
const triggerId = `conversation-${id}`;
|
||||||
|
|
||||||
|
@ -633,7 +574,22 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
>
|
>
|
||||||
{this.renderBackButton()}
|
{this.renderBackButton()}
|
||||||
{this.renderHeader()}
|
{this.renderHeader()}
|
||||||
{!isSMSOnly && this.renderOutgoingCallButtons()}
|
{!isSMSOnly && (
|
||||||
|
<OutgoingCallButtons
|
||||||
|
announcementsOnly={announcementsOnly}
|
||||||
|
areWeAdmin={areWeAdmin}
|
||||||
|
i18n={i18n}
|
||||||
|
isNarrow={isNarrow}
|
||||||
|
onOutgoingAudioCallInConversation={
|
||||||
|
onOutgoingAudioCallInConversation
|
||||||
|
}
|
||||||
|
onOutgoingVideoCallInConversation={
|
||||||
|
onOutgoingVideoCallInConversation
|
||||||
|
}
|
||||||
|
outgoingCallButtonStyle={outgoingCallButtonStyle}
|
||||||
|
showBackButton={showBackButton}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{this.renderSearchButton()}
|
{this.renderSearchButton()}
|
||||||
{this.renderMoreButton(triggerId)}
|
{this.renderMoreButton(triggerId)}
|
||||||
{this.renderMenu(triggerId)}
|
{this.renderMenu(triggerId)}
|
||||||
|
@ -644,3 +600,88 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function OutgoingCallButtons({
|
||||||
|
announcementsOnly,
|
||||||
|
areWeAdmin,
|
||||||
|
i18n,
|
||||||
|
isNarrow,
|
||||||
|
onOutgoingAudioCallInConversation,
|
||||||
|
onOutgoingVideoCallInConversation,
|
||||||
|
outgoingCallButtonStyle,
|
||||||
|
showBackButton,
|
||||||
|
}: { isNarrow: boolean } & Pick<
|
||||||
|
PropsType,
|
||||||
|
| 'announcementsOnly'
|
||||||
|
| 'areWeAdmin'
|
||||||
|
| 'i18n'
|
||||||
|
| 'onOutgoingAudioCallInConversation'
|
||||||
|
| 'onOutgoingVideoCallInConversation'
|
||||||
|
| 'outgoingCallButtonStyle'
|
||||||
|
| 'showBackButton'
|
||||||
|
>): JSX.Element | null {
|
||||||
|
const videoButton = (
|
||||||
|
<button
|
||||||
|
aria-label={i18n('makeOutgoingVideoCall')}
|
||||||
|
className={classNames(
|
||||||
|
'module-ConversationHeader__button',
|
||||||
|
'module-ConversationHeader__button--video',
|
||||||
|
showBackButton ? null : 'module-ConversationHeader__button--show',
|
||||||
|
!showBackButton && announcementsOnly && !areWeAdmin
|
||||||
|
? 'module-ConversationHeader__button--show-disabled'
|
||||||
|
: undefined
|
||||||
|
)}
|
||||||
|
disabled={showBackButton}
|
||||||
|
onClick={onOutgoingVideoCallInConversation}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const startCallShortcuts = useStartCallShortcuts(
|
||||||
|
onOutgoingAudioCallInConversation,
|
||||||
|
onOutgoingVideoCallInConversation
|
||||||
|
);
|
||||||
|
useKeyboardShortcuts(startCallShortcuts);
|
||||||
|
|
||||||
|
switch (outgoingCallButtonStyle) {
|
||||||
|
case OutgoingCallButtonStyle.None:
|
||||||
|
return null;
|
||||||
|
case OutgoingCallButtonStyle.JustVideo:
|
||||||
|
return videoButton;
|
||||||
|
case OutgoingCallButtonStyle.Both:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{videoButton}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onOutgoingAudioCallInConversation}
|
||||||
|
className={classNames(
|
||||||
|
'module-ConversationHeader__button',
|
||||||
|
'module-ConversationHeader__button--audio',
|
||||||
|
showBackButton ? null : 'module-ConversationHeader__button--show'
|
||||||
|
)}
|
||||||
|
disabled={showBackButton}
|
||||||
|
aria-label={i18n('makeOutgoingCall')}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
case OutgoingCallButtonStyle.Join:
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
aria-label={i18n('joinOngoingCall')}
|
||||||
|
className={classNames(
|
||||||
|
'module-ConversationHeader__button',
|
||||||
|
'module-ConversationHeader__button--join-call',
|
||||||
|
showBackButton ? null : 'module-ConversationHeader__button--show'
|
||||||
|
)}
|
||||||
|
disabled={showBackButton}
|
||||||
|
onClick={onOutgoingVideoCallInConversation}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{isNarrow ? null : i18n('joinOngoingCall')}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw missingCaseError(outgoingCallButtonStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,106 @@ function isCmdOrCtrl(ev: KeyboardEvent): boolean {
|
||||||
return commandKey || controlKey;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useActiveCallShortcuts(
|
||||||
|
hangUp: () => 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();
|
||||||
|
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(
|
export function useStartRecordingShortcut(
|
||||||
startAudioRecording: () => unknown
|
startAudioRecording: () => unknown
|
||||||
): KeyboardShortcutHandlerType {
|
): KeyboardShortcutHandlerType {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue