mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
refactor: use menu provider logic for player menus
This commit is contained in:
parent
c3361481b4
commit
63da56b4a0
24 changed files with 274 additions and 114 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -48,6 +48,7 @@
|
|||
"@babel/preset-react": "7.16.0",
|
||||
"@types/classnames": "^2.3.1",
|
||||
"@types/react": "^18.2.9",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"babel-loader": "8.2.3",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
"copy-webpack-plugin": "9.0.1",
|
||||
|
|
@ -3265,6 +3266,15 @@
|
|||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "18.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz",
|
||||
"integrity": "sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.17.1",
|
||||
"dev": true,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
"@babel/preset-react": "7.16.0",
|
||||
"@types/classnames": "^2.3.1",
|
||||
"@types/react": "^18.2.9",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"babel-loader": "8.2.3",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
"copy-webpack-plugin": "9.0.1",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const ErrorDialog = require('./ErrorDialog');
|
|||
const withProtectedRoutes = require('./withProtectedRoutes');
|
||||
const routerViewsConfig = require('./routerViewsConfig');
|
||||
const styles = require('./styles');
|
||||
const { MenuProvider } = require('stremio/common');
|
||||
|
||||
const RouterWithProtectedRoutes = withCoreSuspender(withProtectedRoutes(Router));
|
||||
|
||||
|
|
@ -163,14 +164,16 @@ const App = () => {
|
|||
:
|
||||
<ToastProvider className={styles['toasts-container']}>
|
||||
<TooltipProvider className={styles['tooltip-container']}>
|
||||
<ServicesToaster />
|
||||
<DeepLinkHandler />
|
||||
<SearchParamsHandler />
|
||||
<RouterWithProtectedRoutes
|
||||
className={styles['router']}
|
||||
viewsConfig={routerViewsConfig}
|
||||
onPathNotMatch={onPathNotMatch}
|
||||
/>
|
||||
<MenuProvider className={styles['menu-container']}>
|
||||
<ServicesToaster />
|
||||
<DeepLinkHandler />
|
||||
<SearchParamsHandler />
|
||||
<RouterWithProtectedRoutes
|
||||
className={styles['router']}
|
||||
viewsConfig={routerViewsConfig}
|
||||
onPathNotMatch={onPathNotMatch}
|
||||
/>
|
||||
</MenuProvider>
|
||||
</TooltipProvider>
|
||||
</ToastProvider>
|
||||
:
|
||||
|
|
|
|||
|
|
@ -103,12 +103,12 @@ html {
|
|||
height: 100%;
|
||||
|
||||
.toasts-container {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
top: calc(1.2 * var(--horizontal-nav-bar-size));
|
||||
right: 0;
|
||||
bottom: calc(1.2 * var(--horizontal-nav-bar-size));
|
||||
left: auto;
|
||||
z-index: 1;
|
||||
padding: 0 calc(0.5 * var(--horizontal-nav-bar-size));
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
|
|
@ -120,6 +120,7 @@ html {
|
|||
}
|
||||
|
||||
.tooltip-container {
|
||||
z-index: 1;
|
||||
height: 2.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -137,6 +138,15 @@ html {
|
|||
}
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--modal-background-color);
|
||||
box-shadow: 0 1.35rem 2.7rem rgba(0, 0, 0, 0.4), 0 1.1rem 0.85rem rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(15px);
|
||||
}
|
||||
|
||||
.router {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
|
|
|||
52
src/common/Menu/Menu.tsx
Normal file
52
src/common/Menu/Menu.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import useMenu from './useMenu';
|
||||
import useKeyboardEvent from '../useKeyboardEvent';
|
||||
import styles from './styles.less';
|
||||
|
||||
type Props = {
|
||||
children: JSX.Element,
|
||||
className: string,
|
||||
shortcut: string,
|
||||
align: 'left' | 'right',
|
||||
};
|
||||
|
||||
const createId = () => (Math.random() + 1).toString(36).substring(7);
|
||||
|
||||
const Menu = ({ children, className, shortcut, align }: Props) => {
|
||||
const menu = useMenu();
|
||||
|
||||
const id = useRef(createId());
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onToggle = () => {
|
||||
menu.toggle(id.current);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const parent = element.current?.parentElement;
|
||||
parent.addEventListener('click', onToggle);
|
||||
|
||||
menu.create({
|
||||
id: id.current,
|
||||
className,
|
||||
parent,
|
||||
align,
|
||||
});
|
||||
|
||||
return () => {
|
||||
parent.removeEventListener('click', onToggle);
|
||||
menu.remove(id.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useKeyboardEvent(shortcut, onToggle, !shortcut);
|
||||
|
||||
return <>
|
||||
<div ref={element} className={styles['menu-placeholder']} />
|
||||
{menu.active?.id === id.current && menu.render(children)}
|
||||
</>;
|
||||
};
|
||||
|
||||
export default Menu;
|
||||
17
src/common/Menu/MenuContext.ts
Normal file
17
src/common/Menu/MenuContext.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import { createContext } from 'react';
|
||||
import { type Menu } from './types';
|
||||
|
||||
type MenuContext = {
|
||||
active?: Menu,
|
||||
create: (menu: Omit<Menu, 'open'>) => void,
|
||||
remove: (id: string) => void,
|
||||
render: (content: JSX.Element) => void,
|
||||
toggle: (id: string) => void,
|
||||
};
|
||||
|
||||
const MenuContext = createContext<MenuContext>({} as MenuContext);
|
||||
|
||||
export default MenuContext;
|
||||
|
||||
82
src/common/Menu/MenuProvider.tsx
Normal file
82
src/common/Menu/MenuProvider.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import classNames from 'classnames';
|
||||
import useKeyboardEvent from '../useKeyboardEvent';
|
||||
import MenuContext from './MenuContext';
|
||||
import { type Menu } from './types';
|
||||
|
||||
type Props = {
|
||||
children: JSX.Element,
|
||||
className: string,
|
||||
};
|
||||
|
||||
const MenuProvider = ({ children, className }: Props) => {
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [menus, setMenus] = useState<Menu[]>([]);
|
||||
const [style, setStyle] = useState<React.CSSProperties>(null);
|
||||
|
||||
const active = useMemo(() => menus.find(({ open }) => open), [menus]);
|
||||
|
||||
const create = (menu: Omit<Menu, 'open'>) => {
|
||||
setMenus((menus) => ([
|
||||
...menus,
|
||||
{
|
||||
...menu,
|
||||
open: false,
|
||||
},
|
||||
]));
|
||||
};
|
||||
|
||||
const remove = (id: string) => {
|
||||
setMenus((menus) => menus.filter((menu) => menu.id !== id));
|
||||
};
|
||||
|
||||
const render = (content: JSX.Element) => {
|
||||
return createPortal(content, container.current);
|
||||
};
|
||||
|
||||
const toggle = (id: string) => {
|
||||
setMenus((menus) => menus.map((menu) => ({
|
||||
...menu,
|
||||
open: menu.id === id && !menu.open,
|
||||
})));
|
||||
};
|
||||
|
||||
const closeAll = () => {
|
||||
setMenus((menus) => menus.map((menu) => ({
|
||||
...menu,
|
||||
open: false,
|
||||
})));
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setStyle(null);
|
||||
|
||||
if (container.current && active) {
|
||||
const menu = container.current.getBoundingClientRect();
|
||||
const parent = active.parent.getBoundingClientRect();
|
||||
|
||||
const y = (parent.top + menu.height) < window.innerHeight ? 'bottom' : 'top';
|
||||
const x = (parent.left + menu.width) < window.innerWidth ? 'left' : 'right';
|
||||
|
||||
setStyle({
|
||||
top: y === 'top' ? parent.top - menu.height : parent.bottom,
|
||||
left: (active.align ?? x) === 'left' ? parent.left : parent.right - menu.width,
|
||||
});
|
||||
}
|
||||
}, [active]);
|
||||
|
||||
useKeyboardEvent('Escape', closeAll);
|
||||
|
||||
return (
|
||||
<MenuContext.Provider value={{ active, create, remove, render, toggle }}>
|
||||
{children}
|
||||
<div ref={container} className={classNames(className, active?.className)} style={style} />
|
||||
</MenuContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuProvider;
|
||||
9
src/common/Menu/index.ts
Normal file
9
src/common/Menu/index.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import Menu from './Menu';
|
||||
import MenuProvider from './MenuProvider';
|
||||
|
||||
export {
|
||||
Menu,
|
||||
MenuProvider,
|
||||
};
|
||||
11
src/common/Menu/styles.less
Normal file
11
src/common/Menu/styles.less
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
.menu-placeholder {
|
||||
z-index: -1;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
7
src/common/Menu/types.d.ts
vendored
Normal file
7
src/common/Menu/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export type Menu = {
|
||||
id: string,
|
||||
className: string,
|
||||
parent: HTMLElement,
|
||||
align: 'left' | 'right',
|
||||
open: boolean,
|
||||
};
|
||||
8
src/common/Menu/useMenu.ts
Normal file
8
src/common/Menu/useMenu.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import { useContext } from 'react';
|
||||
import MenuContext from './MenuContext';
|
||||
|
||||
const useMenu = () => useContext(MenuContext);
|
||||
|
||||
export default useMenu;
|
||||
|
|
@ -48,6 +48,7 @@ const EventModal = require('./EventModal');
|
|||
const { default: useOnClickOutside } = require('./useOnClickOutside');
|
||||
const { default: useKeyboardEvent } = require('./useKeyboardEvent');
|
||||
const { default: useMouseEvent } = require('./useMouseEvent');
|
||||
const { MenuProvider, Menu } = require('./Menu');
|
||||
|
||||
module.exports = {
|
||||
AddonDetailsModal,
|
||||
|
|
@ -102,4 +103,6 @@ module.exports = {
|
|||
useOnClickOutside,
|
||||
useKeyboardEvent,
|
||||
useMouseEvent,
|
||||
MenuProvider,
|
||||
Menu,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@
|
|||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const useOnClickOutside = (ref: React.MutableRefObject<HTMLElement>, handler: () => void) => {
|
||||
const useOnClickOutside = (ref: React.MutableRefObject<HTMLElement>, handler: () => void, ignore?: boolean) => {
|
||||
useEffect(() => {
|
||||
const onClickOutside = (event: MouseEvent) => {
|
||||
const element = event.target as Node;
|
||||
if (ref.current && !ref.current.contains(element)) {
|
||||
handler();
|
||||
!ignore && handler();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', onClickOutside);
|
||||
return () => document.removeEventListener('click', onClickOutside);
|
||||
document.addEventListener('mouseup', onClickOutside);
|
||||
return () => document.removeEventListener('mouseup', onClickOutside);
|
||||
}, [handler]);
|
||||
};
|
||||
|
||||
|
|
|
|||
2
src/modules.d.ts
vendored
2
src/modules.d.ts
vendored
|
|
@ -1,2 +1,2 @@
|
|||
declare module '*';
|
||||
declare module '*.less';
|
||||
declare module 'stremio/common';
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Icon from '@stremio/stremio-icons/react';
|
||||
import { Button, useBinaryState, useOnClickOutside, useKeyboardEvent } from 'stremio/common';
|
||||
import { Button } from 'stremio/common';
|
||||
import styles from './styles.less';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -12,53 +12,19 @@ type Props = {
|
|||
disabled?: boolean,
|
||||
icon: string,
|
||||
title?: string,
|
||||
shortcut?: string,
|
||||
onMenuChange?: (state: boolean) => void,
|
||||
onClick?: () => void,
|
||||
};
|
||||
|
||||
const Control = ({ children, className, disabled, icon, title, shortcut, onMenuChange, onClick }: Props) => {
|
||||
const ref = useRef(null);
|
||||
const [menuOpen,, closeMenu, toggleMenu] = useBinaryState();
|
||||
|
||||
const onButtonClick = () => {
|
||||
toggleMenu();
|
||||
onClick && onClick();
|
||||
};
|
||||
|
||||
const onMenuClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const onClickOutside = useCallback(() => {
|
||||
menuOpen && closeMenu();
|
||||
}, [menuOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
onMenuChange && onMenuChange(menuOpen);
|
||||
}, [menuOpen, onMenuChange]);
|
||||
|
||||
useOnClickOutside(ref, onClickOutside);
|
||||
useKeyboardEvent('Escape', closeMenu);
|
||||
shortcut && useKeyboardEvent(shortcut, toggleMenu);
|
||||
|
||||
const Control = ({ children, className, disabled, icon, title, onClick }: Props) => {
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
className={classNames(className, styles['control-button'], { 'disabled': disabled })}
|
||||
tabIndex={-1}
|
||||
title={title}
|
||||
onClick={onButtonClick}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Icon className={styles['icon']} name={icon} />
|
||||
{
|
||||
children && menuOpen ?
|
||||
<div className={styles['menu-container']} onClick={onMenuClick}>
|
||||
{children}
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,15 +22,4 @@
|
|||
height: 2.5rem;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 8rem;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--modal-background-color);
|
||||
box-shadow: 0 1.35rem 2.7rem rgba(0, 0, 0, 0.4), 0 1.1rem 0.85rem rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(15px);
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,6 @@ const ControlBar = ({
|
|||
onSubtitlesOffsetChanged,
|
||||
onSubtitlesSizeChanged,
|
||||
onExtraSubtitlesDelayChanged,
|
||||
onMenuChange,
|
||||
...props
|
||||
}) => {
|
||||
const { chromecast } = useServices();
|
||||
|
|
@ -73,14 +72,6 @@ const ControlBar = ({
|
|||
return streamingServer.playbackDevices !== null && streamingServer.playbackDevices.type === 'Ready' ? streamingServer.playbackDevices.content : [];
|
||||
}, [streamingServer]);
|
||||
|
||||
const onMuteButtonClick = React.useCallback(() => {
|
||||
muted ? onUnmuteRequested() : onMuteRequested();
|
||||
}, [muted, onMuteRequested, onUnmuteRequested]);
|
||||
|
||||
const onChromecastButtonClick = React.useCallback(() => {
|
||||
chromecast.transport.requestSession();
|
||||
}, []);
|
||||
|
||||
const volumeIcon = React.useMemo(() => {
|
||||
return (typeof muted === 'boolean' && muted) ? 'volume-mute' :
|
||||
(volume === null || isNaN(volume)) ? 'volume-off' :
|
||||
|
|
@ -89,6 +80,14 @@ const ControlBar = ({
|
|||
'volume-high';
|
||||
}, [muted, volume]);
|
||||
|
||||
const onMuteButtonClick = React.useCallback(() => {
|
||||
muted ? onUnmuteRequested() : onMuteRequested();
|
||||
}, [muted, onMuteRequested, onUnmuteRequested]);
|
||||
|
||||
const onChromecastButtonClick = React.useCallback(() => {
|
||||
chromecast.transport.requestSession();
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const onStateChanged = () => setChromecastServiceActive(chromecast.active);
|
||||
chromecast.on('stateChanged', onStateChanged);
|
||||
|
|
@ -140,16 +139,21 @@ const ControlBar = ({
|
|||
onClick={toogleMobileMenu}
|
||||
/>
|
||||
<div className={classnames(styles['controls-menu-container'], { 'open': mobileMenuOpen })}>
|
||||
<Control icon={'network'} disabled={!statistics || statistics.infoHash === null || !stream} shortcut={'KeyD'} onMenuChange={onMenuChange}>
|
||||
<StatisticsMenu {...statistics} />
|
||||
</Control>
|
||||
<Control icon={'speed'} disabled={!playbackSpeed} shortcut={'KeyR'} onMenuChange={onMenuChange}>
|
||||
{
|
||||
statistics?.infoHash ?
|
||||
<Control icon={'network'}>
|
||||
<StatisticsMenu {...statistics} />
|
||||
</Control>
|
||||
:
|
||||
null
|
||||
}
|
||||
<Control icon={'speed'} disabled={!playbackSpeed}>
|
||||
<SpeedMenu
|
||||
playbackSpeed={playbackSpeed}
|
||||
onChange={onPlaybackSpeedChangeRequested}
|
||||
/>
|
||||
</Control>
|
||||
<Control icon={'about'} disabled={!metaItem || !stream} shortcut={'KeyI'} onMenuChange={onMenuChange}>
|
||||
<Control icon={'about'} disabled={!metaItem || !stream}>
|
||||
<InfoMenu
|
||||
stream={stream}
|
||||
addon={addon}
|
||||
|
|
@ -157,7 +161,7 @@ const ControlBar = ({
|
|||
/>
|
||||
</Control>
|
||||
<Control icon={'cast'} disabled={!chromecastServiceActive} onClick={onChromecastButtonClick} />
|
||||
<Control icon={'subtitles'} disabled={tracks.length === 0} shortcut={'KeyS'} onMenuChange={onMenuChange}>
|
||||
<Control icon={'subtitles'} disabled={tracks.length === 0}>
|
||||
<SubtitlesMenu
|
||||
audioTracks={audioTracks}
|
||||
selectedAudioTrackId={selectedAudioTrackId}
|
||||
|
|
@ -182,7 +186,7 @@ const ControlBar = ({
|
|||
</Control>
|
||||
{
|
||||
metaItem?.videos?.length > 0 ?
|
||||
<Control icon={'episodes'} shortcut={'KeyV'} onMenuChange={onMenuChange}>
|
||||
<Control icon={'episodes'}>
|
||||
<VideosMenu
|
||||
metaItem={metaItem}
|
||||
seriesInfo={seriesInfo}
|
||||
|
|
@ -191,7 +195,7 @@ const ControlBar = ({
|
|||
:
|
||||
null
|
||||
}
|
||||
<Control icon={'more-horizontal'} onMenuChange={onMenuChange}>
|
||||
<Control icon={'more-horizontal'}>
|
||||
<OptionsMenu
|
||||
stream={stream}
|
||||
playbackDevices={playbackDevices}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const PropTypes = require('prop-types');
|
|||
const classnames = require('classnames');
|
||||
// const Stream = require('stremio/routes/MetaDetails/StreamsList/Stream');
|
||||
// const AddonDetails = require('stremio/common/AddonDetailsModal/AddonDetails');
|
||||
const { MetaPreview, CONSTANTS } = require('stremio/common');
|
||||
const { MetaPreview, Menu, CONSTANTS } = require('stremio/common');
|
||||
const styles = require('./styles');
|
||||
|
||||
const InfoMenu = ({ ...props }) => {
|
||||
|
|
@ -18,11 +18,8 @@ const InfoMenu = ({ ...props }) => {
|
|||
:
|
||||
null;
|
||||
}, [props.metaItem]);
|
||||
const onMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.infoMenuClosePrevented = true;
|
||||
}, []);
|
||||
return (
|
||||
<div className={classnames(styles['info-menu-container'])} onMouseDown={onMouseDown}>
|
||||
<Menu className={classnames(styles['info-menu-container'])} shortcut={'KeyI'}>
|
||||
{
|
||||
metaItem !== null ?
|
||||
<MetaPreview
|
||||
|
|
@ -63,7 +60,7 @@ const InfoMenu = ({ ...props }) => {
|
|||
:
|
||||
null
|
||||
} */}
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const { useToast } = require('stremio/common');
|
||||
const { Menu, useToast } = require('stremio/common');
|
||||
const { useServices } = require('stremio/services');
|
||||
const Option = require('./Option');
|
||||
const styles = require('./styles');
|
||||
|
|
@ -65,11 +65,8 @@ const OptionsMenu = ({ className, stream, playbackDevices }) => {
|
|||
});
|
||||
}
|
||||
}, [streamingUrl]);
|
||||
const onMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.optionsMenuClosePrevented = true;
|
||||
}, []);
|
||||
return (
|
||||
<div className={classnames(className, styles['options-menu-container'])} onMouseDown={onMouseDown}>
|
||||
<Menu className={classnames(className, styles['options-menu-container'])}>
|
||||
{
|
||||
streamingUrl || downloadUrl ?
|
||||
<Option
|
||||
|
|
@ -104,7 +101,7 @@ const OptionsMenu = ({ className, stream, playbackDevices }) => {
|
|||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const { Menu } = require('stremio/common');
|
||||
const Option = require('./Option');
|
||||
const styles = require('./styles');
|
||||
|
||||
|
|
@ -12,10 +13,6 @@ const RATES = Array.from(Array(8).keys(), (n) => n * 0.25 + 0.25).reverse();
|
|||
const SpeedMenu = ({ className, playbackSpeed, onChange }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.speedMenuClosePrevented = true;
|
||||
}, []);
|
||||
|
||||
const onOptionSelect = React.useCallback((value) => {
|
||||
if (typeof onChange === 'function') {
|
||||
onChange(value);
|
||||
|
|
@ -23,7 +20,7 @@ const SpeedMenu = ({ className, playbackSpeed, onChange }) => {
|
|||
}, [onChange]);
|
||||
|
||||
return (
|
||||
<div className={classnames(className, styles['speed-menu-container'])} onMouseDown={onMouseDown}>
|
||||
<Menu className={classnames(className, styles['speed-menu-container'])} shortcut={'KeyR'} align={'right'}>
|
||||
<div className={styles['title']}>
|
||||
{ t('PLAYBACK_SPEED') }
|
||||
</div>
|
||||
|
|
@ -40,7 +37,7 @@ const SpeedMenu = ({ className, playbackSpeed, onChange }) => {
|
|||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@
|
|||
const React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const { Menu } = require('stremio/common');
|
||||
const styles = require('./styles.less');
|
||||
|
||||
const StatisticsMenu = ({ className, peers, speed, completed, infoHash }) => {
|
||||
return (
|
||||
<div className={classNames(className, styles['statistics-menu-container'])}>
|
||||
<Menu className={classNames(className, styles['statistics-menu-container'])} shortcut={'KeyD'}>
|
||||
<div className={styles['title']}>
|
||||
Statistics
|
||||
</div>
|
||||
|
|
@ -45,7 +46,7 @@ const StatisticsMenu = ({ className, peers, speed, completed, infoHash }) => {
|
|||
{ infoHash }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { Button, CONSTANTS, comparatorWithPriorities, languageNames } = require('stremio/common');
|
||||
const { Button, Menu, CONSTANTS, comparatorWithPriorities, languageNames } = require('stremio/common');
|
||||
const DiscreteSelectInput = require('./DiscreteSelectInput');
|
||||
const styles = require('./styles');
|
||||
const { t } = require('i18next');
|
||||
|
|
@ -58,9 +58,6 @@ const SubtitlesMenu = React.memo((props) => {
|
|||
.filter(({ lang }) => lang === selectedSubtitlesLanguage)
|
||||
.sort((t1, t2) => comparatorWithPriorities(ORIGIN_PRIORITIES)(t1.origin, t2.origin));
|
||||
}, [props.subtitlesTracks, props.extraSubtitlesTracks, selectedSubtitlesLanguage]);
|
||||
const onMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.subtitlesMenuClosePrevented = true;
|
||||
}, []);
|
||||
const subtitlesLanguageOnClick = React.useCallback((event) => {
|
||||
const track = (Array.isArray(props.subtitlesTracks) ? props.subtitlesTracks : [])
|
||||
.concat(Array.isArray(props.extraSubtitlesTracks) ? props.extraSubtitlesTracks : [])
|
||||
|
|
@ -150,7 +147,7 @@ const SubtitlesMenu = React.memo((props) => {
|
|||
}
|
||||
}, [props.onAudioTrackSelected]);
|
||||
return (
|
||||
<div className={classnames(props.className, styles['subtitles-menu-container'])} onMouseDown={onMouseDown}>
|
||||
<Menu className={classnames(props.className, styles['subtitles-menu-container'])} shortcut={'KeyS'}>
|
||||
{
|
||||
Array.isArray(props.audioTracks) && props.audioTracks.length > 1 ?
|
||||
<div className={styles['languages-container']}>
|
||||
|
|
@ -278,7 +275,7 @@ const SubtitlesMenu = React.memo((props) => {
|
|||
onChange={onSubtitlesOffsetChanged}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { Menu } = require('stremio/common');
|
||||
const Video = require('../../../../MetaDetails/VideosList/Video');
|
||||
const styles = require('./styles');
|
||||
|
||||
const VideosMenu = ({ className, metaItem, seriesInfo }) => {
|
||||
const onMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.videosMenuClosePrevented = true;
|
||||
}, []);
|
||||
const videos = React.useMemo(() => {
|
||||
return seriesInfo && typeof seriesInfo.season === 'number' && Array.isArray(metaItem.videos) ?
|
||||
metaItem.videos.filter(({ season }) => season === seriesInfo.season)
|
||||
|
|
@ -17,7 +15,7 @@ const VideosMenu = ({ className, metaItem, seriesInfo }) => {
|
|||
metaItem.videos;
|
||||
}, [metaItem, seriesInfo]);
|
||||
return (
|
||||
<div className={classnames(className, styles['videos-menu-container'])} onMouseDown={onMouseDown}>
|
||||
<Menu className={classnames(className, styles['videos-menu-container'])} shortcut={'KeyV'}>
|
||||
{
|
||||
videos.map((video, index) => (
|
||||
<Video
|
||||
|
|
@ -35,7 +33,7 @@ const VideosMenu = ({ className, metaItem, seriesInfo }) => {
|
|||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const langs = require('langs');
|
|||
const { useTranslation } = require('react-i18next');
|
||||
const { useServices } = require('stremio/services');
|
||||
const { HorizontalNavBar, useFullscreen, useBinaryState, useToast, useStreamingServer, useKeyboardEvent, useMouseEvent, withCoreSuspender } = require('stremio/common');
|
||||
const { default: useMenu } = require('stremio/common/Menu/useMenu');
|
||||
const BufferingLoader = require('./BufferingLoader');
|
||||
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
|
||||
const Error = require('./Error');
|
||||
|
|
@ -26,6 +27,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
return queryParams.has('forceTranscoding');
|
||||
}, [queryParams]);
|
||||
|
||||
const menu = useMenu();
|
||||
const [player, videoParamsChanged, timeChanged, pausedChanged, ended, nextVideo] = usePlayer(urlParams);
|
||||
const [settings, updateSettings] = useSettings();
|
||||
const streamingServer = useStreamingServer();
|
||||
|
|
@ -40,7 +42,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
|
||||
const [,,, toggleFullscreen] = useFullscreen();
|
||||
|
||||
const [areMenusOpen, setMenusState] = React.useState(false);
|
||||
const areMenusOpen = React.useMemo(() => menu.active, [menu.active]);
|
||||
const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false);
|
||||
|
||||
const ignoreShortcuts = React.useMemo(() => {
|
||||
|
|
@ -519,7 +521,6 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
onSubtitlesOffsetChanged={onSubtitlesOffsetChanged}
|
||||
onSubtitlesSizeChanged={onSubtitlesSizeChanged}
|
||||
onExtraSubtitlesDelayChanged={onExtraSubtitlesDelayChanged}
|
||||
onMenuChange={setMenusState}
|
||||
onMouseMove={onBarMouseMove}
|
||||
onMouseOver={onBarMouseMove}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue