MetaDetails adapted to changes in core

This commit is contained in:
nklhrstv 2020-10-27 14:52:30 +02:00
parent d32fe5123a
commit 07398b56cc
8 changed files with 207 additions and 202 deletions

View file

@ -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>
: :

View file

@ -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;

View file

@ -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

View file

@ -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
}; };

View file

@ -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;

View 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;

View file

@ -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;

View 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;