diff --git a/src/App/App.js b/src/App/App.js index 0e5bb3fe1..437332f63 100644 --- a/src/App/App.js +++ b/src/App/App.js @@ -5,7 +5,7 @@ const React = require('react'); const { useTranslation } = require('react-i18next'); const { useCore } = require('stremio/core'); const { Router } = require('stremio-router'); -const { Shell, Chromecast, KeyboardShortcuts, ServicesProvider, GamepadProvider } = require('stremio/services'); +const { Shell, Chromecast, ServicesProvider, GamepadProvider } = require('stremio/services'); const { NotFound } = require('stremio/routes'); const { FullscreenProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, useShell, useBinaryState, useProfile, withCoreSuspender, onFileDrop } = require('stremio/common'); const ServicesToaster = require('./ServicesToaster'); @@ -33,13 +33,12 @@ const App = () => { return { shell: new Shell(), chromecast: new Chromecast(), - keyboardShortcuts: new KeyboardShortcuts(), }; }, []); const [shortcutModalOpen,, closeShortcutsModal, toggleShortcutModal] = useBinaryState(false); const [gamepadModalOpen,, closeGamepadModal, toggleGamepadModal] = useBinaryState(false); - const onShortcut = React.useCallback((name) => { + const onShortcut = React.useCallback((name, combo, key) => { switch (name) { case 'shortcuts': toggleShortcutModal(); @@ -47,6 +46,18 @@ const App = () => { case 'gamepadGuide': toggleGamepadModal(); break; + case 'navigateSearch': + window.location = '#/search'; + break; + case 'navigateTabs': { + const routes = ['', 'discover', 'library', 'calendar', 'addons', 'settings']; + const index = key - 1; + if (index in routes) window.location = `#/${routes[index]}`; + break; + } + case 'navigateHistory': + combo === 0 ? window.history.back() : window.history.forward(); + break; } }, [toggleShortcutModal, toggleGamepadModal]); @@ -90,12 +101,10 @@ const App = () => { services.chromecast.on('stateChanged', onChromecastStateChange); services.shell.start(); services.chromecast.start(); - services.keyboardShortcuts.start(); window.services = services; return () => { services.shell.stop(); services.chromecast.stop(); - services.keyboardShortcuts.stop(); services.chromecast.off('stateChanged', onChromecastStateChange); }; }, []); diff --git a/src/common/Fullscreen/FullscreenProvider.tsx b/src/common/Fullscreen/FullscreenProvider.tsx index 9a0b9f876..55bbf4568 100644 --- a/src/common/Fullscreen/FullscreenProvider.tsx +++ b/src/common/Fullscreen/FullscreenProvider.tsx @@ -11,16 +11,6 @@ type Props = { children: React.ReactNode, }; -const isTextInputFocused = () => { - const activeElement = document.activeElement; - - return activeElement instanceof HTMLElement && - (activeElement.tagName === 'INPUT' || - activeElement.tagName === 'TEXTAREA' || - activeElement.tagName === 'SELECT' || - activeElement.isContentEditable); -}; - const FullscreenProvider = ({ children }: Props) => { const shell = useShell(); const [settings] = useSettings(); @@ -57,12 +47,7 @@ const FullscreenProvider = ({ children }: Props) => { fullscreen ? exitFullscreen() : requestFullscreen(); }, [fullscreen, exitFullscreen, requestFullscreen]); - const toggleFullscreenFromShortcut = useCallback(() => { - if (isTextInputFocused()) return; - toggleFullscreen(); - }, [toggleFullscreen]); - - onShortcut('fullscreen', toggleFullscreenFromShortcut, [toggleFullscreenFromShortcut]); + onShortcut('fullscreen', toggleFullscreen, [toggleFullscreen]); useEffect(() => { const onWindowVisibilityChanged = (state: WindowVisibility) => { diff --git a/src/common/Shortcuts/Shortcuts.tsx b/src/common/Shortcuts/Shortcuts.tsx index fd558f7d6..c27f5b25e 100644 --- a/src/common/Shortcuts/Shortcuts.tsx +++ b/src/common/Shortcuts/Shortcuts.tsx @@ -16,16 +16,26 @@ const ShortcutsContext = createContext({} as ShortcutsContext) type Props = { children: JSX.Element, - onShortcut: (name: ShortcutName) => void, + onShortcut: (name: ShortcutName, combo: number, key: string) => void, }; const REPEAT_THROTTLE_MS = 130; +const isInputFocused = () => { + const inputElements = ['INPUT', 'TEXTAREA', 'SELECT']; + const activeElement = document.activeElement; + + return activeElement instanceof HTMLElement && + (inputElements.includes(activeElement.tagName) || activeElement.isContentEditable); +}; + const ShortcutsProvider = ({ children, onShortcut }: Props) => { const listeners = useRef>>(new Map()); const lastRepeatTime = useRef>(new Map()); const onKeyDown = useCallback(({ ctrlKey, shiftKey, altKey, metaKey, code, key, repeat }: KeyboardEvent) => { + if (isInputFocused()) return; + if (repeat) { const now = Date.now(); const last = lastRepeatTime.current.get(code) ?? 0; @@ -43,7 +53,7 @@ const ShortcutsProvider = ({ children, onShortcut }: Props) => { const combo = combos.indexOf(keys); listeners.current.get(name)?.forEach((listener) => listener(combo)); - onShortcut(name as ShortcutName); + onShortcut(name as ShortcutName, combo, key); } })); }, [onShortcut]); diff --git a/src/common/Shortcuts/shortcuts.json b/src/common/Shortcuts/shortcuts.json index fed30b599..9b4b5b3e5 100644 --- a/src/common/Shortcuts/shortcuts.json +++ b/src/common/Shortcuts/shortcuts.json @@ -13,6 +13,11 @@ "label": "SETTINGS_SHORTCUT_GO_TO_SEARCH", "combos": [["0"]] }, + { + "name": "navigateHistory", + "label": "SETTINGS_SHORTCUT_NAVIGATE_HISTORY", + "combos": [["Backspace"], ["Ctrl", "Backspace"]] + }, { "name": "fullscreen", "label": "SETTINGS_SHORTCUT_FULLSCREEN", diff --git a/src/components/ShortcutsGroup/Combos/Combos.less b/src/components/ShortcutsGroup/Combos/Combos.less index a862d54ca..b3b245f7a 100644 --- a/src/components/ShortcutsGroup/Combos/Combos.less +++ b/src/components/ShortcutsGroup/Combos/Combos.less @@ -1,6 +1,9 @@ .combos { position: relative; display: flex; + flex-wrap: wrap; + row-gap: 1rem; + justify-content: end; overflow: visible; .combo { diff --git a/src/components/ShortcutsGroup/ShortcutsGroup.less b/src/components/ShortcutsGroup/ShortcutsGroup.less index cbc08ada0..9ca472d93 100644 --- a/src/components/ShortcutsGroup/ShortcutsGroup.less +++ b/src/components/ShortcutsGroup/ShortcutsGroup.less @@ -1,7 +1,7 @@ .shortcuts-group { flex: 1 1 0; position: relative; - width: 30rem; + width: 35rem; display: flex; flex-direction: column; gap: 2rem; @@ -26,7 +26,7 @@ .shortcut { position: relative; display: flex; - align-items: center; + align-items: baseline; justify-content: space-between; gap: 2rem; overflow: visible; @@ -35,7 +35,6 @@ position: relative; font-size: 1rem; color: var(--primary-foreground-color); - white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } diff --git a/src/services/KeyboardShortcuts/KeyboardShortcuts.js b/src/services/KeyboardShortcuts/KeyboardShortcuts.js deleted file mode 100644 index 4bc4683fc..000000000 --- a/src/services/KeyboardShortcuts/KeyboardShortcuts.js +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (C) 2017-2023 Smart code 203358507 - -const EventEmitter = require('eventemitter3'); - -function KeyboardShortcuts() { - let active = false; - - const events = new EventEmitter(); - - function onKeyDown(event) { - if (event.keyboardShortcutPrevented || event.target.tagName === 'INPUT' || event.ctrlKey || event.altKey || event.shiftKey || event.metaKey) { - return; - } - - switch (event.code) { - case 'Digit0': { - event.preventDefault(); - window.location = '#/search'; - break; - } - case 'Digit1': { - event.preventDefault(); - window.location = '#/'; - break; - } - case 'Digit2': { - event.preventDefault(); - window.location = '#/discover'; - break; - } - case 'Digit3': { - event.preventDefault(); - window.location = '#/library'; - break; - } - case 'Digit4': { - event.preventDefault(); - window.location = '#/calendar'; - break; - } - case 'Digit5': { - event.preventDefault(); - window.location = '#/addons'; - break; - } - case 'Digit6': { - event.preventDefault(); - window.location = '#/settings'; - break; - } - case 'Backspace': { - event.preventDefault(); - if (event.ctrlKey) { - window.history.forward(); - } else { - window.history.back(); - } - - break; - } - } - } - function onStateChanged() { - events.emit('stateChanged'); - } - - Object.defineProperties(this, { - active: { - configurable: false, - enumerable: true, - get: function() { - return active; - } - } - }); - - this.start = function() { - if (active) { - return; - } - - window.addEventListener('keydown', onKeyDown); - active = true; - onStateChanged(); - }; - this.stop = function() { - window.removeEventListener('keydown', onKeyDown); - active = false; - onStateChanged(); - }; -} - -module.exports = KeyboardShortcuts; diff --git a/src/services/KeyboardShortcuts/index.js b/src/services/KeyboardShortcuts/index.js deleted file mode 100644 index 9eb346014..000000000 --- a/src/services/KeyboardShortcuts/index.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (C) 2017-2023 Smart code 203358507 - -const KeyboardShortcuts = require('./KeyboardShortcuts'); - -module.exports = KeyboardShortcuts; diff --git a/src/services/ServicesContext/types.d.ts b/src/services/ServicesContext/types.d.ts index 49b80525b..82bba1378 100644 --- a/src/services/ServicesContext/types.d.ts +++ b/src/services/ServicesContext/types.d.ts @@ -1,5 +1,4 @@ type ServicesContext = { shell: any, chromecast: any, - keyboardShortcuts: any, }; diff --git a/src/services/index.js b/src/services/index.js index aede80a89..2a4c1543c 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -1,14 +1,12 @@ // Copyright (C) 2017-2023 Smart code 203358507 const Chromecast = require('./Chromecast'); -const KeyboardShortcuts = require('./KeyboardShortcuts'); const { ServicesProvider, useServices } = require('./ServicesContext'); const { GamepadProvider, useGamepad } = require('./GamepadContext'); const Shell = require('./Shell'); module.exports = { Chromecast, - KeyboardShortcuts, ServicesProvider, useServices, Shell,