feat(EpisodePicker): added season and episode picker when no streams loaded

This commit is contained in:
Botzy 2025-02-07 17:27:53 +02:00
parent e8a6e72b13
commit 15c6a231a6
5 changed files with 86 additions and 3 deletions

View file

@ -76,6 +76,18 @@ const MetaDetails = ({ urlParams, queryParams }) => {
const seasonOnSelect = React.useCallback((event) => {
setSeason(event.value);
}, [setSeason]);
const handleEpisodeSearch = React.useCallback((season, episode) => {
const searchVideoId = streamPath.id.replace(/(:\d+:\d+)$/, `:${season}:${episode}`);
const videoFound = metaDetails.metaItem.content.content.videos.find((video) => video.id === searchVideoId);
if (videoFound) {
if (typeof videoFound.deepLinks.player === 'string') {
window.location = videoFound.deepLinks.player;
} else if (typeof videoFound.deepLinks.metaDetailsStreams === 'string') {
window.location.replace(videoFound.deepLinks.metaDetailsStreams);
}
}
}, [streamPath, metaDetails.metaItem]);
const renderBackgroundImageFallback = React.useCallback(() => null, []);
const renderBackground = React.useMemo(() => !!(
metaPath &&
@ -129,7 +141,7 @@ const MetaDetails = ({ urlParams, queryParams }) => {
metaDetails.metaItem === null ?
<div className={styles['meta-message-container']}>
<Image className={styles['image']} src={require('/images/empty.png')} alt={' '} />
<div className={styles['message-label']}>No addons ware requested for this meta!</div>
<div className={styles['message-label']}>No addons were requested for this meta!</div>
</div>
:
metaDetails.metaItem.content.type === 'Err' ?
@ -169,6 +181,7 @@ const MetaDetails = ({ urlParams, queryParams }) => {
className={styles['streams-list']}
streams={metaDetails.streams}
video={video}
onEpisodeSearch={handleEpisodeSearch}
/>
:
metaPath !== null ?

View file

@ -0,0 +1,19 @@
// Copyright (C) 2017-2025 Smart code 203358507
.button-container {
display: flex;
align-items: center;
justify-content: center;
padding: 1rem 2.625rem;
margin: 2rem 0;
background-color: var(--primary-accent-color);
transition: 0.3s all ease-in-out;
border-radius: 2.625rem;
color: var(--primary-foreground-color);
border: 2px solid transparent;
&:hover, &:focus {
outline: var(--focus-outline-size) solid var(--primary-foreground-color);
background-color: transparent;
}
}

View file

@ -0,0 +1,35 @@
// Copyright (C) 2017-2025 Smart code 203358507
import React, { useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, NumberInput } from 'stremio/components';
import styles from './EpisodePicker.less';
type Props = {
className?: string,
seriesId: string;
onSubmit: (season: number, episode: number) => void;
};
export const EpisodePicker = ({ className, seriesId, onSubmit }: Props) => {
const { t } = useTranslation();
const [initialSeason, initialEpisode] = React.useMemo(() => {
const [, season, episode] = seriesId ? seriesId.split(':') : [];
return [parseInt(season || '1'), parseInt(episode || '1')];
}, [seriesId]);
const seasonRef = useRef(null);
const episodeRef = useRef(null);
const handleSubmit = React.useCallback(() => {
const season = seasonRef.current?.value;
const episode = episodeRef.current?.value;
if (typeof onSubmit === 'function') onSubmit(season, episode);
}, [onSubmit, seasonRef, episodeRef]);
return <div className={className}>
<NumberInput ref={seasonRef} min={1} label={t('SEASON')} defaultValue={initialSeason} showButtons />
<NumberInput ref={episodeRef} min={1} label={t('EPISODE')} defaultValue={initialEpisode} showButtons />
<Button className={styles['button-container']} onClick={handleSubmit}>{t('SIDEBAR_SHOW_STREAMS')}</Button>
</div>;
};
export default EpisodePicker;

View file

@ -0,0 +1,7 @@
// Copyright (C) 2017-2025 Smart code 203358507
import SeasonEpisodePicker from './EpisodePicker';
export default {
SeasonEpisodePicker
};

View file

@ -10,10 +10,11 @@ const { useServices } = require('stremio/services');
const Stream = require('./Stream');
const styles = require('./styles');
const { usePlatform, useProfile } = require('stremio/common');
const { SeasonEpisodePicker } = require('./EpisodePicker');
const ALL_ADDONS_KEY = 'ALL';
const StreamsList = ({ className, video, ...props }) => {
const StreamsList = ({ className, video, onEpisodeSearch, ...props }) => {
const { t } = useTranslation();
const { core } = useServices();
const platform = usePlatform();
@ -93,6 +94,11 @@ const StreamsList = ({ className, video, ...props }) => {
onSelect: onAddonSelected
};
}, [streamsByAddon, selectedAddon]);
const handleEpisodePicker = React.useCallback((season, episode) => {
onEpisodeSearch(season, episode);
}, [onEpisodeSearch]);
return (
<div className={classnames(className, styles['streams-list-container'])}>
<div className={styles['select-choices-wrapper']}>
@ -122,12 +128,14 @@ const StreamsList = ({ className, video, ...props }) => {
{
props.streams.length === 0 ?
<div className={styles['message-container']}>
<SeasonEpisodePicker seriesId={video?.id} onSubmit={handleEpisodePicker} />
<Image className={styles['image']} src={require('/images/empty.png')} alt={' '} />
<div className={styles['label']}>No addons were requested for streams!</div>
</div>
:
props.streams.every((streams) => streams.content.type === 'Err') ?
<div className={styles['message-container']}>
<SeasonEpisodePicker seriesId={video?.id} onSubmit={handleEpisodePicker} />
<Image className={styles['image']} src={require('/images/empty.png')} alt={' '} />
<div className={styles['label']}>{t('NO_STREAM')}</div>
{
@ -193,7 +201,8 @@ const StreamsList = ({ className, video, ...props }) => {
StreamsList.propTypes = {
className: PropTypes.string,
streams: PropTypes.arrayOf(PropTypes.object).isRequired,
video: PropTypes.object
video: PropTypes.object,
onEpisodeSearch: PropTypes.func
};
module.exports = StreamsList;