mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-18 17:02:12 +00:00
Merge branch 'search' of github.com:Stremio/stremio-web into development
This commit is contained in:
commit
1126ff1482
7 changed files with 238 additions and 53 deletions
|
|
@ -7,7 +7,7 @@ const MetaItem = require('stremio/common/MetaItem');
|
|||
const MetaRowPlaceholder = require('./MetaRowPlaceholder');
|
||||
const styles = require('./styles');
|
||||
|
||||
const MetaRow = ({ className, title, message, items, maximumItemsCount, itemMenuOptions }) => {
|
||||
const MetaRow = ({ className, title, message, items, maximumItemsCount, itemMenuOptions, catalogHref }) => {
|
||||
maximumItemsCount = maximumItemsCount !== null && isFinite(maximumItemsCount) ? maximumItemsCount : 20;
|
||||
items = Array.isArray(items) ? items.slice(0, maximumItemsCount) : [];
|
||||
return (
|
||||
|
|
@ -38,7 +38,7 @@ const MetaRow = ({ className, title, message, items, maximumItemsCount, itemMenu
|
|||
<div key={index} className={classnames(styles['meta-item'], styles['poster-shape-poster'])} />
|
||||
))}
|
||||
</div>
|
||||
<Button className={styles['see-all-container']} title={'SEE ALL'}>
|
||||
<Button className={styles['see-all-container']} title={'SEE ALL'} href={catalogHref}>
|
||||
<div className={styles['label']}>SEE ALL</div>
|
||||
<Icon className={styles['icon']} icon={'ic_arrow_thin_right'} />
|
||||
</Button>
|
||||
|
|
@ -58,7 +58,8 @@ MetaRow.propTypes = {
|
|||
posterShape: PropTypes.string
|
||||
})),
|
||||
maximumItemsCount: PropTypes.number,
|
||||
itemMenuOptions: PropTypes.any
|
||||
itemMenuOptions: PropTypes.any,
|
||||
catalogHref: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = MetaRow;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const SearchBar = ({ className }) => {
|
|||
if (routeActive) {
|
||||
const { search: locationSearch } = UrlUtils.parse(locationHash.slice(1));
|
||||
const queryParams = new URLSearchParams(locationSearch);
|
||||
return queryParams.has('q') ? queryParams.get('q') : '';
|
||||
return queryParams.has('search') ? queryParams.get('search') : '';
|
||||
}
|
||||
|
||||
return '';
|
||||
|
|
@ -32,7 +32,7 @@ const SearchBar = ({ className }) => {
|
|||
}, [routeActive]);
|
||||
const queryInputOnSubmit = React.useCallback(() => {
|
||||
if (routeActive) {
|
||||
window.location.replace(`#/search?q=${searchInputRef.current.value}`);
|
||||
window.location.replace(`#/search?search=${searchInputRef.current.value}`);
|
||||
}
|
||||
}, [routeActive]);
|
||||
React.useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -20,32 +20,32 @@ const Board = () => {
|
|||
<div className={styles['board-container']}>
|
||||
<MainNavBar className={styles['nav-bar']} />
|
||||
<div className={styles['board-content']}>
|
||||
{catalogs.map(({ req, content }, index) => {
|
||||
{catalogs.map(({ request, content }, index) => {
|
||||
switch (content.type) {
|
||||
case 'Ready':
|
||||
return (
|
||||
<MetaRow
|
||||
key={`${index}${req.base}${content.type}`}
|
||||
key={`${index}${request.base}${content.type}`}
|
||||
className={styles['board-row']}
|
||||
title={`${req.path.id} - ${req.path.type_name}`}
|
||||
title={`${request.path.id} - ${request.path.type_name}`}
|
||||
items={content.content}
|
||||
/>
|
||||
);
|
||||
case 'Message':
|
||||
return (
|
||||
<MetaRow
|
||||
key={`${index}${req.base}${content.type}`}
|
||||
key={`${index}${request.base}${content.type}`}
|
||||
className={styles['board-row']}
|
||||
title={`${req.path.id} - ${req.path.type_name}`}
|
||||
title={`${request.path.id} - ${request.path.type_name}`}
|
||||
message={content.content}
|
||||
/>
|
||||
);
|
||||
case 'Loading':
|
||||
return (
|
||||
<MetaRow.Placeholder
|
||||
key={`${index}${req.base}${content.type}`}
|
||||
key={`${index}${request.base}${content.type}`}
|
||||
className={styles['board-row-placeholder']}
|
||||
title={`${req.path.id} - ${req.path.type_name}`}
|
||||
title={`${request.path.id} - ${request.path.type_name}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ const useCatalogs = () => {
|
|||
React.useEffect(() => {
|
||||
const onNewState = () => {
|
||||
const state = core.getState();
|
||||
setCatalogs(state.board.groups);
|
||||
setCatalogs(state.board.items_groups);
|
||||
};
|
||||
core.on('NewModel', onNewState);
|
||||
core.dispatch({
|
||||
action: 'Load',
|
||||
args: {
|
||||
load: 'CatalogGrouped',
|
||||
load: 'CatalogsGrouped',
|
||||
args: { extra: [] }
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,21 +1,77 @@
|
|||
const React = require('react');
|
||||
const Icon = require('stremio-icons/dom');
|
||||
const { MainNavBar, MetaRow } = require('stremio/common');
|
||||
const useSearch = require('./useSearch');
|
||||
const styles = require('./styles');
|
||||
|
||||
const Search = ({ queryParams }) => {
|
||||
const groups = useSearch(queryParams.get('q'));
|
||||
const search = useSearch(queryParams);
|
||||
const searchSelected = React.useMemo(() => {
|
||||
return search.selected.some(([name, value]) => name === 'search' && value.length > 0)
|
||||
}, [search.selected]);
|
||||
return (
|
||||
<div className={styles['search-container']}>
|
||||
<MainNavBar className={styles['nav-bar']} />
|
||||
<div className={styles['search-content']}>
|
||||
{groups.map((group, index) => (
|
||||
<MetaRow
|
||||
{...group}
|
||||
key={index}
|
||||
className={styles['search-row']}
|
||||
/>
|
||||
))}
|
||||
{
|
||||
searchSelected ?
|
||||
search.items_groups && search.items_groups.length > 0 ?
|
||||
search.items_groups.some(group => group.content.type !== 'Err') ?
|
||||
search.items_groups.map(({ href, request, content }, index) => {
|
||||
switch (content.type) {
|
||||
case 'Ready':
|
||||
return (
|
||||
<MetaRow
|
||||
key={`${index}${request.base}${content.type} Ready`}
|
||||
className={styles['search-row']}
|
||||
title={`${request.path.id} - ${request.path.type_name}`}
|
||||
items={content.content}
|
||||
catalogHref={href}
|
||||
/>
|
||||
);
|
||||
case 'Err':
|
||||
return (
|
||||
<MetaRow
|
||||
key={`${index}${request.base}${content.type}`}
|
||||
className={styles['search-row']}
|
||||
title={`${request.path.id} - ${request.path.type_name} Err`}
|
||||
message={`${content.content.type} ${typeof content.content.content === 'string' ? content.content.content : ''}`}
|
||||
/>
|
||||
);
|
||||
case 'Loading':
|
||||
return (
|
||||
<MetaRow.Placeholder
|
||||
key={`${index}${request.base}${content.type} Loading`}
|
||||
className={styles['search-row-placeholder']}
|
||||
title={`${request.path.id} - ${request.path.type_name}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})
|
||||
:
|
||||
<div className={styles['message-container']}>
|
||||
<div className={styles['message-content']}>
|
||||
<div className={styles['label']}>No metadata was found</div>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div className={styles['message-container']}>
|
||||
<div className={styles['message-content']}>
|
||||
<div className={styles['label']}> No addons were requested for metadata</div>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div className={styles['message-container']}>
|
||||
<div className={styles['message-content']}>
|
||||
<Icon className={styles['icon']} icon={'ic_movies'} />
|
||||
<div className={styles['label']}>Search for movies, series, YouTube and TV channels</div>
|
||||
</div>
|
||||
<div className={styles['message-content']}>
|
||||
<Icon className={styles['icon']} icon={'ic_actor'} />
|
||||
<div className={styles['label']}>Search for actors, directors and writers</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
:import('~stremio/common/MetaRow/styles.less') {
|
||||
meta-item: meta-item;
|
||||
}
|
||||
|
||||
:import('~stremio/common/MetaRow/MetaRowPlaceholder/styles.less') {
|
||||
meta-item-placeholder: meta-item;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -19,7 +25,7 @@
|
|||
align-self: stretch;
|
||||
overflow-y: auto;
|
||||
|
||||
.search-row {
|
||||
.search-row, .search-row-placeholder {
|
||||
margin: 4rem 2rem;
|
||||
|
||||
&:first-child {
|
||||
|
|
@ -29,10 +35,111 @@
|
|||
&:last-child {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
&:nth-child(n+6) {
|
||||
display: none;
|
||||
.message-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
margin: 5rem;
|
||||
|
||||
.message-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.icon {
|
||||
flex: none;
|
||||
width: 6rem;
|
||||
height: 6rem;
|
||||
margin-bottom: 2rem;
|
||||
fill: var(--color-surfacelighter40);
|
||||
}
|
||||
|
||||
.label {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
flex-basis: auto;
|
||||
font-size: 1.2rem;
|
||||
color: var(--color-surfacelighter40);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 5rem;;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: @large) {
|
||||
.board-container {
|
||||
.board-content {
|
||||
.board-row, .board-row-placeholder {
|
||||
.meta-item, .meta-item-placeholder {
|
||||
&:nth-child(n+9) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @large) {
|
||||
.search-container {
|
||||
.search-content {
|
||||
.search-row, .search-row-placeholder {
|
||||
.meta-item, .meta-item-placeholder {
|
||||
&:nth-child(n+8) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @medium) {
|
||||
.search-container {
|
||||
.search-content {
|
||||
.search-row, .search-row-placeholder {
|
||||
.meta-item, .meta-item-placeholder {
|
||||
&:nth-child(n+7) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @small) {
|
||||
.search-container {
|
||||
.search-content {
|
||||
.search-row, .search-row-placeholder {
|
||||
.meta-item, .meta-item-placeholder {
|
||||
&:nth-child(n+6) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @xsmall) {
|
||||
.search-container {
|
||||
.search-content {
|
||||
.search-row, .search-row-placeholder {
|
||||
.meta-item, .meta-item-placeholder {
|
||||
&:nth-child(n+5) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,53 @@
|
|||
const React = require('react');
|
||||
const { useServices } = require('stremio/services');
|
||||
|
||||
const useSearch = (query) => {
|
||||
const items = React.useMemo(() => {
|
||||
return [
|
||||
{
|
||||
title: 'demo addon',
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'movie',
|
||||
name: 'Stremio demo item movie 1',
|
||||
poster: '/images/intro_background.jpg',
|
||||
logo: '/images/default_avatar.png',
|
||||
posterShape: 'poster'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'movie',
|
||||
name: 'Stremio demo item movie 2',
|
||||
poster: '/images/intro_background.jpg',
|
||||
logo: '/images/default_avatar.png',
|
||||
posterShape: 'poster'
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
}, [query]);
|
||||
return items;
|
||||
const mapSearchState = (state) => {
|
||||
const query = state.search.selected.reduceRight((query, [name, value]) => {
|
||||
if (name === 'search') {
|
||||
return value;
|
||||
}
|
||||
return query;
|
||||
}, '');
|
||||
const selected = state.search.selected;
|
||||
const items_groups = state.search.items_groups.map((group) => {
|
||||
group.href = `#/discover/${encodeURIComponent(group.request.base)}/${encodeURIComponent(group.request.path.id)}/${encodeURIComponent(group.request.path.type_name)}?search=${query}`;
|
||||
return group;
|
||||
});
|
||||
return { selected, items_groups };
|
||||
};
|
||||
|
||||
const useSearch = (queryParams) => {
|
||||
const { core } = useServices();
|
||||
const [search, setSearch] = React.useState(() => {
|
||||
const state = core.getState();
|
||||
const search = mapSearchState(state);
|
||||
return search;
|
||||
});
|
||||
React.useEffect(() => {
|
||||
const onNewState = () => {
|
||||
const state = core.getState();
|
||||
const search = mapSearchState(state);
|
||||
setSearch(search);
|
||||
};
|
||||
core.on('NewModel', onNewState);
|
||||
if (queryParams.has('search')) {
|
||||
core.dispatch({
|
||||
action: 'Load',
|
||||
args: {
|
||||
load: 'CatalogsGrouped',
|
||||
args: {
|
||||
extra: [
|
||||
['search', queryParams.get('search')]
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
core.off('NewModel', onNewState);
|
||||
};
|
||||
}, [queryParams]);
|
||||
return search;
|
||||
};
|
||||
|
||||
module.exports = useSearch;
|
||||
|
|
|
|||
Loading…
Reference in a new issue