diff --git a/package.json b/package.json index a5018a8d..1fa075fd 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "start": "concurrently -k \"BROWSER=none PORT=3001 react-scripts start\" \"wait-on http://localhost:3001 && electron .\"", "icon-gen": "mkdirp icon-build && svg2png src/icon.svg -o ./icon-build/app-512.png -w 512 -h 512 && mkdirp icns-build && icon-gen -i src/icon.svg -o ./icns-build --icns --icns-sizes 512,1024 && mkdirp build-resources/appx && npx svg2png src/icon.svg --output=build-resources/appx/StoreLogo.png --width=50 --height=50 && npx svg2png src/icon.svg --output=build-resources/appx/Square150x150Logo.png --width=300 --height=300 && npx svg2png src/icon.svg --output=build-resources/appx/Square44x44Logo.png --width=44 --height=44 && npx svg2png src/icon.svg --output=build-resources/appx/Wide310x150Logo.png --width=620 --height=300", - "download-ffmpeg": "mkdir -p ffmpeg-mac && wget https://github.com/mifi/ffmpeg-build-script/releases/download/n4.2.2/ffmpeg -O ffmpeg-mac/ffmpeg && wget https://github.com/mifi/ffmpeg-build-script/releases/download/n4.2.2/ffprobe -O ffmpeg-mac/ffprobe && chmod +x ffmpeg-mac/ffmpeg && chmod +x ffmpeg-mac/ffprobe", + "download-ffmpeg": "mkdir -p ffmpeg-mac && wget https://github.com/mifi/ffmpeg-build-script/releases/download/n4.3.1/ffmpeg -O ffmpeg-mac/ffmpeg && wget https://github.com/mifi/ffmpeg-build-script/releases/download/n4.3.1/ffprobe -O ffmpeg-mac/ffprobe && chmod +x ffmpeg-mac/ffmpeg && chmod +x ffmpeg-mac/ffprobe", "build": "yarn icon-gen && react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", @@ -98,6 +98,7 @@ "i18next-fs-backend": "^1.0.1", "json5": "^2.1.3", "mime-types": "^2.1.14", + "mousetrap": "^1.6.5", "open": "^7.0.3", "react-syntax-highlighter": "^13.0.0", "read-chunk": "^2.0.0", diff --git a/src/App.jsx b/src/App.jsx index b419af1b..fdbd3c65 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -11,6 +11,7 @@ import filePathToUrl from 'file-url'; import i18n from 'i18next'; import { useTranslation } from 'react-i18next'; import withReactContent from 'sweetalert2-react-content'; +import Mousetrap from 'mousetrap'; import fromPairs from 'lodash/fromPairs'; import clamp from 'lodash/clamp'; @@ -1326,49 +1327,43 @@ const App = memo(() => { const zoomIn = () => { zoomRel(1); return false; }; const zoomOut = () => { zoomRel(-1); return false; }; - function onKeyDown(e) { - // console.log(e.key); - switch (e.key) { - case '+': return addCutSegment(); - case ' ': return togglePlayReset(); - case 'k': case 'K': return togglePlayNoReset(); - case 'j': case 'J': return reducePlaybackRate(); - case 'l': case 'L': return increasePlaybackRate(); - case 'ArrowLeft': { - if (e.ctrlKey || e.metaKey) return seekBackwardsPercent(); - if (e.altKey) return seekBackwardsKeyframe(); - if (e.shiftKey) return jumpCutStart(); - return seekBackwards(); - } - case 'ArrowRight': { - if (e.ctrlKey || e.metaKey) return seekForwardsPercent(); - if (e.altKey) return seekForwardsKeyframe(); - if (e.shiftKey) return jumpCutEnd(); - return seekForwards(); - } - case 'ArrowUp': { - if (e.ctrlKey || e.metaKey) return zoomIn(); - return jumpPrevSegment(); - } - case 'ArrowDown': { - if (e.ctrlKey || e.metaKey) return zoomOut(); - return jumpNextSegment(); - } - case 'z': case 'Z': return toggleComfortZoom(); - case ',': return seekBackwardsShort(); - case '.': return seekForwardsShort(); - case 'c': case 'C': return capture(); - case 'i': case 'I': return setCutStart(); - case 'o': case 'O': return setCutEnd(); - case 'Backspace': return removeCutSegment(); - case 'd': case 'D': return deleteSource(); - case 'b': case 'B': return splitCurrentSegment(); - case 'r': case 'R': return increaseRotation(); - default: return undefined; - } - } - document.addEventListener('keydown', onKeyDown); - return () => document.removeEventListener('keydown', onKeyDown); + // mousetrap seems to be the only lib properly handling layouts that require shift to be pressed to get a particular key #520 + // Also document.addEventListener needs custom handling of modifier keys or C will be triggered by CTRL+C, etc + const mousetrap = new Mousetrap(); + // mousetrap.bind(':', () => console.log('test')); + mousetrap.bind('plus', () => addCutSegment()); + mousetrap.bind('space', () => togglePlayReset()); + mousetrap.bind('k', () => togglePlayNoReset()); + mousetrap.bind('j', () => reducePlaybackRate()); + mousetrap.bind('l', () => increasePlaybackRate()); + mousetrap.bind('z', () => toggleComfortZoom()); + mousetrap.bind(',', () => seekBackwardsShort()); + mousetrap.bind('.', () => seekForwardsShort()); + mousetrap.bind('c', () => capture()); + mousetrap.bind('i', () => setCutStart()); + mousetrap.bind('o', () => setCutEnd()); + mousetrap.bind('backspace', () => removeCutSegment()); + mousetrap.bind('d', () => deleteSource()); + mousetrap.bind('b', () => splitCurrentSegment()); + mousetrap.bind('r', () => increaseRotation()); + + mousetrap.bind('left', () => seekBackwards()); + mousetrap.bind(['ctrl+left', 'command+left'], () => seekBackwardsPercent()); + mousetrap.bind('alt+left', () => seekBackwardsKeyframe()); + mousetrap.bind('shift+left', () => jumpCutStart()); + + mousetrap.bind('right', () => seekForwards()); + mousetrap.bind(['ctrl+right', 'command+right'], () => seekForwardsPercent()); + mousetrap.bind('alt+right', () => seekForwardsKeyframe()); + mousetrap.bind('shift+right', () => jumpCutEnd()); + + mousetrap.bind('up', () => jumpPrevSegment()); + mousetrap.bind(['ctrl+up', 'command+up'], () => zoomIn()); + + mousetrap.bind('down', () => jumpNextSegment()); + mousetrap.bind(['ctrl+down', 'command+down'], () => zoomOut()); + + return () => mousetrap.reset(); }, [ addCutSegment, capture, changePlaybackRate, togglePlay, removeCutSegment, setCutEnd, setCutStart, seekRel, seekRelPercent, shortStep, deleteSource, jumpSeg, @@ -1377,14 +1372,14 @@ const App = memo(() => { ]); useEffect(() => { - function onKeyDown(e) { - if (!['e', 'E'].includes(e.key)) return; + function onKeyPress() { if (exportConfirmVisible) onExportConfirm(); else onExportPress(); } - document.addEventListener('keydown', onKeyDown); - return () => document.removeEventListener('keydown', onKeyDown); + const mousetrap = new Mousetrap(); + mousetrap.bind('e', onKeyPress); + return () => mousetrap.reset(); }, [exportConfirmVisible, onExportConfirm, onExportPress]); useEffect(() => { @@ -1394,16 +1389,10 @@ const App = memo(() => { setSettingsVisible(false); } - function onKeyDown(e) { - switch (e.key) { - case 'h': case 'H': return toggleHelp(); - case 'Escape': return onEscPress(); - default: return undefined; - } - } - - document.addEventListener('keydown', onKeyDown); - return () => document.removeEventListener('keydown', onKeyDown); + const mousetrap = new Mousetrap(); + mousetrap.bind('h', toggleHelp); + mousetrap.bind('escape', onEscPress); + return () => mousetrap.reset(); }, [closeExportConfirm, toggleHelp]); useEffect(() => { diff --git a/yarn.lock b/yarn.lock index 8e55c994..d5569b0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8797,6 +8797,11 @@ mount-point@^3.0.0: pify "^2.3.0" pinkie-promise "^2.0.1" +mousetrap@^1.6.5: + version "1.6.5" + resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9" + integrity sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA== + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"