mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-05-17 22:31:53 +00:00
Merge pull request #1237 from AKnassa/fix/fullscreen-state-desync-on-route-change
App: Correctly display fullscreen status
This commit is contained in:
commit
6fc21e314d
10 changed files with 170 additions and 106 deletions
|
|
@ -6,7 +6,7 @@ const { useTranslation } = require('react-i18next');
|
||||||
const { Router } = require('stremio-router');
|
const { Router } = require('stremio-router');
|
||||||
const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider, GamepadProvider } = require('stremio/services');
|
const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider, GamepadProvider } = require('stremio/services');
|
||||||
const { NotFound } = require('stremio/routes');
|
const { NotFound } = require('stremio/routes');
|
||||||
const { FileDropProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, withCoreSuspender, useShell, useBinaryState } = require('stremio/common');
|
const { FileDropProvider, FullscreenProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, withCoreSuspender, useShell, useBinaryState } = require('stremio/common');
|
||||||
const ServicesToaster = require('./ServicesToaster');
|
const ServicesToaster = require('./ServicesToaster');
|
||||||
const DeepLinkHandler = require('./DeepLinkHandler');
|
const DeepLinkHandler = require('./DeepLinkHandler');
|
||||||
const SearchParamsHandler = require('./SearchParamsHandler');
|
const SearchParamsHandler = require('./SearchParamsHandler');
|
||||||
|
|
@ -231,21 +231,23 @@ const App = () => {
|
||||||
<FileDropProvider className={styles['file-drop-container']}>
|
<FileDropProvider className={styles['file-drop-container']}>
|
||||||
<GamepadProvider enabled={gamepadSupportEnabled} onGuide={toggleGamepadModal}>
|
<GamepadProvider enabled={gamepadSupportEnabled} onGuide={toggleGamepadModal}>
|
||||||
<ShortcutsProvider onShortcut={onShortcut}>
|
<ShortcutsProvider onShortcut={onShortcut}>
|
||||||
{
|
<FullscreenProvider>
|
||||||
shortcutModalOpen && <ShortcutsModal onClose={closeShortcutsModal}/>
|
{
|
||||||
}
|
shortcutModalOpen && <ShortcutsModal onClose={closeShortcutsModal}/>
|
||||||
{
|
}
|
||||||
gamepadModalOpen && <GamepadModal onClose={closeGamepadModal}/>
|
{
|
||||||
}
|
gamepadModalOpen && <GamepadModal onClose={closeGamepadModal}/>
|
||||||
<ServicesToaster />
|
}
|
||||||
<DeepLinkHandler />
|
<ServicesToaster />
|
||||||
<SearchParamsHandler />
|
<DeepLinkHandler />
|
||||||
<UpdaterBanner className={styles['updater-banner-container']} />
|
<SearchParamsHandler />
|
||||||
<RouterWithProtectedRoutes
|
<UpdaterBanner className={styles['updater-banner-container']} />
|
||||||
className={styles['router']}
|
<RouterWithProtectedRoutes
|
||||||
viewsConfig={routerViewsConfig}
|
className={styles['router']}
|
||||||
onPathNotMatch={onPathNotMatch}
|
viewsConfig={routerViewsConfig}
|
||||||
/>
|
onPathNotMatch={onPathNotMatch}
|
||||||
|
/>
|
||||||
|
</FullscreenProvider>
|
||||||
</ShortcutsProvider>
|
</ShortcutsProvider>
|
||||||
</GamepadProvider>
|
</GamepadProvider>
|
||||||
</FileDropProvider>
|
</FileDropProvider>
|
||||||
|
|
|
||||||
16
src/common/Fullscreen/FullscreenContext.ts
Normal file
16
src/common/Fullscreen/FullscreenContext.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (C) 2017-2026 Smart code 203358507
|
||||||
|
|
||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export type FullscreenContextValue = readonly [
|
||||||
|
fullscreen: boolean,
|
||||||
|
requestFullscreen: () => Promise<void> | void,
|
||||||
|
exitFullscreen: () => void,
|
||||||
|
toggleFullscreen: () => void,
|
||||||
|
];
|
||||||
|
|
||||||
|
const FullscreenContext = createContext<FullscreenContextValue | null>(null);
|
||||||
|
|
||||||
|
FullscreenContext.displayName = 'FullscreenContext';
|
||||||
|
|
||||||
|
export default FullscreenContext;
|
||||||
109
src/common/Fullscreen/FullscreenProvider.tsx
Normal file
109
src/common/Fullscreen/FullscreenProvider.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
// Copyright (C) 2017-2026 Smart code 203358507
|
||||||
|
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { withCoreSuspender } from '../CoreSuspender';
|
||||||
|
import onShortcut from '../Shortcuts/onShortcut';
|
||||||
|
import useSettings from '../useSettings';
|
||||||
|
import useShell, { type WindowVisibility } from '../useShell';
|
||||||
|
import FullscreenContext, { type FullscreenContextValue } from './FullscreenContext';
|
||||||
|
|
||||||
|
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();
|
||||||
|
const escExitFullscreen = settings.escExitFullscreen;
|
||||||
|
|
||||||
|
const [fullscreen, setFullscreen] = useState<boolean>(() => {
|
||||||
|
if (typeof document === 'undefined') return false;
|
||||||
|
return document.fullscreenElement === document.documentElement;
|
||||||
|
});
|
||||||
|
|
||||||
|
const requestFullscreen = useCallback(async () => {
|
||||||
|
if (shell.active) {
|
||||||
|
shell.send('win-set-visibility', { fullscreen: true });
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await document.documentElement.requestFullscreen();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error enabling fullscreen', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [shell]);
|
||||||
|
|
||||||
|
const exitFullscreen = useCallback(() => {
|
||||||
|
if (shell.active) {
|
||||||
|
shell.send('win-set-visibility', { fullscreen: false });
|
||||||
|
} else {
|
||||||
|
if (document.fullscreenElement === document.documentElement) {
|
||||||
|
document.exitFullscreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [shell]);
|
||||||
|
|
||||||
|
const toggleFullscreen = useCallback(() => {
|
||||||
|
fullscreen ? exitFullscreen() : requestFullscreen();
|
||||||
|
}, [fullscreen, exitFullscreen, requestFullscreen]);
|
||||||
|
|
||||||
|
const toggleFullscreenFromShortcut = useCallback(() => {
|
||||||
|
if (isTextInputFocused()) return;
|
||||||
|
toggleFullscreen();
|
||||||
|
}, [toggleFullscreen]);
|
||||||
|
|
||||||
|
onShortcut('fullscreen', toggleFullscreenFromShortcut, [toggleFullscreenFromShortcut]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onWindowVisibilityChanged = (state: WindowVisibility) => {
|
||||||
|
setFullscreen(state.isFullscreen === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFullscreenChange = () => {
|
||||||
|
setFullscreen(document.fullscreenElement === document.documentElement);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.code === 'Escape' && escExitFullscreen) {
|
||||||
|
exitFullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.code === 'F11' && 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);
|
||||||
|
};
|
||||||
|
}, [shell, toggleFullscreen, exitFullscreen, escExitFullscreen]);
|
||||||
|
|
||||||
|
const value = useMemo<FullscreenContextValue>(
|
||||||
|
() => [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen],
|
||||||
|
[fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FullscreenContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</FullscreenContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withCoreSuspender(FullscreenProvider);
|
||||||
7
src/common/Fullscreen/index.ts
Normal file
7
src/common/Fullscreen/index.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Copyright (C) 2017-2026 Smart code 203358507
|
||||||
|
|
||||||
|
import FullscreenProvider from './FullscreenProvider';
|
||||||
|
import useFullscreen from './useFullscreen';
|
||||||
|
|
||||||
|
export { FullscreenProvider, useFullscreen };
|
||||||
|
export default useFullscreen;
|
||||||
15
src/common/Fullscreen/useFullscreen.ts
Normal file
15
src/common/Fullscreen/useFullscreen.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright (C) 2017-2026 Smart code 203358507
|
||||||
|
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import FullscreenContext from './FullscreenContext';
|
||||||
|
|
||||||
|
const useFullscreen = () => {
|
||||||
|
const value = useContext(FullscreenContext);
|
||||||
|
if (value === null) {
|
||||||
|
throw new Error('useFullscreen must be used inside FullscreenProvider');
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useFullscreen;
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (C) 2017-2023 Smart code 203358507
|
// Copyright (C) 2017-2023 Smart code 203358507
|
||||||
|
|
||||||
const { FileDropProvider, onFileDrop } = require('./FileDrop');
|
const { FileDropProvider, onFileDrop } = require('./FileDrop');
|
||||||
|
const { FullscreenProvider, useFullscreen } = require('./Fullscreen');
|
||||||
const { PlatformProvider, usePlatform } = require('./Platform');
|
const { PlatformProvider, usePlatform } = require('./Platform');
|
||||||
const { ToastProvider, useToast } = require('./Toast');
|
const { ToastProvider, useToast } = require('./Toast');
|
||||||
const { TooltipProvider, Tooltip } = require('./Tooltips');
|
const { TooltipProvider, Tooltip } = require('./Tooltips');
|
||||||
|
|
@ -14,7 +15,6 @@ 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 { default: useFullscreen } = require('./useFullscreen');
|
|
||||||
const { default: useInterval } = require('./useInterval');
|
const { default: useInterval } = require('./useInterval');
|
||||||
const useLiveRef = require('./useLiveRef');
|
const useLiveRef = require('./useLiveRef');
|
||||||
const useModelState = require('./useModelState');
|
const useModelState = require('./useModelState');
|
||||||
|
|
@ -34,6 +34,7 @@ const { default: useLanguageSorting } = require('./useLanguageSorting');
|
||||||
module.exports = {
|
module.exports = {
|
||||||
FileDropProvider,
|
FileDropProvider,
|
||||||
onFileDrop,
|
onFileDrop,
|
||||||
|
FullscreenProvider,
|
||||||
PlatformProvider,
|
PlatformProvider,
|
||||||
usePlatform,
|
usePlatform,
|
||||||
ShortcutsProvider,
|
ShortcutsProvider,
|
||||||
|
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
// Copyright (C) 2017-2023 Smart code 203358507
|
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
import useShell, { type WindowVisibility } from './useShell';
|
|
||||||
import useSettings from './useSettings';
|
|
||||||
|
|
||||||
const useFullscreen = () => {
|
|
||||||
const shell = useShell();
|
|
||||||
const [settings] = useSettings();
|
|
||||||
|
|
||||||
const [fullscreen, setFullscreen] = useState(false);
|
|
||||||
|
|
||||||
const requestFullscreen = useCallback(async () => {
|
|
||||||
if (shell.active) {
|
|
||||||
shell.send('win-set-visibility', { fullscreen: true });
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
await document.documentElement.requestFullscreen();
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error enabling fullscreen', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const exitFullscreen = useCallback(() => {
|
|
||||||
if (shell.active) {
|
|
||||||
shell.send('win-set-visibility', { fullscreen: false });
|
|
||||||
} else {
|
|
||||||
if (document.fullscreenElement === document.documentElement) {
|
|
||||||
document.exitFullscreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const toggleFullscreen = useCallback(() => {
|
|
||||||
fullscreen ? exitFullscreen() : requestFullscreen();
|
|
||||||
}, [fullscreen]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const onWindowVisibilityChanged = (state: WindowVisibility) => {
|
|
||||||
setFullscreen(state.isFullscreen === true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFullscreenChange = () => {
|
|
||||||
setFullscreen(document.fullscreenElement === document.documentElement);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
|
||||||
|
|
||||||
const activeElement = document.activeElement as HTMLElement;
|
|
||||||
|
|
||||||
const inputFocused =
|
|
||||||
activeElement &&
|
|
||||||
(activeElement.tagName === 'INPUT' ||
|
|
||||||
activeElement.tagName === 'TEXTAREA' ||
|
|
||||||
activeElement.tagName === 'SELECT' ||
|
|
||||||
activeElement.isContentEditable);
|
|
||||||
|
|
||||||
if (event.code === 'Escape' && settings.escExitFullscreen) {
|
|
||||||
exitFullscreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.code === 'KeyF' && !inputFocused) {
|
|
||||||
toggleFullscreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.code === 'F11' && 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, toggleFullscreen]);
|
|
||||||
|
|
||||||
return [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useFullscreen;
|
|
||||||
|
|
@ -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 { default: useFullscreen } = require('stremio/common/useFullscreen');
|
const { useFullscreen } = require('stremio/common/Fullscreen');
|
||||||
const usePWA = require('stremio/common/usePWA');
|
const usePWA = require('stremio/common/usePWA');
|
||||||
const { useHorizontalNavGamepadNavigation } = require('stremio/services/GamepadNavigation');
|
const { useHorizontalNavGamepadNavigation } = require('stremio/services/GamepadNavigation');
|
||||||
const SearchBar = require('./SearchBar');
|
const SearchBar = require('./SearchBar');
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ const { useTranslation } = require('react-i18next');
|
||||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||||
const { useServices } = require('stremio/services');
|
const { useServices } = require('stremio/services');
|
||||||
const { Button } = require('stremio/components');
|
const { Button } = require('stremio/components');
|
||||||
const { default: useFullscreen } = require('stremio/common/useFullscreen');
|
const { useFullscreen } = require('stremio/common/Fullscreen');
|
||||||
const useProfile = require('stremio/common/useProfile');
|
const useProfile = require('stremio/common/useProfile');
|
||||||
const usePWA = require('stremio/common/usePWA');
|
const usePWA = require('stremio/common/usePWA');
|
||||||
const { default: usePlayUrl } = require('stremio/common/usePlayUrl');
|
const { default: usePlayUrl } = require('stremio/common/usePlayUrl');
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useGamepad } from '../GamepadContext';
|
import { useGamepad } from '../GamepadContext';
|
||||||
import useFullscreen from 'stremio/common/useFullscreen';
|
import useFullscreen from 'stremio/common/Fullscreen';
|
||||||
|
|
||||||
const useHorizontalNavGamepadNavigation = (gamepadHandlerId: string, enableGoBack: boolean) => {
|
const useHorizontalNavGamepadNavigation = (gamepadHandlerId: string, enableGoBack: boolean) => {
|
||||||
const gamepad = useGamepad();
|
const gamepad = useGamepad();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue