mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-21 15:52:02 +00:00
202 lines
8.6 KiB
JavaScript
202 lines
8.6 KiB
JavaScript
// Copyright (C) 2017-2023 Smart code 203358507
|
|
|
|
const React = require('react');
|
|
const PropTypes = require('prop-types');
|
|
const classnames = require('classnames');
|
|
const { t } = require('i18next');
|
|
const { useServices } = require('stremio/services');
|
|
const { useProfile } = require('stremio/common');
|
|
const { Image, SearchBar, Toggle, Video } = require('stremio/components');
|
|
const SeasonsBar = require('./SeasonsBar');
|
|
const { default: EpisodePicker } = require('../EpisodePicker');
|
|
const styles = require('./styles');
|
|
|
|
const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect, toggleNotifications }) => {
|
|
const { core } = useServices();
|
|
const profile = useProfile();
|
|
const showNotificationsToggle = React.useMemo(() => {
|
|
return metaItem?.content?.content?.inLibrary && metaItem?.content?.content?.videos?.length;
|
|
}, [metaItem]);
|
|
const videos = React.useMemo(() => {
|
|
return metaItem && metaItem.content.type === 'Ready' ?
|
|
metaItem.content.content.videos
|
|
:
|
|
[];
|
|
}, [metaItem]);
|
|
const seasons = React.useMemo(() => {
|
|
return videos
|
|
.map(({ season }) => season)
|
|
.filter((season, index, seasons) => {
|
|
return season !== null &&
|
|
!isNaN(season) &&
|
|
typeof season === 'number' &&
|
|
seasons.indexOf(season) === index;
|
|
})
|
|
.sort((a, b) => (a || Number.MAX_SAFE_INTEGER) - (b || Number.MAX_SAFE_INTEGER));
|
|
}, [videos]);
|
|
const selectedSeason = React.useMemo(() => {
|
|
if (seasons.includes(season)) {
|
|
return season;
|
|
}
|
|
|
|
const video = videos?.find((video) => video.id === libraryItem?.state.video_id);
|
|
|
|
if (video && video.season && seasons.includes(video.season)) {
|
|
return video.season;
|
|
}
|
|
|
|
const nonSpecialSeasons = seasons.filter((season) => season !== 0);
|
|
if (nonSpecialSeasons.length > 0) {
|
|
return nonSpecialSeasons[0];
|
|
}
|
|
|
|
if (seasons.length > 0) {
|
|
return seasons[0];
|
|
}
|
|
|
|
return null;
|
|
}, [seasons, season, videos, libraryItem]);
|
|
const videosForSeason = React.useMemo(() => {
|
|
return videos
|
|
.filter((video) => {
|
|
return selectedSeason === null || video.season === selectedSeason;
|
|
})
|
|
.sort((a, b) => {
|
|
return a.episode - b.episode;
|
|
});
|
|
}, [videos, selectedSeason]);
|
|
|
|
const seasonWatched = React.useMemo(() => {
|
|
return videosForSeason.every((video) => video.watched);
|
|
}, [videosForSeason]);
|
|
|
|
const [search, setSearch] = React.useState('');
|
|
const searchInputOnChange = React.useCallback((event) => {
|
|
setSearch(event.currentTarget.value);
|
|
}, []);
|
|
|
|
const onMarkVideoAsWatched = (video, watched) => {
|
|
core.transport.dispatch({
|
|
action: 'MetaDetails',
|
|
args: {
|
|
action: 'MarkVideoAsWatched',
|
|
args: [video, !watched]
|
|
}
|
|
});
|
|
};
|
|
|
|
const onMarkSeasonAsWatched = (season, watched) => {
|
|
core.transport.dispatch({
|
|
action: 'MetaDetails',
|
|
args: {
|
|
action: 'MarkSeasonAsWatched',
|
|
args: [season, !watched]
|
|
}
|
|
});
|
|
};
|
|
|
|
const onSeasonSearch = (value) => {
|
|
if (value) {
|
|
seasonOnSelect({
|
|
type: 'select',
|
|
value,
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className={classnames(className, styles['videos-list-container'])}>
|
|
{
|
|
!metaItem || metaItem.content.type === 'Loading' ?
|
|
<React.Fragment>
|
|
<SeasonsBar.Placeholder className={styles['seasons-bar']} />
|
|
<SearchBar.Placeholder className={styles['search-bar']} title={t('SEARCH_VIDEOS')} />
|
|
<div className={styles['videos-scroll-container']}>
|
|
<Video.Placeholder />
|
|
<Video.Placeholder />
|
|
<Video.Placeholder />
|
|
<Video.Placeholder />
|
|
<Video.Placeholder />
|
|
</div>
|
|
</React.Fragment>
|
|
:
|
|
metaItem.content.type === 'Err' || videosForSeason.length === 0 ?
|
|
<div className={styles['message-container']}>
|
|
<EpisodePicker className={styles['episode-picker']} onSubmit={onSeasonSearch} />
|
|
<Image className={styles['image']} src={require('/images/empty.png')} alt={' '} />
|
|
<div className={styles['label']}>{t('ERR_NO_VIDEOS_FOR_META')}</div>
|
|
</div>
|
|
:
|
|
<React.Fragment>
|
|
{
|
|
showNotificationsToggle && libraryItem ?
|
|
<Toggle className={styles['notifications-toggle']} checked={!libraryItem.state.noNotif} onClick={toggleNotifications}>
|
|
{t('DETAIL_RECEIVE_NOTIF_SERIES')}
|
|
</Toggle>
|
|
:
|
|
null
|
|
}
|
|
{
|
|
seasons.length > 0 ?
|
|
<SeasonsBar
|
|
className={styles['seasons-bar']}
|
|
season={selectedSeason}
|
|
seasons={seasons}
|
|
onSelect={seasonOnSelect}
|
|
/>
|
|
:
|
|
null
|
|
}
|
|
<SearchBar
|
|
className={styles['search-bar']}
|
|
title={t('SEARCH_VIDEOS')}
|
|
value={search}
|
|
onChange={searchInputOnChange}
|
|
/>
|
|
<div className={styles['videos-container']}>
|
|
{
|
|
videosForSeason
|
|
.filter((video) => {
|
|
return search.length === 0 ||
|
|
(
|
|
(typeof video.title === 'string' && video.title.toLowerCase().includes(search.toLowerCase())) ||
|
|
(!isNaN(video.released.getTime()) && video.released.toLocaleString(profile.settings.interfaceLanguage, { year: '2-digit', month: 'short', day: 'numeric' }).toLowerCase().includes(search.toLowerCase()))
|
|
);
|
|
})
|
|
.map((video, index) => (
|
|
<Video
|
|
key={index}
|
|
id={video.id}
|
|
title={video.title}
|
|
thumbnail={video.thumbnail}
|
|
season={video.season}
|
|
episode={video.episode}
|
|
released={video.released}
|
|
upcoming={video.upcoming}
|
|
watched={video.watched}
|
|
progress={video.progress}
|
|
deepLinks={video.deepLinks}
|
|
scheduled={video.scheduled}
|
|
seasonWatched={seasonWatched}
|
|
onMarkVideoAsWatched={onMarkVideoAsWatched}
|
|
onMarkSeasonAsWatched={onMarkSeasonAsWatched}
|
|
/>
|
|
))
|
|
}
|
|
</div>
|
|
</React.Fragment>
|
|
}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
VideosList.propTypes = {
|
|
className: PropTypes.string,
|
|
metaItem: PropTypes.object,
|
|
libraryItem: PropTypes.object,
|
|
season: PropTypes.number,
|
|
seasonOnSelect: PropTypes.func,
|
|
toggleNotifications: PropTypes.func,
|
|
};
|
|
|
|
module.exports = VideosList;
|