mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-21 11:42:05 +00:00
feat(shell): implement escape key fullscreen behavior
This commit is contained in:
parent
2da5a0c6d1
commit
e0d1662f86
9 changed files with 99 additions and 61 deletions
|
|
@ -14,12 +14,13 @@ const languages = require('./languages');
|
||||||
const routesRegexp = require('./routesRegexp');
|
const routesRegexp = require('./routesRegexp');
|
||||||
const useAnimationFrame = require('./useAnimationFrame');
|
const useAnimationFrame = require('./useAnimationFrame');
|
||||||
const useBinaryState = require('./useBinaryState');
|
const useBinaryState = require('./useBinaryState');
|
||||||
const useFullscreen = require('./useFullscreen');
|
const { default: useFullscreen } = require('./useFullscreen');
|
||||||
const useLiveRef = require('./useLiveRef');
|
const useLiveRef = require('./useLiveRef');
|
||||||
const useModelState = require('./useModelState');
|
const useModelState = require('./useModelState');
|
||||||
const useNotifications = require('./useNotifications');
|
const useNotifications = require('./useNotifications');
|
||||||
const useOnScrollToBottom = require('./useOnScrollToBottom');
|
const useOnScrollToBottom = require('./useOnScrollToBottom');
|
||||||
const useProfile = require('./useProfile');
|
const useProfile = require('./useProfile');
|
||||||
|
const { default: useSettings } = require('./useSettings');
|
||||||
const { default: useShell } = require('./useShell');
|
const { default: useShell } = require('./useShell');
|
||||||
const useStreamingServer = require('./useStreamingServer');
|
const useStreamingServer = require('./useStreamingServer');
|
||||||
const useTorrent = require('./useTorrent');
|
const useTorrent = require('./useTorrent');
|
||||||
|
|
@ -52,6 +53,7 @@ module.exports = {
|
||||||
useNotifications,
|
useNotifications,
|
||||||
useOnScrollToBottom,
|
useOnScrollToBottom,
|
||||||
useProfile,
|
useProfile,
|
||||||
|
useSettings,
|
||||||
useShell,
|
useShell,
|
||||||
useStreamingServer,
|
useStreamingServer,
|
||||||
useTorrent,
|
useTorrent,
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Copyright (C) 2017-2023 Smart code 203358507
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
|
|
||||||
const useFullscreen = () => {
|
|
||||||
const [fullscreen, setFullscreen] = React.useState(document.fullscreenElement === document.documentElement);
|
|
||||||
const requestFullscreen = React.useCallback(() => {
|
|
||||||
document.documentElement.requestFullscreen();
|
|
||||||
}, []);
|
|
||||||
const exitFullscreen = React.useCallback(() => {
|
|
||||||
document.exitFullscreen();
|
|
||||||
}, []);
|
|
||||||
const toggleFullscreen = React.useCallback(() => {
|
|
||||||
if (fullscreen) {
|
|
||||||
exitFullscreen();
|
|
||||||
} else {
|
|
||||||
requestFullscreen();
|
|
||||||
}
|
|
||||||
}, [fullscreen]);
|
|
||||||
React.useEffect(() => {
|
|
||||||
const onFullscreenChange = () => {
|
|
||||||
setFullscreen(document.fullscreenElement === document.documentElement);
|
|
||||||
};
|
|
||||||
document.addEventListener('fullscreenchange', onFullscreenChange);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('fullscreenchange', onFullscreenChange);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
return [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen];
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = useFullscreen;
|
|
||||||
66
src/common/useFullscreen.ts
Normal file
66
src/common/useFullscreen.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright (C) 2017-2023 Smart code 203358507
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import useShell, { type WindowVisibilityState } from './useShell';
|
||||||
|
import useSettings from './useSettings';
|
||||||
|
|
||||||
|
const useFullscreen = () => {
|
||||||
|
const shell = useShell();
|
||||||
|
const [settings] = useSettings();
|
||||||
|
|
||||||
|
const [fullscreen, setFullscreen] = useState(false);
|
||||||
|
|
||||||
|
const requestFullscreen = useCallback(() => {
|
||||||
|
if (shell.active) {
|
||||||
|
shell.send('win-set-visibility', { fullscreen: true });
|
||||||
|
} else {
|
||||||
|
document.documentElement.requestFullscreen();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const exitFullscreen = useCallback(() => {
|
||||||
|
if (shell.active) {
|
||||||
|
shell.send('win-set-visibility', { fullscreen: false });
|
||||||
|
} else {
|
||||||
|
document.exitFullscreen();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleFullscreen = useCallback(() => {
|
||||||
|
fullscreen ? exitFullscreen() : requestFullscreen();
|
||||||
|
}, [fullscreen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onWindowVisibilityChanged = (state: WindowVisibilityState) => {
|
||||||
|
setFullscreen(state.isFullscreen === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFullscreenChange = () => {
|
||||||
|
setFullscreen(document.fullscreenElement === document.documentElement);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.code === 'Escape' && settings.escExitFullscreen) {
|
||||||
|
exitFullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((event.code === 'F11' || event.code === 'KeyF') && shell.active) {
|
||||||
|
toggleFullscreen();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
shell.on('win-visibility-changed', onWindowVisibilityChanged);
|
||||||
|
document.addEventListener('keydown', onKeyDown);
|
||||||
|
document.addEventListener('fullscreenchange', onFullscreenChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
shell.off('win-visibility-changed', onWindowVisibilityChanged);
|
||||||
|
document.removeEventListener('keydown', onKeyDown);
|
||||||
|
document.removeEventListener('fullscreenchange', onFullscreenChange);
|
||||||
|
};
|
||||||
|
}, [settings.escExitFullscreen]);
|
||||||
|
|
||||||
|
return [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useFullscreen;
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
// Copyright (C) 2017-2023 Smart code 203358507
|
// Copyright (C) 2017-2025 Smart code 203358507
|
||||||
|
|
||||||
const React = require('react');
|
import { useCallback } from 'react';
|
||||||
const { useServices } = require('stremio/services');
|
import { useServices } from 'stremio/services';
|
||||||
const { useProfile } = require('stremio/common');
|
import useProfile from './useProfile';
|
||||||
|
|
||||||
const useSettings = () => {
|
const useSettings = (): [Settings, (settings: Settings) => void] => {
|
||||||
const { core } = useServices();
|
const { core } = useServices();
|
||||||
const profile = useProfile();
|
const profile = useProfile();
|
||||||
const updateSettings = React.useCallback((settings) => {
|
|
||||||
|
const updateSettings = useCallback((settings: Settings) => {
|
||||||
core.transport.dispatch({
|
core.transport.dispatch({
|
||||||
action: 'Ctx',
|
action: 'Ctx',
|
||||||
args: {
|
args: {
|
||||||
|
|
@ -19,7 +20,8 @@ const useSettings = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [profile]);
|
}, [profile]);
|
||||||
|
|
||||||
return [profile.settings, updateSettings];
|
return [profile.settings, updateSettings];
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = useSettings;
|
export default useSettings;
|
||||||
|
|
@ -17,6 +17,10 @@ type ShellEvent = {
|
||||||
args: string[];
|
args: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type WindowVisibilityState = {
|
||||||
|
isFullscreen: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const createId = () => Math.floor(Math.random() * 9999) + 1;
|
const createId = () => Math.floor(Math.random() * 9999) + 1;
|
||||||
|
|
||||||
const useShell = () => {
|
const useShell = () => {
|
||||||
|
|
@ -28,7 +32,7 @@ const useShell = () => {
|
||||||
events.off(name, listener);
|
events.off(name, listener);
|
||||||
};
|
};
|
||||||
|
|
||||||
const send = (method: string, ...args: (string | number)[]) => {
|
const send = (method: string, ...args: (string | number | object)[]) => {
|
||||||
try {
|
try {
|
||||||
transport?.postMessage(JSON.stringify({
|
transport?.postMessage(JSON.stringify({
|
||||||
id: createId(),
|
id: createId(),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ const PropTypes = require('prop-types');
|
||||||
const classnames = require('classnames');
|
const classnames = require('classnames');
|
||||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||||
const { Button, Image } = require('stremio/components');
|
const { Button, Image } = require('stremio/components');
|
||||||
const useFullscreen = require('stremio/common/useFullscreen');
|
const { default: useFullscreen } = require('stremio/common/useFullscreen');
|
||||||
const usePWA = require('stremio/common/usePWA');
|
const usePWA = require('stremio/common/usePWA');
|
||||||
const SearchBar = require('./SearchBar');
|
const SearchBar = require('./SearchBar');
|
||||||
const NavMenu = require('./NavMenu');
|
const NavMenu = require('./NavMenu');
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const langs = require('langs');
|
||||||
const { useTranslation } = require('react-i18next');
|
const { useTranslation } = require('react-i18next');
|
||||||
const { useRouteFocused } = require('stremio-router');
|
const { useRouteFocused } = require('stremio-router');
|
||||||
const { useServices } = require('stremio/services');
|
const { useServices } = require('stremio/services');
|
||||||
const { onFileDrop, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS } = require('stremio/common');
|
const { onFileDrop, useSettings, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS } = require('stremio/common');
|
||||||
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
|
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
|
||||||
const BufferingLoader = require('./BufferingLoader');
|
const BufferingLoader = require('./BufferingLoader');
|
||||||
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
|
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
|
||||||
|
|
@ -23,7 +23,6 @@ const SpeedMenu = require('./SpeedMenu');
|
||||||
const { default: SideDrawerButton } = require('./SideDrawerButton');
|
const { default: SideDrawerButton } = require('./SideDrawerButton');
|
||||||
const { default: SideDrawer } = require('./SideDrawer');
|
const { default: SideDrawer } = require('./SideDrawer');
|
||||||
const usePlayer = require('./usePlayer');
|
const usePlayer = require('./usePlayer');
|
||||||
const useSettings = require('./useSettings');
|
|
||||||
const useStatistics = require('./useStatistics');
|
const useStatistics = require('./useStatistics');
|
||||||
const useVideo = require('./useVideo');
|
const useVideo = require('./useVideo');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
@ -565,6 +564,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
}
|
}
|
||||||
case 'Escape': {
|
case 'Escape': {
|
||||||
closeMenus();
|
closeMenus();
|
||||||
|
!settings.escExitFullscreen && window.history.back();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -595,7 +595,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
window.removeEventListener('keyup', onKeyUp);
|
window.removeEventListener('keyup', onKeyUp);
|
||||||
window.removeEventListener('wheel', onWheel);
|
window.removeEventListener('wheel', onWheel);
|
||||||
};
|
};
|
||||||
}, [player.metaItem, player.selected, streamingServer.statistics, settings.seekTimeDuration, settings.seekShortTimeDuration, routeFocused, menusOpen, nextVideoPopupOpen, video.state.paused, video.state.time, video.state.volume, video.state.audioTracks, video.state.subtitlesTracks, video.state.extraSubtitlesTracks, video.state.playbackSpeed, toggleSubtitlesMenu, toggleStatisticsMenu, toggleSideDrawer]);
|
}, [player.metaItem, player.selected, streamingServer.statistics, settings.seekTimeDuration, settings.seekShortTimeDuration, settings.escExitFullscreen, routeFocused, menusOpen, nextVideoPopupOpen, video.state.paused, video.state.time, video.state.volume, video.state.audioTracks, video.state.subtitlesTracks, video.state.extraSubtitlesTracks, video.state.playbackSpeed, toggleSubtitlesMenu, toggleStatisticsMenu, toggleSideDrawer]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
video.events.on('error', onError);
|
video.events.on('error', onError);
|
||||||
|
|
|
||||||
2
src/routes/Player/useSettings.d.ts
vendored
2
src/routes/Player/useSettings.d.ts
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
declare const useSettings: () => [Settings, (settings: any) => void];
|
|
||||||
export = useSettings;
|
|
||||||
|
|
@ -342,6 +342,18 @@ const Settings = () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
shell.active &&
|
||||||
|
<div className={styles['option-container']}>
|
||||||
|
<div className={styles['option-name-container']}>
|
||||||
|
<div className={styles['label']}>{ t('SETTINGS_FULLSCREEN_EXIT') }</div>
|
||||||
|
</div>
|
||||||
|
<Toggle
|
||||||
|
className={classnames(styles['option-input-container'], styles['toggle-container'])}
|
||||||
|
{...escExitFullscreenToggle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<div className={styles['option-container']}>
|
<div className={styles['option-container']}>
|
||||||
<div className={styles['option-name-container']}>
|
<div className={styles['option-name-container']}>
|
||||||
<div className={styles['label']}>{ t('SETTINGS_BLUR_UNWATCHED_IMAGE') }</div>
|
<div className={styles['label']}>{ t('SETTINGS_BLUR_UNWATCHED_IMAGE') }</div>
|
||||||
|
|
@ -368,20 +380,6 @@ const Settings = () => {
|
||||||
{...subtitlesLanguageSelect}
|
{...subtitlesLanguageSelect}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{
|
|
||||||
shell.active ?
|
|
||||||
<div className={styles['option-container']}>
|
|
||||||
<div className={styles['option-name-container']}>
|
|
||||||
<div className={styles['label']}>{ t('SETTINGS_FULLSCREEN_EXIT') }</div>
|
|
||||||
</div>
|
|
||||||
<Toggle
|
|
||||||
className={classnames(styles['option-input-container'], styles['toggle-container'])}
|
|
||||||
{...escExitFullscreenToggle}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
<div className={styles['option-container']}>
|
<div className={styles['option-container']}>
|
||||||
<div className={styles['option-name-container']}>
|
<div className={styles['option-name-container']}>
|
||||||
<div className={styles['label']}>{ t('SETTINGS_SUBTITLES_SIZE') }</div>
|
<div className={styles['label']}>{ t('SETTINGS_SUBTITLES_SIZE') }</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue