From 6f8a318fa2e472da8621293bc458562b576699fb Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 8 May 2026 11:36:13 +0200 Subject: [PATCH 1/2] refactor: move shortcuts logic to provider --- src/App/App.js | 19 +++- src/common/Fullscreen/FullscreenProvider.tsx | 17 +--- src/common/Shortcuts/Shortcuts.tsx | 14 ++- src/common/Shortcuts/shortcuts.json | 11 ++- .../ShortcutsGroup/Combos/Combos.less | 3 + .../ShortcutsGroup/ShortcutsGroup.less | 5 +- .../KeyboardShortcuts/KeyboardShortcuts.js | 93 ------------------- src/services/KeyboardShortcuts/index.js | 5 - src/services/ServicesContext/types.d.ts | 1 - src/services/index.js | 2 - 10 files changed, 40 insertions(+), 130 deletions(-) delete mode 100644 src/services/KeyboardShortcuts/KeyboardShortcuts.js delete mode 100644 src/services/KeyboardShortcuts/index.js 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 2300602c5..9095bc61f 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 4ff42666d..a41924f2f 100644 --- a/src/common/Shortcuts/shortcuts.json +++ b/src/common/Shortcuts/shortcuts.json @@ -14,15 +14,20 @@ "combos": [["0"]] }, { - "name": "fullscreen", - "label": "SETTINGS_SHORTCUT_FULLSCREEN", - "combos": [["F"]] + "name": "navigateHistory", + "label": "SETTINGS_SHORTCUT_NAVIGATE_HISTORY", + "combos": [["Backspace"], ["Ctrl", "Backspace"]] }, { "name": "exit", "label": "SETTINGS_SHORTCUT_EXIT_BACK", "combos": [["Backspace"]] }, + { + "name": "fullscreen", + "label": "SETTINGS_SHORTCUT_FULLSCREEN", + "combos": [["F"]] + }, { "name": "shortcuts", "label": "SETTINGS_SHORTCUT_SHORTCUTS", 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, From e7d44ddfb837c96720615e81afb7cfba9a4ace22 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 8 May 2026 14:15:11 +0200 Subject: [PATCH 2/2] chore: update translations --- package.json | 2 +- pnpm-lock.yaml | 19 ++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index dd73785af..0b8bf16f0 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "react-i18next": "^15.7.4", "react-is": "18.3.1", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", - "stremio-translations": "github:Stremio/stremio-translations#d9cd2fb88268b365b14101452665de698f9c15e9", + "stremio-translations": "github:Stremio/stremio-translations#5b00ce88c124eedfc34cba7f363dedeab38071d9", "url": "0.11.4", "use-long-press": "^3.3.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 28201c156..cc1c0fabc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,8 +90,8 @@ importers: specifier: github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6 version: https://codeload.github.com/Stremio/spatial-navigation/tar.gz/64871b1422466f5f45d24ebc8bbd315b2ebab6a6 stremio-translations: - specifier: github:Stremio/stremio-translations#d9cd2fb88268b365b14101452665de698f9c15e9 - version: https://codeload.github.com/Stremio/stremio-translations/tar.gz/d9cd2fb88268b365b14101452665de698f9c15e9 + specifier: github:Stremio/stremio-translations#5b00ce88c124eedfc34cba7f363dedeab38071d9 + version: https://codeload.github.com/Stremio/stremio-translations/tar.gz/5b00ce88c124eedfc34cba7f363dedeab38071d9 url: specifier: 0.11.4 version: 0.11.4 @@ -123,12 +123,12 @@ importers: '@types/lodash.throttle': specifier: ^4.1.9 version: 4.1.9 - '@types/node': - specifier: ^25.6.0 - version: 25.6.0 '@types/magnet-uri': specifier: ^5.1.5 version: 5.1.5 + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 '@types/react': specifier: ^18.3.28 version: 18.3.28 @@ -1497,9 +1497,6 @@ packages: '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - '@types/node-forge@1.3.14': - resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} - '@types/node@25.6.0': resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} @@ -4424,8 +4421,8 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} - stremio-translations@https://codeload.github.com/Stremio/stremio-translations/tar.gz/d9cd2fb88268b365b14101452665de698f9c15e9: - resolution: {tarball: https://codeload.github.com/Stremio/stremio-translations/tar.gz/d9cd2fb88268b365b14101452665de698f9c15e9} + stremio-translations@https://codeload.github.com/Stremio/stremio-translations/tar.gz/5b00ce88c124eedfc34cba7f363dedeab38071d9: + resolution: {tarball: https://codeload.github.com/Stremio/stremio-translations/tar.gz/5b00ce88c124eedfc34cba7f363dedeab38071d9} version: 1.52.0 string-length@4.0.2: @@ -10003,7 +10000,7 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - stremio-translations@https://codeload.github.com/Stremio/stremio-translations/tar.gz/d9cd2fb88268b365b14101452665de698f9c15e9: {} + stremio-translations@https://codeload.github.com/Stremio/stremio-translations/tar.gz/5b00ce88c124eedfc34cba7f363dedeab38071d9: {} string-length@4.0.2: dependencies: