mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-05-10 11:20:49 +00:00
refactor: improve navigation
This commit is contained in:
parent
972bd23991
commit
70e14c4871
4 changed files with 60 additions and 10 deletions
|
|
@ -9,6 +9,7 @@ type ActiveButton = string | null;
|
|||
|
||||
const CX = 400;
|
||||
const BTN = { L1: 'L1', L2: 'L2', R1: 'R1', R2: 'R2' };
|
||||
const ARROW = { UP: '↑', DOWN: '↓', LEFT: '←', RIGHT: '→' };
|
||||
|
||||
const GamepadDiagram = () => {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -30,6 +31,7 @@ const GamepadDiagram = () => {
|
|||
gamepad?.on('buttonLT', 'gamepad-diagram', flash('l1'));
|
||||
gamepad?.on('buttonRT', 'gamepad-diagram', flash('r1'));
|
||||
gamepad?.on('analog', 'gamepad-diagram', (dir: string) => flash('stick-' + dir)());
|
||||
gamepad?.on('analogRight', 'gamepad-diagram', (dir: string) => flash('rstick-' + dir)());
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
|
|
@ -40,6 +42,7 @@ const GamepadDiagram = () => {
|
|||
gamepad?.off('buttonLT', 'gamepad-diagram');
|
||||
gamepad?.off('buttonRT', 'gamepad-diagram');
|
||||
gamepad?.off('analog', 'gamepad-diagram');
|
||||
gamepad?.off('analogRight', 'gamepad-diagram');
|
||||
};
|
||||
}, [gamepad]);
|
||||
|
||||
|
|
@ -168,8 +171,14 @@ const GamepadDiagram = () => {
|
|||
</g>
|
||||
|
||||
{/* ===== RIGHT STICK (CX+STX) ===== */}
|
||||
<circle cx={CX + STX} cy={240 + BY} r={'26'} fill={'#1a1530'} stroke={'#3d3660'} strokeWidth={'1.5'} />
|
||||
<circle cx={CX + STX} cy={240 + BY} r={'17'} fill={'#1e1a35'} stroke={'#3d3660'} strokeWidth={'1'} opacity={'0.35'} />
|
||||
<g filter={active?.startsWith('rstick-') ? 'url(#glow)' : undefined}>
|
||||
<circle cx={CX + STX} cy={240 + BY} r={'26'} fill={'#1a1530'} stroke={active?.startsWith('rstick-') ? '#7b5bf5' : '#3d3660'} strokeWidth={'2'} />
|
||||
<circle cx={CX + STX} cy={240 + BY} r={'17'} fill={'#252040'} stroke={'#4a4075'} strokeWidth={'1.5'} />
|
||||
<text x={CX + STX} y={232 + BY} textAnchor={'middle'} fill={active === 'rstick-up' ? '#fff' : '#5848a0'} fontSize={'9'} fontWeight={active === 'rstick-up' ? '700' : '400'}>{ARROW.UP}</text>
|
||||
<text x={CX + STX} y={253 + BY} textAnchor={'middle'} fill={active === 'rstick-down' ? '#fff' : '#5848a0'} fontSize={'9'} fontWeight={active === 'rstick-down' ? '700' : '400'}>{ARROW.DOWN}</text>
|
||||
<text x={CX + STX - 11} y={244 + BY} textAnchor={'middle'} fill={active === 'rstick-left' ? '#fff' : '#5848a0'} fontSize={'9'} fontWeight={active === 'rstick-left' ? '700' : '400'}>{ARROW.LEFT}</text>
|
||||
<text x={CX + STX + 11} y={244 + BY} textAnchor={'middle'} fill={active === 'rstick-right' ? '#fff' : '#5848a0'} fontSize={'9'} fontWeight={active === 'rstick-right' ? '700' : '400'}>{ARROW.RIGHT}</text>
|
||||
</g>
|
||||
|
||||
{/* ============================= */}
|
||||
{/* ===== LABELS — LEFT ===== */}
|
||||
|
|
@ -184,7 +193,6 @@ const GamepadDiagram = () => {
|
|||
<line x1={CX - STX - 24} y1={232 + BY} x2={'85'} y2={168} stroke={'#7b5bf5'} strokeWidth={'1'} opacity={'0.4'} />
|
||||
<circle cx={'85'} cy={168} r={'2'} fill={'#7b5bf5'} />
|
||||
<text x={'80'} y={164} textAnchor={'end'} fill={'#c4b5fd'} fontSize={'12'} fontWeight={'500'}>{t('GAMEPAD_ACTION_NAVIGATE')}</text>
|
||||
<text x={'80'} y={179} textAnchor={'end'} fill={'#8b7faa'} fontSize={'10'}>{t('GAMEPAD_LABEL_STICK_PLAYER')}</text>
|
||||
|
||||
{/* □ Square */}
|
||||
<line x1={CX + BX - 44} y1={148 + BY} x2={'85'} y2={248} stroke={'#5848a0'} strokeWidth={'1'} opacity={'0.35'} />
|
||||
|
|
@ -216,6 +224,11 @@ const GamepadDiagram = () => {
|
|||
<text x={'720'} y={204} textAnchor={'start'} fill={'#c4b5fd'} fontSize={'12'} fontWeight={'500'}>{t('GAMEPAD_ACTION_SELECT')}</text>
|
||||
<text x={'720'} y={219} textAnchor={'start'} fill={'#8b7faa'} fontSize={'10'}>{t('GAMEPAD_LABEL_PLAY_PAUSE_PLAYER')}</text>
|
||||
|
||||
{/* Right stick */}
|
||||
<line x1={CX + STX + 24} y1={234 + BY} x2={'715'} y2={268} stroke={'#5848a0'} strokeWidth={'1'} opacity={'0.4'} />
|
||||
<circle cx={'715'} cy={268} r={'2'} fill={'#5848a0'} />
|
||||
<text x={'720'} y={264} textAnchor={'start'} fill={'#c4b5fd'} fontSize={'12'} fontWeight={'500'}>{t('GAMEPAD_LABEL_SEEK_VOL')}</text>
|
||||
|
||||
{/* Compat note */}
|
||||
<text x={CX} y={'475'} textAnchor={'middle'} fill={'#5848a0'} fontSize={'11'}>{t('GAMEPAD_LABEL_COMPAT')}</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const CIRCLE = '○';
|
|||
const TRIANGLE = '△';
|
||||
const SQUARE = '□';
|
||||
const L_STICK = 'L stick';
|
||||
const R_STICK = 'R stick';
|
||||
const L1 = 'L1';
|
||||
const R1 = 'R1';
|
||||
const LEFT = '←';
|
||||
|
|
@ -108,22 +109,22 @@ const GamepadModal = ({ onClose }: Props) => {
|
|||
<span className={styles['action']}>{t('GAMEPAD_ACTION_PLAY_PAUSE')}</span>
|
||||
</div>
|
||||
<div className={styles['mapping']}>
|
||||
<kbd className={styles['kbd']}>{L_STICK}</kbd>
|
||||
<kbd className={styles['kbd']}>{R_STICK}</kbd>
|
||||
<span className={styles['dir']}>{LEFT}</span>
|
||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_SEEK_BACK')}</span>
|
||||
</div>
|
||||
<div className={styles['mapping']}>
|
||||
<kbd className={styles['kbd']}>{L_STICK}</kbd>
|
||||
<kbd className={styles['kbd']}>{R_STICK}</kbd>
|
||||
<span className={styles['dir']}>{RIGHT}</span>
|
||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_SEEK_FWD')}</span>
|
||||
</div>
|
||||
<div className={styles['mapping']}>
|
||||
<kbd className={styles['kbd']}>{L_STICK}</kbd>
|
||||
<kbd className={styles['kbd']}>{R_STICK}</kbd>
|
||||
<span className={styles['dir']}>{UP}</span>
|
||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_VOL_UP')}</span>
|
||||
</div>
|
||||
<div className={styles['mapping']}>
|
||||
<kbd className={styles['kbd']}>{L_STICK}</kbd>
|
||||
<kbd className={styles['kbd']}>{R_STICK}</kbd>
|
||||
<span className={styles['dir']}>{DOWN}</span>
|
||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_VOL_DOWN')}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const langs = require('langs');
|
|||
const { useTranslation } = require('react-i18next');
|
||||
const { useRouteFocused } = require('stremio-router');
|
||||
const { useServices, useGamepad } = require('stremio/services');
|
||||
const { useContentGamepadNavigation } = require('stremio/services/GamepadNavigation');
|
||||
const { onFileDrop, useSettings, useProfile, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS, useShell, usePlatform, onShortcut } = require('stremio/common');
|
||||
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
|
||||
const BufferingLoader = require('./BufferingLoader');
|
||||
|
|
@ -60,6 +61,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
});
|
||||
const playbackDevices = React.useMemo(() => streamingServer.playbackDevices !== null && streamingServer.playbackDevices.type === 'Ready' ? streamingServer.playbackDevices.content : [], [streamingServer]);
|
||||
|
||||
const playerRef = React.useRef(null);
|
||||
const bufferingRef = React.useRef();
|
||||
const errorRef = React.useRef();
|
||||
|
||||
|
|
@ -444,13 +446,15 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
}
|
||||
}, [onSeekPrev, onSeekNext, onVolumeUp, onVolumeDown]);
|
||||
|
||||
useContentGamepadNavigation(playerRef, GAMEPAD_HANDLER_ID);
|
||||
|
||||
React.useEffect(() => {
|
||||
gamepad?.on('buttonA', GAMEPAD_HANDLER_ID, onPlayPause);
|
||||
gamepad?.on('analog', GAMEPAD_HANDLER_ID, onGamepadSeekAndVol);
|
||||
gamepad?.on('analogRight', GAMEPAD_HANDLER_ID, onGamepadSeekAndVol);
|
||||
|
||||
return () => {
|
||||
gamepad?.off('buttonA', GAMEPAD_HANDLER_ID);
|
||||
gamepad?.off('analog', GAMEPAD_HANDLER_ID);
|
||||
gamepad?.off('analogRight', GAMEPAD_HANDLER_ID);
|
||||
};
|
||||
}, [onPlayPause, onGamepadSeekAndVol]);
|
||||
|
||||
|
|
@ -969,7 +973,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<div className={classnames(styles['player-container'], { [styles['overlayHidden']]: overlayHidden })}
|
||||
<div ref={playerRef} className={classnames(styles['player-container'], { [styles['overlayHidden']]: overlayHidden })}
|
||||
onMouseDown={onContainerMouseDown}
|
||||
onMouseMove={onContainerMouseMove}
|
||||
onMouseOver={onContainerMouseMove}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const GamepadProvider: React.FC<{
|
|||
const lastButtonState = useRef<number[]>([]);
|
||||
const lastButtonPressedTime = useRef<number>(0);
|
||||
const axisTimer = useRef<number>(0);
|
||||
const axisTimerRight = useRef<number>(0);
|
||||
const eventHandlers = useRef<GamepadEventHandlers>(new Map());
|
||||
|
||||
const on = useCallback((event: string, id: string, callback: (data?: any) => void) => {
|
||||
|
|
@ -170,6 +171,37 @@ const GamepadProvider: React.FC<{
|
|||
}
|
||||
|
||||
if (axisHandled) axisTimer.current = currentTime;
|
||||
|
||||
let rightAxisHandled = false;
|
||||
|
||||
if (controller.axes.length > 2) {
|
||||
if (controller.axes[2] < -deadZone) {
|
||||
if (currentTime - axisTimerRight.current > maxSpeed + (2000 - Math.abs(controller.axes[2]) * 2000)) {
|
||||
emit('analogRight', 'left');
|
||||
rightAxisHandled = true;
|
||||
}
|
||||
}
|
||||
if (controller.axes[2] > deadZone) {
|
||||
if (currentTime - axisTimerRight.current > maxSpeed + (2000 - Math.abs(controller.axes[2]) * 2000)) {
|
||||
emit('analogRight', 'right');
|
||||
rightAxisHandled = true;
|
||||
}
|
||||
}
|
||||
if (controller.axes[3] < -deadZone) {
|
||||
if (currentTime - axisTimerRight.current > maxSpeed + (2000 - Math.abs(controller.axes[3]) * 2000)) {
|
||||
emit('analogRight', 'up');
|
||||
rightAxisHandled = true;
|
||||
}
|
||||
}
|
||||
if (controller.axes[3] > deadZone) {
|
||||
if (currentTime - axisTimerRight.current > maxSpeed + (2000 - Math.abs(controller.axes[3]) * 2000)) {
|
||||
emit('analogRight', 'down');
|
||||
rightAxisHandled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rightAxisHandled) axisTimerRight.current = currentTime;
|
||||
});
|
||||
}
|
||||
animationFrameId = requestAnimationFrame(updateStatus);
|
||||
|
|
|
|||
Loading…
Reference in a new issue