mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-08-16 15:44:49 +00:00
Merge pull request #1316 from Stremio/fix-virtual-keyboard-shortcuts
Fix virtual keyboard shortcuts
This commit is contained in:
commit
accc308aa1
6 changed files with 98 additions and 12 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { withCoreSuspender } from '../CoreSuspender';
|
||||
import { getKeyboardShortcutKey } from '../Shortcuts';
|
||||
import onShortcut from '../Shortcuts/onShortcut';
|
||||
import useSettings from '../useSettings';
|
||||
import FullscreenContext, { type FullscreenContextValue } from './FullscreenContext';
|
||||
|
|
@ -80,11 +81,13 @@ const FullscreenProvider = ({ children }: Props) => {
|
|||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.code === 'Escape' && escExitFullscreen) {
|
||||
const keyboardKey = getKeyboardShortcutKey(event);
|
||||
|
||||
if (keyboardKey === 'Escape' && escExitFullscreen) {
|
||||
exitFullscreen();
|
||||
}
|
||||
|
||||
if (event.code === 'F11' && shell.active) {
|
||||
if (keyboardKey === 'F11' && shell.active) {
|
||||
toggleFullscreen();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { createContext, useCallback, useContext, useEffect, useRef } from 'react';
|
||||
import { getKeyboardShortcutKey, getKeyboardShortcutKeys } from './keyboard';
|
||||
import shortcuts from './shortcuts.json';
|
||||
|
||||
const SHORTCUTS = shortcuts.map(({ shortcuts }) => shortcuts).flat();
|
||||
|
|
@ -33,14 +34,17 @@ const ShortcutsProvider = ({ children, onShortcut }: Props) => {
|
|||
const listeners = useRef<Map<ShortcutName, Set<ShortcutListener>>>(new Map());
|
||||
const lastRepeatTime = useRef<Map<string, number>>(new Map());
|
||||
|
||||
const onKeyDown = useCallback(({ ctrlKey, shiftKey, altKey, metaKey, code, key, repeat }: KeyboardEvent) => {
|
||||
const onKeyDown = useCallback((event: KeyboardEvent) => {
|
||||
const { ctrlKey, shiftKey, altKey, metaKey, key, repeat } = event;
|
||||
if (isInputFocused()) return;
|
||||
|
||||
const shortcutKeys = getKeyboardShortcutKeys(event);
|
||||
const repeatKey = getKeyboardShortcutKey(event);
|
||||
if (repeat) {
|
||||
const now = Date.now();
|
||||
const last = lastRepeatTime.current.get(code) ?? 0;
|
||||
const last = lastRepeatTime.current.get(repeatKey) ?? 0;
|
||||
if (now - last < REPEAT_THROTTLE_MS) return;
|
||||
lastRepeatTime.current.set(code, now);
|
||||
lastRepeatTime.current.set(repeatKey, now);
|
||||
}
|
||||
|
||||
SHORTCUTS.forEach(({ name, combos }) => combos.forEach((keys) => {
|
||||
|
|
@ -48,8 +52,13 @@ const ShortcutsProvider = ({ children, onShortcut }: Props) => {
|
|||
&& (keys.includes('Shift') === shiftKey)
|
||||
&& !altKey
|
||||
&& !metaKey;
|
||||
const keyMatched = keys.some((shortcutKey) => (
|
||||
shortcutKey !== 'Ctrl'
|
||||
&& shortcutKey !== 'Shift'
|
||||
&& shortcutKeys.includes(shortcutKey)
|
||||
));
|
||||
|
||||
if (modifers && (keys.includes(code) || keys.includes(key.toUpperCase()))) {
|
||||
if (modifers && keyMatched) {
|
||||
const combo = combos.indexOf(keys);
|
||||
listeners.current.get(name)?.forEach((listener) => listener(combo, key));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { ShortcutsProvider, useShortcuts } from './Shortcuts';
|
||||
import { getKeyboardShortcutKey } from './keyboard';
|
||||
import onShortcut from './onShortcut';
|
||||
|
||||
export {
|
||||
ShortcutsProvider,
|
||||
useShortcuts,
|
||||
onShortcut,
|
||||
getKeyboardShortcutKey,
|
||||
};
|
||||
|
|
|
|||
69
src/common/Shortcuts/keyboard.ts
Normal file
69
src/common/Shortcuts/keyboard.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
const KEY_CODE_MAP: Record<number, string> = {
|
||||
8: 'Backspace',
|
||||
27: 'Escape',
|
||||
32: 'Space',
|
||||
37: 'ArrowLeft',
|
||||
38: 'ArrowUp',
|
||||
39: 'ArrowRight',
|
||||
40: 'ArrowDown',
|
||||
122: 'F11',
|
||||
187: '=',
|
||||
189: '-',
|
||||
191: '/',
|
||||
219: '[',
|
||||
221: ']',
|
||||
};
|
||||
|
||||
const KEY_MAP: Record<string, string> = {
|
||||
' ': 'Space',
|
||||
Spacebar: 'Space',
|
||||
Esc: 'Escape',
|
||||
Left: 'ArrowLeft',
|
||||
Up: 'ArrowUp',
|
||||
Right: 'ArrowRight',
|
||||
Down: 'ArrowDown',
|
||||
};
|
||||
|
||||
const keyFromKeyCode = (keyCode: number) => {
|
||||
if (KEY_CODE_MAP[keyCode]) {
|
||||
return KEY_CODE_MAP[keyCode];
|
||||
}
|
||||
|
||||
if (keyCode >= 48 && keyCode <= 57) {
|
||||
return String.fromCharCode(keyCode);
|
||||
}
|
||||
|
||||
if (keyCode >= 65 && keyCode <= 90) {
|
||||
return String.fromCharCode(keyCode);
|
||||
}
|
||||
|
||||
if (keyCode >= 96 && keyCode <= 105) {
|
||||
return String(keyCode - 96);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const normalizeKeyboardKey = ({ key, keyCode, which }: KeyboardEvent) => {
|
||||
const mappedKey = KEY_MAP[key] ?? key;
|
||||
if (mappedKey && mappedKey !== 'Unidentified') {
|
||||
return mappedKey.length === 1 ? mappedKey.toUpperCase() : mappedKey;
|
||||
}
|
||||
|
||||
return keyFromKeyCode(keyCode || which) ?? mappedKey;
|
||||
};
|
||||
|
||||
const getKeyboardShortcutKey = (event: KeyboardEvent) => {
|
||||
return event.code && event.code !== 'Unidentified' ? event.code : normalizeKeyboardKey(event);
|
||||
};
|
||||
|
||||
const getKeyboardShortcutKeys = (event: KeyboardEvent) => {
|
||||
const normalizedKey = normalizeKeyboardKey(event);
|
||||
const code = event.code !== 'Unidentified' ? event.code : '';
|
||||
return code && code !== normalizedKey ? [code, normalizedKey] : [normalizedKey];
|
||||
};
|
||||
|
||||
export {
|
||||
getKeyboardShortcutKey,
|
||||
getKeyboardShortcutKeys,
|
||||
};
|
||||
|
|
@ -5,7 +5,7 @@ const { FullscreenProvider, useFullscreen } = require('./Fullscreen');
|
|||
const { PlatformProvider, usePlatform } = require('./Platform');
|
||||
const { ToastProvider, useToast } = require('./Toast');
|
||||
const { TooltipProvider, Tooltip } = require('./Tooltips');
|
||||
const { ShortcutsProvider, useShortcuts, onShortcut } = require('./Shortcuts');
|
||||
const { ShortcutsProvider, useShortcuts, onShortcut, getKeyboardShortcutKey } = require('./Shortcuts');
|
||||
const { DiscordProvider, useDiscord, EMPTY_DISCORD_TIMESTAMPS, getPlaybackDiscordActivity } = require('./Discord');
|
||||
const CONSTANTS = require('./CONSTANTS');
|
||||
const { withCoreSuspender, useCoreSuspender } = require('./CoreSuspender');
|
||||
|
|
@ -42,6 +42,7 @@ module.exports = {
|
|||
ShortcutsProvider,
|
||||
useShortcuts,
|
||||
onShortcut,
|
||||
getKeyboardShortcutKey,
|
||||
ToastProvider,
|
||||
useToast,
|
||||
TooltipProvider,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
|
|||
const { useCore } = require('stremio/core');
|
||||
const { useServices, useGamepad } = require('stremio/services');
|
||||
const { useContentGamepadNavigation } = require('stremio/services/GamepadNavigation');
|
||||
const { useSettings, useProfile, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, usePlatform, onShortcut, useDiscord, EMPTY_DISCORD_TIMESTAMPS, getPlaybackDiscordActivity } = require('stremio/common');
|
||||
const { useSettings, useProfile, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, usePlatform, onShortcut, getKeyboardShortcutKey, useDiscord, EMPTY_DISCORD_TIMESTAMPS, getPlaybackDiscordActivity } = require('stremio/common');
|
||||
const { default: toPath } = require('stremio-router/toPath');
|
||||
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
|
||||
const { default: Buffering } = require('./Buffering');
|
||||
|
|
@ -673,7 +673,8 @@ const Player = () => {
|
|||
}
|
||||
|
||||
const onKeyDown = (e) => {
|
||||
if (e.code !== 'Space' || e.repeat) return;
|
||||
const keyboardKey = getKeyboardShortcutKey(e);
|
||||
if (keyboardKey !== 'Space' || e.repeat) return;
|
||||
if (menusOpen || e.ctrlKey || e.metaKey || e.altKey) return;
|
||||
|
||||
longPress.current = false;
|
||||
|
|
@ -685,14 +686,15 @@ const Player = () => {
|
|||
};
|
||||
|
||||
const onKeyUp = (e) => {
|
||||
if (e.code !== 'Space' && e.code !== 'ArrowRight' && e.code !== 'ArrowLeft') return;
|
||||
const keyboardKey = getKeyboardShortcutKey(e);
|
||||
if (keyboardKey !== 'Space' && keyboardKey !== 'ArrowRight' && keyboardKey !== 'ArrowLeft') return;
|
||||
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
||||
|
||||
if (e.code === 'ArrowRight' || e.code === 'ArrowLeft') {
|
||||
if (keyboardKey === 'ArrowRight' || keyboardKey === 'ArrowLeft') {
|
||||
setSeeking(false);
|
||||
return;
|
||||
}
|
||||
if (e.code === 'Space') {
|
||||
if (keyboardKey === 'Space') {
|
||||
clearTimeout(pressTimer.current);
|
||||
pressTimer.current = null;
|
||||
if (longPress.current) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue