mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Backport pull request #5517 from jellyfin-web/release-10.9.z
Fix video osd not hiding in experimental layout
Original-merge: ea1d069e90
Merged-by: thornbill <thornbill@users.noreply.github.com>
Backported-by: Joshua M. Boniface <joshua@boniface.me>
This commit is contained in:
parent
cb01afce02
commit
017734a0bb
7 changed files with 117 additions and 29 deletions
|
@ -18,17 +18,30 @@ interface AppToolbarProps {
|
||||||
onDrawerButtonClick: (event: React.MouseEvent<HTMLElement>) => void
|
onDrawerButtonClick: (event: React.MouseEvent<HTMLElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PUBLIC_PATHS = [
|
||||||
|
'/addserver.html',
|
||||||
|
'/selectserver.html',
|
||||||
|
'/login.html',
|
||||||
|
'/forgotpassword.html',
|
||||||
|
'/forgotpasswordpin.html'
|
||||||
|
];
|
||||||
|
|
||||||
const ExperimentalAppToolbar: FC<AppToolbarProps> = ({
|
const ExperimentalAppToolbar: FC<AppToolbarProps> = ({
|
||||||
isDrawerAvailable,
|
isDrawerAvailable,
|
||||||
isDrawerOpen,
|
isDrawerOpen,
|
||||||
onDrawerButtonClick
|
onDrawerButtonClick
|
||||||
}) => {
|
}) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
|
// The video osd does not show the standard toolbar
|
||||||
|
if (location.pathname === '/video') return null;
|
||||||
|
|
||||||
const isTabsAvailable = isTabPath(location.pathname);
|
const isTabsAvailable = isTabPath(location.pathname);
|
||||||
|
const isPublicPath = PUBLIC_PATHS.includes(location.pathname);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppToolbar
|
<AppToolbar
|
||||||
buttons={
|
buttons={!isPublicPath && (
|
||||||
<>
|
<>
|
||||||
<SyncPlayButton />
|
<SyncPlayButton />
|
||||||
<RemotePlayButton />
|
<RemotePlayButton />
|
||||||
|
@ -45,10 +58,11 @@ const ExperimentalAppToolbar: FC<AppToolbarProps> = ({
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
isDrawerAvailable={isDrawerAvailable}
|
isDrawerAvailable={isDrawerAvailable}
|
||||||
isDrawerOpen={isDrawerOpen}
|
isDrawerOpen={isDrawerOpen}
|
||||||
onDrawerButtonClick={onDrawerButtonClick}
|
onDrawerButtonClick={onDrawerButtonClick}
|
||||||
|
isUserMenuAvailable={!isPublicPath}
|
||||||
>
|
>
|
||||||
{isTabsAvailable && (<AppTabs isDrawerOpen={isDrawerOpen} />)}
|
{isTabsAvailable && (<AppTabs isDrawerOpen={isDrawerOpen} />)}
|
||||||
</AppToolbar>
|
</AppToolbar>
|
||||||
|
|
|
@ -49,16 +49,6 @@ export const LEGACY_USER_ROUTES: LegacyRoute[] = [
|
||||||
controller: 'user/subtitles/index',
|
controller: 'user/subtitles/index',
|
||||||
view: 'user/subtitles/index.html'
|
view: 'user/subtitles/index.html'
|
||||||
}
|
}
|
||||||
}, {
|
|
||||||
path: 'video',
|
|
||||||
pageProps: {
|
|
||||||
controller: 'playback/video/index',
|
|
||||||
view: 'playback/video/index.html',
|
|
||||||
type: 'video-osd',
|
|
||||||
isFullscreen: true,
|
|
||||||
isNowPlayingBarEnabled: false,
|
|
||||||
isThemeMediaSupported: true
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
path: 'queue',
|
path: 'queue',
|
||||||
pageProps: {
|
pageProps: {
|
||||||
|
|
|
@ -7,8 +7,10 @@ import { toAsyncPageRoute } from 'components/router/AsyncRoute';
|
||||||
import { toViewManagerPageRoute } from 'components/router/LegacyRoute';
|
import { toViewManagerPageRoute } from 'components/router/LegacyRoute';
|
||||||
import { toRedirectRoute } from 'components/router/Redirect';
|
import { toRedirectRoute } from 'components/router/Redirect';
|
||||||
import AppLayout from '../AppLayout';
|
import AppLayout from '../AppLayout';
|
||||||
|
|
||||||
import { ASYNC_USER_ROUTES } from './asyncRoutes';
|
import { ASYNC_USER_ROUTES } from './asyncRoutes';
|
||||||
import { LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './legacyRoutes';
|
import { LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './legacyRoutes';
|
||||||
|
import VideoPage from './video';
|
||||||
|
|
||||||
export const EXPERIMENTAL_APP_ROUTES: RouteObject[] = [
|
export const EXPERIMENTAL_APP_ROUTES: RouteObject[] = [
|
||||||
{
|
{
|
||||||
|
@ -20,7 +22,13 @@ export const EXPERIMENTAL_APP_ROUTES: RouteObject[] = [
|
||||||
element: <ConnectionRequired isUserRequired />,
|
element: <ConnectionRequired isUserRequired />,
|
||||||
children: [
|
children: [
|
||||||
...ASYNC_USER_ROUTES.map(toAsyncPageRoute),
|
...ASYNC_USER_ROUTES.map(toAsyncPageRoute),
|
||||||
...LEGACY_USER_ROUTES.map(toViewManagerPageRoute)
|
...LEGACY_USER_ROUTES.map(toViewManagerPageRoute),
|
||||||
|
|
||||||
|
// The video page is special since it combines new controls with the legacy view
|
||||||
|
{
|
||||||
|
path: 'video',
|
||||||
|
element: <VideoPage />
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
72
src/apps/experimental/routes/video/index.tsx
Normal file
72
src/apps/experimental/routes/video/index.tsx
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import Box from '@mui/material/Box/Box';
|
||||||
|
import Fade from '@mui/material/Fade/Fade';
|
||||||
|
import React, { useRef, type FC, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import RemotePlayButton from 'apps/experimental/components/AppToolbar/RemotePlayButton';
|
||||||
|
import SyncPlayButton from 'apps/experimental/components/AppToolbar/SyncPlayButton';
|
||||||
|
import AppToolbar from 'components/toolbar/AppToolbar';
|
||||||
|
import ViewManagerPage from 'components/viewManager/ViewManagerPage';
|
||||||
|
import { EventType } from 'types/eventType';
|
||||||
|
import Events, { type Event } from 'utils/events';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Video player page component that renders mui controls for the top controls and the legacy view for everything else.
|
||||||
|
*/
|
||||||
|
const VideoPage: FC = () => {
|
||||||
|
const documentRef = useRef<Document>(document);
|
||||||
|
const [ isVisible, setIsVisible ] = useState(true);
|
||||||
|
|
||||||
|
const onShowVideoOsd = (_e: Event, isShowing: boolean) => {
|
||||||
|
setIsVisible(isShowing);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const doc = documentRef.current;
|
||||||
|
|
||||||
|
if (doc) Events.on(doc, EventType.SHOW_VIDEO_OSD, onShowVideoOsd);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (doc) Events.off(doc, EventType.SHOW_VIDEO_OSD, onShowVideoOsd);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Fade
|
||||||
|
in={isVisible}
|
||||||
|
easing='fade-out'
|
||||||
|
>
|
||||||
|
<Box sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
color: 'white'
|
||||||
|
}}>
|
||||||
|
<AppToolbar
|
||||||
|
isDrawerAvailable={false}
|
||||||
|
isDrawerOpen={false}
|
||||||
|
isUserMenuAvailable={false}
|
||||||
|
buttons={
|
||||||
|
<>
|
||||||
|
<SyncPlayButton />
|
||||||
|
<RemotePlayButton />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Fade>
|
||||||
|
|
||||||
|
<ViewManagerPage
|
||||||
|
controller='playback/video/index'
|
||||||
|
view='playback/video/index.html'
|
||||||
|
type='video-osd'
|
||||||
|
isFullscreen
|
||||||
|
isNowPlayingBarEnabled={false}
|
||||||
|
isThemeMediaSupported
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VideoPage;
|
|
@ -5,7 +5,6 @@ import IconButton from '@mui/material/IconButton';
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
import React, { FC, ReactNode } from 'react';
|
import React, { FC, ReactNode } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { appRouter } from 'components/router/appRouter';
|
import { appRouter } from 'components/router/appRouter';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
|
@ -17,7 +16,8 @@ interface AppToolbarProps {
|
||||||
buttons?: ReactNode
|
buttons?: ReactNode
|
||||||
isDrawerAvailable: boolean
|
isDrawerAvailable: boolean
|
||||||
isDrawerOpen: boolean
|
isDrawerOpen: boolean
|
||||||
onDrawerButtonClick: (event: React.MouseEvent<HTMLElement>) => void
|
onDrawerButtonClick?: (event: React.MouseEvent<HTMLElement>) => void,
|
||||||
|
isUserMenuAvailable?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const onBackButtonClick = () => {
|
const onBackButtonClick = () => {
|
||||||
|
@ -32,17 +32,14 @@ const AppToolbar: FC<AppToolbarProps> = ({
|
||||||
children,
|
children,
|
||||||
isDrawerAvailable,
|
isDrawerAvailable,
|
||||||
isDrawerOpen,
|
isDrawerOpen,
|
||||||
onDrawerButtonClick
|
onDrawerButtonClick = () => { /* no-op */ },
|
||||||
|
isUserMenuAvailable = true
|
||||||
}) => {
|
}) => {
|
||||||
const { user } = useApi();
|
const { user } = useApi();
|
||||||
const isUserLoggedIn = Boolean(user);
|
const isUserLoggedIn = Boolean(user);
|
||||||
const currentLocation = useLocation();
|
|
||||||
|
|
||||||
const isBackButtonAvailable = appRouter.canGoBack();
|
const isBackButtonAvailable = appRouter.canGoBack();
|
||||||
|
|
||||||
// Handles a specific case to hide the user menu on the select server page while authenticated
|
|
||||||
const isUserMenuAvailable = currentLocation.pathname !== '/selectserver.html';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Toolbar
|
<Toolbar
|
||||||
variant='dense'
|
variant='dense'
|
||||||
|
@ -84,16 +81,14 @@ const AppToolbar: FC<AppToolbarProps> = ({
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
{isUserLoggedIn && isUserMenuAvailable && (
|
<Box sx={{ display: 'flex', flexGrow: 1, justifyContent: 'flex-end' }}>
|
||||||
<>
|
{buttons}
|
||||||
<Box sx={{ display: 'flex', flexGrow: 1, justifyContent: 'flex-end' }}>
|
</Box>
|
||||||
{buttons}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{ flexGrow: 0 }}>
|
{isUserLoggedIn && isUserMenuAvailable && (
|
||||||
<UserMenuButton />
|
<Box sx={{ flexGrow: 0 }}>
|
||||||
</Box>
|
<UserMenuButton />
|
||||||
</>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
);
|
);
|
||||||
|
|
|
@ -28,6 +28,7 @@ import LibraryMenu from '../../../scripts/libraryMenu';
|
||||||
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components/backdrop/backdrop';
|
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components/backdrop/backdrop';
|
||||||
import { pluginManager } from '../../../components/pluginManager';
|
import { pluginManager } from '../../../components/pluginManager';
|
||||||
import { PluginType } from '../../../types/plugin.ts';
|
import { PluginType } from '../../../types/plugin.ts';
|
||||||
|
import { EventType } from 'types/eventType';
|
||||||
|
|
||||||
const TICKS_PER_MINUTE = 600000000;
|
const TICKS_PER_MINUTE = 600000000;
|
||||||
const TICKS_PER_SECOND = 10000000;
|
const TICKS_PER_SECOND = 10000000;
|
||||||
|
@ -280,12 +281,14 @@ export default function (view) {
|
||||||
let mouseIsDown = false;
|
let mouseIsDown = false;
|
||||||
|
|
||||||
function showOsd(focusElement) {
|
function showOsd(focusElement) {
|
||||||
|
Events.trigger(document, EventType.SHOW_VIDEO_OSD, [ true ]);
|
||||||
slideDownToShow(headerElement);
|
slideDownToShow(headerElement);
|
||||||
showMainOsdControls(focusElement);
|
showMainOsdControls(focusElement);
|
||||||
resetIdle();
|
resetIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideOsd() {
|
function hideOsd() {
|
||||||
|
Events.trigger(document, EventType.SHOW_VIDEO_OSD, [ false ]);
|
||||||
slideUpToHide(headerElement);
|
slideUpToHide(headerElement);
|
||||||
hideMainOsdControls();
|
hideMainOsdControls();
|
||||||
mouseManager.hideCursor();
|
mouseManager.hideCursor();
|
||||||
|
|
6
src/types/eventType.ts
Normal file
6
src/types/eventType.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Custom event types.
|
||||||
|
*/
|
||||||
|
export enum EventType {
|
||||||
|
SHOW_VIDEO_OSD = 'SHOW_VIDEO_OSD'
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue