mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-05-24 04:22:12 +00:00
Add navigation with React Router V6
This commit is contained in:
parent
87ccc591df
commit
7a3f331865
59 changed files with 615 additions and 394 deletions
|
|
@ -40,6 +40,8 @@
|
|||
"react-focus-lock": "2.13.7",
|
||||
"react-i18next": "^15.7.4",
|
||||
"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#f8ef365fcc90b7904a11ad2e3ebb95c0c9b16163",
|
||||
"url": "0.11.4",
|
||||
|
|
|
|||
|
|
@ -86,6 +86,12 @@ importers:
|
|||
react-is:
|
||||
specifier: 18.3.1
|
||||
version: 18.3.1
|
||||
react-router:
|
||||
specifier: 6.30.0
|
||||
version: 6.30.0(react@18.3.1)
|
||||
react-router-dom:
|
||||
specifier: 6.30.0
|
||||
version: 6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
spatial-navigation-polyfill:
|
||||
specifier: github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6
|
||||
version: https://codeload.github.com/Stremio/spatial-navigation/tar.gz/64871b1422466f5f45d24ebc8bbd315b2ebab6a6
|
||||
|
|
@ -1180,6 +1186,10 @@ packages:
|
|||
resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@remix-run/router@1.23.0':
|
||||
resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@rollup/plugin-babel@6.1.0':
|
||||
resolution: {integrity: sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
|
@ -4096,6 +4106,19 @@ packages:
|
|||
react-is@18.3.1:
|
||||
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
||||
|
||||
react-router-dom@6.30.0:
|
||||
resolution: {integrity: sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=16.8'
|
||||
react-dom: '>=16.8'
|
||||
|
||||
react-router@6.30.0:
|
||||
resolution: {integrity: sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=16.8'
|
||||
|
||||
react@18.3.1:
|
||||
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -6283,6 +6306,8 @@ snapshots:
|
|||
tslib: 2.8.1
|
||||
tsyringe: 4.10.0
|
||||
|
||||
'@remix-run/router@1.23.0': {}
|
||||
|
||||
'@rollup/plugin-babel@6.1.0(@babel/core@7.29.0)(@types/babel__core@7.20.5)(rollup@4.60.3)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
|
|
@ -9591,6 +9616,18 @@ snapshots:
|
|||
|
||||
react-is@18.3.1: {}
|
||||
|
||||
react-router-dom@6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@remix-run/router': 1.23.0
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-router: 6.30.0(react@18.3.1)
|
||||
|
||||
react-router@6.30.0(react@18.3.1):
|
||||
dependencies:
|
||||
'@remix-run/router': 1.23.0
|
||||
react: 18.3.1
|
||||
|
||||
react@18.3.1:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
|
|
|||
|
|
@ -6,19 +6,15 @@ const { useTranslation } = require('react-i18next');
|
|||
const { useCore } = require('stremio/core');
|
||||
const { Router } = require('stremio-router');
|
||||
const { Chromecast, ServicesProvider, GamepadProvider } = require('stremio/services');
|
||||
const { NotFound } = require('stremio/routes');
|
||||
const { FullscreenProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, useBinaryState, useProfile, withCoreSuspender, onFileDrop, usePlatform } = require('stremio/common');
|
||||
const ServicesToaster = require('./ServicesToaster');
|
||||
const DeepLinkHandler = require('./DeepLinkHandler');
|
||||
const SearchParamsHandler = require('./SearchParamsHandler');
|
||||
const { default: UpdaterBanner } = require('./UpdaterBanner');
|
||||
const { default: ShortcutsModal } = require('./ShortcutsModal');
|
||||
const { default: GamepadModal } = require('./GamepadModal');
|
||||
const withProtectedRoutes = require('./withProtectedRoutes');
|
||||
const routerViewsConfig = require('./routerViewsConfig');
|
||||
const styles = require('./styles');
|
||||
|
||||
const RouterWithProtectedRoutes = withProtectedRoutes(Router);
|
||||
const RouterWithProtectedRoutes = withCoreSuspender(Router);
|
||||
|
||||
const App = () => {
|
||||
const core = useCore();
|
||||
|
|
@ -26,9 +22,6 @@ const App = () => {
|
|||
const { i18n } = useTranslation();
|
||||
const { shell } = usePlatform();
|
||||
const [gamepadSupportEnabled, setGamepadSupportEnabled] = React.useState(false);
|
||||
const onPathNotMatch = React.useCallback(() => {
|
||||
return NotFound;
|
||||
}, []);
|
||||
const services = React.useMemo(() => {
|
||||
return {
|
||||
chromecast: new Chromecast(),
|
||||
|
|
@ -37,7 +30,7 @@ const App = () => {
|
|||
const [shortcutModalOpen,, closeShortcutsModal, toggleShortcutModal] = useBinaryState(false);
|
||||
const [gamepadModalOpen,, closeGamepadModal, toggleGamepadModal] = useBinaryState(false);
|
||||
|
||||
const onShortcut = React.useCallback((name, combo, key) => {
|
||||
const onShortcut = React.useCallback((name) => {
|
||||
switch (name) {
|
||||
case 'shortcuts':
|
||||
toggleShortcutModal();
|
||||
|
|
@ -45,18 +38,6 @@ const App = () => {
|
|||
case 'gamepadGuide':
|
||||
toggleGamepadModal();
|
||||
break;
|
||||
case 'navigateSearch':
|
||||
window.location = '#/search';
|
||||
break;
|
||||
case 'navigateTabs': {
|
||||
const routes = ['', 'discover', 'library', 'calendar', 'addons', 'settings'];
|
||||
const index = key - 1;
|
||||
if (index in routes) window.location = `#/${routes[index]}`;
|
||||
break;
|
||||
}
|
||||
case 'navigateHistory':
|
||||
combo === 0 ? window.history.back() : window.history.forward();
|
||||
break;
|
||||
}
|
||||
}, [toggleShortcutModal, toggleGamepadModal]);
|
||||
|
||||
|
|
@ -106,31 +87,6 @@ const App = () => {
|
|||
};
|
||||
}, []);
|
||||
|
||||
// Handle shell events
|
||||
React.useEffect(() => {
|
||||
const onOpenMedia = (data) => {
|
||||
try {
|
||||
const { protocol, hostname, pathname, searchParams } = new URL(data);
|
||||
if (protocol === CONSTANTS.PROTOCOL) {
|
||||
if (hostname.length) {
|
||||
const transportUrl = `https://${hostname}${pathname}`;
|
||||
window.location.href = `#/addons?addon=${encodeURIComponent(transportUrl)}`;
|
||||
} else {
|
||||
window.location.href = `#${pathname}?${searchParams.toString()}`;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to open media:', e);
|
||||
}
|
||||
};
|
||||
|
||||
shell.on('open-media', onOpenMedia);
|
||||
|
||||
return () => {
|
||||
shell.off('open-media', onOpenMedia);
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof profile.settings?.interfaceLanguage === 'string') {
|
||||
i18n.changeLanguage(profile.settings.interfaceLanguage);
|
||||
|
|
@ -196,13 +152,10 @@ const App = () => {
|
|||
gamepadModalOpen && <GamepadModal onClose={closeGamepadModal}/>
|
||||
}
|
||||
<ServicesToaster />
|
||||
<DeepLinkHandler />
|
||||
<SearchParamsHandler />
|
||||
<UpdaterBanner className={styles['updater-banner-container']} />
|
||||
<RouterWithProtectedRoutes
|
||||
className={styles['router']}
|
||||
viewsConfig={routerViewsConfig}
|
||||
onPathNotMatch={onPathNotMatch}
|
||||
/>
|
||||
</FullscreenProvider>
|
||||
</ShortcutsProvider>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useNavigate } = require('react-router');
|
||||
const { withCoreSuspender, useStreamingServer } = require('stremio/common');
|
||||
const { default: toPath } = require('stremio/common/toPath');
|
||||
|
||||
const DeepLinkHandler = () => {
|
||||
const navigate = useNavigate();
|
||||
const streamingServer = useStreamingServer();
|
||||
React.useEffect(() => {
|
||||
if (streamingServer.torrent !== null) {
|
||||
|
|
@ -11,7 +14,7 @@ const DeepLinkHandler = () => {
|
|||
if (type === 'Ready') {
|
||||
const [, deepLinks] = content;
|
||||
if (typeof deepLinks.metaDetailsVideos === 'string') {
|
||||
window.location = deepLinks.metaDetailsVideos;
|
||||
navigate(toPath(deepLinks.metaDetailsVideos));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
35
src/App/OpenMediaHandler.tsx
Normal file
35
src/App/OpenMediaHandler.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (C) 2017-2026 Smart code 203358507
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { usePlatform, CONSTANTS } from 'stremio/common';
|
||||
|
||||
const OpenMediaHandler = () => {
|
||||
const navigate = useNavigate();
|
||||
const { shell } = usePlatform();
|
||||
|
||||
useEffect(() => {
|
||||
const onOpenMedia = (data: string) => {
|
||||
try {
|
||||
const { protocol, hostname, pathname, searchParams } = new URL(data);
|
||||
if (protocol === CONSTANTS.PROTOCOL) {
|
||||
if (hostname.length) {
|
||||
const transportUrl = `https://${hostname}${pathname}`;
|
||||
navigate(`/addons?addon=${encodeURIComponent(transportUrl)}`);
|
||||
} else {
|
||||
navigate(`${pathname}?${searchParams.toString()}`);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to open media:', e);
|
||||
}
|
||||
};
|
||||
|
||||
shell.on('open-media', onOpenMedia);
|
||||
return () => shell.off('open-media', onOpenMedia);
|
||||
}, [navigate, shell]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default OpenMediaHandler;
|
||||
|
|
@ -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;
|
||||
|
|
@ -4,7 +4,7 @@ import shortcuts from './shortcuts.json';
|
|||
const SHORTCUTS = shortcuts.map(({ shortcuts }) => shortcuts).flat();
|
||||
|
||||
export type ShortcutName = string;
|
||||
export type ShortcutListener = (combo: number) => void;
|
||||
export type ShortcutListener = (combo: number, key: string) => void;
|
||||
|
||||
interface ShortcutsContext {
|
||||
grouped: ShortcutGroup[],
|
||||
|
|
@ -51,7 +51,7 @@ const ShortcutsProvider = ({ children, onShortcut }: Props) => {
|
|||
|
||||
if (modifers && (keys.includes(code) || keys.includes(key.toUpperCase()))) {
|
||||
const combo = combos.indexOf(keys);
|
||||
listeners.current.get(name)?.forEach((listener) => listener(combo));
|
||||
listeners.current.get(name)?.forEach((listener) => listener(combo, key));
|
||||
|
||||
onShortcut(name as ShortcutName, combo, key);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 useStreamingServer = require('./useStreamingServer');
|
||||
const { default: useTimeout } = require('./useTimeout');
|
||||
|
|
@ -61,6 +62,7 @@ module.exports = {
|
|||
useNotifications,
|
||||
useOnScrollToBottom,
|
||||
useProfile,
|
||||
useRouteFocused,
|
||||
useSettings,
|
||||
useStreamingServer,
|
||||
useTimeout,
|
||||
|
|
|
|||
72
src/common/routerPaths.tsx
Normal file
72
src/common/routerPaths.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright (C) 2017-2025 Smart code 203358507
|
||||
|
||||
import React from 'react';
|
||||
import routes from 'stremio/routes';
|
||||
|
||||
export const routerPaths = [
|
||||
{
|
||||
path: '/intro',
|
||||
view: 1,
|
||||
element: <routes.Intro />,
|
||||
},
|
||||
{
|
||||
path: '/discover/:transportUrl?/:type?/:catalogId?',
|
||||
view: 1,
|
||||
element: <routes.Discover />,
|
||||
},
|
||||
{
|
||||
path: '/library/:type?',
|
||||
view: 1,
|
||||
element: <routes.Library />,
|
||||
},
|
||||
{
|
||||
path: '/calendar/:year?/:month?',
|
||||
view: 1,
|
||||
element: <routes.Calendar />,
|
||||
},
|
||||
{
|
||||
path: '/continuewatching/:type?',
|
||||
view: 1,
|
||||
element: <routes.Library />,
|
||||
},
|
||||
{
|
||||
path: '/search',
|
||||
view: 1,
|
||||
element: <routes.Search />,
|
||||
},
|
||||
{
|
||||
path: '/metadetails/:type?/:id?/:videoId?',
|
||||
view: 2,
|
||||
element: <routes.MetaDetails />,
|
||||
},
|
||||
{
|
||||
path: '/detail/:type?/:id?/:videoId?',
|
||||
view: 2,
|
||||
element: <routes.MetaDetails />,
|
||||
},
|
||||
{
|
||||
path: '/addons/:type?/:transportUrl?/:catalogId?',
|
||||
view: 3,
|
||||
element: <routes.Addons />,
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
view: 3,
|
||||
element: <routes.Settings />,
|
||||
},
|
||||
{
|
||||
path: '/player/:stream?/:streamTransportUrl?/:metaTransportUrl?/:type?/:id?/:videoId?',
|
||||
view: 4,
|
||||
element: <routes.Player />,
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
view: 0,
|
||||
element: <routes.Board />,
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
view: 1,
|
||||
element: <routes.NotFound />,
|
||||
},
|
||||
];
|
||||
5
src/common/toPath.ts
Normal file
5
src/common/toPath.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2026 Smart code 203358507
|
||||
|
||||
const toPath = (link: string): string => link.startsWith('#') ? link.slice(1) : link;
|
||||
|
||||
export default toPath;
|
||||
|
|
@ -6,7 +6,7 @@ const { deepEqual } = require('fast-equals');
|
|||
const intersection = require('lodash.intersection');
|
||||
const { useCore } = require('stremio/core');
|
||||
const { useCoreSuspender } = require('stremio/common/CoreSuspender');
|
||||
const { useRouteFocused } = require('stremio-router');
|
||||
const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
|
||||
|
||||
const useModelState = ({ action, ...args }) => {
|
||||
const core = useCore();
|
||||
|
|
|
|||
39
src/common/useNavigateWithOrigin.ts
Normal file
39
src/common/useNavigateWithOrigin.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
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): string | undefined {
|
||||
if (location.state?.from) {
|
||||
const from = location.state.from as Location;
|
||||
return from.pathname + (from.search || '');
|
||||
}
|
||||
return sessionStorage.getItem(ORIGIN_KEY) || fallback;
|
||||
}
|
||||
|
||||
return {
|
||||
navigateWithOrigin,
|
||||
getStoredOrigin,
|
||||
setOriginPath,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import magnet from 'magnet-uri';
|
||||
import { useCore } from 'stremio/core';
|
||||
import useToast from 'stremio/common/Toast/useToast';
|
||||
|
|
@ -8,6 +9,7 @@ import useStreamingServer from 'stremio/common/useStreamingServer';
|
|||
const HTTP_REGEX = /^https?:\/\/.+/i;
|
||||
|
||||
const usePlayUrl = () => {
|
||||
const navigate = useNavigate();
|
||||
const core = useCore();
|
||||
const toast = useToast();
|
||||
const { createTorrentFromMagnet } = useTorrent();
|
||||
|
|
@ -30,7 +32,7 @@ const usePlayUrl = () => {
|
|||
url: trimmed,
|
||||
});
|
||||
if (typeof encoded === 'string') {
|
||||
window.location.hash = `#/player/${encodeURIComponent(encoded)}`;
|
||||
navigate(`/player/${encodeURIComponent(encoded)}`);
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
|||
29
src/common/useRouteFocused.ts
Normal file
29
src/common/useRouteFocused.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (C) 2017-2025 Smart code 203358507
|
||||
|
||||
import React from 'react';
|
||||
|
||||
const RouteFocusedContext = React.createContext(true);
|
||||
|
||||
const useRouteFocused = () => {
|
||||
const routeFocused = React.useContext(RouteFocusedContext);
|
||||
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 routeFocused && isFocused;
|
||||
};
|
||||
|
||||
export const RouteFocusedProvider = RouteFocusedContext.Provider;
|
||||
|
||||
export default useRouteFocused;
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useNavigate } = require('react-router');
|
||||
const { default: toPath } = require('stremio/common/toPath');
|
||||
const PropTypes = require('prop-types');
|
||||
const { useCore } = require('stremio/core');
|
||||
const MetaItem = require('stremio/components/MetaItem');
|
||||
const { t } = require('i18next');
|
||||
|
||||
const LibItem = ({ _id, removable, notifications, watched, ...props }) => {
|
||||
const navigate = useNavigate();
|
||||
const core = useCore();
|
||||
|
||||
const newVideos = React.useMemo(() => {
|
||||
|
|
@ -49,7 +52,7 @@ 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;
|
||||
navigate(toPath(props.deepLinks.player));
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -57,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(toPath(props.deepLinks.metaDetailsVideos));
|
||||
} else if (typeof props.deepLinks.metaDetailsStreams === 'string') {
|
||||
window.location = props.deepLinks.metaDetailsStreams;
|
||||
navigate(toPath(props.deepLinks.metaDetailsStreams));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +125,7 @@ const LibItem = ({ _id, removable, notifications, watched, ...props }) => {
|
|||
if (props.deepLinks && typeof props.deepLinks.player === 'string') {
|
||||
return (event) => {
|
||||
event.preventDefault();
|
||||
window.location = props.deepLinks.player;
|
||||
navigate(toPath(props.deepLinks.player));
|
||||
};
|
||||
}
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import { useContentGamepadNavigation, useVerticalNavGamepadNavigation } from 'st
|
|||
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 = {
|
||||
|
|
|
|||
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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,10 +13,15 @@ const NavMenu = require('./NavMenu');
|
|||
const styles = require('./styles');
|
||||
const { t } = require('i18next');
|
||||
|
||||
const HorizontalNavBar = React.memo(({ className, route, query, title, backButton, searchBar, fullscreenButton, navMenu, hdrInfo, ...props }) => {
|
||||
const HorizontalNavBar = React.memo(({ className, route, query, title, backButton, searchBar, fullscreenButton, navMenu, originPath, hdrInfo, ...props }) => {
|
||||
const navigate = useNavigate();
|
||||
const backButtonOnClick = React.useCallback(() => {
|
||||
window.history.back();
|
||||
}, []);
|
||||
if (originPath) {
|
||||
navigate(originPath);
|
||||
} else {
|
||||
navigate(-1);
|
||||
}
|
||||
}, [originPath, navigate]);
|
||||
const [fullscreen, requestFullscreen, exitFullscreen, , supported] = useFullscreen();
|
||||
const renderNavMenuLabel = React.useCallback(({ ref, className, onClick, children, }) => (
|
||||
<Button ref={ref} className={classnames(className, styles['button-container'], styles['menu-button-container'])} tabIndex={-1} onClick={onClick}>
|
||||
|
|
@ -91,6 +97,7 @@ HorizontalNavBar.propTypes = {
|
|||
searchBar: PropTypes.bool,
|
||||
fullscreenButton: PropTypes.bool,
|
||||
navMenu: PropTypes.bool,
|
||||
originPath: PropTypes.string,
|
||||
hdrInfo: PropTypes.shape({
|
||||
gamma: PropTypes.string,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
@ -18,6 +19,7 @@ const styles = require('./styles');
|
|||
|
||||
const NavMenuContent = ({ onClick }) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const core = useCore();
|
||||
const profile = useProfile();
|
||||
const streamingServer = useStreamingServer();
|
||||
|
|
@ -54,6 +56,12 @@ const NavMenuContent = ({ onClick }) => {
|
|||
console.error(e);
|
||||
}
|
||||
}, [handlePlayUrl]);
|
||||
const handleAuth = React.useCallback(() => {
|
||||
return profile.auth !== null
|
||||
? logoutButtonOnClick()
|
||||
: navigate('/intro');
|
||||
}, [profile.auth, logoutButtonOnClick, navigate]);
|
||||
|
||||
return (
|
||||
<div className={classnames(styles['nav-menu-container'], 'animation-fade-in', { [styles['with-warning']]: !streamingServerWarningDismissed } )} onClick={onClick}>
|
||||
<div className={styles['user-info-container']}>
|
||||
|
|
@ -73,7 +81,7 @@ const NavMenuContent = ({ onClick }) => {
|
|||
<div className={styles['email-container']}>
|
||||
<div className={styles['email-label']}>{profile.auth === null ? t('ANONYMOUS_USER') : profile.auth.user.email}</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>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 { default: usePlayUrl } = require('stremio/common/usePlayUrl');
|
||||
|
|
@ -21,17 +23,18 @@ const SearchBar = React.memo(({ className, query, active }) => {
|
|||
const routeFocused = useRouteFocused();
|
||||
const searchHistory = useSearchHistory();
|
||||
const localSearch = useLocalSearch();
|
||||
const navigate = useNavigate();
|
||||
const { handlePlayUrl } = usePlayUrl();
|
||||
|
||||
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]);
|
||||
|
||||
|
|
@ -66,7 +69,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();
|
||||
}
|
||||
}, []);
|
||||
|
|
@ -74,7 +77,8 @@ const SearchBar = React.memo(({ className, query, active }) => {
|
|||
const queryInputClear = React.useCallback(() => {
|
||||
searchInputRef.current.value = '';
|
||||
setCurrentQuery('');
|
||||
window.location.hash = '/search';
|
||||
setSearchParams({});
|
||||
navigate('/search');
|
||||
}, []);
|
||||
|
||||
const updateLocalSearchDebounced = React.useCallback(debounce((query) => {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<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 ?
|
||||
<Image
|
||||
|
|
@ -45,7 +46,7 @@ const NavTabButton = ({ className, logo, icon, label, href, selected, onClick })
|
|||
:
|
||||
null
|
||||
}
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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 { useCore } = require('stremio/core');
|
||||
const { Button } = require('stremio/components');
|
||||
const { default: TextInput } = require('stremio/components/TextInput');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -2,20 +2,26 @@
|
|||
|
||||
const React = require('react');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const { useNavigate } = require('react-router');
|
||||
const { default: toPath } = require('stremio/common/toPath');
|
||||
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 = ({ className, id, title, thumbnail, season, episode, released, upcoming, watched, progress, scheduled, seasonWatched, selected, deepLinks, onMarkVideoAsWatched, onMarkSeasonAsWatched, ...props }) => {
|
||||
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) => {
|
||||
|
|
@ -63,9 +69,9 @@ const Video = ({ className, id, title, thumbnail, season, episode, released, upc
|
|||
const videoButtonOnClick = React.useCallback(() => {
|
||||
if (deepLinks) {
|
||||
if (typeof deepLinks.player === 'string') {
|
||||
window.location = deepLinks.player;
|
||||
navigate(toPath(deepLinks.player));
|
||||
} else if (typeof deepLinks.metaDetailsStreams === 'string') {
|
||||
window.location.replace(deepLinks.metaDetailsStreams);
|
||||
navigate(toPath(deepLinks.metaDetailsStreams), { replace: !platform.isMobile });
|
||||
}
|
||||
}
|
||||
}, [deepLinks]);
|
||||
|
|
|
|||
|
|
@ -3,21 +3,25 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const { ModalsContainerProvider } = require('../ModalsContainerContext');
|
||||
const { RouteFocusedProvider } = require('stremio/common/useRouteFocused');
|
||||
|
||||
const Route = ({ children }) => {
|
||||
const Route = ({ component, focused }) => {
|
||||
return (
|
||||
<div className={'route-container'}>
|
||||
<ModalsContainerProvider>
|
||||
<div className={'route-content'}>
|
||||
{children}
|
||||
</div>
|
||||
</ModalsContainerProvider>
|
||||
<RouteFocusedProvider value={focused}>
|
||||
<ModalsContainerProvider>
|
||||
<div className={'route-content'}>
|
||||
{component}
|
||||
</div>
|
||||
</ModalsContainerProvider>
|
||||
</RouteFocusedProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Route.propTypes = {
|
||||
children: PropTypes.node
|
||||
component: PropTypes.node,
|
||||
focused: PropTypes.bool,
|
||||
};
|
||||
|
||||
module.exports = Route;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -1,107 +1,58 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const ReactIs = require('react-is');
|
||||
const { HashRouter } = require('react-router-dom');
|
||||
const { useNavigate } = require('react-router');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const UrlUtils = require('url');
|
||||
const { deepEqual } = require('fast-equals');
|
||||
const { RouteFocusedProvider } = require('../RouteFocusedContext');
|
||||
const Route = require('../Route');
|
||||
const routeConfigForPath = require('./routeConfigForPath');
|
||||
const urlParamsForPath = require('./urlParamsForPath');
|
||||
const { useShortcuts } = require('stremio/common');
|
||||
const DeepLinkHandler = require('stremio/App/DeepLinkHandler');
|
||||
const { default: OpenMediaHandler } = require('stremio/App/OpenMediaHandler');
|
||||
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 NAVIGATE_TABS_ROUTES = ['/', '/discover', '/library', '/calendar', '/addons', '/settings'];
|
||||
|
||||
return;
|
||||
}
|
||||
const KeyboardNavigationHandler = () => {
|
||||
const navigate = useNavigate();
|
||||
const { on, off } = useShortcuts();
|
||||
|
||||
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 && deepEqual(view.urlParams, urlParams) ?
|
||||
view.urlParams
|
||||
:
|
||||
urlParams,
|
||||
queryParams: view !== null && deepEqual(Array.from(view.queryParams.entries()), Array.from(queryParams.entries())) ?
|
||||
view.queryParams
|
||||
:
|
||||
queryParams
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
React.useEffect(() => {
|
||||
const onNavigateSearch = () => navigate('/search');
|
||||
const onNavigateTabs = (_combo, key) => {
|
||||
const index = Number(key) - 1;
|
||||
if (index >= 0 && index < NAVIGATE_TABS_ROUTES.length) navigate(NAVIGATE_TABS_ROUTES[index]);
|
||||
};
|
||||
window.addEventListener('hashchange', onLocationHashChange);
|
||||
onLocationHashChange();
|
||||
const onNavigateHistory = (combo) => navigate(combo === 0 ? -1 : 1);
|
||||
|
||||
on('navigateSearch', onNavigateSearch);
|
||||
on('navigateTabs', onNavigateTabs);
|
||||
on('navigateHistory', onNavigateHistory);
|
||||
return () => {
|
||||
window.removeEventListener('hashchange', onLocationHashChange);
|
||||
off('navigateSearch', onNavigateSearch);
|
||||
off('navigateTabs', onNavigateTabs);
|
||||
off('navigateHistory', onNavigateHistory);
|
||||
};
|
||||
}, [onPathNotMatch, onRouteChange]);
|
||||
}, [navigate, on, off]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const Router = ({ className }) => {
|
||||
|
||||
return (
|
||||
<div className={classnames(className, 'routes-container')}>
|
||||
{
|
||||
views
|
||||
.filter((view) => view !== null)
|
||||
.map(({ key, component, urlParams, queryParams }, index, views) => (
|
||||
<RouteFocusedProvider key={key} value={index === views.length - 1}>
|
||||
<Route>
|
||||
{React.createElement(component, { urlParams, queryParams })}
|
||||
</Route>
|
||||
</RouteFocusedProvider>
|
||||
))
|
||||
}
|
||||
<HashRouter>
|
||||
<DeepLinkHandler />
|
||||
<OpenMediaHandler />
|
||||
<KeyboardNavigationHandler />
|
||||
<Routes />
|
||||
</HashRouter>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
|
|
|
|||
90
src/router/Router/Routes.tsx
Normal file
90
src/router/Router/Routes.tsx
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright (C) 2017-2025 Smart code 203358507
|
||||
|
||||
import React from 'react';
|
||||
import { Routes as RRoutes, Route as RRoute, useLocation, useNavigate, matchPath } from 'react-router';
|
||||
import type { Location } from 'react-router';
|
||||
import { routerPaths } from 'stremio/common/routerPaths';
|
||||
import Route from '../Route/Route';
|
||||
import { useProfile } from 'stremio/common';
|
||||
|
||||
type RouterPath = typeof routerPaths[number];
|
||||
type CachedView = {
|
||||
key: string,
|
||||
location: Location,
|
||||
route: RouterPath,
|
||||
};
|
||||
|
||||
const VIEW_COUNT = Math.max(...routerPaths.map((route) => route.view)) + 1;
|
||||
|
||||
const getRouteForLocation = (location: Location) => {
|
||||
return routerPaths.find((route) => matchPath({ path: route.path, end: true }, location.pathname));
|
||||
};
|
||||
|
||||
const getNextViews = (currentViews: (CachedView | null)[], location: Location) => {
|
||||
const route = getRouteForLocation(location);
|
||||
if (!route) {
|
||||
return currentViews;
|
||||
}
|
||||
|
||||
return Array.from({ length: VIEW_COUNT }, (_, index) => {
|
||||
if (index < route.view) {
|
||||
return currentViews[index] || null;
|
||||
}
|
||||
|
||||
if (index === route.view) {
|
||||
return {
|
||||
key: `${route.view}:${route.path}`,
|
||||
location,
|
||||
route,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
const Routes = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const profile = useProfile();
|
||||
const previousAuthRef = React.useRef(profile.auth);
|
||||
const [views, setViews] = React.useState<(CachedView | null)[]>(() => getNextViews([], location));
|
||||
|
||||
/**
|
||||
* 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.pathname, profile.auth]);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
setViews((currentViews) => getNextViews(currentViews, location));
|
||||
}, [location]);
|
||||
|
||||
const visibleViews = views.filter((view): view is CachedView => view !== null);
|
||||
|
||||
return <>
|
||||
{
|
||||
visibleViews.map((view, index) => (
|
||||
<RRoutes key={view.key} location={view.location}>
|
||||
<RRoute
|
||||
path={view.route.path}
|
||||
element={<Route component={view.route.element} focused={index === visibleViews.length - 1} />}
|
||||
/>
|
||||
</RRoutes>
|
||||
))
|
||||
}
|
||||
</>;
|
||||
};
|
||||
|
||||
export default Routes;
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
@ -17,14 +18,21 @@ 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 = useCore();
|
||||
const toast = useToast();
|
||||
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);
|
||||
|
|
@ -304,16 +312,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 = () => (
|
||||
<MainNavBars className={styles['addons-container']} route={'addons'} />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useNavigate } = require('react-router');
|
||||
const { useTranslate } = require('stremio/common');
|
||||
const { default: toPath } = require('stremio/common/toPath');
|
||||
|
||||
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 +22,10 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => {
|
|||
.find(({ id }) => id === remoteAddons.selected.request.path.id);
|
||||
return selectableCatalog ? t.stringWithPrefix(selectableCatalog.name.toUpperCase(), 'ADDON_') : remoteAddons.selected.request.path.id;
|
||||
}
|
||||
: null,
|
||||
:
|
||||
null,
|
||||
onSelect: (value) => {
|
||||
window.location = value;
|
||||
navigate(toPath(value));
|
||||
}
|
||||
};
|
||||
const selectedType = installedAddons.selected !== null
|
||||
|
|
@ -53,7 +56,7 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => {
|
|||
t.string('SELECT_TYPE');
|
||||
},
|
||||
onSelect: (value) => {
|
||||
window.location = value;
|
||||
navigate(toPath(value));
|
||||
}
|
||||
};
|
||||
return [catalogSelect, typeSelect];
|
||||
|
|
@ -61,8 +64,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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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<HTMLDivElement>(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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import toPath from 'stremio/common/toPath';
|
||||
import Icon from '@stremio/stremio-icons/react';
|
||||
import { Button } from 'stremio/components';
|
||||
import useCalendarDate from '../useCalendarDate';
|
||||
|
|
@ -14,17 +16,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(toPath(prev.deepLinks.calendar));
|
||||
}, [prev]);
|
||||
|
||||
const onNext = useCallback(() => {
|
||||
window.location.href = next.deepLinks.calendar;
|
||||
navigate(toPath(next.deepLinks.calendar));
|
||||
}, [next]);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -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<HTMLDivElement>) => {
|
||||
setOriginPath();
|
||||
event.stopPropagation();
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Params<string>>) => {
|
||||
const action = React.useMemo(() => {
|
||||
const args = urlParams.year && urlParams.month ? {
|
||||
year: parseInt(urlParams.year),
|
||||
|
|
|
|||
|
|
@ -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 { useCore } = require('stremio/core');
|
||||
|
|
@ -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 = useCore();
|
||||
const [discover, loadNextPage] = useDiscover(urlParams, queryParams);
|
||||
|
|
@ -245,15 +253,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 = () => (
|
||||
<MainNavBars className={styles['discover-container']} route={'discover'} />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useNavigate } = require('react-router');
|
||||
const { useTranslate } = require('stremio/common');
|
||||
const { default: toPath } = require('stremio/common/toPath');
|
||||
|
||||
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 +20,7 @@ const mapSelectableInputs = (discover, t) => {
|
|||
? () => t.stringWithPrefix(discover.selected.request.path.type, 'TYPE_')
|
||||
: t.string('SELECT_TYPE'),
|
||||
onSelect: (value) => {
|
||||
window.location = value;
|
||||
navigate(toPath(value));
|
||||
}
|
||||
};
|
||||
const catalogSelect = {
|
||||
|
|
@ -40,7 +42,7 @@ const mapSelectableInputs = (discover, t) => {
|
|||
:
|
||||
t.string('SELECT_CATALOG'),
|
||||
onSelect: (value) => {
|
||||
window.location =value;
|
||||
navigate(toPath(value));
|
||||
}
|
||||
};
|
||||
const extraSelects = discover.selectable.extra.map(({ name, isRequired, options }) => {
|
||||
|
|
@ -63,7 +65,7 @@ const mapSelectableInputs = (discover, t) => {
|
|||
: selectedExtra ? t.string(selectedExtra.value) : () => t.string(name.toUpperCase()),
|
||||
onSelect: (value) => {
|
||||
const { href } = JSON.parse(value);
|
||||
window.location = href;
|
||||
navigate(toPath(href));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
@ -72,8 +74,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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 { useCore } = require('stremio/core');
|
||||
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 = useCore();
|
||||
const { t } = useTranslation();
|
||||
const routeFocused = useRouteFocused();
|
||||
|
|
@ -163,7 +166,7 @@ const Intro = ({ queryParams }) => {
|
|||
dispatch({ type: 'error', error: t('MUST_ACCEPT_TERMS') });
|
||||
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'))) {
|
||||
|
|
@ -272,7 +275,7 @@ const Intro = ({ queryParams }) => {
|
|||
if (name === 'UserAuthenticated') {
|
||||
closeLoaderModal();
|
||||
if (routeFocused) {
|
||||
window.location = '#/';
|
||||
navigate('/');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -431,8 +434,4 @@ const Intro = ({ queryParams }) => {
|
|||
);
|
||||
};
|
||||
|
||||
Intro.propTypes = {
|
||||
queryParams: PropTypes.instanceOf(URLSearchParams)
|
||||
};
|
||||
|
||||
module.exports = Intro;
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
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 { default: toPath } = require('stremio/common/toPath');
|
||||
const { DelayedRenderer, Chips, Image, MainNavBars, LibItem, MultiselectMenu } = require('stremio/components');
|
||||
const { default: Placeholder } = require('./Placeholder');
|
||||
const useLibrary = require('./useLibrary');
|
||||
|
|
@ -15,39 +18,36 @@ const styles = require('./styles');
|
|||
const SCROLL_TO_BOTTOM_TRESHOLD = 400;
|
||||
|
||||
function withModel(Library) {
|
||||
const withModel = ({ urlParams, queryParams }) => {
|
||||
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 (
|
||||
<NotFound />
|
||||
);
|
||||
}
|
||||
}, [location?.pathname]);
|
||||
|
||||
return (
|
||||
<Library
|
||||
key={model}
|
||||
model={model}
|
||||
urlParams={urlParams}
|
||||
queryParams={queryParams}
|
||||
/>
|
||||
);
|
||||
if (model === null) return <NotFound />;
|
||||
|
||||
return <Library model={model} />;
|
||||
};
|
||||
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();
|
||||
|
|
@ -67,7 +67,7 @@ const Library = ({ model, urlParams, queryParams }) => {
|
|||
}, [profile.auth, library.selected]);
|
||||
React.useEffect(() => {
|
||||
if (!library.selected?.type && typeSelect.value) {
|
||||
window.location = typeSelect.value;
|
||||
navigate(toPath(typeSelect.value));
|
||||
}
|
||||
}, [typeSelect.value, library.selected]);
|
||||
return (
|
||||
|
|
@ -120,10 +120,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 }) => (
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
// 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 { default: toPath } = require('stremio/common/toPath');
|
||||
|
||||
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 +15,7 @@ const mapSelectableInputs = (library, t) => {
|
|||
})),
|
||||
value: selectedType?.deepLinks.library,
|
||||
onSelect: (value) => {
|
||||
window.location = value;
|
||||
navigate(toPath(value));
|
||||
}
|
||||
};
|
||||
const sortChips = {
|
||||
|
|
@ -25,7 +28,7 @@ const mapSelectableInputs = (library, t) => {
|
|||
.filter(({ selected }) => selected)
|
||||
.map(({ deepLinks }) => deepLinks.library),
|
||||
onSelect: (value) => {
|
||||
window.location = value;
|
||||
navigate(toPath(value));
|
||||
}
|
||||
};
|
||||
return [typeSelect, sortChips, library.selectable.nextPage];
|
||||
|
|
@ -33,8 +36,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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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('/');
|
||||
if (splitPath[splitPath.length - 1] === '') {
|
||||
splitPath.pop();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
// 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 { useCore } = require('stremio/core');
|
||||
const { useContentGamepadNavigation } = require('stremio/services/GamepadNavigation');
|
||||
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');
|
||||
|
|
@ -15,12 +16,21 @@ 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 contentRef = React.useRef(null);
|
||||
const { t } = useTranslation();
|
||||
const core = useCore();
|
||||
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 ?
|
||||
|
|
@ -95,14 +105,12 @@ 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 = (urlParams.videoId === undefined || urlParams.videoId === null || urlParams.videoId === '') ?
|
||||
url + (!url.endsWith('/') ? '/' : '') + searchVideoHash
|
||||
: 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(() => !!(
|
||||
|
|
@ -112,6 +120,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]);
|
||||
|
||||
useContentGamepadNavigation(contentRef, urlParams.path);
|
||||
return (
|
||||
|
|
@ -134,6 +143,7 @@ const MetaDetails = ({ urlParams, queryParams }) => {
|
|||
backButton={true}
|
||||
fullscreenButton={true}
|
||||
navMenu={true}
|
||||
originPath={originPath}
|
||||
/>
|
||||
<div ref={contentRef} className={styles['metadetails-content']}>
|
||||
{
|
||||
|
|
@ -239,16 +249,6 @@ const MetaDetails = ({ urlParams, queryParams }) => {
|
|||
);
|
||||
};
|
||||
|
||||
MetaDetails.propTypes = {
|
||||
urlParams: PropTypes.shape({
|
||||
path: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
videoId: PropTypes.string
|
||||
}),
|
||||
queryParams: PropTypes.instanceOf(URLSearchParams)
|
||||
};
|
||||
|
||||
const MetaDetailsFallback = () => (
|
||||
<div className={styles['metadetails-container']}>
|
||||
<HorizontalNavBar
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const { t } = require('i18next');
|
|||
const { useCore } = require('stremio/core');
|
||||
const { useProfile, usePlatform, useToast, useBinaryState } = require('stremio/common');
|
||||
const { Button, Image, Popup } = require('stremio/components');
|
||||
const { useRouteFocused } = require('stremio-router');
|
||||
const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
|
||||
const StreamPlaceholder = require('./StreamPlaceholder');
|
||||
const styles = require('./styles');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useNavigate } = require('react-router');
|
||||
const { default: toPath } = require('stremio/common/toPath');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
|
|
@ -19,6 +21,7 @@ const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => {
|
|||
const core = useCore();
|
||||
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 +33,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(toPath(navigateTo), { replace: true });
|
||||
} else {
|
||||
window.history.back();
|
||||
navigate(-1);
|
||||
}
|
||||
}, [video]);
|
||||
const countLoadingAddons = React.useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useLocation, useNavigate, useSearchParams } = require('react-router-dom');
|
||||
|
||||
const useSeason = (urlParams, queryParams) => {
|
||||
const useSeason = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const [queryParams] = useSearchParams();
|
||||
const season = React.useMemo(() => {
|
||||
return queryParams.has('season') && !isNaN(queryParams.get('season')) ?
|
||||
parseInt(queryParams.get('season'), 10)
|
||||
|
|
@ -12,12 +16,11 @@ const useSeason = (urlParams, queryParams) => {
|
|||
const setSeason = React.useCallback((season) => {
|
||||
const nextQueryParams = new URLSearchParams(queryParams);
|
||||
nextQueryParams.set('season', season);
|
||||
const path = urlParams.path.endsWith('/') ?
|
||||
urlParams.path.slice(0, -1):
|
||||
urlParams.path;
|
||||
|
||||
window.location.replace(`#${path}?${nextQueryParams}`);
|
||||
}, [urlParams, queryParams]);
|
||||
const path = location.pathname.endsWith('/') ?
|
||||
location.pathname.slice(0, -1) :
|
||||
location.pathname;
|
||||
navigate(`${path}?${nextQueryParams}`, { replace: true });
|
||||
}, [location.pathname, queryParams, navigate]);
|
||||
return [season, setSeason];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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 { usePlatform } = require('stremio/common');
|
||||
const { Slider } = require('stremio/components');
|
||||
const styles = require('./styles');
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
// 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 { useCore } = require('stremio/core');
|
||||
const { useServices, useGamepad } = require('stremio/services');
|
||||
const { useContentGamepadNavigation } = require('stremio/services/GamepadNavigation');
|
||||
const { useSettings, useProfile, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, usePlatform, onShortcut } = require('stremio/common');
|
||||
const { default: toPath } = require('stremio/common/toPath');
|
||||
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
|
||||
const { default: Buffering } = require('./Buffering');
|
||||
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
|
||||
|
|
@ -38,7 +40,18 @@ const findTrackById = (tracks, id) => tracks.find((track) => track.id === id);
|
|||
|
||||
const GAMEPAD_HANDLER_ID = 'player';
|
||||
|
||||
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 core = useCore();
|
||||
|
|
@ -141,21 +154,22 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
if (bingeWatching) {
|
||||
if (deepLinks.player) {
|
||||
isNavigating.current = true;
|
||||
window.location.replace(deepLinks.player);
|
||||
navigate(toPath(deepLinks.player), { replace: true });
|
||||
} else if (deepLinks.metaDetailsStreams) {
|
||||
isNavigating.current = true;
|
||||
window.location.replace(deepLinks.metaDetailsStreams);
|
||||
navigate(toPath(deepLinks.metaDetailsStreams), { replace: true });
|
||||
}
|
||||
} else {
|
||||
window.history.back();
|
||||
navigate(-1);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (deepLinks.player) {
|
||||
isNavigating.current = true;
|
||||
window.location.replace(deepLinks.player);
|
||||
navigate(toPath(deepLinks.player), { replace: true });
|
||||
} else if (deepLinks.metaDetailsStreams) {
|
||||
isNavigating.current = true;
|
||||
window.location.replace(deepLinks.metaDetailsStreams);
|
||||
navigate(toPath(deepLinks.metaDetailsStreams), { replace: true });
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
|
@ -175,7 +189,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
handleNextVideoNavigation(deepLinks, profile.settings.bingeWatching, true);
|
||||
|
||||
} else {
|
||||
window.history.back();
|
||||
navigate(-1);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
@ -642,7 +656,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
|
||||
onShortcut('exit', () => {
|
||||
closeMenus();
|
||||
!settings.escExitFullscreen && window.history.back();
|
||||
!settings.escExitFullscreen && navigate(-1);
|
||||
}, [settings.escExitFullscreen]);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
|
|
@ -919,7 +933,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}
|
||||
/>
|
||||
</Transition>
|
||||
<Transition when={subtitlesMenuOpen} name={'fade'}>
|
||||
|
|
@ -956,18 +970,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 = () => (
|
||||
<div className={classnames(styles['player-container'])} />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 <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);
|
||||
|
|
|
|||
|
|
@ -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 { usePlatform, useProfile, useStreamingServer, withCoreSuspender } from 'stremio/common';
|
||||
import { usePlatform, useProfile, useStreamingServer, useRouteFocused, withCoreSuspender } from 'stremio/common';
|
||||
import { MainNavBars } from 'stremio/components';
|
||||
import { SECTIONS } from './constants';
|
||||
import Menu from './Menu';
|
||||
|
|
@ -17,7 +16,7 @@ import Info from './Info';
|
|||
import styles from './Settings.less';
|
||||
|
||||
const Settings = () => {
|
||||
const { routeFocused } = useRouteFocused();
|
||||
const routeFocused = useRouteFocused();
|
||||
const profile = useProfile();
|
||||
const platform = usePlatform();
|
||||
const streamingServer = useStreamingServer();
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
// Copyright (C) 2017-2026 Smart code 203358507
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useGamepad } from '../GamepadContext';
|
||||
import useFullscreen from 'stremio/common/Fullscreen';
|
||||
|
||||
const useHorizontalNavGamepadNavigation = (gamepadHandlerId: string, enableGoBack: boolean) => {
|
||||
const gamepad = useGamepad();
|
||||
const navigate = useNavigate();
|
||||
const [fullscreen,,,toggleFullscreen] = useFullscreen();
|
||||
|
||||
useEffect(() => {
|
||||
const goBack = () => enableGoBack && window.history.back();
|
||||
const goBack = () => enableGoBack && navigate(-1);
|
||||
|
||||
gamepad?.on('buttonY', gamepadHandlerId, toggleFullscreen as () => void);
|
||||
gamepad?.on('buttonB', gamepadHandlerId, goBack);
|
||||
|
|
@ -18,7 +20,7 @@ const useHorizontalNavGamepadNavigation = (gamepadHandlerId: string, enableGoBac
|
|||
gamepad?.off('buttonY', gamepadHandlerId);
|
||||
gamepad?.off('buttonB', gamepadHandlerId);
|
||||
};
|
||||
}, [gamepad, gamepadHandlerId, enableGoBack, fullscreen]);
|
||||
}, [gamepad, gamepadHandlerId, enableGoBack, fullscreen, navigate]);
|
||||
};
|
||||
|
||||
export default useHorizontalNavGamepadNavigation;
|
||||
|
|
|
|||
Loading…
Reference in a new issue