mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
Merge pull request #864 from Stremio/feat/replace-multiselect-multiselectmenu
Refactor: Replace Multiselect with MultiselectMenu component
This commit is contained in:
commit
718a64877c
14 changed files with 110 additions and 101 deletions
|
|
@ -63,7 +63,7 @@ const Dropdown = ({ level, setLevel, options, onSelect, selectedOption, menuOpen
|
|||
.filter((option: MultiselectMenuOption) => !option.hidden)
|
||||
.map((option: MultiselectMenuOption) => (
|
||||
<Option
|
||||
key={option.id}
|
||||
key={option.value}
|
||||
ref={handleSetOptionRef(option.value)}
|
||||
option={option}
|
||||
onSelect={onSelect}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover, &.active {
|
||||
background-color: var(--overlay-color);
|
||||
}
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ const MultiselectMenu = ({ className, title, options, selectedOption, onSelect }
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(styles['multiselect-menu'], className)} ref={multiselectMenuRef}>
|
||||
<div className={classNames(styles['multiselect-menu'], { [styles['active']]: menuOpen }, className)} ref={multiselectMenuRef}>
|
||||
<Button
|
||||
className={classNames(styles['multiselect-button'], { [styles['open']]: menuOpen })}
|
||||
onClick={toggleMenu}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ const classnames = require('classnames');
|
|||
const { useTranslation } = require('react-i18next');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const { usePlatform, useBinaryState, withCoreSuspender } = require('stremio/common');
|
||||
const { AddonDetailsModal, Button, Image, MainNavBars, Multiselect, ModalDialog, SearchBar, SharePrompt, TextInput } = require('stremio/components');
|
||||
const { AddonDetailsModal, Button, Image, MainNavBars, ModalDialog, SearchBar, SharePrompt, TextInput, MultiselectMenu } = require('stremio/components');
|
||||
const { useServices } = require('stremio/services');
|
||||
const Addon = require('./Addon');
|
||||
const useInstalledAddons = require('./useInstalledAddons');
|
||||
|
|
@ -107,7 +107,7 @@ const Addons = ({ urlParams, queryParams }) => {
|
|||
<div className={styles['addons-content']}>
|
||||
<div className={styles['selectable-inputs-container']}>
|
||||
{selectInputs.map((selectInput, index) => (
|
||||
<Multiselect
|
||||
<MultiselectMenu
|
||||
{...selectInput}
|
||||
key={index}
|
||||
className={styles['select-input-container']}
|
||||
|
|
@ -218,7 +218,7 @@ const Addons = ({ urlParams, queryParams }) => {
|
|||
filtersModalOpen ?
|
||||
<ModalDialog title={'Addons filters'} className={styles['filters-modal']} onCloseRequest={closeFiltersModal}>
|
||||
{selectInputs.map((selectInput, index) => (
|
||||
<Multiselect
|
||||
<MultiselectMenu
|
||||
{...selectInput}
|
||||
key={index}
|
||||
className={styles['select-input-container']}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@
|
|||
}
|
||||
|
||||
.select-input-container {
|
||||
background-color: var(--overlay-color);
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
flex-basis: 15rem;
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ const React = require('react');
|
|||
const { useTranslate } = require('stremio/common');
|
||||
|
||||
const mapSelectableInputs = (installedAddons, remoteAddons, t) => {
|
||||
const selectedCatalog = remoteAddons.selectable.catalogs.concat(installedAddons.selectable.catalogs).find(({ selected }) => selected);
|
||||
const catalogSelect = {
|
||||
title: t.string('SELECT_CATALOG'),
|
||||
options: remoteAddons.selectable.catalogs
|
||||
.concat(installedAddons.selectable.catalogs)
|
||||
.map(({ name, deepLinks }) => ({
|
||||
|
|
@ -13,24 +13,27 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => {
|
|||
label: t.stringWithPrefix(name, 'ADDON_'),
|
||||
title: t.stringWithPrefix(name, 'ADDON_'),
|
||||
})),
|
||||
selected: remoteAddons.selectable.catalogs
|
||||
.concat(installedAddons.selectable.catalogs)
|
||||
.filter(({ selected }) => selected)
|
||||
.map(({ deepLinks }) => deepLinks.addons),
|
||||
renderLabelText: remoteAddons.selected !== null ?
|
||||
selectedOption: selectedCatalog
|
||||
? {
|
||||
label: t.stringWithPrefix(selectedCatalog.name, 'ADDON_'),
|
||||
value: selectedCatalog.deepLinks.addons,
|
||||
}
|
||||
: undefined,
|
||||
title: remoteAddons.selected !== null ?
|
||||
() => {
|
||||
const selectableCatalog = remoteAddons.selectable.catalogs
|
||||
.find(({ id }) => id === remoteAddons.selected.request.path.id);
|
||||
return selectableCatalog ? t.stringWithPrefix(selectableCatalog.name, 'ADDON_') : remoteAddons.selected.request.path.id;
|
||||
}
|
||||
:
|
||||
null,
|
||||
onSelect: (event) => {
|
||||
window.location = event.value;
|
||||
: null,
|
||||
onSelect: (value) => {
|
||||
window.location = value;
|
||||
}
|
||||
};
|
||||
const selectedType = installedAddons.selected !== null
|
||||
? installedAddons.selectable.types.find(({ selected }) => selected)
|
||||
: remoteAddons.selectable.types.find(({ selected }) => selected);
|
||||
const typeSelect = {
|
||||
title: t.string('SELECT_TYPE'),
|
||||
options: installedAddons.selected !== null ?
|
||||
installedAddons.selectable.types.map(({ type, deepLinks }) => ({
|
||||
value: deepLinks.addons,
|
||||
|
|
@ -41,15 +44,13 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => {
|
|||
value: deepLinks.addons,
|
||||
label: t.stringWithPrefix(type, 'TYPE_')
|
||||
})),
|
||||
selected: installedAddons.selected !== null ?
|
||||
installedAddons.selectable.types
|
||||
.filter(({ selected }) => selected)
|
||||
.map(({ deepLinks }) => deepLinks.addons)
|
||||
:
|
||||
remoteAddons.selectable.types
|
||||
.filter(({ selected }) => selected)
|
||||
.map(({ deepLinks }) => deepLinks.addons),
|
||||
renderLabelText: () => {
|
||||
selectedOption: selectedType
|
||||
? {
|
||||
label: selectedType.type !== null ? t.stringWithPrefix(selectedType.type, 'TYPE_') : t.string('TYPE_ALL'),
|
||||
value: selectedType.deepLinks.addons
|
||||
}
|
||||
: undefined,
|
||||
title: () => {
|
||||
return installedAddons.selected !== null ?
|
||||
installedAddons.selected.request.type === null ?
|
||||
t.string('TYPE_ALL')
|
||||
|
|
@ -61,8 +62,8 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => {
|
|||
:
|
||||
typeSelect.title;
|
||||
},
|
||||
onSelect: (event) => {
|
||||
window.location = event.value;
|
||||
onSelect: (value) => {
|
||||
window.location = value;
|
||||
}
|
||||
};
|
||||
return [catalogSelect, typeSelect];
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ const classnames = require('classnames');
|
|||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const { useServices } = require('stremio/services');
|
||||
const { CONSTANTS, useBinaryState, useOnScrollToBottom, withCoreSuspender } = require('stremio/common');
|
||||
const { AddonDetailsModal, Button, DelayedRenderer, Image, MainNavBars, MetaItem, MetaPreview, Multiselect, ModalDialog } = require('stremio/components');
|
||||
const { AddonDetailsModal, Button, DelayedRenderer, Image, MainNavBars, MetaItem, MetaPreview, ModalDialog, MultiselectMenu } = require('stremio/components');
|
||||
const useDiscover = require('./useDiscover');
|
||||
const useSelectableInputs = require('./useSelectableInputs');
|
||||
const styles = require('./styles');
|
||||
|
|
@ -100,14 +100,13 @@ const Discover = ({ urlParams, queryParams }) => {
|
|||
<div className={styles['discover-content']}>
|
||||
<div className={styles['catalog-container']}>
|
||||
<div className={styles['selectable-inputs-container']}>
|
||||
{selectInputs.map(({ title, options, selected, renderLabelText, onSelect }, index) => (
|
||||
<Multiselect
|
||||
{selectInputs.map(({ title, options, selectedOption, onSelect }, index) => (
|
||||
<MultiselectMenu
|
||||
key={index}
|
||||
className={styles['select-input']}
|
||||
title={title}
|
||||
options={options}
|
||||
selected={selected}
|
||||
renderLabelText={renderLabelText}
|
||||
selectedOption={selectedOption}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
|
|
@ -203,14 +202,13 @@ const Discover = ({ urlParams, queryParams }) => {
|
|||
{
|
||||
inputsModalOpen ?
|
||||
<ModalDialog title={'Catalog filters'} className={styles['selectable-inputs-modal']} onCloseRequest={closeInputsModal}>
|
||||
{selectInputs.map(({ title, options, selected, renderLabelText, onSelect }, index) => (
|
||||
<Multiselect
|
||||
{selectInputs.map(({ title, options, selectedOption, onSelect }, index) => (
|
||||
<MultiselectMenu
|
||||
key={index}
|
||||
className={styles['select-input']}
|
||||
title={title}
|
||||
options={options}
|
||||
selected={selected}
|
||||
renderLabelText={renderLabelText}
|
||||
selectedOption={selectedOption}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
|
||||
.select-input {
|
||||
flex: 0 1 15rem;
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: 1.5rem;
|
||||
|
|
|
|||
|
|
@ -4,72 +4,79 @@ const React = require('react');
|
|||
const { useTranslate } = require('stremio/common');
|
||||
|
||||
const mapSelectableInputs = (discover, t) => {
|
||||
const selectedType = discover.selectable.types.find(({ selected }) => selected);
|
||||
const typeSelect = {
|
||||
title: t.string('SELECT_TYPE'),
|
||||
options: discover.selectable.types
|
||||
.map(({ type, deepLinks }) => ({
|
||||
value: deepLinks.discover,
|
||||
label: t.stringWithPrefix(type, 'TYPE_')
|
||||
})),
|
||||
selected: discover.selectable.types
|
||||
.filter(({ selected }) => selected)
|
||||
.map(({ deepLinks }) => deepLinks.discover),
|
||||
renderLabelText: discover.selected !== null ?
|
||||
() => t.stringWithPrefix(discover.selected.request.path.type, 'TYPE_')
|
||||
:
|
||||
null,
|
||||
onSelect: (event) => {
|
||||
window.location = event.value;
|
||||
selectedOption: selectedType
|
||||
? {
|
||||
label: t.stringWithPrefix(selectedType.type, 'TYPE_'),
|
||||
value: selectedType.deepLinks.discover,
|
||||
}
|
||||
: undefined,
|
||||
title: discover.selected !== null
|
||||
? () => t.stringWithPrefix(discover.selected.request.path.type, 'TYPE_')
|
||||
: t.string('SELECT_TYPE'),
|
||||
onSelect: (value) => {
|
||||
window.location = value;
|
||||
}
|
||||
};
|
||||
const selectedCatalog = discover.selectable.catalogs.find(({ selected }) => selected);
|
||||
const catalogSelect = {
|
||||
title: t.string('SELECT_CATALOG'),
|
||||
options: discover.selectable.catalogs
|
||||
.map(({ id, name, addon, deepLinks }) => ({
|
||||
value: deepLinks.discover,
|
||||
label: t.catalogTitle({ addon, id, name }),
|
||||
title: `${name} (${addon.manifest.name})`
|
||||
})),
|
||||
selected: discover.selectable.catalogs
|
||||
.filter(({ selected }) => selected)
|
||||
.map(({ deepLinks }) => deepLinks.discover),
|
||||
renderLabelText: discover.selected !== null ?
|
||||
() => {
|
||||
selectedOption: discover.selected?.request.path.id
|
||||
? {
|
||||
label: t.catalogTitle({ addon: selectedCatalog.addon, id: selectedCatalog.id, name: selectedCatalog.name }),
|
||||
value: selectedCatalog.deepLinks.discover
|
||||
}
|
||||
: undefined,
|
||||
title: discover.selected !== null
|
||||
? () => {
|
||||
const selectableCatalog = discover.selectable.catalogs
|
||||
.find(({ id }) => id === discover.selected.request.path.id);
|
||||
return selectableCatalog ? t.catalogTitle(selectableCatalog, false) : discover.selected.request.path.id;
|
||||
}
|
||||
:
|
||||
null,
|
||||
onSelect: (event) => {
|
||||
window.location = event.value;
|
||||
t.string('SELECT_CATALOG'),
|
||||
onSelect: (value) => {
|
||||
window.location =value;
|
||||
}
|
||||
};
|
||||
const extraSelects = discover.selectable.extra.map(({ name, isRequired, options }) => ({
|
||||
title: t.stringWithPrefix(name, 'SELECT_'),
|
||||
isRequired: isRequired,
|
||||
options: options.map(({ value, deepLinks }) => ({
|
||||
label: typeof value === 'string' ? t.stringWithPrefix(value) : t.string('NONE'),
|
||||
value: JSON.stringify({
|
||||
href: deepLinks.discover,
|
||||
value
|
||||
})
|
||||
})),
|
||||
selected: options
|
||||
.filter(({ selected }) => selected)
|
||||
.map(({ value, deepLinks }) => JSON.stringify({
|
||||
href: deepLinks.discover,
|
||||
value
|
||||
const extraSelects = discover.selectable.extra.map(({ name, isRequired, options }) => {
|
||||
const selectedExtra = options.find(({ selected }) => selected);
|
||||
return {
|
||||
isRequired: isRequired,
|
||||
options: options.map(({ value, deepLinks }) => ({
|
||||
label: typeof value === 'string' ? t.stringWithPrefix(value) : t.string('NONE'),
|
||||
value: JSON.stringify({
|
||||
href: deepLinks.discover,
|
||||
value
|
||||
})
|
||||
})),
|
||||
renderLabelText: options.some(({ selected, value }) => selected && value === null) ?
|
||||
() => t.stringWithPrefix(name, 'SELECT_')
|
||||
:
|
||||
null,
|
||||
onSelect: (event) => {
|
||||
const { href } = JSON.parse(event.value);
|
||||
window.location = href;
|
||||
}
|
||||
}));
|
||||
selectedOption: {
|
||||
label: typeof selectedExtra.value === 'string' ? t.stringWithPrefix(selectedExtra.value) : t.string('NONE'),
|
||||
value: JSON.stringify({
|
||||
href: selectedExtra.deepLinks.discover,
|
||||
value: selectedExtra.value,
|
||||
})
|
||||
},
|
||||
title: options.some(({ selected, value }) => selected && value === null) ?
|
||||
() => t.stringWithPrefix(name, 'SELECT_')
|
||||
: t.stringWithPrefix(selectedExtra.value),
|
||||
onSelect: (value) => {
|
||||
const { href } = JSON.parse(value);
|
||||
window.location = href;
|
||||
}
|
||||
};
|
||||
});
|
||||
return [[typeSelect, catalogSelect, ...extraSelects], discover.selectable.nextPage];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const PropTypes = require('prop-types');
|
|||
const classnames = require('classnames');
|
||||
const NotFound = require('stremio/routes/NotFound');
|
||||
const { useProfile, useNotifications, routesRegexp, useOnScrollToBottom, withCoreSuspender } = require('stremio/common');
|
||||
const { DelayedRenderer, Chips, Image, MainNavBars, Multiselect, LibItem } = require('stremio/components');
|
||||
const { DelayedRenderer, Chips, Image, MainNavBars, LibItem, MultiselectMenu } = require('stremio/components');
|
||||
const { default: Placeholder } = require('./Placeholder');
|
||||
const useLibrary = require('./useLibrary');
|
||||
const useSelectableInputs = require('./useSelectableInputs');
|
||||
|
|
@ -64,17 +64,17 @@ const Library = ({ model, urlParams, queryParams }) => {
|
|||
}
|
||||
}, [profile.auth, library.selected]);
|
||||
React.useEffect(() => {
|
||||
if (!library.selected?.type && typeSelect.selected) {
|
||||
window.location = typeSelect.selected[0];
|
||||
if (!library.selected?.type && typeSelect.selectedOption) {
|
||||
window.location = typeSelect.selectedOption.value;
|
||||
}
|
||||
}, [typeSelect.selected, library.selected]);
|
||||
}, [typeSelect.selectedOption, library.selected]);
|
||||
return (
|
||||
<MainNavBars className={styles['library-container']} route={model}>
|
||||
{
|
||||
profile.auth !== null ?
|
||||
<div className={styles['library-content']}>
|
||||
<div className={styles['selectable-inputs-container']}>
|
||||
<Multiselect {...typeSelect} className={styles['select-input-container']} />
|
||||
<MultiselectMenu {...typeSelect} className={styles['select-input-container']} />
|
||||
<Chips {...sortChips} className={styles['select-input-container']} />
|
||||
</div>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
flex-shrink: 1;
|
||||
flex-basis: 15rem;
|
||||
height: 2.75rem;
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 1.5rem;
|
||||
|
|
|
|||
|
|
@ -2,22 +2,20 @@
|
|||
|
||||
const React = require('react');
|
||||
const { useTranslate } = require('stremio/common');
|
||||
|
||||
const mapSelectableInputs = (library, t) => {
|
||||
const selectedType = library.selectable.types
|
||||
.filter(({ selected }) => selected).map(({ deepLinks }) => deepLinks.library);
|
||||
const selectedType = library.selectable.types.find(({ selected }) => selected) || library.selectable.types.find(({ type }) => type === null);
|
||||
const typeSelect = {
|
||||
title: t.string('SELECT_TYPE'),
|
||||
options: library.selectable.types
|
||||
.map(({ type, deepLinks }) => ({
|
||||
value: deepLinks.library,
|
||||
label: type === null ? t.string('TYPE_ALL') : t.stringWithPrefix(type, 'TYPE_')
|
||||
})),
|
||||
selected: selectedType.length
|
||||
? selectedType
|
||||
: [library.selectable.types[0]].map(({ deepLinks }) => deepLinks.library),
|
||||
onSelect: (event) => {
|
||||
window.location = event.value;
|
||||
selectedOption: {
|
||||
label: selectedType?.type === null ? t.string('TYPE_ALL') : t.stringWithPrefix(selectedType?.type, 'TYPE_'),
|
||||
value: selectedType?.deepLinks.library
|
||||
},
|
||||
onSelect: (value) => {
|
||||
window.location = value;
|
||||
}
|
||||
};
|
||||
const sortChips = {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const PropTypes = require('prop-types');
|
|||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const { Button, Image, Multiselect } = require('stremio/components');
|
||||
const { Button, Image, MultiselectMenu } = require('stremio/components');
|
||||
const { useServices } = require('stremio/services');
|
||||
const Stream = require('./Stream');
|
||||
const styles = require('./styles');
|
||||
|
|
@ -21,9 +21,9 @@ const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => {
|
|||
const profile = useProfile();
|
||||
const streamsContainerRef = React.useRef(null);
|
||||
const [selectedAddon, setSelectedAddon] = React.useState(ALL_ADDONS_KEY);
|
||||
const onAddonSelected = React.useCallback((event) => {
|
||||
const onAddonSelected = React.useCallback((value) => {
|
||||
streamsContainerRef.current.scrollTo({ top: 0, left: 0, behavior: platform.name === 'ios' ? 'smooth' : 'instant' });
|
||||
setSelectedAddon(event.value);
|
||||
setSelectedAddon(value);
|
||||
}, [platform]);
|
||||
const showInstallAddonsButton = React.useMemo(() => {
|
||||
return !profile || profile.auth === null || profile.auth?.user?.isNewUser === true && !video?.upcoming;
|
||||
|
|
@ -77,7 +77,6 @@ const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => {
|
|||
}, [streamsByAddon, selectedAddon]);
|
||||
const selectableOptions = React.useMemo(() => {
|
||||
return {
|
||||
title: 'Select Addon',
|
||||
options: [
|
||||
{
|
||||
value: ALL_ADDONS_KEY,
|
||||
|
|
@ -90,7 +89,10 @@ const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => {
|
|||
title: streamsByAddon[transportUrl].addon.manifest.name,
|
||||
}))
|
||||
],
|
||||
selected: [selectedAddon],
|
||||
selectedOption: {
|
||||
label: selectedAddon === ALL_ADDONS_KEY ? t('ALL_ADDONS') : streamsByAddon[selectedAddon]?.addon.manifest.name,
|
||||
value: selectedAddon
|
||||
},
|
||||
onSelect: onAddonSelected
|
||||
};
|
||||
}, [streamsByAddon, selectedAddon]);
|
||||
|
|
@ -117,7 +119,7 @@ const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => {
|
|||
}
|
||||
{
|
||||
Object.keys(streamsByAddon).length > 1 ?
|
||||
<Multiselect
|
||||
<MultiselectMenu
|
||||
{...selectableOptions}
|
||||
className={styles['select-input-container']}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@
|
|||
.select-input-container {
|
||||
min-width: 40%;
|
||||
flex-grow: 1;
|
||||
background: none;
|
||||
background-color: none;
|
||||
|
||||
&:hover, &:focus, &:global(.active) {
|
||||
background-color: var(--overlay-color);
|
||||
|
|
|
|||
Loading…
Reference in a new issue