This commit is contained in:
Botsy 2025-06-20 08:37:10 +00:00 committed by GitHub
commit 7cb3fc2a4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 372 additions and 349 deletions

43
package-lock.json generated
View file

@ -35,6 +35,8 @@
"react-focus-lock": "2.13.2", "react-focus-lock": "2.13.2",
"react-i18next": "^15.1.3", "react-i18next": "^15.1.3",
"react-is": "18.3.1", "react-is": "18.3.1",
"react-router": "6.30.0",
"react-router-dom": "6.30.0",
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
"stremio-translations": "github:Stremio/stremio-translations#8212fa77c4febd22ddb611590e9fb574dc845416", "stremio-translations": "github:Stremio/stremio-translations#8212fa77c4febd22ddb611590e9fb574dc845416",
"url": "0.11.4", "url": "0.11.4",
@ -3105,6 +3107,15 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@remix-run/router": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@rollup/plugin-babel": { "node_modules/@rollup/plugin-babel": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@ -12498,6 +12509,38 @@
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-router": {
"version": "6.30.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz",
"integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
"version": "6.30.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz",
"integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.0",
"react-router": "6.30.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/readable-stream": { "node_modules/readable-stream": {
"version": "3.6.0", "version": "3.6.0",
"dev": true, "dev": true,

View file

@ -40,6 +40,8 @@
"react-focus-lock": "2.13.2", "react-focus-lock": "2.13.2",
"react-i18next": "^15.1.3", "react-i18next": "^15.1.3",
"react-is": "18.3.1", "react-is": "18.3.1",
"react-router": "6.30.0",
"react-router-dom": "6.30.0",
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
"stremio-translations": "github:Stremio/stremio-translations#8212fa77c4febd22ddb611590e9fb574dc845416", "stremio-translations": "github:Stremio/stremio-translations#8212fa77c4febd22ddb611590e9fb574dc845416",
"url": "0.11.4", "url": "0.11.4",

View file

@ -5,25 +5,19 @@ const React = require('react');
const { useTranslation } = require('react-i18next'); const { useTranslation } = require('react-i18next');
const { Router } = require('stremio-router'); const { Router } = require('stremio-router');
const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider } = require('stremio/services'); const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider } = require('stremio/services');
const { NotFound } = require('stremio/routes');
const { FileDropProvider, PlatformProvider, ToastProvider, TooltipProvider, CONSTANTS, withCoreSuspender, useShell } = require('stremio/common'); const { FileDropProvider, PlatformProvider, ToastProvider, TooltipProvider, CONSTANTS, withCoreSuspender, useShell } = 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');
const { default: UpdaterBanner } = require('./UpdaterBanner'); const { default: UpdaterBanner } = require('./UpdaterBanner');
const ErrorDialog = require('./ErrorDialog'); const ErrorDialog = require('./ErrorDialog');
const withProtectedRoutes = require('./withProtectedRoutes');
const routerViewsConfig = require('./routerViewsConfig');
const styles = require('./styles'); const styles = require('./styles');
const RouterWithProtectedRoutes = withCoreSuspender(withProtectedRoutes(Router)); const RouterWithProtectedRoutes = withCoreSuspender(Router);
const App = () => { const App = () => {
const { i18n } = useTranslation(); const { i18n } = useTranslation();
const shell = useShell(); const shell = useShell();
const onPathNotMatch = React.useCallback(() => {
return NotFound;
}, []);
const services = React.useMemo(() => { const services = React.useMemo(() => {
const core = new Core({ const core = new Core({
appVersion: process.env.VERSION, appVersion: process.env.VERSION,
@ -207,11 +201,7 @@ const App = () => {
<DeepLinkHandler /> <DeepLinkHandler />
<SearchParamsHandler /> <SearchParamsHandler />
<UpdaterBanner className={styles['updater-banner-container']} /> <UpdaterBanner className={styles['updater-banner-container']} />
<RouterWithProtectedRoutes <RouterWithProtectedRoutes className={styles['router']} />
className={styles['router']}
viewsConfig={routerViewsConfig}
onPathNotMatch={onPathNotMatch}
/>
</FileDropProvider> </FileDropProvider>
</TooltipProvider> </TooltipProvider>
</ToastProvider> </ToastProvider>

View file

@ -1,29 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
const { Intro } = require('stremio/routes');
const { useProfile } = require('stremio/common');
const withProtectedRoutes = (Component) => {
return function withProtectedRoutes(props) {
const profile = useProfile();
const previousAuthRef = React.useRef(profile.auth);
React.useEffect(() => {
if (previousAuthRef.current !== null && profile.auth === null) {
window.location = '#/intro';
}
previousAuthRef.current = profile.auth;
}, [profile]);
const onRouteChange = React.useCallback((routeConfig) => {
if (profile.auth !== null && routeConfig.component === Intro) {
window.location.replace('#/');
return true;
}
}, [profile]);
return (
<Component {...props} onRouteChange={onRouteChange} />
);
};
};
module.exports = withProtectedRoutes;

View file

@ -20,6 +20,7 @@ const useModelState = require('./useModelState');
const useNotifications = require('./useNotifications'); const useNotifications = require('./useNotifications');
const useOnScrollToBottom = require('./useOnScrollToBottom'); const useOnScrollToBottom = require('./useOnScrollToBottom');
const useProfile = require('./useProfile'); const useProfile = require('./useProfile');
const { default: useRouteFocused } = require('./useRouteFocused');
const { default: useSettings } = require('./useSettings'); const { default: useSettings } = require('./useSettings');
const { default: useShell } = require('./useShell'); const { default: useShell } = require('./useShell');
const useStreamingServer = require('./useStreamingServer'); const useStreamingServer = require('./useStreamingServer');
@ -53,6 +54,7 @@ module.exports = {
useNotifications, useNotifications,
useOnScrollToBottom, useOnScrollToBottom,
useProfile, useProfile,
useRouteFocused,
useSettings, useSettings,
useShell, useShell,
useStreamingServer, useStreamingServer,

View file

@ -0,0 +1,59 @@
// Copyright (C) 2017-2025 Smart code 203358507
import React from 'react';
import routes from 'stremio/routes';
export const routerPaths = [
{
path: '/intro',
element: <routes.Intro />,
},
{
path: '/discover/:transportUrl?/:type?/:catalogId?',
element: <routes.Discover />,
},
{
path: '/library/:type?',
element: <routes.Library />,
},
{
path: '/calendar/:year?/:month?',
element: <routes.Calendar />,
},
{
path: '/continuewatching/:type?',
element: <routes.Library />,
},
{
path: '/search',
element: <routes.Search />,
},
{
path: '/metadetails/:type?/:id?/:videoId?',
element: <routes.MetaDetails />,
},
{
path: '/detail/:type?/:id?/:videoId?',
element: <routes.MetaDetails />,
},
{
path: '/addons/:type?/:transportUrl?/:catalogId?',
element: <routes.Addons />,
},
{
path: '/settings',
element: <routes.Settings />,
},
{
path: '/player/:stream?/:streamTransportUrl?/:metaTransportUrl?/:type?/:id?/:videoId?',
element: <routes.Player />,
},
{
path: '/',
element: <routes.Board />,
},
{
path: '*',
element: <routes.NotFound />,
},
];

View file

@ -5,7 +5,7 @@ const throttle = require('lodash.throttle');
const isEqual = require('lodash.isequal'); const isEqual = require('lodash.isequal');
const intersection = require('lodash.intersection'); const intersection = require('lodash.intersection');
const { useCoreSuspender } = require('stremio/common/CoreSuspender'); const { useCoreSuspender } = require('stremio/common/CoreSuspender');
const { useRouteFocused } = require('stremio-router'); const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { useServices } = require('stremio/services'); const { useServices } = require('stremio/services');
const useModelState = ({ action, ...args }) => { const useModelState = ({ action, ...args }) => {
@ -32,17 +32,17 @@ const useModelState = ({ action, ...args }) => {
} }
} }
); );
React.useInsertionEffect(() => { React.useLayoutEffect(() => {
if (action) { if (action) {
core.transport.dispatch(action, model); core.transport.dispatch(action, model);
} }
}, [action]); }, [action]);
React.useInsertionEffect(() => { React.useLayoutEffect(() => {
return () => { return () => {
core.transport.dispatch({ action: 'Unload' }, model); core.transport.dispatch({ action: 'Unload' }, model);
}; };
}, []); }, []);
React.useInsertionEffect(() => { React.useLayoutEffect(() => {
const onNewState = async (models) => { const onNewState = async (models) => {
if (models.indexOf(model) === -1 && (!Array.isArray(deps) || intersection(deps, models).length === 0)) { if (models.indexOf(model) === -1 && (!Array.isArray(deps) || intersection(deps, models).length === 0)) {
return; return;
@ -67,7 +67,7 @@ const useModelState = ({ action, ...args }) => {
core.transport.off('NewState', onNewStateThrottled); core.transport.off('NewState', onNewStateThrottled);
}; };
}, [routeFocused]); }, [routeFocused]);
React.useInsertionEffect(() => { React.useLayoutEffect(() => {
mountedRef.current = true; mountedRef.current = true;
}, []); }, []);
return state; return state;

View file

@ -0,0 +1,24 @@
// Copyright (C) 2017-2025 Smart code 203358507
import React from 'react';
const useRouteFocused = () => {
const [isFocused, setIsFocused] = React.useState(document.hasFocus());
React.useEffect(() => {
const handleFocus = () => setIsFocused(true);
const handleBlur = () => setIsFocused(false);
window.addEventListener('focus', handleFocus);
window.addEventListener('blur', handleBlur);
return () => {
window.removeEventListener('focus', handleFocus);
window.removeEventListener('blur', handleBlur);
};
}, []);
return isFocused;
};
export default useRouteFocused;

View file

@ -1,23 +1,28 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const { useNavigate } = require('react-router');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const { useServices } = require('stremio/services'); const { useServices } = require('stremio/services');
const LibItem = require('stremio/components/LibItem'); const LibItem = require('stremio/components/LibItem');
const ContinueWatchingItem = ({ _id, notifications, deepLinks, ...props }) => { const ContinueWatchingItem = ({ _id, notifications, deepLinks, ...props }) => {
const { core } = useServices(); const { core } = useServices();
const navigate = useNavigate();
const onClick = React.useCallback(() => { const onClick = React.useCallback(() => {
if (deepLinks?.metaDetailsVideos ?? deepLinks?.metaDetailsStreams) { if (deepLinks?.metaDetailsVideos ?? deepLinks?.metaDetailsStreams) {
window.location = deepLinks?.metaDetailsVideos ?? deepLinks?.metaDetailsStreams; // TODO - remove # from deeplinks in core if possible
const navigateTo = deepLinks?.metaDetailsVideos ?? deepLinks?.metaDetailsStreams;
navigate(navigateTo.replace('#', ''));
} }
}, [deepLinks]); }, [deepLinks]);
const onPlayClick = React.useCallback((event) => { const onPlayClick = React.useCallback((event) => {
event.stopPropagation(); event.stopPropagation();
if (deepLinks?.player ?? deepLinks?.metaDetailsStreams ?? deepLinks?.metaDetailsVideos) { if (deepLinks?.player ?? deepLinks?.metaDetailsStreams ?? deepLinks?.metaDetailsVideos) {
window.location = deepLinks?.player ?? deepLinks?.metaDetailsStreams ?? deepLinks?.metaDetailsVideos; const navigateTo = deepLinks?.player ?? deepLinks?.metaDetailsStreams ?? deepLinks?.metaDetailsVideos;
navigate(navigateTo.replace('#', ''));
} }
}, [deepLinks]); }, [deepLinks]);

View file

@ -1,14 +1,15 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const { useNavigate } = require('react-router');
const { useServices } = require('stremio/services'); const { useServices } = require('stremio/services');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const MetaItem = require('stremio/components/MetaItem'); const MetaItem = require('stremio/components/MetaItem');
const { t } = require('i18next'); const { t } = require('i18next');
const LibItem = ({ _id, removable, notifications, watched, ...props }) => { const LibItem = ({ _id, removable, notifications, watched, ...props }) => {
const { core } = useServices(); const { core } = useServices();
const navigate = useNavigate();
const newVideos = React.useMemo(() => { const newVideos = React.useMemo(() => {
const count = notifications.items?.[_id]?.length ?? 0; const count = notifications.items?.[_id]?.length ?? 0;
@ -50,7 +51,8 @@ const LibItem = ({ _id, removable, notifications, watched, ...props }) => {
switch (event.value) { switch (event.value) {
case 'play': { case 'play': {
if (props.deepLinks && typeof props.deepLinks.player === 'string') { if (props.deepLinks && typeof props.deepLinks.player === 'string') {
window.location = props.deepLinks.player; // TODO: remove # from deeplinks in core for web?
navigate(props.deepLinks.player.replace('#', ''));
} }
break; break;
@ -58,9 +60,9 @@ const LibItem = ({ _id, removable, notifications, watched, ...props }) => {
case 'details': { case 'details': {
if (props.deepLinks) { if (props.deepLinks) {
if (typeof props.deepLinks.metaDetailsVideos === 'string') { if (typeof props.deepLinks.metaDetailsVideos === 'string') {
window.location = props.deepLinks.metaDetailsVideos; navigate(props.deepLinks.metaDetailsVideos.replace('#', ''));
} else if (typeof props.deepLinks.metaDetailsStreams === 'string') { } else if (typeof props.deepLinks.metaDetailsStreams === 'string') {
window.location = props.deepLinks.metaDetailsStreams; navigate(props.deepLinks.metaDetailsStreams.replace('#', ''));
} }
} }

View file

@ -6,12 +6,12 @@ import { VerticalNavBar, HorizontalNavBar } from 'stremio/components/NavBar';
import styles from './MainNavBars.less'; import styles from './MainNavBars.less';
const TABS = [ const TABS = [
{ id: 'board', label: 'Board', icon: 'home', href: '#/' }, { id: 'board', label: 'Board', icon: 'home', href: '/' },
{ id: 'discover', label: 'Discover', icon: 'discover', href: '#/discover' }, { id: 'discover', label: 'Discover', icon: 'discover', href: '/discover' },
{ id: 'library', label: 'Library', icon: 'library', href: '#/library' }, { id: 'library', label: 'Library', icon: 'library', href: '/library' },
{ id: 'calendar', label: 'Calendar', icon: 'calendar', href: '#/calendar' }, { id: 'calendar', label: 'Calendar', icon: 'calendar', href: '/calendar' },
{ id: 'addons', label: 'ADDONS', icon: 'addons', href: '#/addons' }, { id: 'addons', label: 'ADDONS', icon: 'addons', href: '/addons' },
{ id: 'settings', label: 'SETTINGS', icon: 'settings', href: '#/settings' }, { id: 'settings', label: 'SETTINGS', icon: 'settings', href: '/settings' },
]; ];
type Props = { type Props = {

View file

@ -4,10 +4,11 @@ const React = require('react');
const { useTranslation } = require('react-i18next'); const { useTranslation } = require('react-i18next');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const { useRouteFocused, useModalsContainer } = require('stremio-router'); const { useModalsContainer } = require('stremio/router/ModalsContainerContext');
const Modal = require('stremio/router/Modal');
const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { default: Button } = require('stremio/components/Button'); const { default: Button } = require('stremio/components/Button');
const { default: Icon } = require('@stremio/stremio-icons/react'); const { default: Icon } = require('@stremio/stremio-icons/react');
const { Modal } = require('stremio-router');
const styles = require('./styles'); const styles = require('./styles');
const ModalDialog = ({ className, title, buttons, children, dataset, onCloseRequest, background, ...props }) => { const ModalDialog = ({ className, title, buttons, children, dataset, onCloseRequest, background, ...props }) => {

View file

@ -1,6 +1,7 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const { useNavigate } = require('react-router');
const PropTypes = require('prop-types'); 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');
@ -13,8 +14,9 @@ const styles = require('./styles');
const { t } = require('i18next'); const { t } = require('i18next');
const HorizontalNavBar = React.memo(({ className, route, query, title, backButton, searchBar, fullscreenButton, navMenu, ...props }) => { const HorizontalNavBar = React.memo(({ className, route, query, title, backButton, searchBar, fullscreenButton, navMenu, ...props }) => {
const navigate = useNavigate();
const backButtonOnClick = React.useCallback(() => { const backButtonOnClick = React.useCallback(() => {
window.history.back(); navigate(-1);
}, []); }, []);
const [fullscreen, requestFullscreen, exitFullscreen] = useFullscreen(); const [fullscreen, requestFullscreen, exitFullscreen] = useFullscreen();
const [isIOSPWA] = usePWA(); const [isIOSPWA] = usePWA();

View file

@ -3,7 +3,7 @@
const React = require('react'); const React = require('react');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const { useRouteFocused } = require('stremio-router'); const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const Popup = require('stremio/components/Popup'); const Popup = require('stremio/components/Popup');
const useBinaryState = require('stremio/common/useBinaryState'); const useBinaryState = require('stremio/common/useBinaryState');
const NavMenuContent = require('./NavMenuContent'); const NavMenuContent = require('./NavMenuContent');

View file

@ -1,6 +1,7 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const { useNavigate } = require('react-router');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const { useTranslation } = require('react-i18next'); const { useTranslation } = require('react-i18next');
@ -17,6 +18,7 @@ const styles = require('./styles');
const NavMenuContent = ({ onClick }) => { const NavMenuContent = ({ onClick }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate();
const { core } = useServices(); const { core } = useServices();
const profile = useProfile(); const profile = useProfile();
const streamingServer = useStreamingServer(); const streamingServer = useStreamingServer();
@ -45,6 +47,12 @@ const NavMenuContent = ({ onClick }) => {
console.error(e); console.error(e);
} }
}, []); }, []);
const handleAuth = React.useCallback(() => {
return profile.auth !== null
? logoutButtonOnClick()
: navigate('/intro');
}, [profile.auth, logoutButtonOnClick, navigate]);
return ( return (
<div className={classnames(styles['nav-menu-container'], 'animation-fade-in', { [styles['with-warning']]: !streamingServerWarningDismissed } )} onClick={onClick}> <div className={classnames(styles['nav-menu-container'], 'animation-fade-in', { [styles['with-warning']]: !streamingServerWarningDismissed } )} onClick={onClick}>
<div className={styles['user-info-container']}> <div className={styles['user-info-container']}>
@ -64,7 +72,7 @@ const NavMenuContent = ({ onClick }) => {
<div className={styles['email-container']}> <div className={styles['email-container']}>
<div className={styles['email-label']}>{profile.auth === null ? t('ANONYMOUS_USER') : profile.auth.user.email}</div> <div className={styles['email-label']}>{profile.auth === null ? t('ANONYMOUS_USER') : profile.auth.user.email}</div>
</div> </div>
<Button className={styles['logout-button-container']} title={profile.auth === null ? `${t('LOG_IN')} / ${t('SIGN_UP')}` : t('LOG_OUT')} href={profile.auth === null ? '#/intro' : null} onClick={profile.auth !== null ? logoutButtonOnClick : null}> <Button className={styles['logout-button-container']} title={profile.auth === null ? `${t('LOG_IN')} / ${t('SIGN_UP')}` : t('LOG_OUT')} onClick={handleAuth}>
<div className={styles['logout-label']}>{profile.auth === null ? `${t('LOG_IN')} / ${t('SIGN_UP')}` : t('LOG_OUT')}</div> <div className={styles['logout-label']}>{profile.auth === null ? `${t('LOG_IN')} / ${t('SIGN_UP')}` : t('LOG_OUT')}</div>
</Button> </Button>
</div> </div>

View file

@ -1,12 +1,14 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const { useNavigate } = require('react-router');
const { useSearchParams } = require('react-router-dom');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const debounce = require('lodash.debounce'); const debounce = require('lodash.debounce');
const { useTranslation } = require('react-i18next'); const { useTranslation } = require('react-i18next');
const { default: Icon } = require('@stremio/stremio-icons/react'); const { default: Icon } = require('@stremio/stremio-icons/react');
const { useRouteFocused } = require('stremio-router'); const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const Button = require('stremio/components/Button').default; const Button = require('stremio/components/Button').default;
const TextInput = require('stremio/components/TextInput').default; const TextInput = require('stremio/components/TextInput').default;
const useTorrent = require('stremio/common/useTorrent'); const useTorrent = require('stremio/common/useTorrent');
@ -22,16 +24,17 @@ const SearchBar = React.memo(({ className, query, active }) => {
const searchHistory = useSearchHistory(); const searchHistory = useSearchHistory();
const localSearch = useLocalSearch(); const localSearch = useLocalSearch();
const { createTorrentFromMagnet } = useTorrent(); const { createTorrentFromMagnet } = useTorrent();
const navigate = useNavigate();
const [historyOpen, openHistory, closeHistory, ] = useBinaryState(query === null ? true : false); const [historyOpen, openHistory, closeHistory, ] = useBinaryState(query === null ? true : false);
const [currentQuery, setCurrentQuery] = React.useState(query || ''); const [currentQuery, setCurrentQuery] = React.useState(query || '');
const [, setSearchParams] = useSearchParams();
const searchInputRef = React.useRef(null); const searchInputRef = React.useRef(null);
const containerRef = React.useRef(null); const containerRef = React.useRef(null);
const searchBarOnClick = React.useCallback(() => { const searchBarOnClick = React.useCallback(() => {
if (!active) { if (!active) {
window.location = '#/search'; navigate('/search');
} }
}, [active]); }, [active]);
@ -64,7 +67,7 @@ const SearchBar = React.memo(({ className, query, active }) => {
const searchValue = `/search?search=${encodeURIComponent(event.target.value)}`; const searchValue = `/search?search=${encodeURIComponent(event.target.value)}`;
setCurrentQuery(searchValue); setCurrentQuery(searchValue);
if (searchInputRef.current && searchValue) { if (searchInputRef.current && searchValue) {
window.location.hash = searchValue; setSearchParams({ search: event.target.value });
closeHistory(); closeHistory();
} }
}, []); }, []);
@ -72,7 +75,7 @@ const SearchBar = React.memo(({ className, query, active }) => {
const queryInputClear = React.useCallback(() => { const queryInputClear = React.useCallback(() => {
searchInputRef.current.value = ''; searchInputRef.current.value = '';
setCurrentQuery(''); setCurrentQuery('');
window.location.hash = '/search'; navigate('/search', { search: '' });
}, []); }, []);
const updateLocalSearchDebounced = React.useCallback(debounce((query) => { const updateLocalSearchDebounced = React.useCallback(debounce((query) => {

View file

@ -4,8 +4,9 @@ const React = require('react');
const PropTypes = require('prop-types'); 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 { Image } = require('stremio/components');
const styles = require('./styles'); const styles = require('./styles');
const { Link } = require('react-router-dom');
const NavTabButton = ({ className, logo, icon, label, href, selected, onClick }) => { const NavTabButton = ({ className, logo, icon, label, href, selected, onClick }) => {
const renderLogoFallback = React.useCallback(() => ( const renderLogoFallback = React.useCallback(() => (
@ -24,7 +25,7 @@ const NavTabButton = ({ className, logo, icon, label, href, selected, onClick })
}); });
}; };
return ( return (
<Button className={classnames(className, styles['nav-tab-button-container'], { 'selected': selected })} title={label} tabIndex={-1} href={href} onClick={onClick} onDoubleClick={onDoubleClick}> <Link className={classnames(className, styles['nav-tab-button-container'], { 'selected': selected })} title={label} tabIndex={-1} to={href} onClick={onClick} onDoubleClick={onDoubleClick}>
{ {
typeof logo === 'string' && logo.length > 0 ? typeof logo === 'string' && logo.length > 0 ?
<Image <Image
@ -45,7 +46,7 @@ const NavTabButton = ({ className, logo, icon, label, href, selected, onClick })
: :
null null
} }
</Button> </Link>
); );
}; };

View file

@ -4,7 +4,7 @@ const React = require('react');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const FocusLock = require('react-focus-lock').default; const FocusLock = require('react-focus-lock').default;
const { useRouteFocused } = require('stremio-router'); const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const styles = require('./styles'); const styles = require('./styles');
const getAnchorElement = (element) => { const getAnchorElement = (element) => {

View file

@ -5,7 +5,7 @@ const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const { useTranslation } = require('react-i18next'); const { useTranslation } = require('react-i18next');
const { default: Icon } = require('@stremio/stremio-icons/react'); const { default: Icon } = require('@stremio/stremio-icons/react');
const { useRouteFocused } = require('stremio-router'); const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { useServices } = require('stremio/services'); const { useServices } = require('stremio/services');
const { Button } = require('stremio/components'); const { Button } = require('stremio/components');
const { default: TextInput } = require('stremio/components/TextInput'); const { default: TextInput } = require('stremio/components/TextInput');

View file

@ -3,7 +3,7 @@
const React = require('react'); const React = require('react');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const { useRouteFocused } = require('stremio-router'); const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const useAnimationFrame = require('stremio/common/useAnimationFrame'); const useAnimationFrame = require('stremio/common/useAnimationFrame');
const useLiveRef = require('stremio/common/useLiveRef'); const useLiveRef = require('stremio/common/useLiveRef');
const styles = require('./styles'); const styles = require('./styles');

View file

@ -1,10 +1,11 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const { useTranslation } = require('react-i18next'); const { useNavigate } = require('react-router');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const { useRouteFocused } = require('stremio-router'); const { t } = require('i18next');
const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { default: Icon } = require('@stremio/stremio-icons/react'); const { default: Icon } = require('@stremio/stremio-icons/react');
const { Button, Image, Popup } = require('stremio/components'); const { Button, Image, Popup } = require('stremio/components');
const useBinaryState = require('stremio/common/useBinaryState'); const useBinaryState = require('stremio/common/useBinaryState');
@ -13,9 +14,9 @@ const VideoPlaceholder = require('./VideoPlaceholder');
const styles = require('./styles'); const styles = require('./styles');
const Video = ({ className, id, title, thumbnail, season, episode, released, upcoming, watched, progress, scheduled, seasonWatched, deepLinks, onMarkVideoAsWatched, onMarkSeasonAsWatched, ...props }) => { const Video = ({ className, id, title, thumbnail, season, episode, released, upcoming, watched, progress, scheduled, seasonWatched, deepLinks, onMarkVideoAsWatched, onMarkSeasonAsWatched, ...props }) => {
const { t } = useTranslation();
const routeFocused = useRouteFocused(); const routeFocused = useRouteFocused();
const profile = useProfile(); const profile = useProfile();
const navigate = useNavigate();
const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false); const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false);
const popupLabelOnMouseUp = React.useCallback((event) => { const popupLabelOnMouseUp = React.useCallback((event) => {
if (!event.nativeEvent.togglePopupPrevented) { if (!event.nativeEvent.togglePopupPrevented) {
@ -62,9 +63,10 @@ const Video = ({ className, id, title, thumbnail, season, episode, released, upc
const videoButtonOnClick = React.useCallback(() => { const videoButtonOnClick = React.useCallback(() => {
if (deepLinks) { if (deepLinks) {
if (typeof deepLinks.player === 'string') { if (typeof deepLinks.player === 'string') {
window.location = deepLinks.player; // TODO: remove # from deeplinks in core
navigate(deepLinks.player.replace('#', ''));
} else if (typeof deepLinks.metaDetailsStreams === 'string') { } else if (typeof deepLinks.metaDetailsStreams === 'string') {
window.location.replace(deepLinks.metaDetailsStreams); navigate(deepLinks.metaDetailsStreams.replace('#', ''), { replace: true });
} }
} }
}, [deepLinks]); }, [deepLinks]);

View file

@ -4,12 +4,12 @@ const React = require('react');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const { ModalsContainerProvider } = require('../ModalsContainerContext'); const { ModalsContainerProvider } = require('../ModalsContainerContext');
const Route = ({ children }) => { const Route = ({ component }) => {
return ( return (
<div className={'route-container'}> <div className={'route-container'}>
<ModalsContainerProvider> <ModalsContainerProvider>
<div className={'route-content'}> <div className={'route-content'}>
{children} {component}
</div> </div>
</ModalsContainerProvider> </ModalsContainerProvider>
</div> </div>
@ -17,7 +17,7 @@ const Route = ({ children }) => {
}; };
Route.propTypes = { Route.propTypes = {
children: PropTypes.node component: PropTypes.node
}; };
module.exports = Route; module.exports = Route;

View file

@ -1,9 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
const RouteFocusedContext = React.createContext(true);
RouteFocusedContext.displayName = 'RouteFocusedContext';
module.exports = RouteFocusedContext;

View file

@ -1,9 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const RouteFocusedContext = require('./RouteFocusedContext');
const useRouteFocused = require('./useRouteFocused');
module.exports = {
RouteFocusedProvider: RouteFocusedContext.Provider,
useRouteFocused
};

View file

@ -1,10 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
const RouteFocusedContext = require('./RouteFocusedContext');
const useRouteFocused = () => {
return React.useContext(RouteFocusedContext);
};
module.exports = useRouteFocused;

View file

@ -1,107 +1,24 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const ReactIs = require('react-is'); const { HashRouter } = require('react-router-dom');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const UrlUtils = require('url'); const { default: Routes } = require('./Routes');
const isEqual = require('lodash.isequal');
const { RouteFocusedProvider } = require('../RouteFocusedContext');
const Route = require('../Route');
const routeConfigForPath = require('./routeConfigForPath');
const urlParamsForPath = require('./urlParamsForPath');
const Router = ({ className, onPathNotMatch, onRouteChange, ...props }) => { const Router = ({ className }) => {
const viewsConfig = React.useMemo(() => props.viewsConfig, []);
const [views, setViews] = React.useState(() => {
return Array(viewsConfig.length).fill(null);
});
React.useLayoutEffect(() => {
const onLocationHashChange = () => {
const { pathname, query } = UrlUtils.parse(window.location.hash.slice(1));
const queryParams = new URLSearchParams(typeof query === 'string' ? query : '');
const routeConfig = routeConfigForPath(viewsConfig, typeof pathname === 'string' ? pathname : '');
if (routeConfig === null) {
if (typeof onPathNotMatch === 'function') {
const component = onPathNotMatch();
if (ReactIs.isValidElementType(component)) {
setViews((views) => {
return views
.slice(0, viewsConfig.length)
.concat({
key: '-1',
component
});
});
}
}
return;
}
const urlParams = urlParamsForPath(routeConfig, typeof pathname === 'string' ? pathname : '');
const routeViewIndex = viewsConfig.findIndex((vc) => vc.includes(routeConfig));
const routeIndex = viewsConfig[routeViewIndex].findIndex((rc) => rc === routeConfig);
const handled = typeof onRouteChange === 'function' && onRouteChange(routeConfig, urlParams, queryParams);
if (!handled) {
setViews((views) => {
return views
.slice(0, viewsConfig.length)
.map((view, index) => {
if (index < routeViewIndex) {
return view;
} else if (index === routeViewIndex) {
return {
key: `${routeViewIndex}${routeIndex}`,
component: routeConfig.component,
urlParams: view !== null && isEqual(view.urlParams, urlParams) ?
view.urlParams
:
urlParams,
queryParams: view !== null && isEqual(Array.from(view.queryParams.entries()), Array.from(queryParams.entries())) ?
view.queryParams
:
queryParams
};
} else {
return null;
}
});
});
}
};
window.addEventListener('hashchange', onLocationHashChange);
onLocationHashChange();
return () => {
window.removeEventListener('hashchange', onLocationHashChange);
};
}, [onPathNotMatch, onRouteChange]);
return ( return (
<div className={classnames(className, 'routes-container')}> <div className={classnames(className, 'routes-container')}>
{ <HashRouter>
views <Routes />
.filter((view) => view !== null) </HashRouter>
.map(({ key, component, urlParams, queryParams }, index, views) => (
<RouteFocusedProvider key={key} value={index === views.length - 1}>
<Route>
{React.createElement(component, { urlParams, queryParams })}
</Route>
</RouteFocusedProvider>
))
}
</div> </div>
); );
}; };
Router.propTypes = { Router.propTypes = {
className: PropTypes.string, className: PropTypes.string,
onPathNotMatch: PropTypes.func,
onRouteChange: PropTypes.func,
viewsConfig: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.exact({
regexp: PropTypes.instanceOf(RegExp).isRequired,
urlParamsNames: PropTypes.arrayOf(PropTypes.string).isRequired,
component: PropTypes.elementType.isRequired
}))).isRequired
}; };
module.exports = Router; module.exports = Router;

View file

@ -0,0 +1,41 @@
// Copyright (C) 2017-2025 Smart code 203358507
import React from 'react';
import { Routes as RRoutes, Route as RRoute, useLocation, useNavigate } from 'react-router';
import { routerPaths } from 'stremio/common/routerPaths';
import Route from '../Route/Route';
import { useProfile } from 'stremio/common';
const Routes = () => {
const location = useLocation();
const navigate = useNavigate();
const profile = useProfile();
const previousAuthRef = React.useRef(profile.auth);
/**
* Replaced onRouteChange with following useEffect:
*/
React.useEffect(() => {
// Handle redirect if user logs out
if (previousAuthRef.current !== null && profile.auth === null) {
previousAuthRef.current = profile.auth;
navigate('/intro', { replace: true });
}
// Handle redirect if user is logged in on intro screen
if (profile.auth !== null && location.pathname === '/intro') {
navigate('/', { replace: true });
}
previousAuthRef.current = profile.auth;
}, [location, profile.auth, navigate, previousAuthRef.current]);
const routes = routerPaths.map((route) =>
<RRoute key={route.path} path={route.path} element={<Route component={route.element} />} />
);
return <RRoutes location={location}>
{routes}
</RRoutes>;
};
export default Routes;

View file

@ -1,12 +1,10 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const { useRouteFocused } = require('./RouteFocusedContext');
const { useModalsContainer } = require('./ModalsContainerContext'); const { useModalsContainer } = require('./ModalsContainerContext');
const Modal = require('./Modal'); const Modal = require('./Modal');
const Router = require('./Router'); const Router = require('./Router');
module.exports = { module.exports = {
useRouteFocused,
useModalsContainer, useModalsContainer,
Modal, Modal,
Router Router

View file

@ -1,7 +1,8 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const PropTypes = require('prop-types'); const { useParams } = require('react-router');
const { useSearchParams } = require('react-router-dom');
const classnames = require('classnames'); const classnames = require('classnames');
const { useTranslation } = require('react-i18next'); const { useTranslation } = require('react-i18next');
const { default: Icon } = require('@stremio/stremio-icons/react'); const { default: Icon } = require('@stremio/stremio-icons/react');
@ -16,13 +17,15 @@ const useSelectableInputs = require('./useSelectableInputs');
const styles = require('./styles'); const styles = require('./styles');
const { AddonPlaceholder } = require('./AddonPlaceholder'); const { AddonPlaceholder } = require('./AddonPlaceholder');
const Addons = ({ urlParams, queryParams }) => { const Addons = () => {
const urlParams = useParams();
const [queryParams] = useSearchParams();
const { t } = useTranslation(); const { t } = useTranslation();
const platform = usePlatform(); const platform = usePlatform();
const { core } = useServices(); const { core } = useServices();
const installedAddons = useInstalledAddons(urlParams); const installedAddons = useInstalledAddons(urlParams);
const remoteAddons = useRemoteAddons(urlParams); const remoteAddons = useRemoteAddons(urlParams);
const [addonDetailsTransportUrl, setAddonDetailsTransportUrl] = useAddonDetailsTransportUrl(urlParams, queryParams); const [addonDetailsTransportUrl, setAddonDetailsTransportUrl] = useAddonDetailsTransportUrl(urlParams);
const selectInputs = useSelectableInputs(installedAddons, remoteAddons); const selectInputs = useSelectableInputs(installedAddons, remoteAddons);
const [filtersModalOpen, openFiltersModal, closeFiltersModal] = useBinaryState(false); const [filtersModalOpen, openFiltersModal, closeFiltersModal] = useBinaryState(false);
const [addAddonModalOpen, openAddAddonModal, closeAddAddonModal] = useBinaryState(false); const [addAddonModalOpen, openAddAddonModal, closeAddAddonModal] = useBinaryState(false);
@ -292,16 +295,6 @@ const Addons = ({ urlParams, queryParams }) => {
); );
}; };
Addons.propTypes = {
urlParams: PropTypes.shape({
path: PropTypes.string,
transportUrl: PropTypes.string,
catalogId: PropTypes.string,
type: PropTypes.string
}),
queryParams: PropTypes.instanceOf(URLSearchParams)
};
const AddonsFallback = () => ( const AddonsFallback = () => (
<MainNavBars className={styles['addons-container']} route={'addons'} /> <MainNavBars className={styles['addons-container']} route={'addons'} />
); );

View file

@ -1,8 +1,10 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const { useSearchParams } = require('react-router-dom');
const useAddonDetailsTransportUrl = (urlParams, queryParams) => { const useAddonDetailsTransportUrl = (urlParams) => {
const [queryParams, setQueryParams] = useSearchParams();
const transportUrl = React.useMemo(() => { const transportUrl = React.useMemo(() => {
return queryParams.get('addon'); return queryParams.get('addon');
}, [queryParams]); }, [queryParams]);
@ -14,7 +16,7 @@ const useAddonDetailsTransportUrl = (urlParams, queryParams) => {
nextQueryParams.delete('addon'); nextQueryParams.delete('addon');
} }
window.location.replace(`#${urlParams.path}?${nextQueryParams}`); setQueryParams(nextQueryParams);
}, [urlParams, queryParams]); }, [urlParams, queryParams]);
return [transportUrl, setTransportUrl]; return [transportUrl, setTransportUrl];
}; };

View file

@ -1,9 +1,10 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const { useNavigate } = require('react-router');
const { useTranslate } = require('stremio/common'); const { useTranslate } = require('stremio/common');
const mapSelectableInputs = (installedAddons, remoteAddons, t) => { const mapSelectableInputs = (installedAddons, remoteAddons, t, navigate) => {
const selectedCatalog = remoteAddons.selectable.catalogs.concat(installedAddons.selectable.catalogs).find(({ selected }) => selected); const selectedCatalog = remoteAddons.selectable.catalogs.concat(installedAddons.selectable.catalogs).find(({ selected }) => selected);
const catalogSelect = { const catalogSelect = {
options: remoteAddons.selectable.catalogs options: remoteAddons.selectable.catalogs
@ -20,9 +21,10 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => {
.find(({ id }) => id === remoteAddons.selected.request.path.id); .find(({ id }) => id === remoteAddons.selected.request.path.id);
return selectableCatalog ? t.stringWithPrefix(selectableCatalog.name, 'ADDON_') : remoteAddons.selected.request.path.id; return selectableCatalog ? t.stringWithPrefix(selectableCatalog.name, 'ADDON_') : remoteAddons.selected.request.path.id;
} }
: null, :
null,
onSelect: (value) => { onSelect: (value) => {
window.location = value; navigate(value.replace('#', ''));
} }
}; };
const selectedType = installedAddons.selected !== null const selectedType = installedAddons.selected !== null
@ -53,7 +55,7 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => {
typeSelect.title; typeSelect.title;
}, },
onSelect: (value) => { onSelect: (value) => {
window.location = value; navigate(value.replace('#', ''));
} }
}; };
return [catalogSelect, typeSelect]; return [catalogSelect, typeSelect];
@ -61,8 +63,9 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => {
const useSelectableInputs = (installedAddons, remoteAddons) => { const useSelectableInputs = (installedAddons, remoteAddons) => {
const t = useTranslate(); const t = useTranslate();
const navigate = useNavigate();
const selectableInputs = React.useMemo(() => { const selectableInputs = React.useMemo(() => {
return mapSelectableInputs(installedAddons, remoteAddons, t); return mapSelectableInputs(installedAddons, remoteAddons, t, navigate);
}, [installedAddons, remoteAddons]); }, [installedAddons, remoteAddons]);
return selectableInputs; return selectableInputs;
}; };

View file

@ -1,6 +1,7 @@
// Copyright (C) 2017-2024 Smart code 203358507 // Copyright (C) 2017-2024 Smart code 203358507
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { useParams } from 'react-router';
import { useProfile, withCoreSuspender } from 'stremio/common'; import { useProfile, withCoreSuspender } from 'stremio/common';
import { MainNavBars, BottomSheet } from 'stremio/components'; import { MainNavBars, BottomSheet } from 'stremio/components';
import Selector from './Selector'; import Selector from './Selector';
@ -13,11 +14,8 @@ import useCalendarDate from './useCalendarDate';
import styles from './Calendar.less'; import styles from './Calendar.less';
import classNames from 'classnames'; import classNames from 'classnames';
type Props = { const Calendar = () => {
urlParams: UrlParams, const urlParams = useParams();
};
const Calendar = ({ urlParams }: Props) => {
const calendar = useCalendar(urlParams); const calendar = useCalendar(urlParams);
const profile = useProfile(); const profile = useProfile();

View file

@ -1,6 +1,7 @@
// Copyright (C) 2017-2024 Smart code 203358507 // Copyright (C) 2017-2024 Smart code 203358507
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router';
import Icon from '@stremio/stremio-icons/react'; import Icon from '@stremio/stremio-icons/react';
import { Button } from 'stremio/components'; import { Button } from 'stremio/components';
import useCalendarDate from '../useCalendarDate'; import useCalendarDate from '../useCalendarDate';
@ -14,17 +15,18 @@ type Props = {
const Selector = ({ selected, selectable, profile }: Props) => { const Selector = ({ selected, selectable, profile }: Props) => {
const { toMonth } = useCalendarDate(profile); const { toMonth } = useCalendarDate(profile);
const navigate = useNavigate();
const [prev, next] = useMemo(() => ( const [prev, next] = useMemo(() => (
[selectable.prev, selectable.next] [selectable.prev, selectable.next]
), [selectable]); ), [selectable]);
const onPrev = useCallback(() => { const onPrev = useCallback(() => {
window.location.href = prev.deepLinks.calendar; navigate(prev.deepLinks.calendar.replace('#', ''));
}, [prev]); }, [prev]);
const onNext = useCallback(() => { const onNext = useCallback(() => {
window.location.href = next.deepLinks.calendar; navigate(next.deepLinks.calendar.replace('#', ''));
}, [next]); }, [next]);
return ( return (

View file

@ -1,9 +1,10 @@
// Copyright (C) 2017-2024 Smart code 203358507 // Copyright (C) 2017-2024 Smart code 203358507
import React from 'react'; import React from 'react';
import { Params } from 'react-router';
import { useModelState } from 'stremio/common'; import { useModelState } from 'stremio/common';
const useCalendar = (urlParams: UrlParams) => { const useCalendar = (urlParams: Readonly<Params<string>>) => {
const action = React.useMemo(() => { const action = React.useMemo(() => {
const args = urlParams.year && urlParams.month ? { const args = urlParams.year && urlParams.month ? {
year: parseInt(urlParams.year), year: parseInt(urlParams.year),

View file

@ -2,7 +2,8 @@
const React = require('react'); const React = require('react');
const { useTranslation } = require('react-i18next'); const { useTranslation } = require('react-i18next');
const PropTypes = require('prop-types'); const { useParams } = require('react-router');
const { useSearchParams } = require('react-router-dom');
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 { useServices } = require('stremio/services'); const { useServices } = require('stremio/services');
@ -14,7 +15,9 @@ const styles = require('./styles');
const SCROLL_TO_BOTTOM_THRESHOLD = 400; const SCROLL_TO_BOTTOM_THRESHOLD = 400;
const Discover = ({ urlParams, queryParams }) => { const Discover = () => {
const urlParams = useParams();
const [queryParams] = useSearchParams();
const { t } = useTranslation(); const { t } = useTranslation();
const { core } = useServices(); const { core } = useServices();
const [discover, loadNextPage] = useDiscover(urlParams, queryParams); const [discover, loadNextPage] = useDiscover(urlParams, queryParams);
@ -228,15 +231,6 @@ const Discover = ({ urlParams, queryParams }) => {
); );
}; };
Discover.propTypes = {
urlParams: PropTypes.shape({
transportUrl: PropTypes.string,
type: PropTypes.string,
catalogId: PropTypes.string
}),
queryParams: PropTypes.instanceOf(URLSearchParams)
};
const DiscoverFallback = () => ( const DiscoverFallback = () => (
<MainNavBars className={styles['discover-container']} route={'discover'} /> <MainNavBars className={styles['discover-container']} route={'discover'} />
); );

View file

@ -1,9 +1,10 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const { useNavigate } = require('react-router');
const { useTranslate } = require('stremio/common'); const { useTranslate } = require('stremio/common');
const mapSelectableInputs = (discover, t) => { const mapSelectableInputs = (discover, t, navigate) => {
const selectedType = discover.selectable.types.find(({ selected }) => selected); const selectedType = discover.selectable.types.find(({ selected }) => selected);
const typeSelect = { const typeSelect = {
options: discover.selectable.types options: discover.selectable.types
@ -18,7 +19,7 @@ const mapSelectableInputs = (discover, t) => {
? () => t.stringWithPrefix(discover.selected.request.path.type, 'TYPE_') ? () => t.stringWithPrefix(discover.selected.request.path.type, 'TYPE_')
: t.string('SELECT_TYPE'), : t.string('SELECT_TYPE'),
onSelect: (value) => { onSelect: (value) => {
window.location = value; navigate(value.replace('#', ''));
} }
}; };
const selectedCatalog = discover.selectable.catalogs.find(({ selected }) => selected); const selectedCatalog = discover.selectable.catalogs.find(({ selected }) => selected);
@ -40,8 +41,8 @@ const mapSelectableInputs = (discover, t) => {
} }
: :
t.string('SELECT_CATALOG'), t.string('SELECT_CATALOG'),
onSelect: (value) => { onSelect: (event) => {
window.location =value; navigate(event.value.replace('#', ''));
} }
}; };
const extraSelects = discover.selectable.extra.map(({ name, isRequired, options }) => { const extraSelects = discover.selectable.extra.map(({ name, isRequired, options }) => {
@ -64,7 +65,7 @@ const mapSelectableInputs = (discover, t) => {
: t.string(selectedExtra.value), : t.string(selectedExtra.value),
onSelect: (value) => { onSelect: (value) => {
const { href } = JSON.parse(value); const { href } = JSON.parse(value);
window.location = href; navigate(href.replace('#', ''));
} }
}; };
}); });
@ -73,8 +74,9 @@ const mapSelectableInputs = (discover, t) => {
const useSelectableInputs = (discover) => { const useSelectableInputs = (discover) => {
const t = useTranslate(); const t = useTranslate();
const navigate = useNavigate();
const selectableInputs = React.useMemo(() => { const selectableInputs = React.useMemo(() => {
return mapSelectableInputs(discover, t); return mapSelectableInputs(discover, t, navigate);
}, [discover.selected, discover.selectable]); }, [discover.selected, discover.selectable]);
return selectableInputs; return selectableInputs;
}; };

View file

@ -2,12 +2,13 @@
const React = require('react'); const React = require('react');
const { useTranslation } = require('react-i18next'); const { useTranslation } = require('react-i18next');
const PropTypes = require('prop-types'); const { useSearchParams, useNavigate } = require('react-router-dom');
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 { Modal, useRouteFocused } = require('stremio-router'); const Modal = require('stremio/router/Modal');
const { useServices } = require('stremio/services'); const { useServices } = require('stremio/services');
const { useBinaryState } = require('stremio/common'); const { useBinaryState } = require('stremio/common');
const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { Button, Image, Checkbox } = require('stremio/components'); const { Button, Image, Checkbox } = require('stremio/components');
const CredentialsTextInput = require('./CredentialsTextInput'); const CredentialsTextInput = require('./CredentialsTextInput');
const PasswordResetModal = require('./PasswordResetModal'); const PasswordResetModal = require('./PasswordResetModal');
@ -19,7 +20,9 @@ const styles = require('./styles');
const SIGNUP_FORM = 'signup'; const SIGNUP_FORM = 'signup';
const LOGIN_FORM = 'login'; const LOGIN_FORM = 'login';
const Intro = ({ queryParams }) => { const Intro = () => {
const [queryParams, setQueryParams] = useSearchParams();
const navigate = useNavigate();
const { core } = useServices(); const { core } = useServices();
const { t } = useTranslation(); const { t } = useTranslation();
const routeFocused = useRouteFocused(); const routeFocused = useRouteFocused();
@ -163,7 +166,7 @@ const Intro = ({ queryParams }) => {
dispatch({ type: 'error', error: 'You must accept the Terms of Service' }); dispatch({ type: 'error', error: 'You must accept the Terms of Service' });
return; return;
} }
window.location = '#/'; navigate('/');
}, [state.termsAccepted]); }, [state.termsAccepted]);
const signup = React.useCallback(() => { const signup = React.useCallback(() => {
if (typeof state.email !== 'string' || state.email.length === 0 || !emailRef.current.validity.valid) { if (typeof state.email !== 'string' || state.email.length === 0 || !emailRef.current.validity.valid) {
@ -250,7 +253,7 @@ const Intro = ({ queryParams }) => {
}, []); }, []);
const switchFormOnClick = React.useCallback(() => { const switchFormOnClick = React.useCallback(() => {
const queryParams = new URLSearchParams([['form', state.form === SIGNUP_FORM ? LOGIN_FORM : SIGNUP_FORM]]); const queryParams = new URLSearchParams([['form', state.form === SIGNUP_FORM ? LOGIN_FORM : SIGNUP_FORM]]);
window.location = `#/intro?${queryParams.toString()}`; setQueryParams(queryParams);
}, [state.form]); }, [state.form]);
React.useEffect(() => { React.useEffect(() => {
if ([LOGIN_FORM, SIGNUP_FORM].includes(queryParams.get('form'))) { if ([LOGIN_FORM, SIGNUP_FORM].includes(queryParams.get('form'))) {
@ -273,7 +276,7 @@ const Intro = ({ queryParams }) => {
case 'UserAuthenticated': { case 'UserAuthenticated': {
closeLoaderModal(); closeLoaderModal();
if (routeFocused) { if (routeFocused) {
window.location = '#/'; navigate('/');
} }
break; break;
} }
@ -434,8 +437,4 @@ const Intro = ({ queryParams }) => {
); );
}; };
Intro.propTypes = {
queryParams: PropTypes.instanceOf(URLSearchParams)
};
module.exports = Intro; module.exports = Intro;

View file

@ -3,7 +3,7 @@
const React = require('react'); const React = require('react');
const { useTranslation } = require('react-i18next'); const { useTranslation } = require('react-i18next');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const { useRouteFocused } = require('stremio-router'); const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { usePlatform } = require('stremio/common'); const { usePlatform } = require('stremio/common');
const { ModalDialog } = require('stremio/components'); const { ModalDialog } = require('stremio/components');
const CredentialsTextInput = require('../CredentialsTextInput'); const CredentialsTextInput = require('../CredentialsTextInput');

View file

@ -2,10 +2,12 @@
const React = require('react'); const React = require('react');
const { useTranslation } = require('react-i18next'); const { useTranslation } = require('react-i18next');
const { useLocation, useParams, useNavigate } = require('react-router');
const { useSearchParams } = require('react-router-dom');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const NotFound = require('stremio/routes/NotFound'); const NotFound = require('stremio/routes/NotFound');
const { useProfile, useNotifications, routesRegexp, useOnScrollToBottom, withCoreSuspender } = require('stremio/common'); const { useProfile, useNotifications, useOnScrollToBottom, withCoreSuspender } = require('stremio/common');
const { DelayedRenderer, Chips, Image, MainNavBars, LibItem, MultiselectMenu } = require('stremio/components'); const { DelayedRenderer, Chips, Image, MainNavBars, LibItem, MultiselectMenu } = require('stremio/components');
const { default: Placeholder } = require('./Placeholder'); const { default: Placeholder } = require('./Placeholder');
const useLibrary = require('./useLibrary'); const useLibrary = require('./useLibrary');
@ -14,40 +16,34 @@ const styles = require('./styles');
const SCROLL_TO_BOTTOM_TRESHOLD = 400; const SCROLL_TO_BOTTOM_TRESHOLD = 400;
function withModel(Library) { function withModel(Library, useLocation) {
const withModel = ({ urlParams, queryParams }) => { const withModel = () => {
const location = useLocation();
const model = React.useMemo(() => { const model = React.useMemo(() => {
return typeof urlParams.path === 'string' ? return typeof location.pathname === 'string' ?
urlParams.path.match(routesRegexp.library.regexp) ? location.pathname.match('/library') ?
'library' 'library'
: :
urlParams.path.match(routesRegexp.continuewatching.regexp) ? location.pathname.match('/continuewatching') ?
'continue_watching' 'continue_watching'
: :
null null
: :
null; null;
}, [urlParams.path]); }, [location?.pathname]);
if (model === null) {
return (
<NotFound />
);
}
return ( if (model === null) return <NotFound />;
<Library
key={model} return <Library model={model} />;
model={model}
urlParams={urlParams}
queryParams={queryParams}
/>
);
}; };
withModel.displayName = 'withModel'; withModel.displayName = 'withModel';
return withModel; return withModel;
} }
const Library = ({ model, urlParams, queryParams }) => { const Library = ({ model }) => {
const urlParams = useParams();
const [queryParams] = useSearchParams();
const navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
const profile = useProfile(); const profile = useProfile();
const notifications = useNotifications(); const notifications = useNotifications();
@ -66,8 +62,8 @@ const Library = ({ model, urlParams, queryParams }) => {
} }
}, [profile.auth, library.selected]); }, [profile.auth, library.selected]);
React.useEffect(() => { React.useEffect(() => {
if (!library.selected?.type && typeSelect.value) { if (!library.selected?.type && typeSelect.selected) {
window.location = typeSelect.value; navigate(typeSelect.selected[0].replace('#', ''));
} }
}, [typeSelect.value, library.selected]); }, [typeSelect.value, library.selected]);
return ( return (
@ -120,10 +116,6 @@ const Library = ({ model, urlParams, queryParams }) => {
Library.propTypes = { Library.propTypes = {
model: PropTypes.oneOf(['library', 'continue_watching']), model: PropTypes.oneOf(['library', 'continue_watching']),
urlParams: PropTypes.shape({
type: PropTypes.string
}),
queryParams: PropTypes.instanceOf(URLSearchParams)
}; };
const LibraryFallback = ({ model }) => ( const LibraryFallback = ({ model }) => (
@ -132,4 +124,4 @@ const LibraryFallback = ({ model }) => (
LibraryFallback.propTypes = Library.propTypes; LibraryFallback.propTypes = Library.propTypes;
module.exports = withModel(withCoreSuspender(Library, LibraryFallback)); module.exports = withModel(withCoreSuspender(Library, LibraryFallback), useLocation);

View file

@ -1,8 +1,10 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const { useNavigate } = require('react-router');
const { useTranslate } = require('stremio/common'); const { useTranslate } = require('stremio/common');
const mapSelectableInputs = (library, t) => {
const mapSelectableInputs = (library, t, navigate) => {
const selectedType = library.selectable.types.find(({ selected }) => selected) || library.selectable.types.find(({ type }) => type === null); const selectedType = library.selectable.types.find(({ selected }) => selected) || library.selectable.types.find(({ type }) => type === null);
const typeSelect = { const typeSelect = {
options: library.selectable.types options: library.selectable.types
@ -12,7 +14,7 @@ const mapSelectableInputs = (library, t) => {
})), })),
value: selectedType?.deepLinks.library, value: selectedType?.deepLinks.library,
onSelect: (value) => { onSelect: (value) => {
window.location = value; navigate(value.replace('#', ''));
} }
}; };
const sortChips = { const sortChips = {
@ -25,7 +27,7 @@ const mapSelectableInputs = (library, t) => {
.filter(({ selected }) => selected) .filter(({ selected }) => selected)
.map(({ deepLinks }) => deepLinks.library), .map(({ deepLinks }) => deepLinks.library),
onSelect: (value) => { onSelect: (value) => {
window.location = value; navigate(value.replace('#', ''));
} }
}; };
return [typeSelect, sortChips, library.selectable.nextPage]; return [typeSelect, sortChips, library.selectable.nextPage];
@ -33,8 +35,9 @@ const mapSelectableInputs = (library, t) => {
const useSelectableInputs = (library) => { const useSelectableInputs = (library) => {
const t = useTranslate(); const t = useTranslate();
const navigate = useNavigate();
const selectableInputs = React.useMemo(() => { const selectableInputs = React.useMemo(() => {
return mapSelectableInputs(library, t); return mapSelectableInputs(library, t, navigate);
}, [library]); }, [library]);
return selectableInputs; return selectableInputs;
}; };

View file

@ -1,6 +1,7 @@
// Copyright (C) 2017-2025 Smart code 203358507 // Copyright (C) 2017-2025 Smart code 203358507
import React, { useCallback, useMemo, useState, ChangeEvent } from 'react'; import React, { useCallback, useMemo, useState, ChangeEvent } from 'react';
import { useLocation } from 'react-router';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, NumberInput } from 'stremio/components'; import { Button, NumberInput } from 'stremio/components';
import styles from './EpisodePicker.less'; import styles from './EpisodePicker.less';
@ -13,9 +14,10 @@ type Props = {
const EpisodePicker = ({ className, onSubmit }: Props) => { const EpisodePicker = ({ className, onSubmit }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const location = useLocation();
const { initialSeason, initialEpisode } = useMemo(() => { const { initialSeason, initialEpisode } = useMemo(() => {
const splitPath = window.location.hash.split('/'); const splitPath = location.pathname.split('/');
const videoId = decodeURIComponent(splitPath[splitPath.length - 1]); const videoId = decodeURIComponent(splitPath[splitPath.length - 1]);
const [, pathSeason, pathEpisode] = videoId ? videoId.split(':') : []; const [, pathSeason, pathEpisode] = videoId ? videoId.split(':') : [];
return { return {

View file

@ -1,8 +1,8 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const { useParams, useLocation, useNavigate } = require('react-router');
const { useTranslation } = require('react-i18next'); const { useTranslation } = require('react-i18next');
const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const { useServices } = require('stremio/services'); const { useServices } = require('stremio/services');
const { withCoreSuspender } = require('stremio/common'); const { withCoreSuspender } = require('stremio/common');
@ -14,11 +14,19 @@ const useSeason = require('./useSeason');
const useMetaExtensionTabs = require('./useMetaExtensionTabs'); const useMetaExtensionTabs = require('./useMetaExtensionTabs');
const styles = require('./styles'); const styles = require('./styles');
const MetaDetails = ({ urlParams, queryParams }) => { const MetaDetails = () => {
const { type, id, videoId } = useParams();
const location = useLocation();
const navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
const { core } = useServices(); const { core } = useServices();
const urlParams = React.useMemo(() => ({
type,
id,
videoId
}), [type, id, videoId]);
const metaDetails = useMetaDetails(urlParams); const metaDetails = useMetaDetails(urlParams);
const [season, setSeason] = useSeason(urlParams, queryParams); const [season, setSeason] = useSeason(urlParams);
const [tabs, metaExtension, clearMetaExtension] = useMetaExtensionTabs(metaDetails.metaExtensions); const [tabs, metaExtension, clearMetaExtension] = useMetaExtensionTabs(metaDetails.metaExtensions);
const [metaPath, streamPath] = React.useMemo(() => { const [metaPath, streamPath] = React.useMemo(() => {
return metaDetails.selected !== null ? return metaDetails.selected !== null ?
@ -80,10 +88,10 @@ const MetaDetails = ({ urlParams, queryParams }) => {
}, [setSeason]); }, [setSeason]);
const handleEpisodeSearch = React.useCallback((season, episode) => { const handleEpisodeSearch = React.useCallback((season, episode) => {
const searchVideoHash = encodeURIComponent(`${urlParams.id}:${season}:${episode}`); const searchVideoHash = encodeURIComponent(`${urlParams.id}:${season}:${episode}`);
const url = window.location.hash; const url = location.pathname;
const searchVideoPath = url.replace(encodeURIComponent(urlParams.videoId), searchVideoHash); const searchVideoPath = url.replace(encodeURIComponent(urlParams.videoId), searchVideoHash);
window.location = searchVideoPath; navigate(searchVideoPath);
}, [urlParams, window.location]); }, [urlParams, location]);
const renderBackgroundImageFallback = React.useCallback(() => null, []); const renderBackgroundImageFallback = React.useCallback(() => null, []);
const renderBackground = React.useMemo(() => !!( const renderBackground = React.useMemo(() => !!(
@ -214,15 +222,6 @@ const MetaDetails = ({ urlParams, queryParams }) => {
); );
}; };
MetaDetails.propTypes = {
urlParams: PropTypes.shape({
type: PropTypes.string,
id: PropTypes.string,
videoId: PropTypes.string
}),
queryParams: PropTypes.instanceOf(URLSearchParams)
};
const MetaDetailsFallback = () => ( const MetaDetailsFallback = () => (
<div className={styles['metadetails-container']}> <div className={styles['metadetails-container']}>
<HorizontalNavBar <HorizontalNavBar

View file

@ -8,7 +8,7 @@ const { t } = require('i18next');
const { useProfile, usePlatform, useToast, useBinaryState } = require('stremio/common'); const { useProfile, usePlatform, useToast, useBinaryState } = require('stremio/common');
const { Button, Image, Popup } = require('stremio/components'); const { Button, Image, Popup } = require('stremio/components');
const { useServices } = require('stremio/services'); const { useServices } = require('stremio/services');
const { useRouteFocused } = require('stremio-router'); const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const StreamPlaceholder = require('./StreamPlaceholder'); const StreamPlaceholder = require('./StreamPlaceholder');
const styles = require('./styles'); const styles = require('./styles');

View file

@ -1,6 +1,7 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const { useNavigate } = require('react-router');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const { useTranslation } = require('react-i18next'); const { useTranslation } = require('react-i18next');
@ -19,6 +20,7 @@ const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => {
const { core } = useServices(); const { core } = useServices();
const platform = usePlatform(); const platform = usePlatform();
const profile = useProfile(); const profile = useProfile();
const navigate = useNavigate();
const streamsContainerRef = React.useRef(null); const streamsContainerRef = React.useRef(null);
const [selectedAddon, setSelectedAddon] = React.useState(ALL_ADDONS_KEY); const [selectedAddon, setSelectedAddon] = React.useState(ALL_ADDONS_KEY);
const onAddonSelected = React.useCallback((value) => { const onAddonSelected = React.useCallback((value) => {
@ -30,14 +32,13 @@ const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => {
}, [profile, video]); }, [profile, video]);
const backButtonOnClick = React.useCallback(() => { const backButtonOnClick = React.useCallback(() => {
if (video.deepLinks && typeof video.deepLinks.metaDetailsVideos === 'string') { if (video.deepLinks && typeof video.deepLinks.metaDetailsVideos === 'string') {
window.location.replace(video.deepLinks.metaDetailsVideos + ( const navigateTo = `${video.deepLinks.metaDetailsVideos}${
typeof video.season === 'number' ? typeof video.season === 'number'
`?${new URLSearchParams({ 'season': video.season })}` ? `?${new URLSearchParams({ 'season': video.season })}`
: : ''}`;
null navigate(navigateTo.replace('#', ''));
));
} else { } else {
window.history.back(); navigate(-1);
} }
}, [video]); }, [video]);
const countLoadingAddons = React.useMemo(() => { const countLoadingAddons = React.useMemo(() => {

View file

@ -1,8 +1,10 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const { useSearchParams } = require('react-router-dom');
const useSeason = (urlParams, queryParams) => { const useSeason = (urlParams) => {
const [queryParams, setQueryParams] = useSearchParams();
const season = React.useMemo(() => { const season = React.useMemo(() => {
return queryParams.has('season') && !isNaN(queryParams.get('season')) ? return queryParams.has('season') && !isNaN(queryParams.get('season')) ?
parseInt(queryParams.get('season'), 10) parseInt(queryParams.get('season'), 10)
@ -12,7 +14,7 @@ const useSeason = (urlParams, queryParams) => {
const setSeason = React.useCallback((season) => { const setSeason = React.useCallback((season) => {
const nextQueryParams = new URLSearchParams(queryParams); const nextQueryParams = new URLSearchParams(queryParams);
nextQueryParams.set('season', season); nextQueryParams.set('season', season);
window.location.replace(`#${urlParams.path}?${nextQueryParams}`); setQueryParams(nextQueryParams);
}, [urlParams, queryParams]); }, [urlParams, queryParams]);
return [season, setSeason]; return [season, setSeason];
}; };

View file

@ -4,7 +4,7 @@ const React = require('react');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const debounce = require('lodash.debounce'); const debounce = require('lodash.debounce');
const { useRouteFocused } = require('stremio-router'); const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { useBinaryState } = require('stremio/common'); const { useBinaryState } = require('stremio/common');
const { Button, Slider } = require('stremio/components'); const { Button, Slider } = require('stremio/components');
const formatTime = require('./formatTime'); const formatTime = require('./formatTime');

View file

@ -4,7 +4,7 @@ const React = require('react');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const debounce = require('lodash.debounce'); const debounce = require('lodash.debounce');
const { useRouteFocused } = require('stremio-router'); const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { useServices } = require('stremio/services'); const { useServices } = require('stremio/services');
const { Slider } = require('stremio/components'); const { Slider } = require('stremio/components');
const styles = require('./styles'); const styles = require('./styles');

View file

@ -1,12 +1,13 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const PropTypes = require('prop-types'); const { useParams, useNavigate } = require('react-router');
const { useSearchParams } = require('react-router-dom');
const classnames = require('classnames'); const classnames = require('classnames');
const debounce = require('lodash.debounce'); const debounce = require('lodash.debounce');
const langs = require('langs'); const langs = require('langs');
const { useTranslation } = require('react-i18next'); const { useTranslation } = require('react-i18next');
const { useRouteFocused } = require('stremio-router'); const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { useServices } = require('stremio/services'); const { useServices } = require('stremio/services');
const { onFileDrop, useSettings, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS, useShell } = require('stremio/common'); const { onFileDrop, useSettings, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS, useShell } = require('stremio/common');
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components'); const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
@ -29,7 +30,10 @@ const styles = require('./styles');
const Video = require('./Video'); const Video = require('./Video');
const { default: Indicator } = require('./Indicator/Indicator'); const { default: Indicator } = require('./Indicator/Indicator');
const Player = ({ urlParams, queryParams }) => { const Player = () => {
const urlParams = useParams();
const [queryParams] = useSearchParams();
const navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
const services = useServices(); const services = useServices();
const shell = useShell(); const shell = useShell();
@ -107,10 +111,10 @@ const Player = ({ urlParams, queryParams }) => {
const handleNextVideoNavigation = React.useCallback((deepLinks) => { const handleNextVideoNavigation = React.useCallback((deepLinks) => {
if (deepLinks.player) { if (deepLinks.player) {
isNavigating.current = true; isNavigating.current = true;
window.location.replace(deepLinks.player); navigate(deepLinks.player.replace('#', ''), { replace: true });
} else if (deepLinks.metaDetailsStreams) { } else if (deepLinks.metaDetailsStreams) {
isNavigating.current = true; isNavigating.current = true;
window.location.replace(deepLinks.metaDetailsStreams); navigate(deepLinks.metaDetailsStreams.replace('#', ''), { replace: true });
} }
}, []); }, []);
@ -123,7 +127,7 @@ const Player = ({ urlParams, queryParams }) => {
if (player.nextVideo !== null) { if (player.nextVideo !== null) {
onNextVideoRequested(); onNextVideoRequested();
} else { } else {
window.history.back(); navigate(-1);
} }
}, [player.nextVideo, onNextVideoRequested]); }, [player.nextVideo, onNextVideoRequested]);
@ -608,7 +612,7 @@ const Player = ({ urlParams, queryParams }) => {
} }
case 'Escape': { case 'Escape': {
closeMenus(); closeMenus();
!settings.escExitFullscreen && window.history.back(); !settings.escExitFullscreen && navigate(-1);
break; break;
} }
} }
@ -893,18 +897,6 @@ const Player = ({ urlParams, queryParams }) => {
); );
}; };
Player.propTypes = {
urlParams: PropTypes.shape({
stream: PropTypes.string,
streamTransportUrl: PropTypes.string,
metaTransportUrl: PropTypes.string,
type: PropTypes.string,
id: PropTypes.string,
videoId: PropTypes.string
}),
queryParams: PropTypes.instanceOf(URLSearchParams)
};
const PlayerFallback = () => ( const PlayerFallback = () => (
<div className={classnames(styles['player-container'])} /> <div className={classnames(styles['player-container'])} />
); );

View file

@ -1,7 +1,6 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2023 Smart code 203358507
const React = require('react'); const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const debounce = require('lodash.debounce'); const debounce = require('lodash.debounce');
const useTranslate = require('stremio/common/useTranslate'); const useTranslate = require('stremio/common/useTranslate');
@ -10,10 +9,12 @@ const { withCoreSuspender, getVisibleChildrenRange } = require('stremio/common')
const { Image, MainNavBars, MetaItem, MetaRow } = require('stremio/components'); const { Image, MainNavBars, MetaItem, MetaRow } = require('stremio/components');
const useSearch = require('./useSearch'); const useSearch = require('./useSearch');
const styles = require('./styles'); const styles = require('./styles');
const { useSearchParams } = require('react-router-dom');
const THRESHOLD = 100; const THRESHOLD = 100;
const Search = ({ queryParams }) => { const Search = () => {
const [queryParams] = useSearchParams();
const t = useTranslate(); const t = useTranslate();
const [search, loadSearchRows] = useSearch(queryParams); const [search, loadSearchRows] = useSearch(queryParams);
const query = React.useMemo(() => { const query = React.useMemo(() => {
@ -127,14 +128,9 @@ const Search = ({ queryParams }) => {
); );
}; };
Search.propTypes = { const SearchFallback = () => {
queryParams: PropTypes.instanceOf(URLSearchParams) const [queryParams] = useSearchParams();
return <MainNavBars className={styles['search-container']} route={'search'} query={queryParams.get('search') ?? queryParams.get('query')} />;
}; };
const SearchFallback = ({ queryParams }) => (
<MainNavBars className={styles['search-container']} route={'search'} query={queryParams.get('search') ?? queryParams.get('query')} />
);
SearchFallback.propTypes = Search.propTypes;
module.exports = withCoreSuspender(Search, SearchFallback); module.exports = withCoreSuspender(Search, SearchFallback);

View file

@ -3,8 +3,7 @@
import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import throttle from 'lodash.throttle'; import throttle from 'lodash.throttle';
import { useRouteFocused } from 'stremio-router'; import { useProfile, useStreamingServer, useRouteFocused, withCoreSuspender } from 'stremio/common';
import { useProfile, useStreamingServer, withCoreSuspender } from 'stremio/common';
import { MainNavBars } from 'stremio/components'; import { MainNavBars } from 'stremio/components';
import { SECTIONS } from './constants'; import { SECTIONS } from './constants';
import Menu from './Menu'; import Menu from './Menu';
@ -16,7 +15,7 @@ import Info from './Info';
import styles from './Settings.less'; import styles from './Settings.less';
const Settings = () => { const Settings = () => {
const { routeFocused } = useRouteFocused(); const routeFocused = useRouteFocused();
const profile = useProfile(); const profile = useProfile();
const streamingServer = useStreamingServer(); const streamingServer = useStreamingServer();