mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-05-12 17:31:01 +00:00
fix: tests + stale callbacks
This commit is contained in:
parent
f95273b8ce
commit
0b601010e2
4 changed files with 46 additions and 35 deletions
|
|
@ -8,6 +8,7 @@ import styles from './styles.less';
|
||||||
type ActiveButton = string | null;
|
type ActiveButton = string | null;
|
||||||
|
|
||||||
const CX = 400;
|
const CX = 400;
|
||||||
|
const BTN = { L1: 'L1', L2: 'L2', R1: 'R1', R2: 'R2' };
|
||||||
|
|
||||||
const GamepadDiagram = () => {
|
const GamepadDiagram = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
@ -79,12 +80,12 @@ const GamepadDiagram = () => {
|
||||||
d={`M${CX - SX - 38},68 Q${CX - SX - 40},48 ${CX - SX - 28},42 L${CX - SX + 28},42 Q${CX - SX + 40},48 ${CX - SX + 38},68 Z`}
|
d={`M${CX - SX - 38},68 Q${CX - SX - 40},48 ${CX - SX - 28},42 L${CX - SX + 28},42 Q${CX - SX + 40},48 ${CX - SX + 38},68 Z`}
|
||||||
fill={'url(#triggerGrad)'} stroke={'#3d3660'} strokeWidth={'1'} opacity={'0.7'}
|
fill={'url(#triggerGrad)'} stroke={'#3d3660'} strokeWidth={'1'} opacity={'0.7'}
|
||||||
/>
|
/>
|
||||||
<text x={CX - SX} y={'58'} textAnchor={'middle'} fill={'#8b7faa'} fontSize={'8'} fontWeight={'500'}>L2</text>
|
<text x={CX - SX} y={'58'} textAnchor={'middle'} fill={'#8b7faa'} fontSize={'8'} fontWeight={'500'}>{BTN.L2}</text>
|
||||||
<path
|
<path
|
||||||
d={`M${CX + SX - 38},68 Q${CX + SX - 40},48 ${CX + SX - 28},42 L${CX + SX + 28},42 Q${CX + SX + 40},48 ${CX + SX + 38},68 Z`}
|
d={`M${CX + SX - 38},68 Q${CX + SX - 40},48 ${CX + SX - 28},42 L${CX + SX + 28},42 Q${CX + SX + 40},48 ${CX + SX + 38},68 Z`}
|
||||||
fill={'url(#triggerGrad)'} stroke={'#3d3660'} strokeWidth={'1'} opacity={'0.7'}
|
fill={'url(#triggerGrad)'} stroke={'#3d3660'} strokeWidth={'1'} opacity={'0.7'}
|
||||||
/>
|
/>
|
||||||
<text x={CX + SX} y={'58'} textAnchor={'middle'} fill={'#8b7faa'} fontSize={'8'} fontWeight={'500'}>R2</text>
|
<text x={CX + SX} y={'58'} textAnchor={'middle'} fill={'#8b7faa'} fontSize={'8'} fontWeight={'500'}>{BTN.R2}</text>
|
||||||
|
|
||||||
{/* ===== CONTROLLER BODY ===== */}
|
{/* ===== CONTROLLER BODY ===== */}
|
||||||
<path
|
<path
|
||||||
|
|
@ -120,14 +121,14 @@ const GamepadDiagram = () => {
|
||||||
d={`M${CX - SX - 40},74 Q${CX - SX - 38},66 ${CX - SX - 30},64 L${CX - SX + 30},64 Q${CX - SX + 38},66 ${CX - SX + 40},74 L${CX - SX + 36},82 Q${CX - SX + 34},85 ${CX - SX + 28},85 L${CX - SX - 28},85 Q${CX - SX - 34},85 ${CX - SX - 36},82 Z`}
|
d={`M${CX - SX - 40},74 Q${CX - SX - 38},66 ${CX - SX - 30},64 L${CX - SX + 30},64 Q${CX - SX + 38},66 ${CX - SX + 40},74 L${CX - SX + 36},82 Q${CX - SX + 34},85 ${CX - SX + 28},85 L${CX - SX - 28},85 Q${CX - SX - 34},85 ${CX - SX - 36},82 Z`}
|
||||||
fill={'url(#bumperGrad)'} stroke={glow('l1') || '#5848a0'} strokeWidth={'1.2'} opacity={glowOp('l1') || 0.9}
|
fill={'url(#bumperGrad)'} stroke={glow('l1') || '#5848a0'} strokeWidth={'1.2'} opacity={glowOp('l1') || 0.9}
|
||||||
/>
|
/>
|
||||||
<text x={CX - SX} y={'78'} textAnchor={'middle'} fill={'#a89ecc'} fontSize={'9'} fontWeight={'600'}>L1</text>
|
<text x={CX - SX} y={'78'} textAnchor={'middle'} fill={'#a89ecc'} fontSize={'9'} fontWeight={'600'}>{BTN.L1}</text>
|
||||||
</g>
|
</g>
|
||||||
<g filter={active === 'r1' ? 'url(#glow)' : undefined}>
|
<g filter={active === 'r1' ? 'url(#glow)' : undefined}>
|
||||||
<path
|
<path
|
||||||
d={`M${CX + SX - 40},74 Q${CX + SX - 38},66 ${CX + SX - 30},64 L${CX + SX + 30},64 Q${CX + SX + 38},66 ${CX + SX + 40},74 L${CX + SX + 36},82 Q${CX + SX + 34},85 ${CX + SX + 28},85 L${CX + SX - 28},85 Q${CX + SX - 34},85 ${CX + SX - 36},82 Z`}
|
d={`M${CX + SX - 40},74 Q${CX + SX - 38},66 ${CX + SX - 30},64 L${CX + SX + 30},64 Q${CX + SX + 38},66 ${CX + SX + 40},74 L${CX + SX + 36},82 Q${CX + SX + 34},85 ${CX + SX + 28},85 L${CX + SX - 28},85 Q${CX + SX - 34},85 ${CX + SX - 36},82 Z`}
|
||||||
fill={'url(#bumperGrad)'} stroke={glow('r1') || '#5848a0'} strokeWidth={'1.2'} opacity={glowOp('r1') || 0.9}
|
fill={'url(#bumperGrad)'} stroke={glow('r1') || '#5848a0'} strokeWidth={'1.2'} opacity={glowOp('r1') || 0.9}
|
||||||
/>
|
/>
|
||||||
<text x={CX + SX} y={'78'} textAnchor={'middle'} fill={'#a89ecc'} fontSize={'9'} fontWeight={'600'}>R1</text>
|
<text x={CX + SX} y={'78'} textAnchor={'middle'} fill={'#a89ecc'} fontSize={'9'} fontWeight={'600'}>{BTN.R1}</text>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
{/* ===== FACE BUTTONS (right, centered at CX+BX) ===== */}
|
{/* ===== FACE BUTTONS (right, centered at CX+BX) ===== */}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,18 @@ import { useGamepad } from 'stremio/services';
|
||||||
import GamepadDiagram from './GamepadDiagram';
|
import GamepadDiagram from './GamepadDiagram';
|
||||||
import styles from './styles.less';
|
import styles from './styles.less';
|
||||||
|
|
||||||
|
const CROSS = '✕';
|
||||||
|
const CIRCLE = '○';
|
||||||
|
const TRIANGLE = '△';
|
||||||
|
const SQUARE = '□';
|
||||||
|
const L_STICK = 'L stick';
|
||||||
|
const L1 = 'L1';
|
||||||
|
const R1 = 'R1';
|
||||||
|
const LEFT = '←';
|
||||||
|
const RIGHT = '→';
|
||||||
|
const UP = '↑';
|
||||||
|
const DOWN = '↓';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
};
|
};
|
||||||
|
|
@ -52,37 +64,37 @@ const GamepadModal = ({ onClose }: Props) => {
|
||||||
<div className={styles['section']}>
|
<div className={styles['section']}>
|
||||||
<div className={styles['section-title']}>{t('GAMEPAD_SECTION_NAVIGATION')}</div>
|
<div className={styles['section-title']}>{t('GAMEPAD_SECTION_NAVIGATION')}</div>
|
||||||
<div className={styles['mapping']}>
|
<div className={styles['mapping']}>
|
||||||
<kbd className={styles['kbd']}>L stick</kbd>
|
<kbd className={styles['kbd']}>{L_STICK}</kbd>
|
||||||
<span className={styles['dir']} />
|
<span className={styles['dir']} />
|
||||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_NAVIGATE')}</span>
|
<span className={styles['action']}>{t('GAMEPAD_ACTION_NAVIGATE')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['mapping']}>
|
<div className={styles['mapping']}>
|
||||||
<kbd className={styles['kbd']}>✕</kbd>
|
<kbd className={styles['kbd']}>{CROSS}</kbd>
|
||||||
<span className={styles['dir']} />
|
<span className={styles['dir']} />
|
||||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_SELECT')}</span>
|
<span className={styles['action']}>{t('GAMEPAD_ACTION_SELECT')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['mapping']}>
|
<div className={styles['mapping']}>
|
||||||
<kbd className={styles['kbd']}>○</kbd>
|
<kbd className={styles['kbd']}>{CIRCLE}</kbd>
|
||||||
<span className={styles['dir']} />
|
<span className={styles['dir']} />
|
||||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_BACK')}</span>
|
<span className={styles['action']}>{t('GAMEPAD_ACTION_BACK')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['mapping']}>
|
<div className={styles['mapping']}>
|
||||||
<kbd className={styles['kbd']}>△</kbd>
|
<kbd className={styles['kbd']}>{TRIANGLE}</kbd>
|
||||||
<span className={styles['dir']} />
|
<span className={styles['dir']} />
|
||||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_FULLSCREEN')}</span>
|
<span className={styles['action']}>{t('GAMEPAD_ACTION_FULLSCREEN')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['mapping']}>
|
<div className={styles['mapping']}>
|
||||||
<kbd className={styles['kbd']}>□</kbd>
|
<kbd className={styles['kbd']}>{SQUARE}</kbd>
|
||||||
<span className={styles['dir']} />
|
<span className={styles['dir']} />
|
||||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_GUIDE')}</span>
|
<span className={styles['action']}>{t('GAMEPAD_ACTION_GUIDE')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['mapping']}>
|
<div className={styles['mapping']}>
|
||||||
<kbd className={styles['kbd']}>L1</kbd>
|
<kbd className={styles['kbd']}>{L1}</kbd>
|
||||||
<span className={styles['dir']} />
|
<span className={styles['dir']} />
|
||||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_PREV_TAB')}</span>
|
<span className={styles['action']}>{t('GAMEPAD_ACTION_PREV_TAB')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['mapping']}>
|
<div className={styles['mapping']}>
|
||||||
<kbd className={styles['kbd']}>R1</kbd>
|
<kbd className={styles['kbd']}>{R1}</kbd>
|
||||||
<span className={styles['dir']} />
|
<span className={styles['dir']} />
|
||||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_NEXT_TAB')}</span>
|
<span className={styles['action']}>{t('GAMEPAD_ACTION_NEXT_TAB')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -91,28 +103,28 @@ const GamepadModal = ({ onClose }: Props) => {
|
||||||
<div className={styles['section']}>
|
<div className={styles['section']}>
|
||||||
<div className={styles['section-title']}>{t('GAMEPAD_SECTION_PLAYER')}</div>
|
<div className={styles['section-title']}>{t('GAMEPAD_SECTION_PLAYER')}</div>
|
||||||
<div className={styles['mapping']}>
|
<div className={styles['mapping']}>
|
||||||
<kbd className={styles['kbd']}>✕</kbd>
|
<kbd className={styles['kbd']}>{CROSS}</kbd>
|
||||||
<span className={styles['dir']} />
|
<span className={styles['dir']} />
|
||||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_PLAY_PAUSE')}</span>
|
<span className={styles['action']}>{t('GAMEPAD_ACTION_PLAY_PAUSE')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['mapping']}>
|
<div className={styles['mapping']}>
|
||||||
<kbd className={styles['kbd']}>L stick</kbd>
|
<kbd className={styles['kbd']}>{L_STICK}</kbd>
|
||||||
<span className={styles['dir']}>{'←'}</span>
|
<span className={styles['dir']}>{LEFT}</span>
|
||||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_SEEK_BACK')}</span>
|
<span className={styles['action']}>{t('GAMEPAD_ACTION_SEEK_BACK')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['mapping']}>
|
<div className={styles['mapping']}>
|
||||||
<kbd className={styles['kbd']}>L stick</kbd>
|
<kbd className={styles['kbd']}>{L_STICK}</kbd>
|
||||||
<span className={styles['dir']}>{'→'}</span>
|
<span className={styles['dir']}>{RIGHT}</span>
|
||||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_SEEK_FWD')}</span>
|
<span className={styles['action']}>{t('GAMEPAD_ACTION_SEEK_FWD')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['mapping']}>
|
<div className={styles['mapping']}>
|
||||||
<kbd className={styles['kbd']}>L stick</kbd>
|
<kbd className={styles['kbd']}>{L_STICK}</kbd>
|
||||||
<span className={styles['dir']}>{'↑'}</span>
|
<span className={styles['dir']}>{UP}</span>
|
||||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_VOL_UP')}</span>
|
<span className={styles['action']}>{t('GAMEPAD_ACTION_VOL_UP')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['mapping']}>
|
<div className={styles['mapping']}>
|
||||||
<kbd className={styles['kbd']}>L stick</kbd>
|
<kbd className={styles['kbd']}>{L_STICK}</kbd>
|
||||||
<span className={styles['dir']}>{'↓'}</span>
|
<span className={styles['dir']}>{DOWN}</span>
|
||||||
<span className={styles['action']}>{t('GAMEPAD_ACTION_VOL_DOWN')}</span>
|
<span className={styles['action']}>{t('GAMEPAD_ACTION_VOL_DOWN')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -52,37 +52,35 @@ const GamepadProvider: React.FC<{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onGamepadConnected = () => {
|
const onGamepadConnected = useCallback(() => {
|
||||||
// @ts-expect-error show() expects no arguments
|
// @ts-expect-error show() expects no arguments
|
||||||
toast.show({
|
toast.show({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
title: t('GAMEPAD_CONNECTED'),
|
title: t('GAMEPAD_CONNECTED'),
|
||||||
timeout: 4000,
|
timeout: 4000,
|
||||||
});
|
});
|
||||||
};
|
}, [toast, t]);
|
||||||
|
|
||||||
const onGamepadDisconnected = () => {
|
const onGamepadDisconnected = useCallback(() => {
|
||||||
// @ts-expect-error show() expects no arguments
|
// @ts-expect-error show() expects no arguments
|
||||||
toast.show({
|
toast.show({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
title: t('GAMEPAD_DISCONNECTED'),
|
title: t('GAMEPAD_DISCONNECTED'),
|
||||||
timeout: 4000,
|
timeout: 4000,
|
||||||
});
|
});
|
||||||
};
|
}, [toast, t]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (enabled) {
|
if (!enabled) return;
|
||||||
window.addEventListener('gamepadconnected', onGamepadConnected);
|
|
||||||
window.addEventListener('gamepaddisconnected', onGamepadDisconnected);
|
window.addEventListener('gamepadconnected', onGamepadConnected);
|
||||||
}
|
window.addEventListener('gamepaddisconnected', onGamepadDisconnected);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (enabled) {
|
window.removeEventListener('gamepadconnected', onGamepadConnected);
|
||||||
window.removeEventListener('gamepadconnected', onGamepadConnected);
|
window.removeEventListener('gamepaddisconnected', onGamepadDisconnected);
|
||||||
window.removeEventListener('gamepaddisconnected', onGamepadDisconnected);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [enabled]);
|
}, [enabled, onGamepadConnected, onGamepadDisconnected]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (onGuide) {
|
if (onGuide) {
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,8 @@ const useContentGamepadNavigation = (
|
||||||
elements[0].focus();
|
elements[0].focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const isActiveSelectElement = [activeElement.classList].some((className) => /^select-input/.test(className.toString()));
|
const isSelect = Array.from(activeElement.classList).some((cls) => cls.startsWith('select-input'));
|
||||||
if (!isActiveSelectElement) {
|
if (!isSelect) {
|
||||||
activeElement?.click();
|
activeElement?.click();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue