diff --git a/web/libs/editor/src/common/Button/Button.tsx b/web/libs/editor/src/common/Button/Button.tsx index 79f465994815..f225b2aea8f2 100644 --- a/web/libs/editor/src/common/Button/Button.tsx +++ b/web/libs/editor/src/common/Button/Button.tsx @@ -31,6 +31,7 @@ export interface ButtonProps extends HTMLButtonProps { danger?: boolean; style?: CSSProperties; hotkey?: keyof typeof Hotkey.keymap; + hotkeyScope?: string; tooltip?: string; tooltipTheme?: "light" | "dark"; nopadding?: boolean; @@ -63,6 +64,7 @@ export const Button: ButtonType = forwardRef( primary, danger, hotkey, + hotkeyScope, tooltip, tooltipTheme = "light", nopadding, @@ -101,7 +103,7 @@ export const Button: ButtonType = forwardRef( } }, [icon, size]); - useHotkey(hotkey, rest.onClick as unknown as Keymaster.KeyHandler); + useHotkey(hotkey, rest.onClick as unknown as Keymaster.KeyHandler, hotkeyScope); const buttonBody = ( diff --git a/web/libs/editor/src/components/Timeline/Controls.tsx b/web/libs/editor/src/components/Timeline/Controls.tsx index 35bb89dbad42..12191dda8d92 100644 --- a/web/libs/editor/src/components/Timeline/Controls.tsx +++ b/web/libs/editor/src/components/Timeline/Controls.tsx @@ -17,6 +17,7 @@ import { } from "../../assets/icons/timeline"; import { Button, type ButtonProps } from "../../common/Button/Button"; import { Space } from "../../common/Space/Space"; +import { Hotkey } from "../../core/Hotkey"; import { Block, Elem } from "../../utils/bem"; import { isDefined } from "../../utils/utilities"; import { TimelineContext } from "./Context"; @@ -247,6 +248,7 @@ export const Controls: FC = memo( data-testid={`playback-button:${playing ? "pause" : "play"}`} onClick={handlePlay} hotkey={settings?.playpauseHotkey} + hotkeyScope={Hotkey.ALL_SCOPES} > {playing ? : } diff --git a/web/libs/editor/src/core/Hotkey.ts b/web/libs/editor/src/core/Hotkey.ts index bac186488ed5..cfad5f6782b4 100644 --- a/web/libs/editor/src/core/Hotkey.ts +++ b/web/libs/editor/src/core/Hotkey.ts @@ -389,6 +389,8 @@ Hotkey.DEFAULT_SCOPE = DEFAULT_SCOPE; Hotkey.INPUT_SCOPE = INPUT_SCOPE; +Hotkey.ALL_SCOPES = [DEFAULT_SCOPE, INPUT_SCOPE].join(","); + Hotkey.keymap = { ...defaultKeymap } as Keymap; Hotkey.setKeymap = (newKeymap: Keymap) => { diff --git a/web/libs/editor/src/core/settings/keymap.json b/web/libs/editor/src/core/settings/keymap.json index cd887829de0c..962bddae93ac 100644 --- a/web/libs/editor/src/core/settings/keymap.json +++ b/web/libs/editor/src/core/settings/keymap.json @@ -4,7 +4,11 @@ "mac": "command+b", "description": "Back for one second" }, - "audio:playpause": {}, + "audio:playpause": { + "key": "ctrl+p", + "mac": "command+p", + "description": "Play/pause" + }, "ts:grow-left": { "key": "left", "description": "Increase region to the left" @@ -119,7 +123,8 @@ "description": "Delete selected region" }, "media:playpause": { - "key": "alt+space", + "key": "ctrl+alt+space", + "mac": "control+space", "description": "Play/pause" }, "media:step-backward": { diff --git a/web/libs/editor/src/hooks/useHotkey.ts b/web/libs/editor/src/hooks/useHotkey.ts index 88b519f91e37..f579a676112d 100644 --- a/web/libs/editor/src/hooks/useHotkey.ts +++ b/web/libs/editor/src/hooks/useHotkey.ts @@ -6,24 +6,25 @@ type Keyname = keyof typeof Hotkey.keymap; const hotkeys = Hotkey(); -const attachHotkey = (key: Keyname, handler: Keymaster.KeyHandler) => { +const attachHotkey = (key: Keyname, handler: Keymaster.KeyHandler, scope?: string) => { if (Hotkey.keymap[key]) { - hotkeys.overwriteNamed(key as string, handler); + hotkeys.overwriteNamed(key as string, handler, scope); } else { - hotkeys.overwriteKey(key as string, handler); + hotkeys.overwriteKey(key as string, handler, scope); } }; -const removeHotkey = (key: Keyname) => { +const removeHotkey = (key: Keyname, scope?: string) => { if (Hotkey.keymap[key]) { - hotkeys.removeNamed(key as string); + hotkeys.removeNamed(key as string, scope); } else { - hotkeys.removeKey(key as string); + hotkeys.removeKey(key as string, scope); } }; -export const useHotkey = (hotkey?: Keyname, handler?: Keymaster.KeyHandler) => { +export const useHotkey = (hotkey?: Keyname, handler?: Keymaster.KeyHandler, scope?: string) => { const lastHotkey = useRef(null); + const lastScope = useRef(null); const handlerFunction = useRef(handler); // we wanna cache handler function so the prop change does not re-attac a hotkey @@ -34,6 +35,7 @@ export const useHotkey = (hotkey?: Keyname, handler?: Keymaster.KeyHandler) => { useEffect(() => { const hotkeyChanged = hotkey !== lastHotkey.current; + const scopeChanged = scope !== lastScope.current; // hotkey itself only references a cached version of a function // so it's never re-attached even if handler changes @@ -41,17 +43,17 @@ export const useHotkey = (hotkey?: Keyname, handler?: Keymaster.KeyHandler) => { // and will trigger infinite loop if we use it as a dependency for // current effect (() => { - if (!hotkeyChanged) return; + if (!hotkeyChanged && !scopeChanged) return; if (hotkey) { - attachHotkey(hotkey, handlerWrapper.current); + attachHotkey(hotkey, handlerWrapper.current, scope); lastHotkey.current = hotkey; } else if (lastHotkey.current && !hotkey) { - removeHotkey(lastHotkey.current); + removeHotkey(lastHotkey.current, lastScope.current); lastHotkey.current = null; } })(); - }, [hotkey]); + }, [hotkey, scope]); // by changing the ref we can safely update the handler // as refs are mutable and doesn't trigger react-updates diff --git a/web/libs/editor/src/stores/Annotation/Annotation.js b/web/libs/editor/src/stores/Annotation/Annotation.js index 457db481d30a..4b29009d2b55 100644 --- a/web/libs/editor/src/stores/Annotation/Annotation.js +++ b/web/libs/editor/src/stores/Annotation/Annotation.js @@ -888,7 +888,7 @@ const _Annotation = types else audioNode = node; node.hotkey = comb; - hotkeys.addKey(comb, node.onHotKey, "Play an audio", `${Hotkey.DEFAULT_SCOPE},${Hotkey.INPUT_SCOPE}`); + hotkeys.addKey(comb, node.onHotKey, "Play an audio", Hotkey.ALL_SCOPES); audiosNum++; } diff --git a/web/libs/editor/src/tags/object/AudioUltra/view.tsx b/web/libs/editor/src/tags/object/AudioUltra/view.tsx index 6546395d4a52..7d86fdd6eade 100644 --- a/web/libs/editor/src/tags/object/AudioUltra/view.tsx +++ b/web/libs/editor/src/tags/object/AudioUltra/view.tsx @@ -1,5 +1,6 @@ import { observer } from "mobx-react"; -import { type FC, useEffect, useRef } from "react"; +import { type FC, useEffect, useMemo, useRef } from "react"; +import { TimelineContextProvider } from "../../../components/Timeline/Context"; import { Hotkey } from "../../../core/Hotkey"; import { useWaveform } from "../../../lib/AudioUltra/react"; import { Controls } from "../../../components/Timeline/Controls"; @@ -152,6 +153,22 @@ const AudioUltraView: FC = ({ item }) => { }; }, []); + const contextValue = useMemo(() => { + return { + position: 0, + length: 0, + regions: [], + step: 10, + playing: false, + visibleWidth: 0, + seekOffset: 0, + data: undefined, + settings: { + playpauseHotkey: "audio:playpause", + }, + }; + }, []); + return ( {item.errors?.map((error: any, i: any) => ( @@ -163,45 +180,47 @@ const AudioUltraView: FC = ({ item }) => { item.stageRef.current = el; }} /> - controls.setPlaying(true)} - onPause={() => controls.setPlaying(false)} - allowFullscreen={false} - onVolumeChange={(vol) => controls.setVolume(vol)} - onStepBackward={() => { - waveform.current?.seekBackward(NORMALIZED_STEP); - waveform.current?.syncCursor(); - }} - onStepForward={() => { - waveform.current?.seekForward(NORMALIZED_STEP); - waveform.current?.syncCursor(); - }} - onPositionChange={(pos) => { - waveform.current?.seek(pos); - waveform.current?.syncCursor(); - }} - onSpeedChange={(speed) => controls.setRate(speed)} - onZoom={(zoom) => controls.setZoom(zoom)} - amp={controls.amp} - onAmpChange={(amp) => controls.setAmp(amp)} - mediaType="audio" - toggleVisibility={(layerName: string, isVisible: boolean) => { - if (waveform.current) { - const layer = waveform.current?.getLayer(layerName); - - if (layer) { - layer.setVisibility(isVisible); + + controls.setPlaying(true)} + onPause={() => controls.setPlaying(false)} + allowFullscreen={false} + onVolumeChange={(vol) => controls.setVolume(vol)} + onStepBackward={() => { + waveform.current?.seekBackward(NORMALIZED_STEP); + waveform.current?.syncCursor(); + }} + onStepForward={() => { + waveform.current?.seekForward(NORMALIZED_STEP); + waveform.current?.syncCursor(); + }} + onPositionChange={(pos) => { + waveform.current?.seek(pos); + waveform.current?.syncCursor(); + }} + onSpeedChange={(speed) => controls.setRate(speed)} + onZoom={(zoom) => controls.setZoom(zoom)} + amp={controls.amp} + onAmpChange={(amp) => controls.setAmp(amp)} + mediaType="audio" + toggleVisibility={(layerName: string, isVisible: boolean) => { + if (waveform.current) { + const layer = waveform.current?.getLayer(layerName); + + if (layer) { + layer.setVisibility(isVisible); + } } - } - }} - layerVisibility={controls.layerVisibility} - /> + }} + layerVisibility={controls.layerVisibility} + /> + ); };