mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-21 07:32:02 +00:00
Addon layout changed
This commit is contained in:
parent
91c692a0c4
commit
3e1ab699f5
4 changed files with 93 additions and 64 deletions
|
|
@ -2,24 +2,54 @@ const React = require('react');
|
||||||
const PropTypes = require('prop-types');
|
const PropTypes = require('prop-types');
|
||||||
const classnames = require('classnames');
|
const classnames = require('classnames');
|
||||||
const Icon = require('stremio-icons/dom');
|
const Icon = require('stremio-icons/dom');
|
||||||
const { Button } = require('stremio/common');
|
const { Button, Image } = require('stremio/common');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
||||||
const Addon = ({ className, id, name, logo, description, types, version, transportUrl, installed, toggle, onShareButtonClicked }) => {
|
const Addon = ({ className, id, name, version, logo, description, types, installed, onToggle, onShare, dataset }) => {
|
||||||
const onKeyUp = React.useCallback((event) => {
|
const toggleButtonOnClick = React.useCallback((event) => {
|
||||||
if (event.key === 'Enter' && typeof toggle === 'function') {
|
if (typeof onToggle === 'function') {
|
||||||
toggle(event);
|
onToggle({
|
||||||
|
type: 'toggle',
|
||||||
|
nativeEvent: event.nativeEvent,
|
||||||
|
reactEvent: event,
|
||||||
|
dataset: dataset
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [toggle]);
|
}, [onToggle, dataset]);
|
||||||
|
const shareButtonOnClick = React.useCallback((event) => {
|
||||||
|
if (typeof onShare === 'function') {
|
||||||
|
onShare({
|
||||||
|
type: 'share',
|
||||||
|
nativeEvent: event.nativeEvent,
|
||||||
|
reactEvent: event,
|
||||||
|
dataset: dataset
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [onShare, dataset]);
|
||||||
|
const onKeyDown = React.useCallback((event) => {
|
||||||
|
if (event.key === 'Enter' && typeof onToggle === 'function') {
|
||||||
|
onToggle({
|
||||||
|
type: 'toggle',
|
||||||
|
nativeEvent: event.nativeEvent,
|
||||||
|
reactEvent: event,
|
||||||
|
dataset: dataset
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [onToggle, dataset]);
|
||||||
|
const renderLogoFallback = React.useMemo(() => () => {
|
||||||
|
return (
|
||||||
|
<Icon className={styles['icon']} icon={'ic_addons'} />
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<Button className={classnames(styles['addon-container'], className)} data-id={id} onKeyUp={onKeyUp}>
|
<Button className={classnames(className, styles['addon-container'])} onKeyDown={onKeyDown}>
|
||||||
<div className={styles['logo-container']}>
|
<div className={styles['logo-container']}>
|
||||||
{
|
<Image
|
||||||
typeof logo === 'string' && logo.length > 0 ?
|
className={styles['logo']}
|
||||||
<img className={styles['logo']} src={logo} alt={' '} />
|
src={logo}
|
||||||
:
|
alt={' '}
|
||||||
<Icon className={styles['icon']} icon={'ic_addons'} />
|
renderFallback={renderLogoFallback}
|
||||||
}
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['info-container']}>
|
<div className={styles['info-container']}>
|
||||||
<div className={styles['name-container']} title={typeof name === 'string' && name.length > 0 ? name : id}>
|
<div className={styles['name-container']} title={typeof name === 'string' && name.length > 0 ? name : id}>
|
||||||
|
|
@ -32,10 +62,10 @@ const Addon = ({ className, id, name, logo, description, types, version, transpo
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
Array.isArray(types) ?
|
Array.isArray(types) && types.length > 0 ?
|
||||||
<div className={styles['types-container']}>
|
<div className={styles['types-container']}>
|
||||||
{
|
{
|
||||||
types.length <= 1 ?
|
types.length === 1 ?
|
||||||
types.join('')
|
types.join('')
|
||||||
:
|
:
|
||||||
types.slice(0, -1).join(', ') + ' & ' + types[types.length - 1]
|
types.slice(0, -1).join(', ') + ' & ' + types[types.length - 1]
|
||||||
|
|
@ -52,10 +82,10 @@ const Addon = ({ className, id, name, logo, description, types, version, transpo
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['buttons-container']}>
|
<div className={styles['buttons-container']}>
|
||||||
<Button className={installed ? styles['uninstall-button-container'] : styles['install-button-container']} title={installed ? 'Uninstall' : 'Install'} tabIndex={-1} data-id={id} onClick={toggle}>
|
<Button className={installed ? styles['uninstall-button-container'] : styles['install-button-container']} title={installed ? 'Uninstall' : 'Install'} tabIndex={-1} onClick={toggleButtonOnClick}>
|
||||||
<div className={styles['label']}>{installed ? 'Uninstall' : 'Install'}</div>
|
<div className={styles['label']}>{installed ? 'Uninstall' : 'Install'}</div>
|
||||||
</Button>
|
</Button>
|
||||||
<Button className={styles['share-button-container']} title={'Share addon'} tabIndex={-1} onClick={onShareButtonClicked}>
|
<Button className={styles['share-button-container']} title={'Share addon'} tabIndex={-1} onClick={shareButtonOnClick}>
|
||||||
<Icon className={styles['icon']} icon={'ic_share'} />
|
<Icon className={styles['icon']} icon={'ic_share'} />
|
||||||
<div className={styles['label']}>Share addon</div>
|
<div className={styles['label']}>Share addon</div>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -68,14 +98,14 @@ Addon.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
|
version: PropTypes.string,
|
||||||
logo: PropTypes.string,
|
logo: PropTypes.string,
|
||||||
description: PropTypes.string,
|
description: PropTypes.string,
|
||||||
types: PropTypes.arrayOf(PropTypes.string),
|
types: PropTypes.arrayOf(PropTypes.string),
|
||||||
version: PropTypes.string,
|
|
||||||
transportUrl: PropTypes.string,
|
|
||||||
installed: PropTypes.bool,
|
installed: PropTypes.bool,
|
||||||
toggle: PropTypes.func,
|
onToggle: PropTypes.func,
|
||||||
onShareButtonClicked: PropTypes.func
|
onShare: PropTypes.func,
|
||||||
|
dataset: PropTypes.objectOf(PropTypes.string)
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Addon;
|
module.exports = Addon;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
.addon-container {
|
.addon-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background-color: var(--color-backgroundlighter);
|
background-color: var(--color-backgroundlighter);
|
||||||
|
|
@ -17,6 +16,7 @@
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
object-position: center;
|
object-position: center;
|
||||||
}
|
}
|
||||||
|
|
@ -31,14 +31,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-container {
|
.info-container {
|
||||||
flex-grow: 1000;
|
flex-grow: 1;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
min-width: 40rem;
|
|
||||||
padding: 0 0.5rem;
|
padding: 0 0.5rem;
|
||||||
|
|
||||||
.name-container {
|
.name-container {
|
||||||
|
|
@ -47,7 +46,7 @@
|
||||||
flex-basis: auto;
|
flex-basis: auto;
|
||||||
padding: 0 0.5rem;
|
padding: 0 0.5rem;
|
||||||
max-height: 3.6em;
|
max-height: 3.6em;
|
||||||
font-size: 1.5rem;
|
font-size: 1.6rem;
|
||||||
color: var(--color-surfacelighter);
|
color: var(--color-surfacelighter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,6 +54,7 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
flex-basis: auto;
|
flex-basis: auto;
|
||||||
|
margin-top: 0.5rem;
|
||||||
padding: 0 0.5rem;
|
padding: 0 0.5rem;
|
||||||
max-height: 2.4em;
|
max-height: 2.4em;
|
||||||
color: var(--color-surfacelight);
|
color: var(--color-surfacelight);
|
||||||
|
|
@ -83,22 +83,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttons-container {
|
.buttons-container {
|
||||||
flex-grow: 1;
|
flex: none;
|
||||||
flex-shrink: 0;
|
width: 17rem;
|
||||||
flex-basis: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: flex-end;
|
|
||||||
min-width: 17rem;
|
|
||||||
|
|
||||||
.install-button-container, .uninstall-button-container, .share-button-container {
|
.install-button-container, .uninstall-button-container, .share-button-container {
|
||||||
flex: none;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 17rem;
|
|
||||||
height: 3.5rem;
|
height: 3.5rem;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
|
|
||||||
|
|
@ -106,13 +98,8 @@
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
flex: none;
|
flex: none;
|
||||||
display: block;
|
|
||||||
width: 1.5rem;
|
width: 1.5rem;
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
|
|
|
||||||
|
|
@ -6,27 +6,32 @@ const useAddons = require('./useAddons');
|
||||||
const useSelectableInputs = require('./useSelectableInputs');
|
const useSelectableInputs = require('./useSelectableInputs');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
||||||
|
const navigateToAddonDetails = (addonsCatalogRequest, transportUrl) => {
|
||||||
|
const queryParams = new URLSearchParams([['addon', transportUrl]]);
|
||||||
|
if (addonsCatalogRequest !== null) {
|
||||||
|
const addonTransportUrl = encodeURIComponent(addonsCatalogRequest.base);
|
||||||
|
const catalogId = encodeURIComponent(addonsCatalogRequest.path.id);
|
||||||
|
const type = encodeURIComponent(addonsCatalogRequest.path.type_name);
|
||||||
|
window.location.replace(`#/addons/${addonTransportUrl}/${catalogId}/${type}?${queryParams}`);
|
||||||
|
} else {
|
||||||
|
window.location.replace(`#/addons?${queryParams}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const Addons = ({ urlParams, queryParams }) => {
|
const Addons = ({ urlParams, queryParams }) => {
|
||||||
const navigateToAddonDetails = React.useCallback((transportUrl) => {
|
|
||||||
const queryParams = new URLSearchParams([['addon', transportUrl]]);
|
|
||||||
if (typeof urlParams.addonTransportUrl === 'string' && typeof urlParams.catalogId === 'string' && typeof urlParams.type === 'string') {
|
|
||||||
const addonTransportUrl = encodeURIComponent(urlParams.addonTransportUrl);
|
|
||||||
const catalogId = encodeURIComponent(urlParams.catalogId);
|
|
||||||
const type = encodeURIComponent(urlParams.type);
|
|
||||||
window.location.replace(`#/addons/${addonTransportUrl}/${catalogId}/${type}?${queryParams}`);
|
|
||||||
} else {
|
|
||||||
window.location.replace(`#/addons?${queryParams}`);
|
|
||||||
}
|
|
||||||
}, [urlParams]);
|
|
||||||
const addons = useAddons(urlParams);
|
const addons = useAddons(urlParams);
|
||||||
const selectInputs = useSelectableInputs(addons);
|
const selectInputs = useSelectableInputs(addons);
|
||||||
const [addAddonModalOpen, openAddAddonModal, closeAddAddonModal] = useBinaryState(false);
|
const [addAddonModalOpen, openAddAddonModal, closeAddAddonModal] = useBinaryState(false);
|
||||||
const addAddonUrlInputRef = React.useRef(null);
|
const addAddonUrlInputRef = React.useRef(null);
|
||||||
const addAddonOnSubmit = React.useCallback(() => {
|
const addAddonOnSubmit = React.useCallback(() => {
|
||||||
if (addAddonUrlInputRef.current !== null) {
|
if (addAddonUrlInputRef.current !== null) {
|
||||||
navigateToAddonDetails(addAddonUrlInputRef.current.value);
|
const addonsCatalogRequest = addons.catalog_resource !== null ?
|
||||||
|
addons.catalog_resource.request
|
||||||
|
:
|
||||||
|
null;
|
||||||
|
navigateToAddonDetails(addonsCatalogRequest, addAddonUrlInputRef.current.value);
|
||||||
}
|
}
|
||||||
}, [navigateToAddonDetails]);
|
}, [addons]);
|
||||||
const addAddonModalButtons = React.useMemo(() => {
|
const addAddonModalButtons = React.useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
@ -49,19 +54,23 @@ const Addons = ({ urlParams, queryParams }) => {
|
||||||
setSearch(event.currentTarget.value);
|
setSearch(event.currentTarget.value);
|
||||||
}, []);
|
}, []);
|
||||||
const [sharedTransportUrl, setSharedTransportUrl] = React.useState(null);
|
const [sharedTransportUrl, setSharedTransportUrl] = React.useState(null);
|
||||||
const shareModalOnClose = React.useCallback(() => {
|
const clearSharedTransportUrl = React.useCallback(() => {
|
||||||
setSharedTransportUrl(null);
|
setSharedTransportUrl(null);
|
||||||
}, []);
|
}, []);
|
||||||
const onAddonShare = React.useCallback((event) => {
|
const onAddonShare = React.useCallback((event) => {
|
||||||
setSharedTransportUrl(event.dataset.transportUrl);
|
setSharedTransportUrl(event.dataset.transportUrl);
|
||||||
}, []);
|
}, []);
|
||||||
const onAddonToggle = React.useCallback((event) => {
|
const onAddonToggle = React.useCallback((event) => {
|
||||||
navigateToAddonDetails(event.dataset.transportUrl);
|
const addonsCatalogRequest = addons.catalog_resource !== null ?
|
||||||
}, []);
|
addons.catalog_resource.request
|
||||||
|
:
|
||||||
|
null;
|
||||||
|
navigateToAddonDetails(addonsCatalogRequest, event.dataset.transportUrl);
|
||||||
|
}, [addons]);
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
closeAddAddonModal(null);
|
closeAddAddonModal();
|
||||||
setSearch('');
|
setSearch('');
|
||||||
setSharedTransportUrl(null);
|
clearSharedTransportUrl();
|
||||||
}, [urlParams, queryParams]);
|
}, [urlParams, queryParams]);
|
||||||
return (
|
return (
|
||||||
<div className={styles['addons-container']}>
|
<div className={styles['addons-container']}>
|
||||||
|
|
@ -127,12 +136,13 @@ const Addons = ({ urlParams, queryParams }) => {
|
||||||
className={styles['addon']}
|
className={styles['addon']}
|
||||||
id={addon.manifest.id}
|
id={addon.manifest.id}
|
||||||
name={addon.manifest.name}
|
name={addon.manifest.name}
|
||||||
|
version={addon.manifest.version}
|
||||||
logo={addon.manifest.logo}
|
logo={addon.manifest.logo}
|
||||||
description={addon.manifest.description}
|
description={addon.manifest.description}
|
||||||
types={addon.manifest.types}
|
types={addon.manifest.types}
|
||||||
version={addon.manifest.version}
|
installed={addon.installed}
|
||||||
onShare={onAddonShare}
|
|
||||||
onToggle={onAddonToggle}
|
onToggle={onAddonToggle}
|
||||||
|
onShare={onAddonShare}
|
||||||
dataset={{ transportUrl: addon.transportUrl }}
|
dataset={{ transportUrl: addon.transportUrl }}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
|
@ -163,8 +173,11 @@ const Addons = ({ urlParams, queryParams }) => {
|
||||||
<ModalDialog
|
<ModalDialog
|
||||||
className={styles['share-modal-container']}
|
className={styles['share-modal-container']}
|
||||||
title={'Share addon'}
|
title={'Share addon'}
|
||||||
onCloseRequest={shareModalOnClose}>
|
onCloseRequest={clearSharedTransportUrl}>
|
||||||
<SharePrompt className={styles['share-prompt-container']} url={sharedTransportUrl} />
|
<SharePrompt
|
||||||
|
className={styles['share-prompt-container']}
|
||||||
|
url={sharedTransportUrl}
|
||||||
|
/>
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,7 @@ const mapAddonsStateWithCtx = (addons, ctx) => {
|
||||||
version: descriptor.manifest.version,
|
version: descriptor.manifest.version,
|
||||||
logo: descriptor.manifest.logo,
|
logo: descriptor.manifest.logo,
|
||||||
description: descriptor.manifest.description,
|
description: descriptor.manifest.description,
|
||||||
types: descriptor.manifest.types,
|
types: descriptor.manifest.types
|
||||||
catalogs: descriptor.manifest.catalogs,
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue