5421a64b65
## Issue 4945: Simplify / revamp search filters for odysee (and lbry.tv) ## Changes - Tweaked the "claim type = file | channel | everything" a little to hopefully make it more intuitive. - Added "Sort By". - Added "Upload Date". - Fixed the affected files to handle both Desktop and Odysee. - Tweaked the layout to be more condensed so that we can see some results as we change the filter. - Added animations. - Added "Exact match" option that helps user to surround with quotes.
237 lines
7 KiB
JavaScript
237 lines
7 KiB
JavaScript
// @flow
|
|
import { SEARCH_OPTIONS } from 'constants/search';
|
|
import * as ICONS from 'constants/icons';
|
|
import React, { useMemo } from 'react';
|
|
import { Form, FormField } from 'component/common/form';
|
|
import Button from 'component/button';
|
|
import Icon from 'component/common/icon';
|
|
import classnames from 'classnames';
|
|
|
|
const CLAIM_TYPES = {
|
|
[SEARCH_OPTIONS.INCLUDE_FILES]: 'Files',
|
|
[SEARCH_OPTIONS.INCLUDE_CHANNELS]: 'Channels',
|
|
[SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS]: 'Everything',
|
|
};
|
|
|
|
const TYPES_ADVANCED = {
|
|
[SEARCH_OPTIONS.MEDIA_VIDEO]: 'Video',
|
|
[SEARCH_OPTIONS.MEDIA_AUDIO]: 'Audio',
|
|
[SEARCH_OPTIONS.MEDIA_IMAGE]: 'Image',
|
|
[SEARCH_OPTIONS.MEDIA_TEXT]: 'Text',
|
|
[SEARCH_OPTIONS.MEDIA_APPLICATION]: 'Other',
|
|
};
|
|
|
|
const TIME_FILTER = {
|
|
'': 'None',
|
|
[SEARCH_OPTIONS.TIME_FILTER_LAST_HOUR]: 'Last Hour',
|
|
[SEARCH_OPTIONS.TIME_FILTER_TODAY]: 'Last 24 Hours',
|
|
[SEARCH_OPTIONS.TIME_FILTER_THIS_WEEK]: 'This Week',
|
|
[SEARCH_OPTIONS.TIME_FILTER_THIS_MONTH]: 'This Month',
|
|
[SEARCH_OPTIONS.TIME_FILTER_THIS_YEAR]: 'This Year',
|
|
};
|
|
|
|
const SORT_BY = {
|
|
'': 'Relevance',
|
|
[SEARCH_OPTIONS.SORT_DESCENDING]: 'Newest first',
|
|
[SEARCH_OPTIONS.SORT_ACCENDING]: 'Oldest first',
|
|
};
|
|
|
|
type Props = {
|
|
setSearchOption: (string, boolean | string | number) => void,
|
|
options: {},
|
|
simple: boolean,
|
|
expanded: boolean,
|
|
toggleSearchExpanded: () => void,
|
|
};
|
|
|
|
const SearchOptions = (props: Props) => {
|
|
const { options, simple, setSearchOption, expanded, toggleSearchExpanded } = props;
|
|
|
|
const stringifiedOptions = JSON.stringify(options);
|
|
const resultCount = options[SEARCH_OPTIONS.RESULT_COUNT];
|
|
|
|
const isFilteringByChannel = useMemo(() => {
|
|
const jsonOptions = JSON.parse(stringifiedOptions);
|
|
const claimType = String(jsonOptions[SEARCH_OPTIONS.CLAIM_TYPE] || '');
|
|
return claimType.includes(SEARCH_OPTIONS.INCLUDE_CHANNELS);
|
|
}, [stringifiedOptions]);
|
|
|
|
if (simple) {
|
|
delete TYPES_ADVANCED[SEARCH_OPTIONS.MEDIA_APPLICATION];
|
|
}
|
|
|
|
function addRow(label: string, value: any) {
|
|
return (
|
|
<tr>
|
|
<td>
|
|
<legend className="search__legend">{label}</legend>
|
|
</td>
|
|
<td>{value}</td>
|
|
</tr>
|
|
);
|
|
}
|
|
|
|
const OBJ_TO_OPTION_ELEM = (obj) => {
|
|
return Object.entries(obj).map((x) => {
|
|
return (
|
|
<option key={x[0]} value={x[0]}>
|
|
{__(String(x[1]))}
|
|
</option>
|
|
);
|
|
});
|
|
};
|
|
|
|
const typeElem = (
|
|
<>
|
|
<div className="filter-values">
|
|
<div>
|
|
{Object.entries(CLAIM_TYPES).map((t) => {
|
|
const option = t[0];
|
|
if (option === SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS) {
|
|
return null;
|
|
}
|
|
return (
|
|
<Button
|
|
key={option}
|
|
button="alt"
|
|
label={t[1]}
|
|
className={classnames(`button-toggle`, {
|
|
'button-toggle--active': options[SEARCH_OPTIONS.CLAIM_TYPE] === option,
|
|
})}
|
|
onClick={() => setSearchOption(SEARCH_OPTIONS.CLAIM_TYPE, option)}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
<Button
|
|
button="close"
|
|
className={classnames('close-button', {
|
|
'close-button--visible': options[SEARCH_OPTIONS.CLAIM_TYPE] !== SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS,
|
|
})}
|
|
icon={ICONS.REMOVE}
|
|
onClick={() => setSearchOption(SEARCH_OPTIONS.CLAIM_TYPE, SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS)}
|
|
/>
|
|
</div>
|
|
<div className="media-types">
|
|
{options[SEARCH_OPTIONS.CLAIM_TYPE] === SEARCH_OPTIONS.INCLUDE_FILES &&
|
|
Object.entries(TYPES_ADVANCED).map((t) => {
|
|
const option = t[0];
|
|
return (
|
|
<FormField
|
|
key={option}
|
|
name={option}
|
|
type="checkbox"
|
|
blockWrap={false}
|
|
disabled={options[SEARCH_OPTIONS.CLAIM_TYPE] !== SEARCH_OPTIONS.INCLUDE_FILES}
|
|
label={t[1]}
|
|
checked={!isFilteringByChannel && options[option]}
|
|
onChange={() => setSearchOption(option, !options[option])}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
</>
|
|
);
|
|
|
|
const otherOptionsElem = (
|
|
<>
|
|
<div className="filter-values">
|
|
<FormField
|
|
type="checkbox"
|
|
name="exact-match"
|
|
checked={options[SEARCH_OPTIONS.EXACT]}
|
|
onChange={() => setSearchOption(SEARCH_OPTIONS.EXACT, !options[SEARCH_OPTIONS.EXACT])}
|
|
label={__('Exact match')}
|
|
/>
|
|
<Icon
|
|
className="icon--help"
|
|
icon={ICONS.HELP}
|
|
tooltip
|
|
size={16}
|
|
customTooltipText={__(
|
|
'Find results that include all the given words in the exact order.\nThis can also be done by surrounding the search query with quotation marks (e.g. "hello world").'
|
|
)}
|
|
/>
|
|
</div>
|
|
{!simple && (
|
|
<FormField
|
|
type="select"
|
|
name="result-count"
|
|
value={resultCount}
|
|
onChange={(e) => setSearchOption(SEARCH_OPTIONS.RESULT_COUNT, e.target.value)}
|
|
blockWrap={false}
|
|
label={__('Returned Results')}
|
|
>
|
|
<option value={10}>10</option>
|
|
<option value={30}>30</option>
|
|
<option value={50}>50</option>
|
|
<option value={100}>100</option>
|
|
</FormField>
|
|
)}
|
|
</>
|
|
);
|
|
|
|
const uploadDateElem = (
|
|
<div className="filter-values">
|
|
<FormField
|
|
type="select"
|
|
name="upload-date"
|
|
value={options[SEARCH_OPTIONS.TIME_FILTER]}
|
|
onChange={(e) => setSearchOption(SEARCH_OPTIONS.TIME_FILTER, e.target.value)}
|
|
blockWrap={false}
|
|
>
|
|
{OBJ_TO_OPTION_ELEM(TIME_FILTER)}
|
|
</FormField>
|
|
<Button
|
|
button="close"
|
|
className={classnames('close-button', {
|
|
'close-button--visible': options[SEARCH_OPTIONS.TIME_FILTER] !== '',
|
|
})}
|
|
icon={ICONS.REMOVE}
|
|
onClick={() => setSearchOption(SEARCH_OPTIONS.TIME_FILTER, '')}
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
const sortByElem = (
|
|
<div className="filter-values">
|
|
<FormField
|
|
type="select"
|
|
name="sort-by"
|
|
blockWrap={false}
|
|
value={options[SEARCH_OPTIONS.SORT]}
|
|
onChange={(e) => setSearchOption(SEARCH_OPTIONS.SORT, e.target.value)}
|
|
>
|
|
{OBJ_TO_OPTION_ELEM(SORT_BY)}
|
|
</FormField>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div>
|
|
<Button
|
|
button="alt"
|
|
label={__('Filter')}
|
|
icon={ICONS.FILTER}
|
|
iconRight={expanded ? ICONS.UP : ICONS.DOWN}
|
|
onClick={toggleSearchExpanded}
|
|
/>
|
|
<Form
|
|
className={classnames('search__options', {
|
|
'search__options--expanded': expanded,
|
|
})}
|
|
>
|
|
<table className="table table--condensed">
|
|
<tbody>
|
|
{addRow(__('Type'), typeElem)}
|
|
{addRow(__('Upload Date'), uploadDateElem)}
|
|
{addRow(__('Sort By'), sortByElem)}
|
|
{addRow(__('Other Options'), otherOptionsElem)}
|
|
</tbody>
|
|
</table>
|
|
</Form>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SearchOptions;
|