mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
Merge branch 'development' into feat/example-apple-login
This commit is contained in:
commit
9a6f01b6ad
29 changed files with 442 additions and 127 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "stremio",
|
||||
"version": "5.0.0-beta.20",
|
||||
"version": "5.0.0-beta.21",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "stremio",
|
||||
"version": "5.0.0-beta.20",
|
||||
"version": "5.0.0-beta.21",
|
||||
"license": "gpl-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.26.0",
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
"react-i18next": "^15.1.3",
|
||||
"react-is": "18.3.1",
|
||||
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
|
||||
"stremio-translations": "github:Stremio/stremio-translations#4bb1b7e31df274f538b8588c2a2b360d6e14ab27",
|
||||
"stremio-translations": "github:Stremio/stremio-translations#a6be0425573917c2e82b66d28968c1a4d444cb96",
|
||||
"url": "0.11.4",
|
||||
"use-long-press": "^3.2.0"
|
||||
},
|
||||
|
|
@ -13373,8 +13373,8 @@
|
|||
},
|
||||
"node_modules/stremio-translations": {
|
||||
"version": "1.44.10",
|
||||
"resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#4bb1b7e31df274f538b8588c2a2b360d6e14ab27",
|
||||
"integrity": "sha512-+RLkoytMyqP90mn9Wkh1MhwB2fxVuvMxsxxceGnFgYlyyEL8fxuHTRnSaBjWBw+xFtsaeMLmDfA1n3l+UEzg4A==",
|
||||
"resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#a6be0425573917c2e82b66d28968c1a4d444cb96",
|
||||
"integrity": "sha512-77kVE/eos/SA16kzeK7TTWmqoLF0mLPCJXjITwVIVzMHr8XyBPZFOfmiVEg4M6W1W7qYqA+dHhzicyLs7hJhlw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "stremio",
|
||||
"displayName": "Stremio",
|
||||
"version": "5.0.0-beta.20",
|
||||
"version": "5.0.0-beta.21",
|
||||
"author": "Smart Code OOD",
|
||||
"private": true,
|
||||
"license": "gpl-2.0",
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
"react-i18next": "^15.1.3",
|
||||
"react-is": "18.3.1",
|
||||
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
|
||||
"stremio-translations": "github:Stremio/stremio-translations#4bb1b7e31df274f538b8588c2a2b360d6e14ab27",
|
||||
"stremio-translations": "github:Stremio/stremio-translations#a6be0425573917c2e82b66d28968c1a4d444cb96",
|
||||
"url": "0.11.4",
|
||||
"use-long-press": "^3.2.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ const RouterWithProtectedRoutes = withCoreSuspender(withProtectedRoutes(Router))
|
|||
const App = () => {
|
||||
const { i18n } = useTranslation();
|
||||
const shell = useShell();
|
||||
const [windowHidden, setWindowHidden] = React.useState(false);
|
||||
const onPathNotMatch = React.useCallback(() => {
|
||||
return NotFound;
|
||||
}, []);
|
||||
|
|
@ -102,10 +101,6 @@ const App = () => {
|
|||
|
||||
// Handle shell events
|
||||
React.useEffect(() => {
|
||||
const onWindowVisibilityChanged = (state) => {
|
||||
setWindowHidden(state.visible === false && state.visibility === 0);
|
||||
};
|
||||
|
||||
const onOpenMedia = (data) => {
|
||||
if (data.startsWith('stremio:///')) return;
|
||||
if (data.startsWith('stremio://')) {
|
||||
|
|
@ -116,11 +111,9 @@ const App = () => {
|
|||
}
|
||||
};
|
||||
|
||||
shell.on('win-visibility-changed', onWindowVisibilityChanged);
|
||||
shell.on('open-media', onOpenMedia);
|
||||
|
||||
return () => {
|
||||
shell.off('win-visibility-changed', onWindowVisibilityChanged);
|
||||
shell.off('open-media', onOpenMedia);
|
||||
};
|
||||
}, []);
|
||||
|
|
@ -133,7 +126,7 @@ const App = () => {
|
|||
i18n.changeLanguage(args.settings.interfaceLanguage);
|
||||
}
|
||||
|
||||
if (args?.settings?.quitOnClose && windowHidden) {
|
||||
if (args?.settings?.quitOnClose && shell.windowClosed) {
|
||||
shell.send('quit');
|
||||
}
|
||||
|
||||
|
|
@ -146,7 +139,7 @@ const App = () => {
|
|||
i18n.changeLanguage(state.profile.settings.interfaceLanguage);
|
||||
}
|
||||
|
||||
if (state?.profile?.settings?.quitOnClose && windowHidden) {
|
||||
if (state?.profile?.settings?.quitOnClose && shell.windowClosed) {
|
||||
shell.send('quit');
|
||||
}
|
||||
};
|
||||
|
|
@ -191,7 +184,7 @@ const App = () => {
|
|||
services.core.transport.off('CoreEvent', onCoreEvent);
|
||||
}
|
||||
};
|
||||
}, [initialized, windowHidden]);
|
||||
}, [initialized, shell.windowClosed]);
|
||||
return (
|
||||
<React.StrictMode>
|
||||
<ServicesProvider services={services}>
|
||||
|
|
|
|||
|
|
@ -151,14 +151,13 @@ svg {
|
|||
html {
|
||||
width: @html-width;
|
||||
height: @html-height;
|
||||
font-family: 'PlusJakartaSans', 'sans-serif';
|
||||
font-family: 'PlusJakartaSans', 'Arial', 'Helvetica', 'sans-serif';
|
||||
overflow: auto;
|
||||
overscroll-behavior: none;
|
||||
user-select: none;
|
||||
touch-action: manipulation;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
|
||||
@media (display-mode: standalone) {
|
||||
width: @html-standalone-width;
|
||||
height: @html-standalone-height;
|
||||
|
|
@ -168,6 +167,7 @@ html {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(41deg, var(--primary-background-color) 0%, var(--secondary-background-color) 100%);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
:global(#app) {
|
||||
position: relative;
|
||||
|
|
|
|||
|
|
@ -14,12 +14,13 @@ const languages = require('./languages');
|
|||
const routesRegexp = require('./routesRegexp');
|
||||
const useAnimationFrame = require('./useAnimationFrame');
|
||||
const useBinaryState = require('./useBinaryState');
|
||||
const useFullscreen = require('./useFullscreen');
|
||||
const { default: useFullscreen } = require('./useFullscreen');
|
||||
const useLiveRef = require('./useLiveRef');
|
||||
const useModelState = require('./useModelState');
|
||||
const useNotifications = require('./useNotifications');
|
||||
const useOnScrollToBottom = require('./useOnScrollToBottom');
|
||||
const useProfile = require('./useProfile');
|
||||
const { default: useSettings } = require('./useSettings');
|
||||
const { default: useShell } = require('./useShell');
|
||||
const useStreamingServer = require('./useStreamingServer');
|
||||
const useTorrent = require('./useTorrent');
|
||||
|
|
@ -52,6 +53,7 @@ module.exports = {
|
|||
useNotifications,
|
||||
useOnScrollToBottom,
|
||||
useProfile,
|
||||
useSettings,
|
||||
useShell,
|
||||
useStreamingServer,
|
||||
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 WindowVisibility } 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: WindowVisibility) => {
|
||||
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' && 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');
|
||||
const { useServices } = require('stremio/services');
|
||||
const { useProfile } = require('stremio/common');
|
||||
import { useCallback } from 'react';
|
||||
import { useServices } from 'stremio/services';
|
||||
import useProfile from './useProfile';
|
||||
|
||||
const useSettings = () => {
|
||||
const useSettings = (): [Settings, (settings: Settings) => void] => {
|
||||
const { core } = useServices();
|
||||
const profile = useProfile();
|
||||
const updateSettings = React.useCallback((settings) => {
|
||||
|
||||
const updateSettings = useCallback((settings: Settings) => {
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
|
|
@ -19,7 +20,8 @@ const useSettings = () => {
|
|||
}
|
||||
});
|
||||
}, [profile]);
|
||||
|
||||
return [profile.settings, updateSettings];
|
||||
};
|
||||
|
||||
module.exports = useSettings;
|
||||
export default useSettings;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
const SHELL_EVENT_OBJECT = 'transport';
|
||||
|
|
@ -17,9 +17,22 @@ type ShellEvent = {
|
|||
args: string[];
|
||||
};
|
||||
|
||||
export type WindowVisibility = {
|
||||
visible: boolean;
|
||||
visibility: number;
|
||||
isFullscreen: boolean;
|
||||
};
|
||||
|
||||
export type WindowState = {
|
||||
state: number;
|
||||
};
|
||||
|
||||
const createId = () => Math.floor(Math.random() * 9999) + 1;
|
||||
|
||||
const useShell = () => {
|
||||
const [windowClosed, setWindowClosed] = useState(false);
|
||||
const [windowHidden, setWindowHidden] = useState(false);
|
||||
|
||||
const on = (name: string, listener: (arg: any) => void) => {
|
||||
events.on(name, listener);
|
||||
};
|
||||
|
|
@ -28,7 +41,7 @@ const useShell = () => {
|
|||
events.off(name, listener);
|
||||
};
|
||||
|
||||
const send = (method: string, ...args: (string | number)[]) => {
|
||||
const send = (method: string, ...args: (string | number | object)[]) => {
|
||||
try {
|
||||
transport?.postMessage(JSON.stringify({
|
||||
id: createId(),
|
||||
|
|
@ -42,6 +55,24 @@ const useShell = () => {
|
|||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const onWindowVisibilityChanged = (data: WindowVisibility) => {
|
||||
setWindowClosed(data.visible === false && data.visibility === 0);
|
||||
};
|
||||
|
||||
const onWindowStateChanged = (data: WindowState) => {
|
||||
setWindowHidden(data.state === 9);
|
||||
};
|
||||
|
||||
on('win-visibility-changed', onWindowVisibilityChanged);
|
||||
on('win-state-changed', onWindowStateChanged);
|
||||
|
||||
return () => {
|
||||
off('win-visibility-changed', onWindowVisibilityChanged);
|
||||
off('win-state-changed', onWindowStateChanged);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!transport) return;
|
||||
|
||||
|
|
@ -66,6 +97,8 @@ const useShell = () => {
|
|||
send,
|
||||
on,
|
||||
off,
|
||||
windowClosed,
|
||||
windowHidden,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
17
src/components/ContextMenu/ContextMenu.less
Normal file
17
src/components/ContextMenu/ContextMenu.less
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
|
||||
.context-menu-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
.context-menu {
|
||||
position: fixed;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--modal-background-color);
|
||||
box-shadow: 0 1.35rem 2.7rem @color-background-dark5-40,
|
||||
0 1.1rem 0.85rem @color-background-dark5-20;
|
||||
}
|
||||
}
|
||||
101
src/components/ContextMenu/ContextMenu.tsx
Normal file
101
src/components/ContextMenu/ContextMenu.tsx
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import React, { memo, RefObject, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import styles from './ContextMenu.less';
|
||||
|
||||
const PADDING = 8;
|
||||
|
||||
type Coordinates = [number, number];
|
||||
type Size = [number, number];
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode,
|
||||
on: RefObject<HTMLElement>[],
|
||||
autoClose: boolean,
|
||||
};
|
||||
|
||||
const ContextMenu = ({ children, on, autoClose }: Props) => {
|
||||
const [active, setActive] = useState(false);
|
||||
const [position, setPosition] = useState<Coordinates>([0, 0]);
|
||||
const [containerSize, setContainerSize] = useState<Size>([0, 0]);
|
||||
|
||||
const ref = useCallback((element: HTMLDivElement) => {
|
||||
element && setContainerSize([element.offsetWidth, element.offsetHeight]);
|
||||
}, []);
|
||||
|
||||
const style = useMemo(() => {
|
||||
const [viewportWidth, viewportHeight] = [window.innerWidth, window.innerHeight];
|
||||
const [containerWidth, containerHeight] = containerSize;
|
||||
const [x, y] = position;
|
||||
|
||||
const left = Math.max(
|
||||
PADDING,
|
||||
Math.min(
|
||||
x + containerWidth > viewportWidth - PADDING ? x - containerWidth : x,
|
||||
viewportWidth - containerWidth - PADDING
|
||||
)
|
||||
);
|
||||
|
||||
const top = Math.max(
|
||||
PADDING,
|
||||
Math.min(
|
||||
y + containerHeight > viewportHeight - PADDING ? y - containerHeight : y,
|
||||
viewportHeight - containerHeight - PADDING
|
||||
)
|
||||
);
|
||||
|
||||
return { top, left };
|
||||
}, [position, containerSize]);
|
||||
|
||||
const close = () => {
|
||||
setPosition([0, 0]);
|
||||
setActive(false);
|
||||
};
|
||||
|
||||
const stopPropagation = (event: React.MouseEvent | React.TouchEvent) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const onContextMenu = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
setPosition([event.clientX, event.clientY]);
|
||||
setActive(true);
|
||||
};
|
||||
|
||||
const handleKeyDown = useCallback((event: KeyboardEvent) => event.key === 'Escape' && close(), []);
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
autoClose && close();
|
||||
}, [autoClose]);
|
||||
|
||||
useEffect(() => {
|
||||
on.forEach((ref) => ref.current && ref.current.addEventListener('contextmenu', onContextMenu));
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => {
|
||||
on.forEach((ref) => ref.current && ref.current.removeEventListener('contextmenu', onContextMenu));
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [on]);
|
||||
|
||||
return active && createPortal((
|
||||
<div
|
||||
className={styles['context-menu-container']}
|
||||
onMouseDown={close}
|
||||
onTouchStart={close}
|
||||
>
|
||||
<div
|
||||
ref={ref}
|
||||
className={styles['context-menu']}
|
||||
style={style}
|
||||
onMouseDown={stopPropagation}
|
||||
onTouchStart={stopPropagation}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
), document.body);
|
||||
};
|
||||
|
||||
export default memo(ContextMenu);
|
||||
2
src/components/ContextMenu/index.ts
Normal file
2
src/components/ContextMenu/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import ContextMenu from './ContextMenu';
|
||||
export default ContextMenu;
|
||||
|
|
@ -107,7 +107,9 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-radius: 2.5rem;
|
||||
border-radius: 0.5rem;
|
||||
border: var(--focus-outline-size) solid transparent;
|
||||
padding: 0rem 0.5rem;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const PropTypes = require('prop-types');
|
|||
const classnames = require('classnames');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
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 SearchBar = require('./SearchBar');
|
||||
const NavMenu = require('./NavMenu');
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ const { useTranslation } = require('react-i18next');
|
|||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const { useServices } = require('stremio/services');
|
||||
const { Button } = require('stremio/components');
|
||||
const useFullscreen = require('stremio/common/useFullscreen');
|
||||
const { default: useFullscreen } = require('stremio/common/useFullscreen');
|
||||
const useProfile = require('stremio/common/useProfile');
|
||||
const usePWA = require('stremio/common/usePWA');
|
||||
const useTorrent = require('stremio/common/useTorrent');
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import Button from './Button';
|
|||
import Checkbox from './Checkbox';
|
||||
import Chips from './Chips';
|
||||
import ColorInput from './ColorInput';
|
||||
import ContextMenu from './ContextMenu';
|
||||
import ContinueWatchingItem from './ContinueWatchingItem';
|
||||
import DelayedRenderer from './DelayedRenderer';
|
||||
import EventModal from './EventModal';
|
||||
|
|
@ -35,6 +36,7 @@ export {
|
|||
Checkbox,
|
||||
Chips,
|
||||
ColorInput,
|
||||
ContextMenu,
|
||||
ContinueWatchingItem,
|
||||
DelayedRenderer,
|
||||
EventModal,
|
||||
|
|
|
|||
|
|
@ -82,6 +82,46 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.placeholder {
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
|
||||
.text {
|
||||
width: 8rem;
|
||||
height: 1.2rem;
|
||||
background-color: var(--overlay-color);
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
.video {
|
||||
flex: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
height: 3rem;
|
||||
padding: 0 1rem;
|
||||
|
||||
.name {
|
||||
flex: auto;
|
||||
width: 12rem;
|
||||
height: 1.2rem;
|
||||
background-color: var(--overlay-color);
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: none;
|
||||
width: 4rem;
|
||||
height: 1.2rem;
|
||||
background-color: var(--overlay-color);
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.today {
|
||||
.heading {
|
||||
background-color: var(--primary-accent-color);
|
||||
|
|
|
|||
23
src/routes/Calendar/List/Item/ItemPlaceholder.tsx
Normal file
23
src/routes/Calendar/List/Item/ItemPlaceholder.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (C) 2017-2025 Smart code 203358507
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './Item.less';
|
||||
|
||||
const ItemPlaceholder = () => {
|
||||
return (
|
||||
<div className={classNames(styles['item'], styles['placeholder'])}>
|
||||
<div className={styles['heading']}>
|
||||
<div className={styles['text']} />
|
||||
</div>
|
||||
<div className={styles['body']}>
|
||||
<div className={styles['video']}>
|
||||
<div className={styles['name']} />
|
||||
<div className={styles['info']} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItemPlaceholder;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import Item from './Item';
|
||||
import ItemPlaceholder from './ItemPlaceholder';
|
||||
|
||||
export default Item;
|
||||
export { Item, ItemPlaceholder };
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import Item from './Item';
|
||||
import { Item, ItemPlaceholder } from './Item';
|
||||
import styles from './List.less';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -20,16 +20,21 @@ const List = ({ items, selected, monthInfo, profile, onChange }: Props) => {
|
|||
return (
|
||||
<div className={styles['list']}>
|
||||
{
|
||||
filteredItems.map((item) => (
|
||||
<Item
|
||||
key={item.date.day}
|
||||
{...item}
|
||||
selected={selected}
|
||||
monthInfo={monthInfo}
|
||||
profile={profile}
|
||||
onClick={onChange}
|
||||
/>
|
||||
))
|
||||
items.length === 0 ?
|
||||
[1, 2, 3].map((index) => (
|
||||
<ItemPlaceholder key={index} />
|
||||
))
|
||||
:
|
||||
filteredItems.map((item) => (
|
||||
<Item
|
||||
key={item.date.day}
|
||||
{...item}
|
||||
selected={selected}
|
||||
monthInfo={monthInfo}
|
||||
profile={profile}
|
||||
onClick={onChange}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const useDiscover = require('./useDiscover');
|
|||
const useSelectableInputs = require('./useSelectableInputs');
|
||||
const styles = require('./styles');
|
||||
|
||||
const SCROLL_TO_BOTTOM_TRESHOLD = 400;
|
||||
const SCROLL_TO_BOTTOM_THRESHOLD = 400;
|
||||
|
||||
const Discover = ({ urlParams, queryParams }) => {
|
||||
const { core } = useServices();
|
||||
|
|
@ -26,6 +26,15 @@ const Discover = ({ urlParams, queryParams }) => {
|
|||
metasContainerRef.current.scrollTop = 0;
|
||||
}
|
||||
}, [discover.catalog]);
|
||||
React.useEffect(() => {
|
||||
if (hasNextPage && metasContainerRef.current) {
|
||||
const containerHeight = metasContainerRef.current.scrollHeight;
|
||||
const viewportHeight = metasContainerRef.current.clientHeight;
|
||||
if (containerHeight <= viewportHeight + SCROLL_TO_BOTTOM_THRESHOLD) {
|
||||
loadNextPage();
|
||||
}
|
||||
}
|
||||
}, [hasNextPage, loadNextPage]);
|
||||
const selectedMetaItem = React.useMemo(() => {
|
||||
return discover.catalog !== null &&
|
||||
discover.catalog.content.type === 'Ready' &&
|
||||
|
|
@ -76,7 +85,7 @@ const Discover = ({ urlParams, queryParams }) => {
|
|||
loadNextPage();
|
||||
}
|
||||
}, [hasNextPage, loadNextPage]);
|
||||
const onScroll = useOnScrollToBottom(onScrollToBottom, SCROLL_TO_BOTTOM_TRESHOLD);
|
||||
const onScroll = useOnScrollToBottom(onScrollToBottom, SCROLL_TO_BOTTOM_THRESHOLD);
|
||||
React.useEffect(() => {
|
||||
closeInputsModal();
|
||||
closeAddonModal();
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ const classnames = require('classnames');
|
|||
const { Image } = require('stremio/components');
|
||||
const styles = require('./styles');
|
||||
|
||||
const BufferingLoader = ({ className, logo }) => {
|
||||
const BufferingLoader = React.forwardRef(({ className, logo }, ref) => {
|
||||
return (
|
||||
<div className={classnames(className, styles['buffering-loader-container'])}>
|
||||
<div ref={ref} className={classnames(className, styles['buffering-loader-container'])}>
|
||||
<Image
|
||||
className={styles['buffering-loader']}
|
||||
src={logo}
|
||||
|
|
@ -17,11 +17,11 @@ const BufferingLoader = ({ className, logo }) => {
|
|||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
BufferingLoader.propTypes = {
|
||||
className: PropTypes.string,
|
||||
logo: PropTypes.string
|
||||
logo: PropTypes.string,
|
||||
};
|
||||
|
||||
module.exports = BufferingLoader;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const { default: Icon } = require('@stremio/stremio-icons/react');
|
|||
const { Button } = require('stremio/components');
|
||||
const styles = require('./styles');
|
||||
|
||||
const Error = ({ className, code, message, stream }) => {
|
||||
const Error = React.forwardRef(({ className, code, message, stream }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [playlist, fileName] = React.useMemo(() => {
|
||||
|
|
@ -19,7 +19,7 @@ const Error = ({ className, code, message, stream }) => {
|
|||
}, [stream]);
|
||||
|
||||
return (
|
||||
<div className={classNames(className, styles['error'])}>
|
||||
<div ref={ref} className={classNames(className, styles['error'])}>
|
||||
<div className={styles['error-label']} title={message}>{message}</div>
|
||||
{
|
||||
code === 2 ?
|
||||
|
|
@ -44,7 +44,7 @@ const Error = ({ className, code, message, stream }) => {
|
|||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
Error.propTypes = {
|
||||
className: PropTypes.string,
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ const OptionsMenu = ({ className, stream, playbackDevices }) => {
|
|||
const onMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.optionsMenuClosePrevented = true;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={classnames(className, styles['options-menu-container'])} onMouseDown={onMouseDown}>
|
||||
{
|
||||
|
|
@ -112,7 +113,7 @@ const OptionsMenu = ({ className, stream, playbackDevices }) => {
|
|||
OptionsMenu.propTypes = {
|
||||
className: PropTypes.string,
|
||||
stream: PropTypes.object,
|
||||
playbackDevices: PropTypes.array
|
||||
playbackDevices: PropTypes.array,
|
||||
};
|
||||
|
||||
module.exports = OptionsMenu;
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ const langs = require('langs');
|
|||
const { useTranslation } = require('react-i18next');
|
||||
const { useRouteFocused } = require('stremio-router');
|
||||
const { useServices } = require('stremio/services');
|
||||
const { onFileDrop, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS } = require('stremio/common');
|
||||
const { HorizontalNavBar, Transition } = require('stremio/components');
|
||||
const { onFileDrop, useSettings, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS, useShell } = require('stremio/common');
|
||||
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
|
||||
const BufferingLoader = require('./BufferingLoader');
|
||||
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
|
||||
const Error = require('./Error');
|
||||
|
|
@ -23,7 +23,6 @@ const SpeedMenu = require('./SpeedMenu');
|
|||
const { default: SideDrawerButton } = require('./SideDrawerButton');
|
||||
const { default: SideDrawer } = require('./SideDrawer');
|
||||
const usePlayer = require('./usePlayer');
|
||||
const useSettings = require('./useSettings');
|
||||
const useStatistics = require('./useStatistics');
|
||||
const useVideo = require('./useVideo');
|
||||
const styles = require('./styles');
|
||||
|
|
@ -31,7 +30,8 @@ const Video = require('./Video');
|
|||
|
||||
const Player = ({ urlParams, queryParams }) => {
|
||||
const { t } = useTranslation();
|
||||
const { chromecast, shell, core } = useServices();
|
||||
const services = useServices();
|
||||
const shell = useShell();
|
||||
const forceTranscoding = React.useMemo(() => {
|
||||
return queryParams.has('forceTranscoding');
|
||||
}, [queryParams]);
|
||||
|
|
@ -47,8 +47,12 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
const [seeking, setSeeking] = React.useState(false);
|
||||
|
||||
const [casting, setCasting] = React.useState(() => {
|
||||
return chromecast.active && chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED;
|
||||
return services.chromecast.active && services.chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED;
|
||||
});
|
||||
const playbackDevices = React.useMemo(() => streamingServer.playbackDevices !== null && streamingServer.playbackDevices.type === 'Ready' ? streamingServer.playbackDevices.content : [], [streamingServer]);
|
||||
|
||||
const bufferingRef = React.useRef();
|
||||
const errorRef = React.useRef();
|
||||
|
||||
const [immersed, setImmersed] = React.useState(true);
|
||||
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
|
||||
|
|
@ -317,8 +321,8 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
null,
|
||||
seriesInfo: player.seriesInfo,
|
||||
}, {
|
||||
chromecastTransport: chromecast.active ? chromecast.transport : null,
|
||||
shellTransport: shell.active ? shell.transport : null,
|
||||
chromecastTransport: services.chromecast.active ? services.chromecast.transport : null,
|
||||
shellTransport: services.shell.active ? services.shell.transport : null,
|
||||
});
|
||||
}
|
||||
}, [streamingServer.baseUrl, player.selected, forceTranscoding, casting]);
|
||||
|
|
@ -439,12 +443,12 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
const toastFilter = (item) => item?.dataset?.type === 'CoreEvent';
|
||||
toast.addFilter(toastFilter);
|
||||
const onCastStateChange = () => {
|
||||
setCasting(chromecast.active && chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED);
|
||||
setCasting(services.chromecast.active && services.chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED);
|
||||
};
|
||||
const onChromecastServiceStateChange = () => {
|
||||
onCastStateChange();
|
||||
if (chromecast.active) {
|
||||
chromecast.transport.on(
|
||||
if (services.chromecast.active) {
|
||||
services.chromecast.transport.on(
|
||||
cast.framework.CastContextEventType.CAST_STATE_CHANGED,
|
||||
onCastStateChange
|
||||
);
|
||||
|
|
@ -455,15 +459,15 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
onPauseRequested();
|
||||
}
|
||||
};
|
||||
chromecast.on('stateChanged', onChromecastServiceStateChange);
|
||||
core.transport.on('CoreEvent', onCoreEvent);
|
||||
services.chromecast.on('stateChanged', onChromecastServiceStateChange);
|
||||
services.core.transport.on('CoreEvent', onCoreEvent);
|
||||
onChromecastServiceStateChange();
|
||||
return () => {
|
||||
toast.removeFilter(toastFilter);
|
||||
chromecast.off('stateChanged', onChromecastServiceStateChange);
|
||||
core.transport.off('CoreEvent', onCoreEvent);
|
||||
if (chromecast.active) {
|
||||
chromecast.transport.off(
|
||||
services.chromecast.off('stateChanged', onChromecastServiceStateChange);
|
||||
services.core.transport.off('CoreEvent', onCoreEvent);
|
||||
if (services.chromecast.active) {
|
||||
services.chromecast.transport.off(
|
||||
cast.framework.CastContextEventType.CAST_STATE_CHANGED,
|
||||
onCastStateChange
|
||||
);
|
||||
|
|
@ -471,6 +475,12 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (settings.pauseOnMinimize && (shell.windowClosed || shell.windowHidden)) {
|
||||
onPauseRequested();
|
||||
}
|
||||
}, [settings.pauseOnMinimize, shell.windowClosed, shell.windowHidden]);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
const onKeyDown = (event) => {
|
||||
switch (event.code) {
|
||||
|
|
@ -561,6 +571,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
}
|
||||
case 'Escape': {
|
||||
closeMenus();
|
||||
!settings.escExitFullscreen && window.history.back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -591,7 +602,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
window.removeEventListener('keyup', onKeyUp);
|
||||
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(() => {
|
||||
video.events.on('error', onError);
|
||||
|
|
@ -626,7 +637,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
onMouseOver={onContainerMouseMove}
|
||||
onMouseLeave={onContainerMouseLeave}>
|
||||
<Video
|
||||
ref={video.containerElement}
|
||||
ref={video.containerRef}
|
||||
className={styles['layer']}
|
||||
onClick={onVideoClick}
|
||||
onDoubleClick={onVideoDoubleClick}
|
||||
|
|
@ -641,13 +652,18 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
}
|
||||
{
|
||||
(video.state.buffering || !video.state.loaded) && !error ?
|
||||
<BufferingLoader className={classnames(styles['layer'], styles['buffering-layer'])} logo={player?.metaItem?.content?.logo} />
|
||||
<BufferingLoader
|
||||
ref={bufferingRef}
|
||||
className={classnames(styles['layer'], styles['buffering-layer'])}
|
||||
logo={player?.metaItem?.content?.logo}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
error !== null ?
|
||||
<Error
|
||||
ref={errorRef}
|
||||
className={classnames(styles['layer'], styles['error-layer'])}
|
||||
stream={video.state.stream}
|
||||
{...error}
|
||||
|
|
@ -670,6 +686,13 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
:
|
||||
null
|
||||
}
|
||||
<ContextMenu on={[video.containerRef, bufferingRef, errorRef]} autoClose>
|
||||
<OptionsMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
stream={player?.selected?.stream}
|
||||
playbackDevices={playbackDevices}
|
||||
/>
|
||||
</ContextMenu>
|
||||
<HorizontalNavBar
|
||||
className={classnames(styles['layer'], styles['nav-bar-layer'])}
|
||||
title={player.title !== null ? player.title : ''}
|
||||
|
|
@ -798,7 +821,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
<OptionsMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
stream={player.selected.stream}
|
||||
playbackDevices={streamingServer.playbackDevices !== null && streamingServer.playbackDevices.type === 'Ready' ? streamingServer.playbackDevices.content : []}
|
||||
playbackDevices={playbackDevices}
|
||||
/>
|
||||
:
|
||||
null
|
||||
|
|
|
|||
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;
|
||||
|
|
@ -8,7 +8,7 @@ const events = new EventEmitter();
|
|||
|
||||
const useVideo = () => {
|
||||
const video = React.useRef(null);
|
||||
const containerElement = React.useRef(null);
|
||||
const containerRef = React.useRef(null);
|
||||
|
||||
const [state, setState] = React.useState({
|
||||
manifest: null,
|
||||
|
|
@ -42,11 +42,11 @@ const useVideo = () => {
|
|||
});
|
||||
|
||||
const dispatch = (action, options) => {
|
||||
if (video.current && containerElement.current) {
|
||||
if (video.current && containerRef.current) {
|
||||
try {
|
||||
video.current.dispatch(action, {
|
||||
...options,
|
||||
containerElement: containerElement.current,
|
||||
containerElement: containerRef.current,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Video:', error);
|
||||
|
|
@ -157,7 +157,7 @@ const useVideo = () => {
|
|||
|
||||
return {
|
||||
events,
|
||||
containerElement,
|
||||
containerRef,
|
||||
state,
|
||||
load,
|
||||
unload,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ const Settings = () => {
|
|||
bingeWatchingToggle,
|
||||
playInBackgroundToggle,
|
||||
hardwareDecodingToggle,
|
||||
pauseOnMinimizeToggle,
|
||||
} = useProfileSettingsInputs(profile);
|
||||
const {
|
||||
streamingServerRemoteUrlInput,
|
||||
|
|
@ -342,6 +343,18 @@ const Settings = () => {
|
|||
/>
|
||||
</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-name-container']}>
|
||||
<div className={styles['label']}>{ t('SETTINGS_BLUR_UNWATCHED_IMAGE') }</div>
|
||||
|
|
@ -357,7 +370,7 @@ const Settings = () => {
|
|||
<div className={styles['section-title']}>{ t('SETTINGS_NAV_PLAYER') }</div>
|
||||
<div className={styles['section-category-container']}>
|
||||
<Icon className={styles['icon']} name={'subtitles'} />
|
||||
<div className={styles['label']}>{t('SETTINGS_CLOSE_WINDOW')}</div>
|
||||
<div className={styles['label']}>{t('SETTINGS_SECTION_SUBTITLES')}</div>
|
||||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
|
|
@ -368,20 +381,6 @@ const Settings = () => {
|
|||
{...subtitlesLanguageSelect}
|
||||
/>
|
||||
</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-name-container']}>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SUBTITLES_SIZE') }</div>
|
||||
|
|
@ -531,6 +530,18 @@ const Settings = () => {
|
|||
/>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
shell.active &&
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>{ t('SETTINGS_PAUSE_MINIMIZED') }</div>
|
||||
</div>
|
||||
<Toggle
|
||||
className={classnames(styles['option-input-container'], styles['toggle-container'])}
|
||||
{...pauseOnMinimizeToggle}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div ref={streamingServerSectionRef} className={styles['section-container']}>
|
||||
<div className={styles['section-title']}>{ t('SETTINGS_NAV_STREAMING') }</div>
|
||||
|
|
|
|||
|
|
@ -339,6 +339,21 @@ const useProfileSettingsInputs = (profile) => {
|
|||
});
|
||||
}
|
||||
}), [profile.settings]);
|
||||
const pauseOnMinimizeToggle = React.useMemo(() => ({
|
||||
checked: profile.settings.pauseOnMinimize,
|
||||
onClick: () => {
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
action: 'UpdateSettings',
|
||||
args: {
|
||||
...profile.settings,
|
||||
pauseOnMinimize: !profile.settings.pauseOnMinimize,
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}), [profile.settings]);
|
||||
return {
|
||||
interfaceLanguageSelect,
|
||||
hideSpoilersToggle,
|
||||
|
|
@ -358,6 +373,7 @@ const useProfileSettingsInputs = (profile) => {
|
|||
bingeWatchingToggle,
|
||||
playInBackgroundToggle,
|
||||
hardwareDecodingToggle,
|
||||
pauseOnMinimizeToggle,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue