diff --git a/package-lock.json b/package-lock.json
index 7d5ed4487..0ad583043 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -35,6 +35,8 @@
"react-focus-lock": "2.13.2",
"react-i18next": "^15.1.3",
"react-is": "18.3.1",
+ "react-router": "6.30.0",
+ "react-router-dom": "6.30.0",
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
"stremio-translations": "github:Stremio/stremio-translations#abe7684165a031755e9aee39da26daa806ba7824",
"url": "0.11.4",
@@ -3105,6 +3107,15 @@
"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": {
"version": "5.3.1",
"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==",
"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": {
"version": "3.6.0",
"dev": true,
diff --git a/package.json b/package.json
index ffcaef6b2..a31285d10 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,8 @@
"react-focus-lock": "2.13.2",
"react-i18next": "^15.1.3",
"react-is": "18.3.1",
+ "react-router": "6.30.0",
+ "react-router-dom": "6.30.0",
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
"stremio-translations": "github:Stremio/stremio-translations#abe7684165a031755e9aee39da26daa806ba7824",
"url": "0.11.4",
diff --git a/src/App/App.js b/src/App/App.js
index 3e816be9f..a1976b586 100644
--- a/src/App/App.js
+++ b/src/App/App.js
@@ -5,25 +5,19 @@ const React = require('react');
const { useTranslation } = require('react-i18next');
const { Router } = require('stremio-router');
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 ServicesToaster = require('./ServicesToaster');
const DeepLinkHandler = require('./DeepLinkHandler');
const SearchParamsHandler = require('./SearchParamsHandler');
const { default: UpdaterBanner } = require('./UpdaterBanner');
const ErrorDialog = require('./ErrorDialog');
-const withProtectedRoutes = require('./withProtectedRoutes');
-const routerViewsConfig = require('./routerViewsConfig');
const styles = require('./styles');
-const RouterWithProtectedRoutes = withCoreSuspender(withProtectedRoutes(Router));
+const RouterWithProtectedRoutes = withCoreSuspender(Router);
const App = () => {
const { i18n } = useTranslation();
const shell = useShell();
- const onPathNotMatch = React.useCallback(() => {
- return NotFound;
- }, []);
const services = React.useMemo(() => {
const core = new Core({
appVersion: process.env.VERSION,
@@ -207,11 +201,7 @@ const App = () => {
-
+
diff --git a/src/App/withProtectedRoutes.js b/src/App/withProtectedRoutes.js
deleted file mode 100644
index a16e0c0c7..000000000
--- a/src/App/withProtectedRoutes.js
+++ /dev/null
@@ -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 (
-
- );
- };
-};
-
-module.exports = withProtectedRoutes;
diff --git a/src/common/index.js b/src/common/index.js
index 25df5c158..0200e1b17 100644
--- a/src/common/index.js
+++ b/src/common/index.js
@@ -21,6 +21,7 @@ const useModelState = require('./useModelState');
const useNotifications = require('./useNotifications');
const useOnScrollToBottom = require('./useOnScrollToBottom');
const useProfile = require('./useProfile');
+const { default: useRouteFocused } = require('./useRouteFocused');
const { default: useSettings } = require('./useSettings');
const { default: useShell } = require('./useShell');
const useStreamingServer = require('./useStreamingServer');
@@ -57,6 +58,7 @@ module.exports = {
useNotifications,
useOnScrollToBottom,
useProfile,
+ useRouteFocused,
useSettings,
useShell,
useStreamingServer,
diff --git a/src/common/routerPaths.tsx b/src/common/routerPaths.tsx
new file mode 100644
index 000000000..9cd98a9ec
--- /dev/null
+++ b/src/common/routerPaths.tsx
@@ -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: ,
+ },
+ {
+ path: '/discover/:transportUrl?/:type?/:catalogId?',
+ element: ,
+ },
+ {
+ path: '/library/:type?',
+ element: ,
+ },
+ {
+ path: '/calendar/:year?/:month?',
+ element: ,
+ },
+ {
+ path: '/continuewatching/:type?',
+ element: ,
+ },
+ {
+ path: '/search',
+ element: ,
+ },
+ {
+ path: '/metadetails/:type?/:id?/:videoId?',
+ element: ,
+ },
+ {
+ path: '/detail/:type?/:id?/:videoId?',
+ element: ,
+ },
+ {
+ path: '/addons/:type?/:transportUrl?/:catalogId?',
+ element: ,
+ },
+ {
+ path: '/settings',
+ element: ,
+ },
+ {
+ path: '/player/:stream?/:streamTransportUrl?/:metaTransportUrl?/:type?/:id?/:videoId?',
+ element: ,
+ },
+ {
+ path: '/',
+ element: ,
+ },
+ {
+ path: '*',
+ element: ,
+ },
+];
diff --git a/src/common/useModelState.js b/src/common/useModelState.js
index 42672dc22..8f22f2a02 100644
--- a/src/common/useModelState.js
+++ b/src/common/useModelState.js
@@ -5,7 +5,7 @@ const throttle = require('lodash.throttle');
const isEqual = require('lodash.isequal');
const intersection = require('lodash.intersection');
const { useCoreSuspender } = require('stremio/common/CoreSuspender');
-const { useRouteFocused } = require('stremio-router');
+const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { useServices } = require('stremio/services');
const useModelState = ({ action, ...args }) => {
@@ -32,17 +32,17 @@ const useModelState = ({ action, ...args }) => {
}
}
);
- React.useInsertionEffect(() => {
+ React.useLayoutEffect(() => {
if (action) {
core.transport.dispatch(action, model);
}
}, [action]);
- React.useInsertionEffect(() => {
+ React.useLayoutEffect(() => {
return () => {
core.transport.dispatch({ action: 'Unload' }, model);
};
}, []);
- React.useInsertionEffect(() => {
+ React.useLayoutEffect(() => {
const onNewState = async (models) => {
if (models.indexOf(model) === -1 && (!Array.isArray(deps) || intersection(deps, models).length === 0)) {
return;
@@ -67,7 +67,7 @@ const useModelState = ({ action, ...args }) => {
core.transport.off('NewState', onNewStateThrottled);
};
}, [routeFocused]);
- React.useInsertionEffect(() => {
+ React.useLayoutEffect(() => {
mountedRef.current = true;
}, []);
return state;
diff --git a/src/common/useNavigateWithOrigin.ts b/src/common/useNavigateWithOrigin.ts
new file mode 100644
index 000000000..51c58a8da
--- /dev/null
+++ b/src/common/useNavigateWithOrigin.ts
@@ -0,0 +1,35 @@
+import { useLocation, useNavigate, To, Location } from 'react-router-dom';
+
+const ORIGIN_KEY = 'originPath';
+
+export function useNavigateWithOrigin() {
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ function navigateWithOrigin(target: To) {
+ const origin: Location = location.state?.from || location;
+
+ // Save origin in sessionStorage
+ sessionStorage.setItem(ORIGIN_KEY, origin.pathname + origin.search);
+
+ // Navigate and propagate origin
+ navigate(target, {
+ state: { from: origin },
+ });
+ }
+
+ function setOriginPath(path?: string) {
+ const finalPath = path ?? location.pathname + location.search;
+ sessionStorage.setItem(ORIGIN_KEY, finalPath);
+ }
+
+ function getStoredOrigin(fallback = '/'): string {
+ return sessionStorage.getItem(ORIGIN_KEY) || fallback;
+ }
+
+ return {
+ navigateWithOrigin,
+ getStoredOrigin,
+ setOriginPath,
+ };
+}
diff --git a/src/common/useRouteFocused.ts b/src/common/useRouteFocused.ts
new file mode 100644
index 000000000..80037f7d3
--- /dev/null
+++ b/src/common/useRouteFocused.ts
@@ -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;
diff --git a/src/components/ContinueWatchingItem/ContinueWatchingItem.js b/src/components/ContinueWatchingItem/ContinueWatchingItem.js
index 8a0143619..8334cb796 100644
--- a/src/components/ContinueWatchingItem/ContinueWatchingItem.js
+++ b/src/components/ContinueWatchingItem/ContinueWatchingItem.js
@@ -1,23 +1,28 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
+const { useNavigate } = require('react-router');
const PropTypes = require('prop-types');
const { useServices } = require('stremio/services');
const LibItem = require('stremio/components/LibItem');
const ContinueWatchingItem = ({ _id, notifications, deepLinks, ...props }) => {
const { core } = useServices();
+ const navigate = useNavigate();
const onClick = React.useCallback(() => {
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]);
const onPlayClick = React.useCallback((event) => {
event.stopPropagation();
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]);
diff --git a/src/components/LibItem/LibItem.js b/src/components/LibItem/LibItem.js
index a42def27f..e618ee804 100644
--- a/src/components/LibItem/LibItem.js
+++ b/src/components/LibItem/LibItem.js
@@ -1,14 +1,15 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
+const { useNavigate } = require('react-router');
const { useServices } = require('stremio/services');
const PropTypes = require('prop-types');
const MetaItem = require('stremio/components/MetaItem');
const { t } = require('i18next');
const LibItem = ({ _id, removable, notifications, watched, ...props }) => {
-
const { core } = useServices();
+ const navigate = useNavigate();
const newVideos = React.useMemo(() => {
const count = notifications.items?.[_id]?.length ?? 0;
@@ -50,7 +51,8 @@ const LibItem = ({ _id, removable, notifications, watched, ...props }) => {
switch (event.value) {
case 'play': {
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;
@@ -58,9 +60,9 @@ const LibItem = ({ _id, removable, notifications, watched, ...props }) => {
case 'details': {
if (props.deepLinks) {
if (typeof props.deepLinks.metaDetailsVideos === 'string') {
- window.location = props.deepLinks.metaDetailsVideos;
+ navigate(props.deepLinks.metaDetailsVideos.replace('#', ''));
} else if (typeof props.deepLinks.metaDetailsStreams === 'string') {
- window.location = props.deepLinks.metaDetailsStreams;
+ navigate(props.deepLinks.metaDetailsStreams.replace('#', ''));
}
}
diff --git a/src/components/MainNavBars/MainNavBars.tsx b/src/components/MainNavBars/MainNavBars.tsx
index 43d08f5c8..a42273eab 100644
--- a/src/components/MainNavBars/MainNavBars.tsx
+++ b/src/components/MainNavBars/MainNavBars.tsx
@@ -6,12 +6,12 @@ import { VerticalNavBar, HorizontalNavBar } from 'stremio/components/NavBar';
import styles from './MainNavBars.less';
const TABS = [
- { id: 'board', label: 'Board', icon: 'home', href: '#/' },
- { id: 'discover', label: 'Discover', icon: 'discover', href: '#/discover' },
- { id: 'library', label: 'Library', icon: 'library', href: '#/library' },
- { id: 'calendar', label: 'Calendar', icon: 'calendar', href: '#/calendar' },
- { id: 'addons', label: 'ADDONS', icon: 'addons', href: '#/addons' },
- { id: 'settings', label: 'SETTINGS', icon: 'settings', href: '#/settings' },
+ { id: 'board', label: 'Board', icon: 'home', href: '/' },
+ { id: 'discover', label: 'Discover', icon: 'discover', href: '/discover' },
+ { id: 'library', label: 'Library', icon: 'library', href: '/library' },
+ { id: 'calendar', label: 'Calendar', icon: 'calendar', href: '/calendar' },
+ { id: 'addons', label: 'ADDONS', icon: 'addons', href: '/addons' },
+ { id: 'settings', label: 'SETTINGS', icon: 'settings', href: '/settings' },
];
type Props = {
diff --git a/src/components/MetaItem/MetaItem.js b/src/components/MetaItem/MetaItem.js
index 1e19054c1..eec59cefb 100644
--- a/src/components/MetaItem/MetaItem.js
+++ b/src/components/MetaItem/MetaItem.js
@@ -6,6 +6,7 @@ const classnames = require('classnames');
const { useTranslation } = require('react-i18next');
const filterInvalidDOMProps = require('filter-invalid-dom-props').default;
const { default: Icon } = require('@stremio/stremio-icons/react');
+const { useNavigateWithOrigin } = require('stremio/common/useNavigateWithOrigin');
const { default: Button } = require('stremio/components/Button');
const { default: Image } = require('stremio/components/Image');
const Multiselect = require('stremio/components/Multiselect');
@@ -15,6 +16,7 @@ const styles = require('./styles');
const MetaItem = React.memo(({ className, type, name, poster, posterShape, posterChangeCursor, progress, newVideos, options, deepLinks, dataset, optionOnSelect, onDismissClick, onPlayClick, watched, ...props }) => {
const { t } = useTranslation();
+ const { setOriginPath } = useNavigateWithOrigin();
const [menuOpen, onMenuOpen, onMenuClose] = useBinaryState(false);
const href = React.useMemo(() => {
return deepLinks ?
@@ -32,6 +34,7 @@ const MetaItem = React.memo(({ className, type, name, poster, posterShape, poste
null;
}, [deepLinks]);
const metaItemOnClick = React.useCallback((event) => {
+ setOriginPath();
if (event.nativeEvent.selectPrevented) {
event.preventDefault();
} else if (typeof props.onClick === 'function') {
diff --git a/src/components/ModalDialog/ModalDialog.js b/src/components/ModalDialog/ModalDialog.js
index b07481f69..44f1f1fdb 100644
--- a/src/components/ModalDialog/ModalDialog.js
+++ b/src/components/ModalDialog/ModalDialog.js
@@ -4,10 +4,11 @@ const React = require('react');
const { useTranslation } = require('react-i18next');
const PropTypes = require('prop-types');
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: Icon } = require('@stremio/stremio-icons/react');
-const { Modal } = require('stremio-router');
const styles = require('./styles');
const ModalDialog = ({ className, title, buttons, children, dataset, onCloseRequest, background, ...props }) => {
diff --git a/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js b/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js
index 6be35cd5d..e1f418ddd 100644
--- a/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js
+++ b/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js
@@ -1,6 +1,7 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
+const { useNavigate } = require('react-router');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
@@ -12,9 +13,14 @@ const NavMenu = require('./NavMenu');
const styles = require('./styles');
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, originPath, ...props }) => {
+ const navigate = useNavigate();
const backButtonOnClick = React.useCallback(() => {
- window.history.back();
+ if (originPath) {
+ navigate(originPath);
+ } else {
+ navigate(-1);
+ }
}, []);
const [fullscreen, requestFullscreen, exitFullscreen] = useFullscreen();
const [isIOSPWA] = usePWA();
@@ -82,7 +88,8 @@ HorizontalNavBar.propTypes = {
backButton: PropTypes.bool,
searchBar: PropTypes.bool,
fullscreenButton: PropTypes.bool,
- navMenu: PropTypes.bool
+ navMenu: PropTypes.bool,
+ originPath: PropTypes.string,
};
module.exports = HorizontalNavBar;
diff --git a/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenu.js b/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenu.js
index 8d381f3ca..b5de0ee71 100644
--- a/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenu.js
+++ b/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenu.js
@@ -3,7 +3,7 @@
const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
-const { useRouteFocused } = require('stremio-router');
+const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const Popup = require('stremio/components/Popup');
const useBinaryState = require('stremio/common/useBinaryState');
const NavMenuContent = require('./NavMenuContent');
diff --git a/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js b/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js
index de0a02212..72531ba6c 100644
--- a/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js
+++ b/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js
@@ -1,6 +1,7 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
+const { useNavigate } = require('react-router');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { useTranslation } = require('react-i18next');
@@ -17,6 +18,7 @@ const styles = require('./styles');
const NavMenuContent = ({ onClick }) => {
const { t } = useTranslation();
+ const navigate = useNavigate();
const { core } = useServices();
const profile = useProfile();
const streamingServer = useStreamingServer();
@@ -45,6 +47,12 @@ const NavMenuContent = ({ onClick }) => {
console.error(e);
}
}, []);
+ const handleAuth = React.useCallback(() => {
+ return profile.auth !== null
+ ? logoutButtonOnClick()
+ : navigate('/intro');
+ }, [profile.auth, logoutButtonOnClick, navigate]);
+
return (
@@ -64,7 +72,7 @@ const NavMenuContent = ({ onClick }) => {
{profile.auth === null ? t('ANONYMOUS_USER') : profile.auth.user.email}
-
diff --git a/src/components/NavBar/HorizontalNavBar/SearchBar/SearchBar.js b/src/components/NavBar/HorizontalNavBar/SearchBar/SearchBar.js
index 0bc95cd73..885dd196f 100644
--- a/src/components/NavBar/HorizontalNavBar/SearchBar/SearchBar.js
+++ b/src/components/NavBar/HorizontalNavBar/SearchBar/SearchBar.js
@@ -1,12 +1,14 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
+const { useNavigate } = require('react-router');
+const { useSearchParams } = require('react-router-dom');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const debounce = require('lodash.debounce');
const { useTranslation } = require('react-i18next');
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 TextInput = require('stremio/components/TextInput').default;
const useTorrent = require('stremio/common/useTorrent');
@@ -22,16 +24,17 @@ const SearchBar = React.memo(({ className, query, active }) => {
const searchHistory = useSearchHistory();
const localSearch = useLocalSearch();
const { createTorrentFromMagnet } = useTorrent();
+ const navigate = useNavigate();
const [historyOpen, openHistory, closeHistory, ] = useBinaryState(query === null ? true : false);
const [currentQuery, setCurrentQuery] = React.useState(query || '');
-
+ const [, setSearchParams] = useSearchParams();
const searchInputRef = React.useRef(null);
const containerRef = React.useRef(null);
const searchBarOnClick = React.useCallback(() => {
if (!active) {
- window.location = '#/search';
+ navigate('/search');
}
}, [active]);
@@ -64,7 +67,7 @@ const SearchBar = React.memo(({ className, query, active }) => {
const searchValue = `/search?search=${encodeURIComponent(event.target.value)}`;
setCurrentQuery(searchValue);
if (searchInputRef.current && searchValue) {
- window.location.hash = searchValue;
+ setSearchParams({ search: event.target.value });
closeHistory();
}
}, []);
@@ -72,7 +75,7 @@ const SearchBar = React.memo(({ className, query, active }) => {
const queryInputClear = React.useCallback(() => {
searchInputRef.current.value = '';
setCurrentQuery('');
- window.location.hash = '/search';
+ navigate('/search', { search: '' });
}, []);
const updateLocalSearchDebounced = React.useCallback(debounce((query) => {
diff --git a/src/components/NavBar/VerticalNavBar/NavTabButton/NavTabButton.js b/src/components/NavBar/VerticalNavBar/NavTabButton/NavTabButton.js
index 65c6a02a9..e59f106f7 100644
--- a/src/components/NavBar/VerticalNavBar/NavTabButton/NavTabButton.js
+++ b/src/components/NavBar/VerticalNavBar/NavTabButton/NavTabButton.js
@@ -4,8 +4,9 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
-const { Button, Image } = require('stremio/components');
+const { Image } = require('stremio/components');
const styles = require('./styles');
+const { Link } = require('react-router-dom');
const NavTabButton = ({ className, logo, icon, label, href, selected, onClick }) => {
const renderLogoFallback = React.useCallback(() => (
@@ -24,7 +25,7 @@ const NavTabButton = ({ className, logo, icon, label, href, selected, onClick })
});
};
return (
-
+
{
typeof logo === 'string' && logo.length > 0 ?
+
);
};
diff --git a/src/components/Popup/Popup.js b/src/components/Popup/Popup.js
index aad02bdf3..e8fadc6af 100644
--- a/src/components/Popup/Popup.js
+++ b/src/components/Popup/Popup.js
@@ -4,7 +4,7 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const FocusLock = require('react-focus-lock').default;
-const { useRouteFocused } = require('stremio-router');
+const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const styles = require('./styles');
const getAnchorElement = (element) => {
diff --git a/src/components/SharePrompt/SharePrompt.js b/src/components/SharePrompt/SharePrompt.js
index af4393b8a..eaa342dc1 100644
--- a/src/components/SharePrompt/SharePrompt.js
+++ b/src/components/SharePrompt/SharePrompt.js
@@ -5,7 +5,7 @@ const PropTypes = require('prop-types');
const classnames = require('classnames');
const { useTranslation } = require('react-i18next');
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 { Button } = require('stremio/components');
const { default: TextInput } = require('stremio/components/TextInput');
diff --git a/src/components/Slider/Slider.js b/src/components/Slider/Slider.js
index d0d4721b8..a986e196f 100644
--- a/src/components/Slider/Slider.js
+++ b/src/components/Slider/Slider.js
@@ -3,7 +3,7 @@
const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
-const { useRouteFocused } = require('stremio-router');
+const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const useAnimationFrame = require('stremio/common/useAnimationFrame');
const useLiveRef = require('stremio/common/useLiveRef');
const styles = require('./styles');
diff --git a/src/components/Video/Video.js b/src/components/Video/Video.js
index 229fe758d..5d2d4a3cb 100644
--- a/src/components/Video/Video.js
+++ b/src/components/Video/Video.js
@@ -2,20 +2,25 @@
const React = require('react');
const { useTranslation } = require('react-i18next');
+const { useNavigate } = require('react-router');
const PropTypes = require('prop-types');
const classnames = require('classnames');
-const { useRouteFocused } = require('stremio-router');
+const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { default: Icon } = require('@stremio/stremio-icons/react');
const { Button, Image, Popup } = require('stremio/components');
const useBinaryState = require('stremio/common/useBinaryState');
const useProfile = require('stremio/common/useProfile');
+const { usePlatform } = require('stremio/common/Platform');
const VideoPlaceholder = require('./VideoPlaceholder');
const styles = require('./styles');
const Video = React.forwardRef(({ className, id, title, thumbnail, season, episode, released, upcoming, watched, progress, scheduled, seasonWatched, deepLinks, onMarkVideoAsWatched, onMarkSeasonAsWatched, ...props }, ref) => {
const routeFocused = useRouteFocused();
const profile = useProfile();
+ const navigate = useNavigate();
+ const platform = usePlatform();
const { t } = useTranslation();
+
const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false);
const popupLabelOnMouseUp = React.useCallback((event) => {
if (!event.nativeEvent.togglePopupPrevented) {
@@ -62,9 +67,10 @@ const Video = React.forwardRef(({ className, id, title, thumbnail, season, episo
const videoButtonOnClick = React.useCallback(() => {
if (deepLinks) {
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') {
- window.location.replace(deepLinks.metaDetailsStreams);
+ navigate(deepLinks.metaDetailsStreams.replace('#', ''), { replace: !platform.isMobile });
}
}
}, [deepLinks]);
diff --git a/src/router/Route/Route.js b/src/router/Route/Route.js
index c52c3fbf7..ae92115d9 100644
--- a/src/router/Route/Route.js
+++ b/src/router/Route/Route.js
@@ -4,12 +4,12 @@ const React = require('react');
const PropTypes = require('prop-types');
const { ModalsContainerProvider } = require('../ModalsContainerContext');
-const Route = ({ children }) => {
+const Route = ({ component }) => {
return (
- {children}
+ {component}
@@ -17,7 +17,7 @@ const Route = ({ children }) => {
};
Route.propTypes = {
- children: PropTypes.node
+ component: PropTypes.node
};
module.exports = Route;
diff --git a/src/router/RouteFocusedContext/RouteFocusedContext.js b/src/router/RouteFocusedContext/RouteFocusedContext.js
deleted file mode 100644
index 662284253..000000000
--- a/src/router/RouteFocusedContext/RouteFocusedContext.js
+++ /dev/null
@@ -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;
diff --git a/src/router/RouteFocusedContext/index.js b/src/router/RouteFocusedContext/index.js
deleted file mode 100644
index dc4e6cfd5..000000000
--- a/src/router/RouteFocusedContext/index.js
+++ /dev/null
@@ -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
-};
diff --git a/src/router/RouteFocusedContext/useRouteFocused.js b/src/router/RouteFocusedContext/useRouteFocused.js
deleted file mode 100644
index 93bf9ecc9..000000000
--- a/src/router/RouteFocusedContext/useRouteFocused.js
+++ /dev/null
@@ -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;
diff --git a/src/router/Router/Router.js b/src/router/Router/Router.js
index db4c9e957..e2fb9d6f5 100644
--- a/src/router/Router/Router.js
+++ b/src/router/Router/Router.js
@@ -1,107 +1,24 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
-const ReactIs = require('react-is');
+const { HashRouter } = require('react-router-dom');
const PropTypes = require('prop-types');
const classnames = require('classnames');
-const UrlUtils = require('url');
-const isEqual = require('lodash.isequal');
-const { RouteFocusedProvider } = require('../RouteFocusedContext');
-const Route = require('../Route');
-const routeConfigForPath = require('./routeConfigForPath');
-const urlParamsForPath = require('./urlParamsForPath');
+const { default: Routes } = require('./Routes');
-const Router = ({ className, onPathNotMatch, onRouteChange, ...props }) => {
- 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
- });
- });
- }
- }
+const Router = ({ className }) => {
- 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 (
- {
- views
- .filter((view) => view !== null)
- .map(({ key, component, urlParams, queryParams }, index, views) => (
-
-
- {React.createElement(component, { urlParams, queryParams })}
-
-
- ))
- }
+
+
+
);
};
Router.propTypes = {
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;
diff --git a/src/router/Router/Routes.tsx b/src/router/Router/Routes.tsx
new file mode 100644
index 000000000..68ecf3666
--- /dev/null
+++ b/src/router/Router/Routes.tsx
@@ -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) =>
+ } />
+ );
+
+ return
+ {routes}
+ ;
+};
+
+export default Routes;
diff --git a/src/router/index.js b/src/router/index.js
index 03564ad3b..a3fe32526 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,12 +1,10 @@
// Copyright (C) 2017-2023 Smart code 203358507
-const { useRouteFocused } = require('./RouteFocusedContext');
const { useModalsContainer } = require('./ModalsContainerContext');
const Modal = require('./Modal');
const Router = require('./Router');
module.exports = {
- useRouteFocused,
useModalsContainer,
Modal,
Router
diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js
index da9544d51..85e43f7d5 100644
--- a/src/routes/Addons/Addons.js
+++ b/src/routes/Addons/Addons.js
@@ -1,7 +1,8 @@
// Copyright (C) 2017-2023 Smart code 203358507
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 { useTranslation } = require('react-i18next');
const { default: Icon } = require('@stremio/stremio-icons/react');
@@ -16,13 +17,20 @@ const useSelectableInputs = require('./useSelectableInputs');
const styles = require('./styles');
const { AddonPlaceholder } = require('./AddonPlaceholder');
-const Addons = ({ urlParams, queryParams }) => {
+const Addons = () => {
+ const { type, transportUrl, catalogId } = useParams();
+ const [queryParams] = useSearchParams();
+ const urlParams = React.useMemo(() => ({
+ type,
+ transportUrl,
+ catalogId
+ }), [type, transportUrl, catalogId]);
const { t } = useTranslation();
const platform = usePlatform();
const { core } = useServices();
const installedAddons = useInstalledAddons(urlParams);
const remoteAddons = useRemoteAddons(urlParams);
- const [addonDetailsTransportUrl, setAddonDetailsTransportUrl] = useAddonDetailsTransportUrl(urlParams, queryParams);
+ const [addonDetailsTransportUrl, setAddonDetailsTransportUrl] = useAddonDetailsTransportUrl(urlParams);
const selectInputs = useSelectableInputs(installedAddons, remoteAddons);
const [filtersModalOpen, openFiltersModal, closeFiltersModal] = useBinaryState(false);
const [addAddonModalOpen, openAddAddonModal, closeAddAddonModal] = useBinaryState(false);
@@ -292,16 +300,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 = () => (
);
diff --git a/src/routes/Addons/useAddonDetailsTransportUrl.js b/src/routes/Addons/useAddonDetailsTransportUrl.js
index 6ec03134f..51c66afd8 100644
--- a/src/routes/Addons/useAddonDetailsTransportUrl.js
+++ b/src/routes/Addons/useAddonDetailsTransportUrl.js
@@ -1,8 +1,10 @@
// Copyright (C) 2017-2023 Smart code 203358507
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(() => {
return queryParams.get('addon');
}, [queryParams]);
@@ -14,7 +16,7 @@ const useAddonDetailsTransportUrl = (urlParams, queryParams) => {
nextQueryParams.delete('addon');
}
- window.location.replace(`#${urlParams.path}?${nextQueryParams}`);
+ setQueryParams(nextQueryParams);
}, [urlParams, queryParams]);
return [transportUrl, setTransportUrl];
};
diff --git a/src/routes/Addons/useSelectableInputs.js b/src/routes/Addons/useSelectableInputs.js
index 003da745d..c1a0b6432 100644
--- a/src/routes/Addons/useSelectableInputs.js
+++ b/src/routes/Addons/useSelectableInputs.js
@@ -1,9 +1,10 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
+const { useNavigate } = require('react-router');
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 catalogSelect = {
options: remoteAddons.selectable.catalogs
@@ -20,9 +21,10 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => {
.find(({ id }) => id === remoteAddons.selected.request.path.id);
return selectableCatalog ? t.stringWithPrefix(selectableCatalog.name, 'ADDON_') : remoteAddons.selected.request.path.id;
}
- : null,
+ :
+ null,
onSelect: (value) => {
- window.location = value;
+ navigate(value.replace('#', ''));
}
};
const selectedType = installedAddons.selected !== null
@@ -53,7 +55,7 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => {
typeSelect.title;
},
onSelect: (value) => {
- window.location = value;
+ navigate(value.replace('#', ''));
}
};
return [catalogSelect, typeSelect];
@@ -61,8 +63,9 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => {
const useSelectableInputs = (installedAddons, remoteAddons) => {
const t = useTranslate();
+ const navigate = useNavigate();
const selectableInputs = React.useMemo(() => {
- return mapSelectableInputs(installedAddons, remoteAddons, t);
+ return mapSelectableInputs(installedAddons, remoteAddons, t, navigate);
}, [installedAddons, remoteAddons]);
return selectableInputs;
};
diff --git a/src/routes/Calendar/Calendar.tsx b/src/routes/Calendar/Calendar.tsx
index 93f1bbe77..3e73f66ed 100644
--- a/src/routes/Calendar/Calendar.tsx
+++ b/src/routes/Calendar/Calendar.tsx
@@ -1,6 +1,7 @@
// Copyright (C) 2017-2024 Smart code 203358507
import React, { useMemo, useState } from 'react';
+import { useParams } from 'react-router';
import { useProfile, withCoreSuspender } from 'stremio/common';
import { MainNavBars, BottomSheet } from 'stremio/components';
import Selector from './Selector';
@@ -13,11 +14,12 @@ import useCalendarDate from './useCalendarDate';
import styles from './Calendar.less';
import classNames from 'classnames';
-type Props = {
- urlParams: UrlParams,
-};
-
-const Calendar = ({ urlParams }: Props) => {
+const Calendar = () => {
+ const { year, month } = useParams();
+ const urlParams = React.useMemo(() => ({
+ year,
+ month
+ }), [year, month]);
const calendar = useCalendar(urlParams);
const profile = useProfile();
diff --git a/src/routes/Calendar/List/Item/Item.tsx b/src/routes/Calendar/List/Item/Item.tsx
index 7448e96db..9525a4bc8 100644
--- a/src/routes/Calendar/List/Item/Item.tsx
+++ b/src/routes/Calendar/List/Item/Item.tsx
@@ -3,6 +3,7 @@
import React, { useEffect, useMemo, useRef } from 'react';
import Icon from '@stremio/stremio-icons/react';
import classNames from 'classnames';
+import { useNavigateWithOrigin } from 'stremio/common/useNavigateWithOrigin';
import { Button } from 'stremio/components';
import useCalendarDate from '../../useCalendarDate';
import styles from './Item.less';
@@ -18,6 +19,7 @@ type Props = {
const Item = ({ selected, monthInfo, date, items, profile, onClick }: Props) => {
const ref = useRef(null);
+ const { setOriginPath } = useNavigateWithOrigin();
const { toDayMonth } = useCalendarDate(profile);
const [active, today] = useMemo(() => [
@@ -26,6 +28,7 @@ const Item = ({ selected, monthInfo, date, items, profile, onClick }: Props) =>
], [selected, monthInfo, date]);
const onItemClick = () => {
+ setOriginPath();
onClick && onClick(date);
};
diff --git a/src/routes/Calendar/Selector/Selector.tsx b/src/routes/Calendar/Selector/Selector.tsx
index ad13d606c..cca3eae21 100644
--- a/src/routes/Calendar/Selector/Selector.tsx
+++ b/src/routes/Calendar/Selector/Selector.tsx
@@ -1,6 +1,7 @@
// Copyright (C) 2017-2024 Smart code 203358507
import React, { useCallback, useMemo } from 'react';
+import { useNavigate } from 'react-router';
import Icon from '@stremio/stremio-icons/react';
import { Button } from 'stremio/components';
import useCalendarDate from '../useCalendarDate';
@@ -14,17 +15,18 @@ type Props = {
const Selector = ({ selected, selectable, profile }: Props) => {
const { toMonth } = useCalendarDate(profile);
+ const navigate = useNavigate();
const [prev, next] = useMemo(() => (
[selectable.prev, selectable.next]
), [selectable]);
const onPrev = useCallback(() => {
- window.location.href = prev.deepLinks.calendar;
+ navigate(prev.deepLinks.calendar.replace('#', ''));
}, [prev]);
const onNext = useCallback(() => {
- window.location.href = next.deepLinks.calendar;
+ navigate(next.deepLinks.calendar.replace('#', ''));
}, [next]);
return (
diff --git a/src/routes/Calendar/Table/Cell/Cell.tsx b/src/routes/Calendar/Table/Cell/Cell.tsx
index 2db1b0992..b36aea2e3 100644
--- a/src/routes/Calendar/Table/Cell/Cell.tsx
+++ b/src/routes/Calendar/Table/Cell/Cell.tsx
@@ -3,6 +3,7 @@
import React, { useCallback, useMemo, MouseEvent } from 'react';
import Icon from '@stremio/stremio-icons/react';
import classNames from 'classnames';
+import { useNavigateWithOrigin } from 'stremio/common/useNavigateWithOrigin';
import { Button, HorizontalScroll, Image } from 'stremio/components';
import styles from './Cell.less';
@@ -15,6 +16,7 @@ type Props = {
};
const Cell = ({ selected, monthInfo, date, items, onClick }: Props) => {
+ const { setOriginPath } = useNavigateWithOrigin();
const [active, today] = useMemo(() => [
date.day === selected?.day,
date.day === monthInfo.today,
@@ -25,6 +27,7 @@ const Cell = ({ selected, monthInfo, date, items, onClick }: Props) => {
};
const onPosterClick = useCallback((event: MouseEvent) => {
+ setOriginPath();
event.stopPropagation();
}, []);
diff --git a/src/routes/Calendar/useCalendar.ts b/src/routes/Calendar/useCalendar.ts
index 55ffc5d07..924a8138b 100644
--- a/src/routes/Calendar/useCalendar.ts
+++ b/src/routes/Calendar/useCalendar.ts
@@ -1,9 +1,10 @@
// Copyright (C) 2017-2024 Smart code 203358507
import React from 'react';
+import { Params } from 'react-router';
import { useModelState } from 'stremio/common';
-const useCalendar = (urlParams: UrlParams) => {
+const useCalendar = (urlParams: Readonly>) => {
const action = React.useMemo(() => {
const args = urlParams.year && urlParams.month ? {
year: parseInt(urlParams.year),
diff --git a/src/routes/Discover/Discover.js b/src/routes/Discover/Discover.js
index 6f32d1f9a..d2461f857 100644
--- a/src/routes/Discover/Discover.js
+++ b/src/routes/Discover/Discover.js
@@ -2,7 +2,8 @@
const React = require('react');
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 { default: Icon } = require('@stremio/stremio-icons/react');
const { useServices } = require('stremio/services');
@@ -14,7 +15,14 @@ const styles = require('./styles');
const SCROLL_TO_BOTTOM_THRESHOLD = 400;
-const Discover = ({ urlParams, queryParams }) => {
+const Discover = () => {
+ const { type, transportUrl, catalogId } = useParams();
+ const urlParams = React.useMemo(() => ({
+ type,
+ transportUrl,
+ catalogId
+ }), [type, transportUrl, catalogId]);
+ const [queryParams] = useSearchParams();
const { t } = useTranslation();
const { core } = useServices();
const [discover, loadNextPage] = useDiscover(urlParams, queryParams);
@@ -230,15 +238,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 = () => (
);
diff --git a/src/routes/Discover/useSelectableInputs.js b/src/routes/Discover/useSelectableInputs.js
index f548861da..916303261 100644
--- a/src/routes/Discover/useSelectableInputs.js
+++ b/src/routes/Discover/useSelectableInputs.js
@@ -1,9 +1,10 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
+const { useNavigate } = require('react-router');
const { useTranslate } = require('stremio/common');
-const mapSelectableInputs = (discover, t) => {
+const mapSelectableInputs = (discover, t, navigate) => {
const selectedType = discover.selectable.types.find(({ selected }) => selected);
const typeSelect = {
options: discover.selectable.types
@@ -18,7 +19,7 @@ const mapSelectableInputs = (discover, t) => {
? () => t.stringWithPrefix(discover.selected.request.path.type, 'TYPE_')
: t.string('SELECT_TYPE'),
onSelect: (value) => {
- window.location = value;
+ navigate(value.replace('#', ''));
}
};
const catalogSelect = {
@@ -39,8 +40,8 @@ const mapSelectableInputs = (discover, t) => {
}
:
t.string('SELECT_CATALOG'),
- onSelect: (value) => {
- window.location =value;
+ onSelect: (event) => {
+ navigate(event.value.replace('#', ''));
}
};
const extraSelects = discover.selectable.extra.map(({ name, isRequired, options }) => {
@@ -63,7 +64,7 @@ const mapSelectableInputs = (discover, t) => {
: t.string(selectedExtra.value),
onSelect: (value) => {
const { href } = JSON.parse(value);
- window.location = href;
+ navigate(href.replace('#', ''));
}
};
});
@@ -72,8 +73,9 @@ const mapSelectableInputs = (discover, t) => {
const useSelectableInputs = (discover) => {
const t = useTranslate();
+ const navigate = useNavigate();
const selectableInputs = React.useMemo(() => {
- return mapSelectableInputs(discover, t);
+ return mapSelectableInputs(discover, t, navigate);
}, [discover.selected, discover.selectable]);
return selectableInputs;
};
diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js
index 48acadcd4..66ab84d69 100644
--- a/src/routes/Intro/Intro.js
+++ b/src/routes/Intro/Intro.js
@@ -2,12 +2,13 @@
const React = require('react');
const { useTranslation } = require('react-i18next');
-const PropTypes = require('prop-types');
+const { useSearchParams, useNavigate } = require('react-router-dom');
const classnames = require('classnames');
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 { useBinaryState } = require('stremio/common');
+const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { Button, Image, Checkbox } = require('stremio/components');
const CredentialsTextInput = require('./CredentialsTextInput');
const PasswordResetModal = require('./PasswordResetModal');
@@ -19,7 +20,9 @@ const styles = require('./styles');
const SIGNUP_FORM = 'signup';
const LOGIN_FORM = 'login';
-const Intro = ({ queryParams }) => {
+const Intro = () => {
+ const [queryParams, setQueryParams] = useSearchParams();
+ const navigate = useNavigate();
const { core } = useServices();
const { t } = useTranslation();
const routeFocused = useRouteFocused();
@@ -163,7 +166,7 @@ const Intro = ({ queryParams }) => {
dispatch({ type: 'error', error: 'You must accept the Terms of Service' });
return;
}
- window.location = '#/';
+ navigate('/');
}, [state.termsAccepted]);
const signup = React.useCallback(() => {
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 queryParams = new URLSearchParams([['form', state.form === SIGNUP_FORM ? LOGIN_FORM : SIGNUP_FORM]]);
- window.location = `#/intro?${queryParams.toString()}`;
+ setQueryParams(queryParams);
}, [state.form]);
React.useEffect(() => {
if ([LOGIN_FORM, SIGNUP_FORM].includes(queryParams.get('form'))) {
@@ -273,7 +276,7 @@ const Intro = ({ queryParams }) => {
case 'UserAuthenticated': {
closeLoaderModal();
if (routeFocused) {
- window.location = '#/';
+ navigate('/');
}
break;
}
@@ -434,8 +437,4 @@ const Intro = ({ queryParams }) => {
);
};
-Intro.propTypes = {
- queryParams: PropTypes.instanceOf(URLSearchParams)
-};
-
module.exports = Intro;
diff --git a/src/routes/Intro/PasswordResetModal/PasswordResetModal.js b/src/routes/Intro/PasswordResetModal/PasswordResetModal.js
index 8c69c1489..1cb83a3c8 100644
--- a/src/routes/Intro/PasswordResetModal/PasswordResetModal.js
+++ b/src/routes/Intro/PasswordResetModal/PasswordResetModal.js
@@ -3,7 +3,7 @@
const React = require('react');
const { useTranslation } = require('react-i18next');
const PropTypes = require('prop-types');
-const { useRouteFocused } = require('stremio-router');
+const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { usePlatform } = require('stremio/common');
const { ModalDialog } = require('stremio/components');
const CredentialsTextInput = require('../CredentialsTextInput');
diff --git a/src/routes/Library/Library.js b/src/routes/Library/Library.js
index 16a4c79b5..0c165eff6 100644
--- a/src/routes/Library/Library.js
+++ b/src/routes/Library/Library.js
@@ -2,10 +2,12 @@
const React = require('react');
const { useTranslation } = require('react-i18next');
+const { useLocation, useParams, useNavigate } = require('react-router');
+const { useSearchParams } = require('react-router-dom');
const PropTypes = require('prop-types');
const classnames = require('classnames');
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 { default: Placeholder } = require('./Placeholder');
const useLibrary = require('./useLibrary');
@@ -14,40 +16,37 @@ const styles = require('./styles');
const SCROLL_TO_BOTTOM_TRESHOLD = 400;
-function withModel(Library) {
- const withModel = ({ urlParams, queryParams }) => {
+function withModel(Library, useLocation) {
+ const withModel = () => {
+ const location = useLocation();
const model = React.useMemo(() => {
- return typeof urlParams.path === 'string' ?
- urlParams.path.match(routesRegexp.library.regexp) ?
+ return typeof location.pathname === 'string' ?
+ location.pathname.match('/library') ?
'library'
:
- urlParams.path.match(routesRegexp.continuewatching.regexp) ?
+ location.pathname.match('/continuewatching') ?
'continue_watching'
:
null
:
null;
- }, [urlParams.path]);
- if (model === null) {
- return (
-
- );
- }
+ }, [location?.pathname]);
- return (
-
- );
+ if (model === null) return ;
+
+ return ;
};
withModel.displayName = 'withModel';
return withModel;
}
-const Library = ({ model, urlParams, queryParams }) => {
+const Library = ({ model }) => {
+ const { type } = useParams();
+ const urlParams = React.useMemo(() => ({
+ type
+ }), [type]);
+ const [queryParams] = useSearchParams();
+ const navigate = useNavigate();
const { t } = useTranslation();
const profile = useProfile();
const notifications = useNotifications();
@@ -66,8 +65,8 @@ const Library = ({ model, urlParams, queryParams }) => {
}
}, [profile.auth, library.selected]);
React.useEffect(() => {
- if (!library.selected?.type && typeSelect.value) {
- window.location = typeSelect.value;
+ if (!library.selected?.type && typeSelect.selected) {
+ navigate(typeSelect.selected[0].replace('#', ''));
}
}, [typeSelect.value, library.selected]);
return (
@@ -120,10 +119,6 @@ const Library = ({ model, urlParams, queryParams }) => {
Library.propTypes = {
model: PropTypes.oneOf(['library', 'continue_watching']),
- urlParams: PropTypes.shape({
- type: PropTypes.string
- }),
- queryParams: PropTypes.instanceOf(URLSearchParams)
};
const LibraryFallback = ({ model }) => (
@@ -132,4 +127,4 @@ const LibraryFallback = ({ model }) => (
LibraryFallback.propTypes = Library.propTypes;
-module.exports = withModel(withCoreSuspender(Library, LibraryFallback));
+module.exports = withModel(withCoreSuspender(Library, LibraryFallback), useLocation);
diff --git a/src/routes/Library/useSelectableInputs.js b/src/routes/Library/useSelectableInputs.js
index 84816b646..7b72f6b10 100644
--- a/src/routes/Library/useSelectableInputs.js
+++ b/src/routes/Library/useSelectableInputs.js
@@ -1,8 +1,10 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
+const { useNavigate } = require('react-router');
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 typeSelect = {
options: library.selectable.types
@@ -12,7 +14,7 @@ const mapSelectableInputs = (library, t) => {
})),
value: selectedType?.deepLinks.library,
onSelect: (value) => {
- window.location = value;
+ navigate(value.replace('#', ''));
}
};
const sortChips = {
@@ -25,7 +27,7 @@ const mapSelectableInputs = (library, t) => {
.filter(({ selected }) => selected)
.map(({ deepLinks }) => deepLinks.library),
onSelect: (value) => {
- window.location = value;
+ navigate(value.replace('#', ''));
}
};
return [typeSelect, sortChips, library.selectable.nextPage];
@@ -33,8 +35,9 @@ const mapSelectableInputs = (library, t) => {
const useSelectableInputs = (library) => {
const t = useTranslate();
+ const navigate = useNavigate();
const selectableInputs = React.useMemo(() => {
- return mapSelectableInputs(library, t);
+ return mapSelectableInputs(library, t, navigate);
}, [library]);
return selectableInputs;
};
diff --git a/src/routes/MetaDetails/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/EpisodePicker/EpisodePicker.tsx
index 256c827a9..778c806d7 100644
--- a/src/routes/MetaDetails/EpisodePicker/EpisodePicker.tsx
+++ b/src/routes/MetaDetails/EpisodePicker/EpisodePicker.tsx
@@ -1,6 +1,7 @@
// Copyright (C) 2017-2025 Smart code 203358507
import React, { useCallback, useMemo, useState, ChangeEvent } from 'react';
+import { useLocation } from 'react-router';
import { useTranslation } from 'react-i18next';
import { Button, NumberInput } from 'stremio/components';
import styles from './EpisodePicker.less';
@@ -13,9 +14,10 @@ type Props = {
const EpisodePicker = ({ className, onSubmit }: Props) => {
const { t } = useTranslation();
+ const location = useLocation();
const { initialSeason, initialEpisode } = useMemo(() => {
- const splitPath = window.location.hash.split('/');
+ const splitPath = location.pathname.split('/');
const videoId = decodeURIComponent(splitPath[splitPath.length - 1]);
const [, pathSeason, pathEpisode] = videoId ? videoId.split(':') : [];
return {
diff --git a/src/routes/MetaDetails/MetaDetails.js b/src/routes/MetaDetails/MetaDetails.js
index d806ffed5..69669ba02 100644
--- a/src/routes/MetaDetails/MetaDetails.js
+++ b/src/routes/MetaDetails/MetaDetails.js
@@ -1,11 +1,12 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
+const { useParams, useLocation, useNavigate } = require('react-router');
const { useTranslation } = require('react-i18next');
-const PropTypes = require('prop-types');
const classnames = require('classnames');
const { useServices } = require('stremio/services');
const { withCoreSuspender } = require('stremio/common');
+const { useNavigateWithOrigin } = require('stremio/common/useNavigateWithOrigin');
const { VerticalNavBar, HorizontalNavBar, DelayedRenderer, Image, MetaPreview, ModalDialog } = require('stremio/components');
const StreamsList = require('./StreamsList');
const VideosList = require('./VideosList');
@@ -14,11 +15,20 @@ const useSeason = require('./useSeason');
const useMetaExtensionTabs = require('./useMetaExtensionTabs');
const styles = require('./styles');
-const MetaDetails = ({ urlParams, queryParams }) => {
+const MetaDetails = () => {
+ const { type, id, videoId } = useParams();
+ const location = useLocation();
+ const navigate = useNavigate();
+ const { getStoredOrigin } = useNavigateWithOrigin();
const { t } = useTranslation();
const { core } = useServices();
+ const urlParams = React.useMemo(() => ({
+ type,
+ id,
+ videoId
+ }), [type, id, videoId]);
const metaDetails = useMetaDetails(urlParams);
- const [season, setSeason] = useSeason(urlParams, queryParams);
+ const [season, setSeason] = useSeason(urlParams);
const [tabs, metaExtension, clearMetaExtension] = useMetaExtensionTabs(metaDetails.metaExtensions);
const [metaPath, streamPath] = React.useMemo(() => {
return metaDetails.selected !== null ?
@@ -80,10 +90,10 @@ const MetaDetails = ({ urlParams, queryParams }) => {
}, [setSeason]);
const handleEpisodeSearch = React.useCallback((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);
- window.location = searchVideoPath;
- }, [urlParams, window.location]);
+ navigate(searchVideoPath, { replace: true });
+ }, [urlParams, location]);
const renderBackgroundImageFallback = React.useCallback(() => null, []);
const renderBackground = React.useMemo(() => !!(
@@ -93,7 +103,7 @@ const MetaDetails = ({ urlParams, queryParams }) => {
typeof metaDetails.metaItem.content.content?.background === 'string' &&
metaDetails.metaItem.content.content.background.length > 0
), [metaPath, metaDetails]);
-
+ const originPath = React.useMemo(() => getStoredOrigin(), [getStoredOrigin]);
return (
{
@@ -114,6 +124,7 @@ const MetaDetails = ({ urlParams, queryParams }) => {
backButton={true}
fullscreenButton={true}
navMenu={true}
+ originPath={originPath}
/>
{
@@ -216,15 +227,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 { core } = useServices();
const platform = usePlatform();
const profile = useProfile();
+ const navigate = useNavigate();
const streamsContainerRef = React.useRef(null);
const [selectedAddon, setSelectedAddon] = React.useState(ALL_ADDONS_KEY);
const onAddonSelected = React.useCallback((value) => {
@@ -30,14 +32,13 @@ const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => {
}, [profile, video]);
const backButtonOnClick = React.useCallback(() => {
if (video.deepLinks && typeof video.deepLinks.metaDetailsVideos === 'string') {
- window.location.replace(video.deepLinks.metaDetailsVideos + (
- typeof video.season === 'number' ?
- `?${new URLSearchParams({ 'season': video.season })}`
- :
- null
- ));
+ const navigateTo = `${video.deepLinks.metaDetailsVideos}${
+ typeof video.season === 'number'
+ ? `?${new URLSearchParams({ 'season': video.season })}`
+ : ''}`;
+ navigate(navigateTo.replace('#', ''), { replace: true });
} else {
- window.history.back();
+ navigate(-1);
}
}, [video]);
const countLoadingAddons = React.useMemo(() => {
diff --git a/src/routes/MetaDetails/useSeason.js b/src/routes/MetaDetails/useSeason.js
index 9d958a5cf..2409913ea 100644
--- a/src/routes/MetaDetails/useSeason.js
+++ b/src/routes/MetaDetails/useSeason.js
@@ -1,8 +1,10 @@
// Copyright (C) 2017-2023 Smart code 203358507
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(() => {
return queryParams.has('season') && !isNaN(queryParams.get('season')) ?
parseInt(queryParams.get('season'), 10)
@@ -12,7 +14,7 @@ const useSeason = (urlParams, queryParams) => {
const setSeason = React.useCallback((season) => {
const nextQueryParams = new URLSearchParams(queryParams);
nextQueryParams.set('season', season);
- window.location.replace(`#${urlParams.path}?${nextQueryParams}`);
+ setQueryParams(nextQueryParams, { replace: true });
}, [urlParams, queryParams]);
return [season, setSeason];
};
diff --git a/src/routes/Player/ControlBar/SeekBar/SeekBar.js b/src/routes/Player/ControlBar/SeekBar/SeekBar.js
index 3140bf5d1..fc51e9122 100644
--- a/src/routes/Player/ControlBar/SeekBar/SeekBar.js
+++ b/src/routes/Player/ControlBar/SeekBar/SeekBar.js
@@ -4,7 +4,7 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const debounce = require('lodash.debounce');
-const { useRouteFocused } = require('stremio-router');
+const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { useBinaryState } = require('stremio/common');
const { Button, Slider } = require('stremio/components');
const formatTime = require('./formatTime');
diff --git a/src/routes/Player/ControlBar/VolumeSlider/VolumeSlider.js b/src/routes/Player/ControlBar/VolumeSlider/VolumeSlider.js
index 1980b4943..6446f7139 100644
--- a/src/routes/Player/ControlBar/VolumeSlider/VolumeSlider.js
+++ b/src/routes/Player/ControlBar/VolumeSlider/VolumeSlider.js
@@ -4,7 +4,7 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const debounce = require('lodash.debounce');
-const { useRouteFocused } = require('stremio-router');
+const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { useServices } = require('stremio/services');
const { Slider } = require('stremio/components');
const styles = require('./styles');
diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js
index 436f1a781..4c101729c 100644
--- a/src/routes/Player/Player.js
+++ b/src/routes/Player/Player.js
@@ -1,12 +1,13 @@
// Copyright (C) 2017-2023 Smart code 203358507
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 debounce = require('lodash.debounce');
const langs = require('langs');
const { useTranslation } = require('react-i18next');
-const { useRouteFocused } = require('stremio-router');
+const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
const { useServices } = require('stremio/services');
const { onFileDrop, useSettings, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS, useShell } = require('stremio/common');
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
@@ -29,7 +30,18 @@ const styles = require('./styles');
const Video = require('./Video');
const { default: Indicator } = require('./Indicator/Indicator');
-const Player = ({ urlParams, queryParams }) => {
+const Player = () => {
+ const { stream, streamTransportUrl, metaTransportUrl, type, id, videoId } = useParams();
+ const urlParams = React.useMemo(() => ({
+ stream,
+ streamTransportUrl,
+ metaTransportUrl,
+ type,
+ id,
+ videoId
+ }), [stream, streamTransportUrl, metaTransportUrl, type, id, videoId]);
+ const [queryParams] = useSearchParams();
+ const navigate = useNavigate();
const { t } = useTranslation();
const services = useServices();
const shell = useShell();
@@ -107,10 +119,10 @@ const Player = ({ urlParams, queryParams }) => {
const handleNextVideoNavigation = React.useCallback((deepLinks) => {
if (deepLinks.player) {
isNavigating.current = true;
- window.location.replace(deepLinks.player);
+ navigate(deepLinks.player.replace('#', ''), { replace: true });
} else if (deepLinks.metaDetailsStreams) {
isNavigating.current = true;
- window.location.replace(deepLinks.metaDetailsStreams);
+ navigate(deepLinks.metaDetailsStreams.replace('#', ''), { replace: true });
}
}, []);
@@ -128,7 +140,7 @@ const Player = ({ urlParams, queryParams }) => {
const deepLinks = window.playerNextVideo.deepLinks;
handleNextVideoNavigation(deepLinks);
} else {
- window.history.back();
+ navigate(-1);
}
}, []);
@@ -395,7 +407,7 @@ const Player = ({ urlParams, queryParams }) => {
}, [settings.subtitlesOutlineColor]);
React.useEffect(() => {
- !seeking && timeChanged(video.state.time, video.state.duration, video.state.manifest?.name);
+ !isNavigating.current && !seeking && timeChanged(video.state.time, video.state.duration, video.state.manifest?.name);
}, [video.state.time, video.state.duration, video.state.manifest, seeking]);
React.useEffect(() => {
@@ -638,7 +650,7 @@ const Player = ({ urlParams, queryParams }) => {
}
case 'Escape': {
closeMenus();
- !settings.escExitFullscreen && window.history.back();
+ !settings.escExitFullscreen && navigate(-1);
break;
}
}
@@ -866,7 +878,7 @@ const Player = ({ urlParams, queryParams }) => {
metaItem={player.metaItem?.content}
seriesInfo={player.seriesInfo}
closeSideDrawer={closeSideDrawer}
- selected={player.selected?.streamRequest?.path.id}
+ selected={player.selected?.streamRequest?.path?.id}
/>
{
@@ -930,18 +942,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 = () => (
);
diff --git a/src/routes/Search/Search.js b/src/routes/Search/Search.js
index 58e6e834b..4e1955769 100644
--- a/src/routes/Search/Search.js
+++ b/src/routes/Search/Search.js
@@ -1,7 +1,6 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
-const PropTypes = require('prop-types');
const classnames = require('classnames');
const debounce = require('lodash.debounce');
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 useSearch = require('./useSearch');
const styles = require('./styles');
+const { useSearchParams } = require('react-router-dom');
const THRESHOLD = 100;
-const Search = ({ queryParams }) => {
+const Search = () => {
+ const [queryParams] = useSearchParams();
const t = useTranslate();
const [search, loadSearchRows] = useSearch(queryParams);
const query = React.useMemo(() => {
@@ -127,14 +128,9 @@ const Search = ({ queryParams }) => {
);
};
-Search.propTypes = {
- queryParams: PropTypes.instanceOf(URLSearchParams)
+const SearchFallback = () => {
+ const [queryParams] = useSearchParams();
+ return ;
};
-const SearchFallback = ({ queryParams }) => (
-
-);
-
-SearchFallback.propTypes = Search.propTypes;
-
module.exports = withCoreSuspender(Search, SearchFallback);
diff --git a/src/routes/Settings/Settings.tsx b/src/routes/Settings/Settings.tsx
index b37d1f0c6..3e66f2fb5 100644
--- a/src/routes/Settings/Settings.tsx
+++ b/src/routes/Settings/Settings.tsx
@@ -3,8 +3,7 @@
import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import classnames from 'classnames';
import throttle from 'lodash.throttle';
-import { useRouteFocused } from 'stremio-router';
-import { useProfile, useStreamingServer, withCoreSuspender } from 'stremio/common';
+import { useProfile, useStreamingServer, useRouteFocused, withCoreSuspender } from 'stremio/common';
import { MainNavBars } from 'stremio/components';
import { SECTIONS } from './constants';
import Menu from './Menu';
@@ -16,7 +15,7 @@ import Info from './Info';
import styles from './Settings.less';
const Settings = () => {
- const { routeFocused } = useRouteFocused();
+ const routeFocused = useRouteFocused();
const profile = useProfile();
const streamingServer = useStreamingServer();