mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-21 11:42:05 +00:00
MetaDetails adapted to changes in core
This commit is contained in:
parent
d32fe5123a
commit
07398b56cc
8 changed files with 207 additions and 202 deletions
|
|
@ -2,43 +2,30 @@
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const PropTypes = require('prop-types');
|
const PropTypes = require('prop-types');
|
||||||
const { VerticalNavBar, HorizontalNavBar, MetaPreview, ModalDialog, Image, useInLibrary } = require('stremio/common');
|
const { useServices } = require('stremio/services');
|
||||||
|
const { VerticalNavBar, HorizontalNavBar, MetaPreview, ModalDialog, Image } = require('stremio/common');
|
||||||
const StreamsList = require('./StreamsList');
|
const StreamsList = require('./StreamsList');
|
||||||
const VideosList = require('./VideosList');
|
const VideosList = require('./VideosList');
|
||||||
const useMetaDetails = require('./useMetaDetails');
|
const useMetaDetails = require('./useMetaDetails');
|
||||||
const useMetaExtensions = require('./useMetaExtensions');
|
const useSeason = require('./useSeason');
|
||||||
|
const useMetaExtensionTabs = require('./useMetaExtensionTabs');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
||||||
const MetaDetails = ({ urlParams, queryParams }) => {
|
const MetaDetails = ({ urlParams, queryParams }) => {
|
||||||
|
const { core } = useServices();
|
||||||
const metaDetails = useMetaDetails(urlParams);
|
const metaDetails = useMetaDetails(urlParams);
|
||||||
const { tabs, selectedMetaExtension, clearSelectedMetaExtension } = useMetaExtensions(metaDetails.meta_resources);
|
const [season, setSeason] = useSeason(urlParams, queryParams);
|
||||||
const metaResourceRef = React.useMemo(() => {
|
const [tabs, metaExtension, clearMetaExtension] = useMetaExtensionTabs(metaDetails.metaExtensions);
|
||||||
return metaDetails.selected !== null ? metaDetails.selected.meta_resources_ref : null;
|
const [metaPath, streamsPath] = React.useMemo(() => {
|
||||||
}, [metaDetails.selected]);
|
return metaDetails.selected !== null ?
|
||||||
const selectedMetaResource = React.useMemo(() => {
|
[metaDetails.selected.metaPath, metaDetails.selected.streamsPath]
|
||||||
return metaDetails.meta_resources.reduceRight((result, metaResource) => {
|
|
||||||
if (metaResource.content.type === 'Ready') {
|
|
||||||
return metaResource;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}, null);
|
|
||||||
}, [metaDetails]);
|
|
||||||
const streamsResourceRef = metaDetails.selected !== null ? metaDetails.selected.streams_resource_ref : null;
|
|
||||||
const streamsResources = metaDetails.streams_resources;
|
|
||||||
const seasonQueryParam = React.useMemo(() => {
|
|
||||||
return queryParams.has('season') && !isNaN(queryParams.get('season')) ?
|
|
||||||
parseInt(queryParams.get('season'))
|
|
||||||
:
|
:
|
||||||
null;
|
[null, null];
|
||||||
}, [queryParams]);
|
}, [metaDetails.selected]);
|
||||||
const seasonOnSelect = React.useCallback((event) => {
|
const video = React.useMemo(() => {
|
||||||
window.location.replace(`#/metadetails/${selectedMetaResource.request.path.type_name}/${selectedMetaResource.request.path.id}?season=${event.value}`);
|
return streamsPath !== null && metaDetails.metaCatalog !== null && metaDetails.metaCatalog.content.type === 'Ready' ?
|
||||||
}, [selectedMetaResource]);
|
metaDetails.metaCatalog.content.content.videos.reduce((result, video) => {
|
||||||
const selectedVideo = React.useMemo(() => {
|
if (video.id === streamsPath.id) {
|
||||||
return streamsResourceRef !== null && selectedMetaResource !== null ?
|
|
||||||
selectedMetaResource.content.content.videos.reduce((result, video) => {
|
|
||||||
if (video.id === streamsResourceRef.id) {
|
|
||||||
return video;
|
return video;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,14 +33,42 @@ const MetaDetails = ({ urlParams, queryParams }) => {
|
||||||
}, null)
|
}, null)
|
||||||
:
|
:
|
||||||
null;
|
null;
|
||||||
}, [selectedMetaResource, streamsResourceRef]);
|
}, [metaDetails.metaCatalog, streamsPath]);
|
||||||
const [inLibrary, toggleInLibrary] = useInLibrary(selectedMetaResource !== null ? selectedMetaResource.content.content : null);
|
const addToLibrary = React.useCallback(() => {
|
||||||
|
if (metaDetails.metaCatalog === null || metaDetails.metaCatalog.content.type !== 'Ready') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
core.transport.dispatch({
|
||||||
|
action: 'Ctx',
|
||||||
|
args: {
|
||||||
|
action: 'AddToLibrary',
|
||||||
|
args: metaDetails.metaCatalog.content.content
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [metaDetails]);
|
||||||
|
const removeFromLibrary = React.useCallback(() => {
|
||||||
|
if (metaDetails.metaCatalog === null || metaDetails.metaCatalog.content.type !== 'Ready') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
core.transport.dispatch({
|
||||||
|
action: 'Ctx',
|
||||||
|
args: {
|
||||||
|
action: 'RemoveFromLibrary',
|
||||||
|
args: metaDetails.metaCatalog.content.content.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [metaDetails]);
|
||||||
|
const seasonOnSelect = React.useCallback((event) => {
|
||||||
|
setSeason(event.value);
|
||||||
|
}, [setSeason]);
|
||||||
return (
|
return (
|
||||||
<div className={styles['metadetails-container']}>
|
<div className={styles['metadetails-container']}>
|
||||||
<HorizontalNavBar
|
<HorizontalNavBar
|
||||||
className={styles['nav-bar']}
|
className={styles['nav-bar']}
|
||||||
backButton={true}
|
backButton={true}
|
||||||
title={selectedMetaResource !== null ? selectedMetaResource.content.content.name : null}
|
title={metaDetails.metaCatalog !== null && metaDetails.metaCatalog.content.type === 'Ready' ? metaDetails.metaCatalog.content.content.name : null}
|
||||||
/>
|
/>
|
||||||
<div className={styles['metadetails-content']}>
|
<div className={styles['metadetails-content']}>
|
||||||
{
|
{
|
||||||
|
|
@ -61,39 +76,41 @@ const MetaDetails = ({ urlParams, queryParams }) => {
|
||||||
<VerticalNavBar
|
<VerticalNavBar
|
||||||
className={styles['vertical-nav-bar']}
|
className={styles['vertical-nav-bar']}
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
selected={selectedMetaExtension !== null ? selectedMetaExtension.url : null}
|
selected={metaExtension !== null ? metaExtension.url : null}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
metaResourceRef === null ?
|
metaPath === null ?
|
||||||
<div className={styles['meta-message-container']}>
|
<div className={styles['meta-message-container']}>
|
||||||
<Image className={styles['image']} src={'/images/empty.png'} alt={' '} />
|
<Image className={styles['image']} src={'/images/empty.png'} alt={' '} />
|
||||||
<div className={styles['message-label']}>No meta was selected!</div>
|
<div className={styles['message-label']}>No meta was selected!</div>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
metaDetails.meta_resources.length === 0 ?
|
metaDetails.metaCatalog === null ?
|
||||||
<div className={styles['meta-message-container']}>
|
<div className={styles['meta-message-container']}>
|
||||||
<Image className={styles['image']} src={'/images/empty.png'} alt={' '} />
|
<Image className={styles['image']} src={'/images/empty.png'} alt={' '} />
|
||||||
<div className={styles['message-label']}>No addons ware requested for this meta!</div>
|
<div className={styles['message-label']}>No addons ware requested for this meta!</div>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
metaDetails.meta_resources.every((metaResource) => metaResource.content.type === 'Err') ?
|
metaDetails.metaCatalog.content.type === 'Err' ?
|
||||||
<div className={styles['meta-message-container']}>
|
<div className={styles['meta-message-container']}>
|
||||||
<Image className={styles['image']} src={'/images/empty.png'} alt={' '} />
|
<Image className={styles['image']} src={'/images/empty.png'} alt={' '} />
|
||||||
<div className={styles['message-label']}>No metadata was found!</div>
|
<div className={styles['message-label']}>No metadata was found!</div>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
selectedMetaResource !== null ?
|
metaDetails.metaCatalog.content.type === 'Loading' ?
|
||||||
|
<MetaPreview.Placeholder className={styles['meta-preview']} />
|
||||||
|
:
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{
|
{
|
||||||
typeof selectedMetaResource.content.content.background === 'string' &&
|
typeof metaDetails.metaCatalog.content.content.background === 'string' &&
|
||||||
selectedMetaResource.content.content.background.length > 0 ?
|
metaDetails.metaCatalog.content.content.background.length > 0 ?
|
||||||
<div className={styles['background-image-layer']}>
|
<div className={styles['background-image-layer']}>
|
||||||
<Image
|
<Image
|
||||||
className={styles['background-image']}
|
className={styles['background-image']}
|
||||||
src={selectedMetaResource.content.content.background}
|
src={metaDetails.metaCatalog.content.content.background}
|
||||||
alt={' '}
|
alt={' '}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -102,39 +119,37 @@ const MetaDetails = ({ urlParams, queryParams }) => {
|
||||||
}
|
}
|
||||||
<MetaPreview
|
<MetaPreview
|
||||||
className={styles['meta-preview']}
|
className={styles['meta-preview']}
|
||||||
name={selectedMetaResource.content.content.name + (selectedVideo !== null && typeof selectedVideo.title === 'string' ? ` - ${selectedVideo.title}` : '')}
|
name={metaDetails.metaCatalog.content.content.name + (video !== null && typeof video.title === 'string' && video.title.length > 0 ? ` - ${video.title}` : '')}
|
||||||
logo={selectedMetaResource.content.content.logo}
|
logo={metaDetails.metaCatalog.content.content.logo}
|
||||||
runtime={selectedMetaResource.content.content.runtime}
|
runtime={metaDetails.metaCatalog.content.content.runtime}
|
||||||
releaseInfo={selectedMetaResource.content.content.releaseInfo}
|
releaseInfo={metaDetails.metaCatalog.content.content.releaseInfo}
|
||||||
released={selectedMetaResource.content.content.released}
|
released={metaDetails.metaCatalog.content.content.released}
|
||||||
description={
|
description={
|
||||||
selectedVideo !== null && typeof selectedVideo.overview === 'string' && selectedVideo.overview.length > 0 ?
|
video !== null && typeof video.overview === 'string' && video.overview.length > 0 ?
|
||||||
selectedVideo.overview
|
video.overview
|
||||||
:
|
:
|
||||||
selectedMetaResource.content.content.description
|
metaDetails.metaCatalog.content.content.description
|
||||||
}
|
}
|
||||||
links={selectedMetaResource.content.content.links}
|
links={metaDetails.metaCatalog.content.content.links}
|
||||||
trailerStreams={selectedMetaResource.content.content.trailerStreams}
|
trailerStreams={metaDetails.metaCatalog.content.content.trailerStreams}
|
||||||
inLibrary={inLibrary}
|
inLibrary={metaDetails.metaCatalog.content.content.inLibrary}
|
||||||
toggleInLibrary={toggleInLibrary}
|
toggleInLibrary={metaDetails.metaCatalog.content.content.inLibrary ? removeFromLibrary : addToLibrary}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
:
|
|
||||||
<MetaPreview.Placeholder className={styles['meta-preview']} />
|
|
||||||
}
|
}
|
||||||
<div className={styles['spacing']} />
|
<div className={styles['spacing']} />
|
||||||
{
|
{
|
||||||
streamsResourceRef !== null ?
|
streamsPath !== null ?
|
||||||
<StreamsList
|
<StreamsList
|
||||||
className={styles['streams-list']}
|
className={styles['streams-list']}
|
||||||
streamsResources={streamsResources}
|
streamsCatalogs={metaDetails.streamsCatalogs}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
metaResourceRef !== null ?
|
metaPath !== null ?
|
||||||
<VideosList
|
<VideosList
|
||||||
className={styles['videos-list']}
|
className={styles['videos-list']}
|
||||||
metaResource={selectedMetaResource}
|
metaCatalog={metaDetails.metaCatalog}
|
||||||
season={seasonQueryParam}
|
season={season}
|
||||||
seasonOnSelect={seasonOnSelect}
|
seasonOnSelect={seasonOnSelect}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
|
|
@ -142,15 +157,15 @@ const MetaDetails = ({ urlParams, queryParams }) => {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
selectedMetaExtension !== null ?
|
metaExtension !== null ?
|
||||||
<ModalDialog
|
<ModalDialog
|
||||||
className={styles['meta-extension-modal-container']}
|
className={styles['meta-extension-modal-container']}
|
||||||
title={selectedMetaExtension.name}
|
title={metaExtension.name}
|
||||||
onCloseRequest={clearSelectedMetaExtension}>
|
onCloseRequest={clearMetaExtension}>
|
||||||
<iframe
|
<iframe
|
||||||
className={styles['meta-extension-modal-iframe']}
|
className={styles['meta-extension-modal-iframe']}
|
||||||
sandbox={'allow-forms allow-scripts allow-same-origin'}
|
sandbox={'allow-forms allow-scripts allow-same-origin'}
|
||||||
src={selectedMetaExtension.url}
|
src={metaExtension.url}
|
||||||
/>
|
/>
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
:
|
:
|
||||||
|
|
|
||||||
|
|
@ -8,38 +8,48 @@ const { Button, Image } = require('stremio/common');
|
||||||
const Stream = require('./Stream');
|
const Stream = require('./Stream');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
||||||
const StreamsList = ({ className, streamsResources }) => {
|
const StreamsList = ({ className, streamsCatalogs }) => {
|
||||||
const streams = React.useMemo(() => {
|
const streams = React.useMemo(() => {
|
||||||
return streamsResources
|
return streamsCatalogs
|
||||||
.filter((streamsResource) => streamsResource.content.type === 'Ready')
|
.filter((catalog) => catalog.content.type === 'Ready')
|
||||||
.map((streamsResource) => streamsResource.content.content)
|
.map((catalog) => ({
|
||||||
|
...catalog.content.content,
|
||||||
|
addonName: catalog.addonName
|
||||||
|
}))
|
||||||
.flat(1);
|
.flat(1);
|
||||||
}, [streamsResources]);
|
}, [streamsCatalogs]);
|
||||||
return (
|
return (
|
||||||
<div className={classnames(className, styles['streams-list-container'])}>
|
<div className={classnames(className, styles['streams-list-container'])}>
|
||||||
{
|
{
|
||||||
streamsResources.length === 0 ?
|
streamsCatalogs.length === 0 ?
|
||||||
<div className={styles['message-container']}>
|
<div className={styles['message-container']}>
|
||||||
<Image className={styles['image']} src={'/images/empty.png'} alt={' '} />
|
<Image className={styles['image']} src={'/images/empty.png'} alt={' '} />
|
||||||
<div className={styles['label']}>No addons were requested for streams!</div>
|
<div className={styles['label']}>No addons were requested for streams!</div>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
streamsResources.every((streamsResource) => streamsResource.content.type === 'Err') ?
|
streamsCatalogs.every((streamsResource) => streamsResource.content.type === 'Err') ?
|
||||||
<div className={styles['message-container']}>
|
<div className={styles['message-container']}>
|
||||||
<Image className={styles['image']} src={'/images/empty.png'} alt={' '} />
|
<Image className={styles['image']} src={'/images/empty.png'} alt={' '} />
|
||||||
<div className={styles['label']}>No streams were found!</div>
|
<div className={styles['label']}>No streams were found!</div>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
streams.length > 0 ?
|
streams.length === 0 ?
|
||||||
<div className={styles['streams-container']}>
|
<div className={styles['streams-container']}>
|
||||||
{streams.map((stream, index) => (
|
<Stream.Placeholder />
|
||||||
<Stream {...stream} key={index} />
|
<Stream.Placeholder />
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
<div className={styles['streams-container']}>
|
<div className={styles['streams-container']}>
|
||||||
<Stream.Placeholder />
|
{streams.map((stream, index) => (
|
||||||
<Stream.Placeholder />
|
<Stream
|
||||||
|
key={index}
|
||||||
|
addonName={stream.addonName}
|
||||||
|
title={stream.title}
|
||||||
|
thumbnail={stream.thumbnail}
|
||||||
|
progress={stream.progress}
|
||||||
|
deepLinks={stream.deepLinks}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<Button className={styles['install-button-container']} title={'Install Addons'} href={'#/addons'}>
|
<Button className={styles['install-button-container']} title={'Install Addons'} href={'#/addons'}>
|
||||||
|
|
@ -52,7 +62,7 @@ const StreamsList = ({ className, streamsResources }) => {
|
||||||
|
|
||||||
StreamsList.propTypes = {
|
StreamsList.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
streamsResources: PropTypes.arrayOf(PropTypes.object)
|
streamsCatalogs: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = StreamsList;
|
module.exports = StreamsList;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const { Button, Image } = require('stremio/common');
|
||||||
const VideoPlaceholder = require('./VideoPlaceholder');
|
const VideoPlaceholder = require('./VideoPlaceholder');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
||||||
const Video = ({ className, id, title, thumbnail, episode, released, upcoming, watched, progress, deepLinks, ...props }) => {
|
const Video = ({ className, id, title, thumbnail, episode, released, upcoming, watched, progress, scheduled, deepLinks, ...props }) => {
|
||||||
const href = React.useMemo(() => {
|
const href = React.useMemo(() => {
|
||||||
return deepLinks ?
|
return deepLinks ?
|
||||||
typeof deepLinks.player === 'string' ?
|
typeof deepLinks.player === 'string' ?
|
||||||
|
|
@ -53,7 +53,12 @@ const Video = ({ className, id, title, thumbnail, episode, released, upcoming, w
|
||||||
{released.toLocaleString(undefined, { year: '2-digit', month: 'short', day: 'numeric' })}
|
{released.toLocaleString(undefined, { year: '2-digit', month: 'short', day: 'numeric' })}
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
null
|
scheduled ?
|
||||||
|
<div className={styles['released-container']} title={'To be announced'}>
|
||||||
|
TBA
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
null
|
||||||
}
|
}
|
||||||
<div className={styles['upcoming-watched-container']}>
|
<div className={styles['upcoming-watched-container']}>
|
||||||
{
|
{
|
||||||
|
|
@ -99,6 +104,7 @@ Video.propTypes = {
|
||||||
upcoming: PropTypes.bool,
|
upcoming: PropTypes.bool,
|
||||||
watched: PropTypes.bool,
|
watched: PropTypes.bool,
|
||||||
progress: PropTypes.number,
|
progress: PropTypes.number,
|
||||||
|
scheduled: PropTypes.bool,
|
||||||
deepLinks: PropTypes.shape({
|
deepLinks: PropTypes.shape({
|
||||||
meta_details_streams: PropTypes.string,
|
meta_details_streams: PropTypes.string,
|
||||||
player: PropTypes.string
|
player: PropTypes.string
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,13 @@ const SeasonsBar = require('./SeasonsBar');
|
||||||
const Video = require('./Video');
|
const Video = require('./Video');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
||||||
const VideosList = ({ className, metaResource, season, seasonOnSelect }) => {
|
const VideosList = ({ className, metaCatalog, season, seasonOnSelect }) => {
|
||||||
const videos = React.useMemo(() => {
|
const videos = React.useMemo(() => {
|
||||||
return metaResource && metaResource.content.type === 'Ready' ?
|
return metaCatalog && metaCatalog.content.type === 'Ready' ?
|
||||||
metaResource.content.content.videos
|
metaCatalog.content.content.videos
|
||||||
:
|
:
|
||||||
[];
|
[];
|
||||||
}, [metaResource]);
|
}, [metaCatalog]);
|
||||||
const seasons = React.useMemo(() => {
|
const seasons = React.useMemo(() => {
|
||||||
return videos
|
return videos
|
||||||
.map(({ season }) => season)
|
.map(({ season }) => season)
|
||||||
|
|
@ -52,7 +52,7 @@ const VideosList = ({ className, metaResource, season, seasonOnSelect }) => {
|
||||||
return (
|
return (
|
||||||
<div className={classnames(className, styles['videos-list-container'])}>
|
<div className={classnames(className, styles['videos-list-container'])}>
|
||||||
{
|
{
|
||||||
!metaResource || metaResource.content.type === 'Loading' ?
|
!metaCatalog || metaCatalog.content.type === 'Loading' ?
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<SeasonsBar.Placeholder className={styles['seasons-bar']} />
|
<SeasonsBar.Placeholder className={styles['seasons-bar']} />
|
||||||
<SearchBar.Placeholder className={styles['search-bar']} title={'Search videos'} />
|
<SearchBar.Placeholder className={styles['search-bar']} title={'Search videos'} />
|
||||||
|
|
@ -65,7 +65,7 @@ const VideosList = ({ className, metaResource, season, seasonOnSelect }) => {
|
||||||
</div>
|
</div>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
:
|
:
|
||||||
metaResource.content.type === 'Err' || videosForSeason.length === 0 ?
|
metaCatalog.content.type === 'Err' || videosForSeason.length === 0 ?
|
||||||
<div className={styles['message-container']}>
|
<div className={styles['message-container']}>
|
||||||
<Image className={styles['image']} src={'/images/empty.png'} alt={' '} />
|
<Image className={styles['image']} src={'/images/empty.png'} alt={' '} />
|
||||||
<div className={styles['label']}>No videos found for this meta!</div>
|
<div className={styles['label']}>No videos found for this meta!</div>
|
||||||
|
|
@ -96,11 +96,23 @@ const VideosList = ({ className, metaResource, season, seasonOnSelect }) => {
|
||||||
return search.length === 0 ||
|
return search.length === 0 ||
|
||||||
(
|
(
|
||||||
(typeof video.title === 'string' && video.title.toLowerCase().includes(search.toLowerCase())) ||
|
(typeof video.title === 'string' && video.title.toLowerCase().includes(search.toLowerCase())) ||
|
||||||
(video.released.toLocaleString(undefined, { year: '2-digit', month: 'short', day: 'numeric' }).toLowerCase().includes(search.toLowerCase()))
|
(!isNaN(video.released.getTime()) && video.released.toLocaleString(undefined, { year: '2-digit', month: 'short', day: 'numeric' }).toLowerCase().includes(search.toLowerCase()))
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.map((video, index) => (
|
.map((video, index) => (
|
||||||
<Video {...video} key={index} />
|
<Video
|
||||||
|
key={index}
|
||||||
|
id={video.id}
|
||||||
|
title={video.title}
|
||||||
|
thumbnail={video.thumbnail}
|
||||||
|
episode={video.episode}
|
||||||
|
released={video.released}
|
||||||
|
upcoming={video.upcoming}
|
||||||
|
watched={video.watched}
|
||||||
|
progress={video.progress}
|
||||||
|
deepLinks={video.deepLinks}
|
||||||
|
scheduled={video.scheduled}
|
||||||
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -112,7 +124,7 @@ const VideosList = ({ className, metaResource, season, seasonOnSelect }) => {
|
||||||
|
|
||||||
VideosList.propTypes = {
|
VideosList.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
metaResource: PropTypes.object,
|
metaCatalog: PropTypes.object,
|
||||||
season: PropTypes.number,
|
season: PropTypes.number,
|
||||||
seasonOnSelect: PropTypes.func
|
seasonOnSelect: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,111 +1,64 @@
|
||||||
// Copyright (C) 2017-2020 Smart code 203358507
|
// Copyright (C) 2017-2020 Smart code 203358507
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const { CONSTANTS, deepLinking, useModelState } = require('stremio/common');
|
const { useModelState } = require('stremio/common');
|
||||||
|
|
||||||
const initMetaDetailsState = () => ({
|
const init = () => ({
|
||||||
selected: null,
|
selected: null,
|
||||||
meta_resources: [],
|
metaCatalog: null,
|
||||||
streams_resources: []
|
streamsCatalogs: [],
|
||||||
|
metaExtensions: []
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapMetaDetailsStateWithCtx = (meta_details, ctx) => {
|
const map = (metaDetails) => ({
|
||||||
const selected = meta_details.selected;
|
...metaDetails,
|
||||||
const meta_resources = meta_details.meta_resources.map((meta_resource) => {
|
metaCatalog: metaDetails.metaCatalog !== null && metaDetails.metaCatalog.content.type === 'Ready' ?
|
||||||
return meta_resource.content.type === 'Ready' ?
|
{
|
||||||
{
|
...metaDetails.metaCatalog,
|
||||||
request: meta_resource.request,
|
content: {
|
||||||
|
...metaDetails.metaCatalog.content,
|
||||||
content: {
|
content: {
|
||||||
type: 'Ready',
|
...metaDetails.metaCatalog.content.content,
|
||||||
content: {
|
released: new Date(
|
||||||
...meta_resource.content.content,
|
typeof metaDetails.metaCatalog.content.content.released === 'string' ?
|
||||||
|
metaDetails.metaCatalog.content.content.released
|
||||||
|
:
|
||||||
|
NaN
|
||||||
|
),
|
||||||
|
videos: metaDetails.metaCatalog.content.content.videos.map((video) => ({
|
||||||
|
...video,
|
||||||
released: new Date(
|
released: new Date(
|
||||||
typeof meta_resource.content.content.released === 'string' ?
|
typeof video.released === 'string' ?
|
||||||
meta_resource.content.content.released
|
video.released
|
||||||
:
|
:
|
||||||
NaN
|
NaN
|
||||||
),
|
),
|
||||||
videos: meta_resource.content.content.videos.map((video) => ({
|
|
||||||
...video,
|
|
||||||
released: new Date(
|
|
||||||
typeof video.released === 'string' ?
|
|
||||||
video.released
|
|
||||||
:
|
|
||||||
NaN
|
|
||||||
),
|
|
||||||
upcoming: Date.parse(video.released) > Date.now(),
|
|
||||||
// TODO add watched and progress
|
|
||||||
deepLinks: deepLinking.withVideo({
|
|
||||||
video,
|
|
||||||
metaTransportUrl: meta_resource.request.base,
|
|
||||||
metaItem: meta_resource.content.content
|
|
||||||
})
|
|
||||||
})),
|
|
||||||
metaExtensions: meta_resource.content.content.links.filter((link) => link.category === CONSTANTS.META_LINK_CATEGORY)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addon: ctx.profile.addons.reduce((result, addon) => {
|
|
||||||
if (addon.transportUrl === meta_resource.request.base) {
|
|
||||||
return addon;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}, null)
|
|
||||||
}
|
|
||||||
:
|
|
||||||
meta_resource;
|
|
||||||
});
|
|
||||||
const streams_resources = meta_details.streams_resources.map((stream_resource) => {
|
|
||||||
return stream_resource.content.type === 'Ready' ?
|
|
||||||
{
|
|
||||||
request: stream_resource.request,
|
|
||||||
content: {
|
|
||||||
type: 'Ready',
|
|
||||||
content: stream_resource.content.content.map((stream) => ({
|
|
||||||
...stream,
|
|
||||||
// TODO map progress
|
|
||||||
deepLinks: deepLinking.withStream({
|
|
||||||
stream,
|
|
||||||
streamTransportUrl: stream_resource.request.base,
|
|
||||||
// TODO metaTransportUrl should be based on state
|
|
||||||
metaTransportUrl: meta_details.meta_resources.reduceRight((result, meta_resource) => {
|
|
||||||
if (meta_resource.content.type === 'Ready') {
|
|
||||||
return meta_resource.request.base;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}, ''),
|
|
||||||
type: selected.meta_resource_ref.type_name,
|
|
||||||
id: selected.meta_resource_ref.id,
|
|
||||||
videoId: selected.streams_resource_ref.id,
|
|
||||||
})
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:
|
}
|
||||||
stream_resource;
|
:
|
||||||
});
|
metaDetails.metaCatalog
|
||||||
return { selected, meta_resources, streams_resources };
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const useMetaDetails = (urlParams) => {
|
const useMetaDetails = (urlParams) => {
|
||||||
const loadMetaDetailsAction = React.useMemo(() => {
|
const action = React.useMemo(() => {
|
||||||
if (typeof urlParams.type === 'string' && typeof urlParams.id === 'string') {
|
if (typeof urlParams.type === 'string' && typeof urlParams.id === 'string') {
|
||||||
return {
|
return {
|
||||||
action: 'Load',
|
action: 'Load',
|
||||||
args: {
|
args: {
|
||||||
model: 'MetaDetails',
|
model: 'MetaDetails',
|
||||||
args: {
|
args: {
|
||||||
meta_resource_ref: {
|
metaPath: {
|
||||||
resource: 'meta',
|
resource: 'meta',
|
||||||
type_name: urlParams.type,
|
type: urlParams.type,
|
||||||
id: urlParams.id,
|
id: urlParams.id,
|
||||||
extra: []
|
extra: []
|
||||||
},
|
},
|
||||||
streams_resource_ref: typeof urlParams.videoId === 'string' ?
|
streamsPath: typeof urlParams.videoId === 'string' ?
|
||||||
{
|
{
|
||||||
resource: 'stream',
|
resource: 'stream',
|
||||||
type_name: urlParams.type,
|
type: urlParams.type,
|
||||||
id: urlParams.videoId,
|
id: urlParams.videoId,
|
||||||
extra: []
|
extra: []
|
||||||
}
|
}
|
||||||
|
|
@ -120,12 +73,7 @@ const useMetaDetails = (urlParams) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [urlParams]);
|
}, [urlParams]);
|
||||||
return useModelState({
|
return useModelState({ model: 'meta_details', action, map, init });
|
||||||
model: 'meta_details',
|
|
||||||
action: loadMetaDetailsAction,
|
|
||||||
mapWithCtx: mapMetaDetailsStateWithCtx,
|
|
||||||
init: initMetaDetailsState
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = useMetaDetails;
|
module.exports = useMetaDetails;
|
||||||
|
|
|
||||||
23
src/routes/MetaDetails/useMetaExtensionTabs.js
Normal file
23
src/routes/MetaDetails/useMetaExtensionTabs.js
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (C) 2017-2020 Smart code 203358507
|
||||||
|
|
||||||
|
const React = require('react');
|
||||||
|
|
||||||
|
const useMetaExtensionTabs = (metaExtensions) => {
|
||||||
|
const tabs = React.useMemo(() => {
|
||||||
|
return metaExtensions
|
||||||
|
.map((extension) => ({
|
||||||
|
id: extension.url,
|
||||||
|
label: extension.addon.manifest.name,
|
||||||
|
logo: extension.addon.manifest.logo,
|
||||||
|
icon: 'ic_addons',
|
||||||
|
onClick: () => setSelected(extension)
|
||||||
|
}));
|
||||||
|
}, [metaExtensions]);
|
||||||
|
const [selected, setSelected] = React.useState(null);
|
||||||
|
const clear = React.useCallback(() => {
|
||||||
|
setSelected(null);
|
||||||
|
}, []);
|
||||||
|
return [tabs, selected, clear];
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = useMetaExtensionTabs;
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
// Copyright (C) 2017-2020 Smart code 203358507
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
|
|
||||||
const useMetaExtensions = (metaResources) => {
|
|
||||||
const tabs = React.useMemo(() => {
|
|
||||||
return metaResources.filter((metaResource) => metaResource.content.type === 'Ready' && metaResource.content.content.metaExtensions.length > 0 && metaResource.addon !== null)
|
|
||||||
.map((metaResource) => {
|
|
||||||
return metaResource.content.content.metaExtensions.map((metaExtension) => (
|
|
||||||
{
|
|
||||||
id: metaExtension.url,
|
|
||||||
label: metaResource.addon.manifest.name,
|
|
||||||
logo: metaResource.addon.manifest.logo,
|
|
||||||
icon: 'ic_addons',
|
|
||||||
onClick: () => { setSelectedMetaExtension(metaExtension); }
|
|
||||||
}
|
|
||||||
));
|
|
||||||
})
|
|
||||||
.flat(2)
|
|
||||||
.filter((tab, index, tabs) => tabs.findIndex((_tab) => _tab.id === tab.id) === index);
|
|
||||||
}, [metaResources]);
|
|
||||||
const [selectedMetaExtension, setSelectedMetaExtension] = React.useState(null);
|
|
||||||
const clearSelectedMetaExtension = React.useCallback(() => {
|
|
||||||
setSelectedMetaExtension(null);
|
|
||||||
}, []);
|
|
||||||
return { tabs, selectedMetaExtension, clearSelectedMetaExtension };
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = useMetaExtensions;
|
|
||||||
20
src/routes/MetaDetails/useSeason.js
Normal file
20
src/routes/MetaDetails/useSeason.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright (C) 2017-2020 Smart code 203358507
|
||||||
|
|
||||||
|
const React = require('react');
|
||||||
|
|
||||||
|
const useSeason = (urlParams, queryParams) => {
|
||||||
|
const season = React.useMemo(() => {
|
||||||
|
return queryParams.has('season') && !isNaN(queryParams.get('season')) ?
|
||||||
|
parseInt(queryParams.get('season'))
|
||||||
|
:
|
||||||
|
null;
|
||||||
|
}, [queryParams]);
|
||||||
|
const setSeason = React.useCallback((season) => {
|
||||||
|
const nextQueryParams = new URLSearchParams(queryParams);
|
||||||
|
nextQueryParams.set('season', season);
|
||||||
|
window.location.replace(`#${urlParams.path}?${nextQueryParams}`);
|
||||||
|
}, [urlParams, queryParams]);
|
||||||
|
return [season, setSeason];
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = useSeason;
|
||||||
Loading…
Reference in a new issue