diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index 7d4ae7b3..871567b8 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -42,6 +42,7 @@ "Display name": "Display name", "Download debug logs": "Download debug logs", "Element Call Home": "Element Call Home", + "Single-key keyboard shortcuts": "Single-key keyboard shortcuts", "Entering room…": "Entering room…", "Exit full screen": "Exit full screen", "Fetching group call timed out.": "Fetching group call timed out.", @@ -133,6 +134,7 @@ "Walkie-talkie call": "Walkie-talkie call", "Walkie-talkie call name": "Walkie-talkie call name", "WebRTC is not supported or is being blocked in this browser.": "WebRTC is not supported or is being blocked in this browser.", + "Whether to enable single-key keyboard shortcuts, e.g. 'm' to mute/unmute the mic.": "Whether to enable single-key keyboard shortcuts, e.g. 'm' to mute/unmute the mic.", "Yes, join call": "Yes, join call", "You can't talk at the same time": "You can't talk at the same time", "Your recent calls": "Your recent calls" diff --git a/src/config/Config.ts b/src/config/Config.ts index 42d9b1b1..2806a5b0 100644 --- a/src/config/Config.ts +++ b/src/config/Config.ts @@ -14,7 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { DEFAULT_CONFIG, ConfigOptions, ResolvedConfigOptions } from "./ConfigOptions"; +import { + DEFAULT_CONFIG, + ConfigOptions, + ResolvedConfigOptions, +} from "./ConfigOptions"; export class Config { private static internalInstance: Config; diff --git a/src/room/PTTButton.tsx b/src/room/PTTButton.tsx index 3551cfa2..e083759b 100644 --- a/src/room/PTTButton.tsx +++ b/src/room/PTTButton.tsx @@ -23,6 +23,7 @@ import { ReactComponent as MicIcon } from "../icons/Mic.svg"; import { useEventTarget } from "../useEvents"; import { Avatar } from "../Avatar"; import { usePrefersReducedMotion } from "../usePrefersReducedMotion"; +import { getSetting } from "../settings/useSetting"; interface Props { enabled: boolean; @@ -134,6 +135,12 @@ export const PTTButton: React.FC = ({ (e: KeyboardEvent) => { if (e.code === "Space") { if (!enabled) return; + // Check if keyboard shortcuts are enabled + const keyboardShortcuts = getSetting("keyboard-shortcuts", true); + if (!keyboardShortcuts) { + return; + } + e.preventDefault(); hold(); @@ -148,6 +155,12 @@ export const PTTButton: React.FC = ({ useCallback( (e: KeyboardEvent) => { if (e.code === "Space") { + // Check if keyboard shortcuts are enabled + const keyboardShortcuts = getSetting("keyboard-shortcuts", true); + if (!keyboardShortcuts) { + return; + } + e.preventDefault(); unhold(); diff --git a/src/room/useGroupCall.ts b/src/room/useGroupCall.ts index 3a917f95..14e40d14 100644 --- a/src/room/useGroupCall.ts +++ b/src/room/useGroupCall.ts @@ -33,6 +33,8 @@ import { usePageUnload } from "./usePageUnload"; import { PosthogAnalytics } from "../PosthogAnalytics"; import { TranslatedError, translatedError } from "../TranslatedError"; import { ElementWidgetActions, ScreenshareStartData, widget } from "../widget"; +import { getSetting } from "../settings/useSetting"; +import { useEventTarget } from "../useEvents"; export interface UseGroupCallReturnType { state: GroupCallState; @@ -298,11 +300,18 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType { PosthogAnalytics.instance.eventMuteCamera.track(toggleToMute); }, [groupCall]); + const setMicrophoneMuted = useCallback( + (setMuted) => { + groupCall.setMicrophoneMuted(setMuted); + PosthogAnalytics.instance.eventMuteMicrophone.track(setMuted); + }, + [groupCall] + ); + const toggleMicrophoneMuted = useCallback(() => { const toggleToMute = !groupCall.isMicrophoneMuted(); - groupCall.setMicrophoneMuted(toggleToMute); - PosthogAnalytics.instance.eventMuteMicrophone.track(toggleToMute); - }, [groupCall]); + setMicrophoneMuted(toggleToMute); + }, [groupCall, setMicrophoneMuted]); const toggleScreensharing = useCallback(async () => { if (!groupCall.isScreensharing()) { @@ -395,6 +404,68 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType { } }, [t]); + const [spacebarHeld, setSpacebarHeld] = useState(false); + + useEventTarget( + window, + "keydown", + useCallback( + (event: KeyboardEvent) => { + // Check if keyboard shortcuts are enabled + const keyboardShortcuts = getSetting("keyboard-shortcuts", true); + if (!keyboardShortcuts) { + return; + } + + if (event.key === "m") { + toggleMicrophoneMuted(); + } else if (event.key == "v") { + toggleLocalVideoMuted(); + } else if (event.key === " ") { + setSpacebarHeld(true); + setMicrophoneMuted(false); + } + }, + [ + toggleLocalVideoMuted, + toggleMicrophoneMuted, + setMicrophoneMuted, + setSpacebarHeld, + ] + ) + ); + + useEventTarget( + window, + "keyup", + useCallback( + (event: KeyboardEvent) => { + // Check if keyboard shortcuts are enabled + const keyboardShortcuts = getSetting("keyboard-shortcuts", true); + if (!keyboardShortcuts) { + return; + } + + if (event.key === " ") { + setSpacebarHeld(false); + setMicrophoneMuted(true); + } + }, + [setMicrophoneMuted, setSpacebarHeld] + ) + ); + + useEventTarget( + window, + "blur", + useCallback(() => { + if (spacebarHeld) { + setSpacebarHeld(false); + setMicrophoneMuted(true); + } + }, [setMicrophoneMuted, setSpacebarHeld, spacebarHeld]) + ); + return { state, calls, diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index f895538a..5bf02e78 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -28,6 +28,7 @@ import { ReactComponent as OverflowIcon } from "../icons/Overflow.svg"; import { SelectInput } from "../input/SelectInput"; import { useMediaHandler } from "./useMediaHandler"; import { + useKeyboardShortcuts, useSpatialAudio, useShowInspector, useOptInAnalytics, @@ -59,6 +60,7 @@ export const SettingsModal = (props: Props) => { const [spatialAudio, setSpatialAudio] = useSpatialAudio(); const [showInspector, setShowInspector] = useShowInspector(); const [optInAnalytics, setOptInAnalytics] = useOptInAnalytics(); + const [keyboardShortcuts, setKeyboardShortcuts] = useKeyboardShortcuts(); const downloadDebugLog = useDownloadDebugLog(); @@ -166,6 +168,20 @@ export const SettingsModal = (props: Props) => { } /> + + ) => + setKeyboardShortcuts(event.target.checked) + } + /> + (name: string, defaultValue: T): T => { export const useSpatialAudio = () => useSetting("spatial-audio", false); export const useShowInspector = () => useSetting("show-inspector", false); export const useOptInAnalytics = () => useSetting("opt-in-analytics", false); +export const useKeyboardShortcuts = () => + useSetting("keyboard-shortcuts", true);