stremio-web/src/routes/Addons/Addons.js

255 lines
12 KiB
JavaScript

// Copyright (C) 2017-2020 Smart code 203358507
const React = require('react');
const PropTypes = require('prop-types');
const Icon = require('@stremio/stremio-icons/dom');
const { AddonDetailsModal, Button, Image, Multiselect, MainNavBars, TextInput, SearchBar, SharePrompt, ModalDialog, useBinaryState } = require('stremio/common');
const Addon = require('./Addon');
const useInstalledAddons = require('./useInstalledAddons');
const useRemoteAddons = require('./useRemoteAddons');
const useAddonDetailsTransportUrl = require('./useAddonDetailsTransportUrl');
const useSelectableInputs = require('./useSelectableInputs');
const styles = require('./styles');
const { Filters } = require('stremio/common');
const Addons = ({ urlParams, queryParams }) => {
const installedAddons = useInstalledAddons(urlParams);
const remoteAddons = useRemoteAddons(urlParams);
const [addonDetailsTransportUrl, setAddonDetailsTransportUrl] = useAddonDetailsTransportUrl(urlParams, queryParams);
const selectInputs = useSelectableInputs(installedAddons, remoteAddons);
const [addAddonModalOpen, openAddAddonModal, closeAddAddonModal] = useBinaryState(false);
const addAddonUrlInputRef = React.useRef(null);
const addAddonOnSubmit = React.useCallback(() => {
if (addAddonUrlInputRef.current !== null) {
setAddonDetailsTransportUrl(addAddonUrlInputRef.current.value);
}
}, [setAddonDetailsTransportUrl]);
const addAddonModalButtons = React.useMemo(() => {
return [
{
className: styles['cancel-button'],
label: 'Cancel',
props: {
onClick: closeAddAddonModal
}
},
{
label: 'Add',
props: {
onClick: addAddonOnSubmit
}
}
];
}, [addAddonOnSubmit]);
const [search, setSearch] = React.useState('');
const searchInputOnChange = React.useCallback((event) => {
setSearch(event.currentTarget.value);
}, []);
const [sharedAddon, setSharedAddon] = React.useState(null);
const clearSharedAddon = React.useCallback(() => {
setSharedAddon(null);
}, []);
const onAddonShare = React.useCallback((event) => {
setSharedAddon(event.dataset.addon);
}, []);
const onAddonToggle = React.useCallback((event) => {
setAddonDetailsTransportUrl(event.dataset.addon.transportUrl);
}, [setAddonDetailsTransportUrl]);
const closeAddonDetails = React.useCallback(() => {
setAddonDetailsTransportUrl(null);
}, [setAddonDetailsTransportUrl]);
const searchFilterPredicate = React.useCallback((addon) => {
return search.length === 0 ||
(
(typeof addon.manifest.name === 'string' && addon.manifest.name.toLowerCase().includes(search.toLowerCase())) ||
(typeof addon.manifest.description === 'string' && addon.manifest.description.toLowerCase().includes(search.toLowerCase()))
);
}, [search]);
const renderLogoFallback = React.useCallback(() => (
<Icon className={styles['icon']} icon={'ic_addons'} />
), []);
const renderMultiselectsInputs = React.useCallback(() => {
return selectInputs.map((selectInput, index) => (
<Multiselect
{...selectInput}
key={index}
className={styles['select-input-container']}
/>
));
}, [selectInputs]);
React.useLayoutEffect(() => {
closeAddAddonModal();
setSearch('');
clearSharedAddon();
}, [urlParams, queryParams]);
return (
<MainNavBars className={styles['addons-container']} route={'addons'}>
<div className={styles['addons-content']}>
<div className={styles['selectable-inputs-container']}>
<Button className={styles['add-button-container']} title={'Add addon'} onClick={openAddAddonModal}>
<Icon className={styles['icon']} icon={'ic_plus'} />
<div className={styles['add-button-label']}>Add addon</div>
</Button>
<div className={styles['multiselect-inputs-container']}>
{ renderMultiselectsInputs() }
</div>
<div className={styles['spacing']} />
<SearchBar
className={styles['search-bar']}
title={'Search addons'}
value={search}
onChange={searchInputOnChange}
/>
<Filters className={styles['filters']}>
{ renderMultiselectsInputs() }
</Filters>
</div>
{
installedAddons.selected !== null ?
installedAddons.selectable.types.length === 0 ?
<div className={styles['message-container']}>
No addons ware installed!
</div>
:
installedAddons.catalog.length === 0 ?
<div className={styles['message-container']}>
No addons ware installed for that type!
</div>
:
<div className={styles['addons-list-container']}>
{
installedAddons.catalog
.filter(searchFilterPredicate)
.map((addon, index) => (
<Addon
key={index}
className={styles['addon']}
id={addon.manifest.id}
name={addon.manifest.name}
version={addon.manifest.version}
logo={addon.manifest.logo}
description={addon.manifest.description}
types={addon.manifest.types}
installed={addon.installed}
onToggle={onAddonToggle}
onShare={onAddonShare}
dataset={{ addon }}
/>
))
}
</div>
:
remoteAddons.selected !== null ?
remoteAddons.catalog.content.type === 'Err' ?
<div className={styles['message-container']}>
{remoteAddons.catalog.content.content}
</div>
:
remoteAddons.catalog.content.type === 'Loading' ?
<div className={styles['message-container']}>
Loading!
</div>
:
<div className={styles['addons-list-container']}>
{
remoteAddons.catalog.content.content
.filter(searchFilterPredicate)
.map((addon, index) => (
<Addon
key={index}
className={styles['addon']}
id={addon.manifest.id}
name={addon.manifest.name}
version={addon.manifest.version}
logo={addon.manifest.logo}
description={addon.manifest.description}
types={addon.manifest.types}
installed={addon.installed}
onToggle={onAddonToggle}
onShare={onAddonShare}
dataset={{ addon }}
/>
))
}
</div>
:
<div className={styles['message-container']}>
No select
</div>
}
</div>
{
addAddonModalOpen ?
<ModalDialog
className={styles['add-addon-modal-container']}
title={'Add addon'}
buttons={addAddonModalButtons}
onCloseRequest={closeAddAddonModal}>
<div className={styles['notice']}>You can add an addon via an external link, which will appear under Installed addons.</div>
<TextInput
ref={addAddonUrlInputRef}
className={styles['addon-url-input']}
type={'text'}
placeholder={'Paste addon URL'}
autoFocus={true}
onSubmit={addAddonOnSubmit}
/>
</ModalDialog>
:
null
}
{
sharedAddon !== null ?
<ModalDialog
className={styles['share-modal-container']}
title={'Share Addon'}
onCloseRequest={clearSharedAddon}>
<div className={styles['title-container']}>
<Image
className={styles['logo']}
src={sharedAddon.manifest.logo}
alt={' '}
renderFallback={renderLogoFallback}
/>
<div className={styles['name-container']}>
<span className={styles['name']}>{typeof sharedAddon.manifest.name === 'string' && sharedAddon.manifest.name.length > 0 ? sharedAddon.manifest.name : sharedAddon.manifest.id}</span>
{
typeof sharedAddon.manifest.version === 'string' && sharedAddon.manifest.version.length > 0 ?
<span className={styles['version']}>v. {sharedAddon.manifest.version}</span>
:
null
}
</div>
</div>
<SharePrompt
className={styles['share-prompt-container']}
url={sharedAddon.transportUrl}
/>
</ModalDialog>
:
null
}
{
typeof addonDetailsTransportUrl === 'string' ?
<AddonDetailsModal
transportUrl={addonDetailsTransportUrl}
onCloseRequest={closeAddonDetails}
/>
:
null
}
</MainNavBars>
);
};
Addons.propTypes = {
urlParams: PropTypes.shape({
path: PropTypes.string,
transportUrl: PropTypes.string,
catalogId: PropTypes.string,
type: PropTypes.string
}),
queryParams: PropTypes.instanceOf(URLSearchParams)
};
module.exports = Addons;