mirror of
https://github.com/haiwen/seahub.git
synced 2025-07-14 23:46:49 +00:00
Optimize/search filters (#7761)
* optimize ui * optimize filters * update date filter * optimize --------- Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
parent
75cde31370
commit
2ec7bc4da9
@ -11,11 +11,12 @@ import ModalPortal from '../../modal-portal';
|
|||||||
import toaster from '../../toast';
|
import toaster from '../../toast';
|
||||||
import { SEARCH_FILTERS_KEY } from '../../../constants';
|
import { SEARCH_FILTERS_KEY } from '../../../constants';
|
||||||
|
|
||||||
const FilterByCreator = ({ creatorList, onSelect }) => {
|
const FilterByCreator = ({ creatorList, onChange }) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [options, setOptions] = useState([]);
|
const [options, setOptions] = useState([]);
|
||||||
const [selectedOptions, setSelectedOptions] = useState(creatorList || []);
|
const [selectedOptions, setSelectedOptions] = useState(creatorList || []);
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
const [inputFocus, setInputFocus] = useState(false);
|
||||||
|
|
||||||
const toggle = useCallback((e) => {
|
const toggle = useCallback((e) => {
|
||||||
setIsOpen(!isOpen);
|
setIsOpen(!isOpen);
|
||||||
@ -28,7 +29,7 @@ const FilterByCreator = ({ creatorList, onSelect }) => {
|
|||||||
});
|
});
|
||||||
}, [options, searchValue]);
|
}, [options, searchValue]);
|
||||||
|
|
||||||
const onSelectOption = useCallback((e) => {
|
const onChangeOption = useCallback((e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const name = Utils.getEventData(e, 'toggle') ?? e.currentTarget.getAttribute('data-toggle');
|
const name = Utils.getEventData(e, 'toggle') ?? e.currentTarget.getAttribute('data-toggle');
|
||||||
@ -40,17 +41,17 @@ const FilterByCreator = ({ creatorList, onSelect }) => {
|
|||||||
updated = updated.filter((option) => option.name !== name);
|
updated = updated.filter((option) => option.name !== name);
|
||||||
}
|
}
|
||||||
setSelectedOptions(updated);
|
setSelectedOptions(updated);
|
||||||
onSelect(SEARCH_FILTERS_KEY.CREATOR_LIST, updated);
|
onChange(SEARCH_FILTERS_KEY.CREATOR_LIST, updated);
|
||||||
if (displayOptions.length === 1) {
|
if (displayOptions.length === 1) {
|
||||||
setSearchValue('');
|
setSearchValue('');
|
||||||
}
|
}
|
||||||
}, [selectedOptions, displayOptions, options, onSelect]);
|
}, [selectedOptions, displayOptions, options, onChange]);
|
||||||
|
|
||||||
const handleCancel = useCallback((e, name) => {
|
const handleCancel = useCallback((e, name) => {
|
||||||
const updated = selectedOptions.filter((option) => option.name !== name);
|
const updated = selectedOptions.filter((option) => option.name !== name);
|
||||||
setSelectedOptions(updated);
|
setSelectedOptions(updated);
|
||||||
onSelect(SEARCH_FILTERS_KEY.CREATOR_LIST, updated);
|
onChange(SEARCH_FILTERS_KEY.CREATOR_LIST, updated);
|
||||||
}, [selectedOptions, onSelect]);
|
}, [selectedOptions, onChange]);
|
||||||
|
|
||||||
const handleInputChange = useCallback((e) => {
|
const handleInputChange = useCallback((e) => {
|
||||||
const v = e.target.value;
|
const v = e.target.value;
|
||||||
@ -100,7 +101,7 @@ const FilterByCreator = ({ creatorList, onSelect }) => {
|
|||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
<ModalPortal>
|
<ModalPortal>
|
||||||
<DropdownMenu className="search-filter-menu filter-by-creator-menu">
|
<DropdownMenu className="search-filter-menu filter-by-creator-menu">
|
||||||
<div className="input-container">
|
<div className={classNames('input-container', { 'focus': inputFocus })}>
|
||||||
{selectedOptions.map((option) => (
|
{selectedOptions.map((option) => (
|
||||||
<UserItem
|
<UserItem
|
||||||
key={option.name}
|
key={option.name}
|
||||||
@ -114,8 +115,11 @@ const FilterByCreator = ({ creatorList, onSelect }) => {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder={selectedOptions.length ? '' : gettext('Search user')}
|
placeholder={selectedOptions.length ? '' : gettext('Search user')}
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
|
autoFocus
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
onKeyDown={handleInputKeyDown}
|
onKeyDown={handleInputKeyDown}
|
||||||
|
onFocus={() => setInputFocus(true)}
|
||||||
|
onBlur={() => setInputFocus(false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -126,7 +130,7 @@ const FilterByCreator = ({ creatorList, onSelect }) => {
|
|||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
data-toggle={option.name}
|
data-toggle={option.name}
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
onClick={onSelectOption}
|
onClick={onChangeOption}
|
||||||
toggle={false}
|
toggle={false}
|
||||||
>
|
>
|
||||||
{isOpen && <UserItem user={option} />}
|
{isOpen && <UserItem user={option} />}
|
||||||
@ -142,7 +146,7 @@ const FilterByCreator = ({ creatorList, onSelect }) => {
|
|||||||
|
|
||||||
FilterByCreator.propTypes = {
|
FilterByCreator.propTypes = {
|
||||||
creatorList: PropTypes.array.isRequired,
|
creatorList: PropTypes.array.isRequired,
|
||||||
onSelect: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FilterByCreator;
|
export default FilterByCreator;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
|
import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@ -7,14 +7,19 @@ import { Utils } from '../../../utils/utils';
|
|||||||
import Picker from '../../date-and-time-picker';
|
import Picker from '../../date-and-time-picker';
|
||||||
import ModalPortal from '../../modal-portal';
|
import ModalPortal from '../../modal-portal';
|
||||||
import { SEARCH_FILTERS_KEY, SEARCH_FILTER_BY_DATE_OPTION_KEY, SEARCH_FILTER_BY_DATE_TYPE_KEY } from '../../../constants';
|
import { SEARCH_FILTERS_KEY, SEARCH_FILTER_BY_DATE_OPTION_KEY, SEARCH_FILTER_BY_DATE_TYPE_KEY } from '../../../constants';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
const DATE_INPUT_WIDTH = 118;
|
const DATE_INPUT_WIDTH = 118;
|
||||||
|
|
||||||
const FilterByDate = ({ date, onSelect }) => {
|
const FilterByDate = ({ date, onChange }) => {
|
||||||
const [value, setValue] = useState(date.value);
|
const [value, setValue] = useState(date.value);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isTypeOpen, setIsTypeOpen] = useState(false);
|
const [isTypeOpen, setIsTypeOpen] = useState(false);
|
||||||
const [isCustomDate, setIsCustomDate] = useState(date.value === SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM);
|
const [isCustomDate, setIsCustomDate] = useState(date.value === SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM);
|
||||||
|
const [time, setTime] = useState({
|
||||||
|
from: date.from,
|
||||||
|
to: date.to,
|
||||||
|
});
|
||||||
const [type, setType] = useState(date.type);
|
const [type, setType] = useState(date.type);
|
||||||
|
|
||||||
const typeLabel = useMemo(() => {
|
const typeLabel = useMemo(() => {
|
||||||
@ -28,6 +33,11 @@ const FilterByDate = ({ date, onSelect }) => {
|
|||||||
}
|
}
|
||||||
}, [type]);
|
}, [type]);
|
||||||
|
|
||||||
|
const label = useMemo(() => {
|
||||||
|
if (!value || value.length === 0) return gettext('Date');
|
||||||
|
return typeLabel;
|
||||||
|
}, [typeLabel, value]);
|
||||||
|
|
||||||
const typeOptions = useMemo(() => {
|
const typeOptions = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -40,28 +50,6 @@ const FilterByDate = ({ date, onSelect }) => {
|
|||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const label = useMemo(() => {
|
|
||||||
if (!value || value.length === 0) return gettext('Date');
|
|
||||||
const formatDate = (date) => dayjs(date).format('YYYY-MM-DD');
|
|
||||||
const today = dayjs();
|
|
||||||
const prefix = `${typeLabel}: `;
|
|
||||||
|
|
||||||
switch (value) {
|
|
||||||
case SEARCH_FILTER_BY_DATE_OPTION_KEY.TODAY:
|
|
||||||
return `${prefix}${formatDate(today)}`;
|
|
||||||
case SEARCH_FILTER_BY_DATE_OPTION_KEY.LAST_7_DAYS:
|
|
||||||
return `${prefix}${formatDate(today.subtract(6, 'day'))} - ${formatDate(today)}`;
|
|
||||||
case SEARCH_FILTER_BY_DATE_OPTION_KEY.LAST_30_DAYS:
|
|
||||||
return `${prefix}${formatDate(today.subtract(29, 'day'))} - ${formatDate(today)}`;
|
|
||||||
case SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM:
|
|
||||||
return date.start && date.end
|
|
||||||
? `${prefix}${formatDate(date.start)} - ${formatDate(date.end)}`
|
|
||||||
: gettext('Select date range');
|
|
||||||
default:
|
|
||||||
return gettext('Date');
|
|
||||||
}
|
|
||||||
}, [date, value, typeLabel]);
|
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -90,96 +78,102 @@ const FilterByDate = ({ date, onSelect }) => {
|
|||||||
const option = Utils.getEventData(e, 'toggle') ?? e.currentTarget.getAttribute('data-toggle');
|
const option = Utils.getEventData(e, 'toggle') ?? e.currentTarget.getAttribute('data-toggle');
|
||||||
if (option === type) return;
|
if (option === type) return;
|
||||||
setType(option);
|
setType(option);
|
||||||
onSelect(SEARCH_FILTERS_KEY.DATE, {
|
}, [type]);
|
||||||
...date,
|
|
||||||
type: option,
|
|
||||||
});
|
|
||||||
}, [type, onSelect, date]);
|
|
||||||
|
|
||||||
const onClearDate = useCallback(() => {
|
const onClearDate = useCallback(() => {
|
||||||
setValue('');
|
setValue('');
|
||||||
setIsCustomDate(false);
|
setIsCustomDate(false);
|
||||||
onSelect(SEARCH_FILTERS_KEY.DATE, '');
|
setTime({
|
||||||
}, [onSelect]);
|
from: null,
|
||||||
|
to: null,
|
||||||
|
});
|
||||||
|
setIsOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onOptionClick = useCallback((e) => {
|
const onOptionClick = useCallback((e) => {
|
||||||
const option = Utils.getEventData(e, 'toggle') ?? e.currentTarget.getAttribute('data-toggle');
|
const option = Utils.getEventData(e, 'toggle') ?? e.currentTarget.getAttribute('data-toggle');
|
||||||
if (option === value) return;
|
if (option === value) return;
|
||||||
const today = dayjs().endOf('day');
|
const today = dayjs().endOf('day');
|
||||||
setIsCustomDate(option === SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM);
|
const isCustomOption = option === SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM;
|
||||||
|
setIsCustomDate(isCustomOption);
|
||||||
setValue(option);
|
setValue(option);
|
||||||
|
setIsOpen(isCustomOption);
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case SEARCH_FILTER_BY_DATE_OPTION_KEY.TODAY: {
|
case SEARCH_FILTER_BY_DATE_OPTION_KEY.TODAY: {
|
||||||
onSelect(SEARCH_FILTERS_KEY.DATE, {
|
setTime({
|
||||||
value: option,
|
from: dayjs().startOf('day').unix(),
|
||||||
start: dayjs().startOf('day').unix(),
|
to: today.unix()
|
||||||
end: today.unix()
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SEARCH_FILTER_BY_DATE_OPTION_KEY.LAST_7_DAYS: {
|
case SEARCH_FILTER_BY_DATE_OPTION_KEY.LAST_7_DAYS: {
|
||||||
onSelect(SEARCH_FILTERS_KEY.DATE, {
|
setTime({
|
||||||
value: option,
|
from: dayjs().subtract(6, 'day').startOf('day').unix(),
|
||||||
start: dayjs().subtract(6, 'day').startOf('day').unix(),
|
to: today.unix()
|
||||||
end: today.unix()
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SEARCH_FILTER_BY_DATE_OPTION_KEY.LAST_30_DAYS: {
|
case SEARCH_FILTER_BY_DATE_OPTION_KEY.LAST_30_DAYS: {
|
||||||
onSelect(SEARCH_FILTERS_KEY.DATE, {
|
setTime({
|
||||||
value: option,
|
from: dayjs().subtract(30, 'day').startOf('day').unix(),
|
||||||
start: dayjs().subtract(30, 'day').startOf('day').unix(),
|
to: today.unix()
|
||||||
end: today.unix()
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM: {
|
case SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM: {
|
||||||
onSelect(SEARCH_FILTERS_KEY.DATE, {
|
setTime({
|
||||||
value: option,
|
from: null,
|
||||||
start: null,
|
to: null,
|
||||||
end: null,
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [value, onSelect]);
|
}, [value]);
|
||||||
|
|
||||||
const disabledStartDate = useCallback((startDate) => {
|
const disabledStartDate = useCallback((startDate) => {
|
||||||
if (!startDate) return false;
|
if (!startDate) return false;
|
||||||
const today = dayjs();
|
const today = dayjs();
|
||||||
const endValue = date.end;
|
const endValue = time.to;
|
||||||
|
|
||||||
if (!endValue) {
|
if (!endValue) {
|
||||||
return startDate.isAfter(today);
|
return startDate.isAfter(today);
|
||||||
}
|
}
|
||||||
return endValue.isBefore(startDate) || startDate.isAfter(today);
|
return endValue.isBefore(startDate) || startDate.isAfter(today);
|
||||||
}, [date]);
|
}, [time]);
|
||||||
|
|
||||||
const disabledEndDate = useCallback((endDate) => {
|
const disabledEndDate = useCallback((endDate) => {
|
||||||
if (!endDate) return false;
|
if (!endDate) return false;
|
||||||
const today = dayjs();
|
const today = dayjs();
|
||||||
const startValue = date.start;
|
const startValue = time.from;
|
||||||
if (!startValue) {
|
if (!startValue) {
|
||||||
return endDate.isAfter(today);
|
return endDate.isAfter(today);
|
||||||
}
|
}
|
||||||
return endDate.isBefore(startValue) || endDate.isAfter(today);
|
return endDate.isBefore(startValue) || endDate.isAfter(today);
|
||||||
}, [date]);
|
}, [time]);
|
||||||
|
|
||||||
const onChangeCustomDate = useCallback((customDate) => {
|
useEffect(() => {
|
||||||
const newDate = {
|
if (!isOpen) {
|
||||||
...date,
|
if (type !== date.type || time.from !== date.from || time.to !== date.to) {
|
||||||
...customDate,
|
onChange(SEARCH_FILTERS_KEY.DATE, {
|
||||||
};
|
type,
|
||||||
onSelect(SEARCH_FILTERS_KEY.DATE, newDate);
|
value,
|
||||||
}, [date, onSelect]);
|
from: time.from,
|
||||||
|
to: time.to,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isOpen, date, time, type, value, onChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="search-filter filter-by-date-container">
|
<div className="search-filter filter-by-date-container">
|
||||||
<Dropdown isOpen={isOpen} toggle={toggle}>
|
<Dropdown isOpen={isOpen} toggle={toggle}>
|
||||||
<DropdownToggle tag="div" className="search-filter-toggle" onClick={toggle}>
|
<DropdownToggle tag="div" className={classNames('search-filter-toggle', {
|
||||||
|
'active': isOpen && value,
|
||||||
|
'highlighted': value,
|
||||||
|
})} onClick={toggle}>
|
||||||
<div className="filter-label" style={{ maxWidth: 300 }} title={label}>{label}</div>
|
<div className="filter-label" style={{ maxWidth: 300 }} title={label}>{label}</div>
|
||||||
<i
|
<i
|
||||||
className="sf3-font sf3-font-down sf3-font pl-1"
|
className="sf3-font sf3-font-down pl-1"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
toggle();
|
toggle();
|
||||||
@ -193,7 +187,7 @@ const FilterByDate = ({ date, onSelect }) => {
|
|||||||
<DropdownToggle tag="div" className="search-filter-toggle filter-by-date-type-toggle">
|
<DropdownToggle tag="div" className="search-filter-toggle filter-by-date-type-toggle">
|
||||||
<div className="filter-label">{typeLabel}</div>
|
<div className="filter-label">{typeLabel}</div>
|
||||||
<i
|
<i
|
||||||
className="sf3-font sf3-font-down sf3-font pl-1"
|
className="sf3-font sf3-font-down pl-1"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
toggleType();
|
toggleType();
|
||||||
@ -241,8 +235,8 @@ const FilterByDate = ({ date, onSelect }) => {
|
|||||||
<Picker
|
<Picker
|
||||||
showHourAndMinute={false}
|
showHourAndMinute={false}
|
||||||
disabledDate={disabledStartDate}
|
disabledDate={disabledStartDate}
|
||||||
value={date.start}
|
value={time.from}
|
||||||
onChange={(value) => onChangeCustomDate({ start: value })}
|
onChange={(value) => setTime({ ...time, from: value })}
|
||||||
inputWidth={DATE_INPUT_WIDTH}
|
inputWidth={DATE_INPUT_WIDTH}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -251,8 +245,8 @@ const FilterByDate = ({ date, onSelect }) => {
|
|||||||
<Picker
|
<Picker
|
||||||
showHourAndMinute={false}
|
showHourAndMinute={false}
|
||||||
disabledDate={disabledEndDate}
|
disabledDate={disabledEndDate}
|
||||||
value={date.end}
|
value={time.to}
|
||||||
onChange={(value) => onChangeCustomDate({ end: value })}
|
onChange={(value) => setTime({ ...time, to: value })}
|
||||||
inputWidth={DATE_INPUT_WIDTH}
|
inputWidth={DATE_INPUT_WIDTH}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -272,7 +266,7 @@ FilterByDate.propTypes = {
|
|||||||
start: PropTypes.oneOfType([PropTypes.number, PropTypes.object]),
|
start: PropTypes.oneOfType([PropTypes.number, PropTypes.object]),
|
||||||
end: PropTypes.oneOfType([PropTypes.number, PropTypes.object]),
|
end: PropTypes.oneOfType([PropTypes.number, PropTypes.object]),
|
||||||
}),
|
}),
|
||||||
onSelect: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FilterByDate;
|
export default FilterByDate;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { Dropdown, DropdownMenu, DropdownToggle } from 'reactstrap';
|
import { Dropdown, DropdownMenu, DropdownToggle } from 'reactstrap';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@ -6,18 +6,16 @@ import { gettext } from '../../../utils/constants';
|
|||||||
import ModalPortal from '../../modal-portal';
|
import ModalPortal from '../../modal-portal';
|
||||||
import { SEARCH_FILTERS_KEY } from '../../../constants';
|
import { SEARCH_FILTERS_KEY } from '../../../constants';
|
||||||
|
|
||||||
const FilterBySuffix = ({ suffixes, onSelect }) => {
|
const FilterBySuffix = ({ suffixes, onChange }) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [inputValue, setInputValue] = useState(suffixes.join(', '));
|
const [inputValue, setInputValue] = useState(suffixes);
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
const toggle = useCallback(() => setIsOpen(!isOpen), [isOpen]);
|
const toggle = useCallback(() => setIsOpen(!isOpen), [isOpen]);
|
||||||
|
|
||||||
const handleInput = useCallback((e) => {
|
const handleInput = useCallback((e) => {
|
||||||
setInputValue(e.target.value);
|
setInputValue(e.target.value);
|
||||||
const suffixes = e.target.value.split(',').map(suffix => suffix.trim()).filter(Boolean);
|
}, []);
|
||||||
onSelect(SEARCH_FILTERS_KEY.SUFFIXES, suffixes);
|
|
||||||
}, [onSelect]);
|
|
||||||
|
|
||||||
const handleKeyDown = useCallback((e) => {
|
const handleKeyDown = useCallback((e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -26,30 +24,48 @@ const FilterBySuffix = ({ suffixes, onSelect }) => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleClearInput = useCallback(() => {
|
||||||
|
setInputValue('');
|
||||||
|
setIsOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen && inputValue !== suffixes) {
|
||||||
|
onChange(SEARCH_FILTERS_KEY.SUFFIXES, inputValue.replace(/\./g, ''));
|
||||||
|
}
|
||||||
|
}, [isOpen, inputValue, suffixes, onChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="search-filter filter-by-suffix-container">
|
<div className="search-filter filter-by-suffix-container">
|
||||||
<Dropdown isOpen={isOpen} toggle={toggle}>
|
<Dropdown isOpen={isOpen} toggle={toggle}>
|
||||||
<DropdownToggle tag="div" className={classNames('search-filter-toggle', {
|
<DropdownToggle tag="div" className={classNames('search-filter-toggle', {
|
||||||
'active': isOpen && suffixes.length > 0,
|
'active': isOpen && inputValue.length > 0,
|
||||||
'highlighted': suffixes.length > 0,
|
'highlighted': inputValue.length > 0,
|
||||||
})} onClick={toggle}>
|
})} onClick={toggle}>
|
||||||
<div className="filter-label" title={gettext('File suffix')}>{gettext('File suffix')}</div>
|
<div className="filter-label" title={gettext('File suffix')}>{gettext('File suffix')}</div>
|
||||||
<i className="sf3-font sf3-font-down sf3-font pl-1" />
|
<i className="sf3-font sf3-font-down pl-1" />
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
<ModalPortal>
|
<ModalPortal>
|
||||||
<DropdownMenu className="search-filter-menu filter-by-suffix-menu">
|
<DropdownMenu className="search-filter-menu filter-by-suffix-menu p-4">
|
||||||
<div className="input-container">
|
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={gettext('Seperate multiple suffixes by ","(like .sdoc, .pdf)')}
|
className="form-control"
|
||||||
|
placeholder={gettext('Seperate multiple suffixes by ","(like sdoc, pdf)')}
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
autoFocus
|
autoFocus
|
||||||
width={120}
|
|
||||||
onChange={handleInput}
|
onChange={handleInput}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
/>
|
/>
|
||||||
</div>
|
{inputValue.length > 0 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="clear-icon-right sf3-font sf3-font-x-01"
|
||||||
|
onClick={handleClearInput}
|
||||||
|
aria-label={gettext('Clear')}
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</ModalPortal>
|
</ModalPortal>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
@ -58,8 +74,8 @@ const FilterBySuffix = ({ suffixes, onSelect }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
FilterBySuffix.propTypes = {
|
FilterBySuffix.propTypes = {
|
||||||
suffixes: PropTypes.array.isRequired,
|
suffixes: PropTypes.string.isRequired,
|
||||||
onSelect: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FilterBySuffix;
|
export default FilterBySuffix;
|
||||||
|
@ -6,7 +6,7 @@ import { Utils } from '../../../utils/utils';
|
|||||||
import { gettext } from '../../../utils/constants';
|
import { gettext } from '../../../utils/constants';
|
||||||
import { SEARCH_FILTERS_KEY } from '../../../constants';
|
import { SEARCH_FILTERS_KEY } from '../../../constants';
|
||||||
|
|
||||||
const FilterByText = ({ searchFilenameOnly, onSelect }) => {
|
const FilterByText = ({ searchFilenameOnly, onChange }) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [value, setValue] = useState(searchFilenameOnly ? SEARCH_FILTERS_KEY.SEARCH_FILENAME_ONLY : SEARCH_FILTERS_KEY.SEARCH_FILENAME_AND_CONTENT);
|
const [value, setValue] = useState(searchFilenameOnly ? SEARCH_FILTERS_KEY.SEARCH_FILENAME_ONLY : SEARCH_FILTERS_KEY.SEARCH_FILENAME_AND_CONTENT);
|
||||||
|
|
||||||
@ -27,8 +27,8 @@ const FilterByText = ({ searchFilenameOnly, onSelect }) => {
|
|||||||
const option = Utils.getEventData(e, 'toggle') ?? e.currentTarget.getAttribute('data-toggle');
|
const option = Utils.getEventData(e, 'toggle') ?? e.currentTarget.getAttribute('data-toggle');
|
||||||
setValue(option);
|
setValue(option);
|
||||||
const isSearchFilenameOnly = option === SEARCH_FILTERS_KEY.SEARCH_FILENAME_ONLY;
|
const isSearchFilenameOnly = option === SEARCH_FILTERS_KEY.SEARCH_FILENAME_ONLY;
|
||||||
onSelect(SEARCH_FILTERS_KEY.SEARCH_FILENAME_ONLY, isSearchFilenameOnly);
|
onChange(SEARCH_FILTERS_KEY.SEARCH_FILENAME_ONLY, isSearchFilenameOnly);
|
||||||
}, [onSelect]);
|
}, [onChange]);
|
||||||
|
|
||||||
const label = options.find((option) => option.key === value).label;
|
const label = options.find((option) => option.key === value).label;
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ const FilterByText = ({ searchFilenameOnly, onSelect }) => {
|
|||||||
|
|
||||||
FilterByText.propTypes = {
|
FilterByText.propTypes = {
|
||||||
searchFilenameOnly: PropTypes.bool.isRequired,
|
searchFilenameOnly: PropTypes.bool.isRequired,
|
||||||
onSelect: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FilterByText;
|
export default FilterByText;
|
||||||
|
@ -32,6 +32,10 @@
|
|||||||
background-color: #efefef;
|
background-color: #efefef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-filter-toggle .sf3-font-down {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
.search-filters-container .search-filter .filter-label {
|
.search-filters-container .search-filter .filter-label {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -57,6 +61,28 @@
|
|||||||
|
|
||||||
.search-filter-menu.filter-by-suffix-menu {
|
.search-filter-menu.filter-by-suffix-menu {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-filter-menu.filter-by-suffix-menu .clear-icon-right {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: #666;
|
||||||
|
margin: 9px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-filter-menu.filter-by-suffix-menu .clear-icon-right:hover {
|
||||||
|
background-color: #efefef;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-filters-container .search-filters-dropdown-item {
|
.search-filters-container .search-filters-dropdown-item {
|
||||||
@ -91,6 +117,14 @@
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-filter-menu .input-container.focus {
|
||||||
|
background-color: #fff;
|
||||||
|
border-color: #1991eb;
|
||||||
|
box-shadow: 0 0 0 2px rgba(70, 127, 207, .25);
|
||||||
|
color: #495057;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.search-filter-menu .input-container .search-input-wrapper {
|
.search-filter-menu .input-container .search-input-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -150,6 +184,10 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-by-date-menu .filter-by-date-menu-toolbar .filter-by-date-type-toggle .filter-label {
|
||||||
|
color: #7d7d7d;
|
||||||
|
}
|
||||||
|
|
||||||
.filter-by-date-menu .filter-by-date-menu-toolbar .delete-btn {
|
.filter-by-date-menu .filter-by-date-menu-toolbar .delete-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -184,12 +222,12 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-filters-container .search-filter-toggle.active,
|
|
||||||
.search-filters-container .search-filter-toggle.active:hover,
|
.search-filters-container .search-filter-toggle.active:hover,
|
||||||
.search-filters-container .search-filter-toggle.highlighted:hover {
|
.search-filters-container .search-filter-toggle.highlighted:hover {
|
||||||
background-color: rgba(255, 152, 0, 0.2);
|
background-color: rgba(255, 152, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-filters-container .search-filter-toggle.highlighted {
|
.search-filters-container .search-filter-toggle.highlighted,
|
||||||
|
.search-filter-toggle.highlighted .sf3-font-down {
|
||||||
color: #ff9800;
|
color: #ff9800;
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,10 @@ import './index.css';
|
|||||||
const SearchFilters = ({ filters, onChange }) => {
|
const SearchFilters = ({ filters, onChange }) => {
|
||||||
return (
|
return (
|
||||||
<div className="search-filters-container">
|
<div className="search-filters-container">
|
||||||
<FilterBySuffix suffixes={filters.suffixes} onSelect={onChange} />
|
<FilterBySuffix suffixes={filters.suffixes} onChange={onChange} />
|
||||||
<FilterByText searchFilenameOnly={filters.search_filename_only} onSelect={onChange} />
|
<FilterByText searchFilenameOnly={filters.search_filename_only} onChange={onChange} />
|
||||||
<FilterByCreator creatorList={filters.creator_list} onSelect={onChange} />
|
<FilterByCreator creatorList={filters.creator_list} onChange={onChange} />
|
||||||
<FilterByDate date={filters.date} onSelect={onChange} />
|
<FilterByDate date={filters.date} onChange={onChange} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -12,7 +12,7 @@ import { Utils } from '../../utils/utils';
|
|||||||
import toaster from '../toast';
|
import toaster from '../toast';
|
||||||
import Loading from '../loading';
|
import Loading from '../loading';
|
||||||
import { SEARCH_MASK, SEARCH_CONTAINER } from '../../constants/zIndexes';
|
import { SEARCH_MASK, SEARCH_CONTAINER } from '../../constants/zIndexes';
|
||||||
import { PRIVATE_FILE_TYPE, SEARCH_FILTER_BY_DATE_OPTION_KEY, SEARCH_FILTER_BY_DATE_TYPE_KEY, SEARCH_FILTERS_SHOW_KEY } from '../../constants';
|
import { PRIVATE_FILE_TYPE, SEARCH_FILTER_BY_DATE_OPTION_KEY, SEARCH_FILTER_BY_DATE_TYPE_KEY, SEARCH_FILTERS_KEY, SEARCH_FILTERS_SHOW_KEY } from '../../constants';
|
||||||
import SearchFilters from './search-filters';
|
import SearchFilters from './search-filters';
|
||||||
import SearchTags from './search-tags';
|
import SearchTags from './search-tags';
|
||||||
import IconBtn from '../icon-btn';
|
import IconBtn from '../icon-btn';
|
||||||
@ -61,10 +61,10 @@ class Search extends Component {
|
|||||||
date: {
|
date: {
|
||||||
type: SEARCH_FILTER_BY_DATE_TYPE_KEY.CREATE_TIME,
|
type: SEARCH_FILTER_BY_DATE_TYPE_KEY.CREATE_TIME,
|
||||||
value: '',
|
value: '',
|
||||||
start: null,
|
from: null,
|
||||||
end: null,
|
to: null,
|
||||||
},
|
},
|
||||||
suffixes: [],
|
suffixes: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
this.highlightRef = null;
|
this.highlightRef = null;
|
||||||
@ -530,7 +530,7 @@ class Search extends Component {
|
|||||||
start: null,
|
start: null,
|
||||||
end: null,
|
end: null,
|
||||||
},
|
},
|
||||||
suffixes: [],
|
suffixes: '',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -558,7 +558,7 @@ class Search extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredItems = this.filterResults(resultItems);
|
const filteredItems = this.filterByCreator(resultItems);
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
}
|
}
|
||||||
@ -668,72 +668,34 @@ class Search extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
searchRepo = () => {
|
searchRepo = () => {
|
||||||
const { value, filters } = this.state;
|
const { value } = this.state;
|
||||||
const queryData = {
|
const queryData = {
|
||||||
q: value,
|
q: value,
|
||||||
search_repo: this.props.repoID,
|
search_repo: this.props.repoID,
|
||||||
search_ftypes: 'all',
|
search_ftypes: 'all',
|
||||||
search_filename_only: filters.search_filename_only,
|
|
||||||
};
|
};
|
||||||
this.getSearchResult(queryData);
|
this.getSearchResult(this.buildSearchParams(queryData));
|
||||||
};
|
};
|
||||||
|
|
||||||
searchFolder = () => {
|
searchFolder = () => {
|
||||||
const { value, filters } = this.state;
|
const { value } = this.state;
|
||||||
const queryData = {
|
const queryData = {
|
||||||
q: value,
|
q: value,
|
||||||
search_repo: this.props.repoID,
|
search_repo: this.props.repoID,
|
||||||
search_ftypes: 'all',
|
search_ftypes: 'all',
|
||||||
search_path: this.props.path,
|
search_path: this.props.path,
|
||||||
search_filename_only: filters.search_filename_only,
|
|
||||||
};
|
};
|
||||||
this.getSearchResult(queryData);
|
this.getSearchResult(this.buildSearchParams(queryData));
|
||||||
};
|
};
|
||||||
|
|
||||||
searchAllRepos = () => {
|
searchAllRepos = () => {
|
||||||
const { value, filters } = this.state;
|
const { value } = this.state;
|
||||||
const queryData = {
|
const queryData = {
|
||||||
q: value,
|
q: value,
|
||||||
search_repo: 'all',
|
search_repo: 'all',
|
||||||
search_ftypes: 'all',
|
search_ftypes: 'all',
|
||||||
search_filename_only: filters.search_filename_only,
|
|
||||||
};
|
};
|
||||||
this.getSearchResult(queryData);
|
this.getSearchResult(this.buildSearchParams(queryData));
|
||||||
};
|
|
||||||
|
|
||||||
filterResults = (results) => {
|
|
||||||
const { filters } = this.state;
|
|
||||||
return results.filter(item => {
|
|
||||||
if (filters.creator_list && filters.creator_list.length > 0) {
|
|
||||||
if (!filters.creator_list.some(creator => creator.email === item.repo_owner_email)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let startDate = filters.date.start;
|
|
||||||
let endDate = filters.date.end;
|
|
||||||
if (filters.date.value === SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM) {
|
|
||||||
startDate = filters.date.start?.unix();
|
|
||||||
endDate = filters.date.end?.unix();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startDate && item.mtime < startDate) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (endDate && item.mtime > endDate) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters.suffixes && filters.suffixes.length > 0) {
|
|
||||||
const pathParts = item.path.split('.');
|
|
||||||
const suffix = pathParts.length > 1 ? `.${pathParts.pop()}` : '';
|
|
||||||
if (!filters.suffixes.includes(suffix)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
renderResults = (resultItems, isVisited) => {
|
renderResults = (resultItems, isVisited) => {
|
||||||
@ -785,25 +747,78 @@ class Search extends Component {
|
|||||||
this.setState({ isFiltersShow: !isFiltersShow });
|
this.setState({ isFiltersShow: !isFiltersShow });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildSearchParams = (baseParams) => {
|
||||||
|
const { filters } = this.state;
|
||||||
|
const params = { ...baseParams };
|
||||||
|
|
||||||
|
if (filters.search_filename_only) {
|
||||||
|
params.search_filename_only = filters.search_filename_only;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.date.value) {
|
||||||
|
const isCustom = filters.date.value === SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM;
|
||||||
|
params.time_from = isCustom ? filters.date.start?.unix() : filters.date.from;
|
||||||
|
params.time_to = isCustom ? filters.date.end?.unix() : filters.date.to;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.suffixes) {
|
||||||
|
params.input_fexts = filters.suffixes;
|
||||||
|
params.search_ftypes = 'custom';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.creator_list.length > 0) {
|
||||||
|
params.creator_emails = filters.creator_list.map(c => c.email).join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
|
||||||
handleFiltersChange = (key, value) => {
|
handleFiltersChange = (key, value) => {
|
||||||
const newFilters = { ...this.state.filters, [key]: value };
|
const newFilters = { ...this.state.filters, [key]: value };
|
||||||
if (newFilters.search_filename_only !== this.state.filters.search_filename_only) {
|
const hasActiveFilter = newFilters.suffixes || newFilters.creator_list.length > 0 || newFilters.date.value;
|
||||||
this.setState({ filters: newFilters }, () => {
|
this.setState({ filters: newFilters, isFilterControllerActive: hasActiveFilter });
|
||||||
|
|
||||||
|
// build query data
|
||||||
|
if (!this.state.value || !this.state.isResultGotten) return;
|
||||||
|
const queryUpdates = {};
|
||||||
|
|
||||||
|
if (key === SEARCH_FILTERS_KEY.CREATOR_LIST) return;
|
||||||
|
if (key === SEARCH_FILTERS_KEY.SEARCH_FILENAME_ONLY) {
|
||||||
|
queryUpdates.search_filename_only = value;
|
||||||
|
}
|
||||||
|
if (key === SEARCH_FILTERS_KEY.SUFFIXES) {
|
||||||
|
queryUpdates.search_ftypes = 'custom';
|
||||||
|
queryUpdates.input_fexts = value;
|
||||||
|
if (!value) {
|
||||||
|
queryUpdates.search_ftypes = 'all';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (key === SEARCH_FILTERS_KEY.DATE) {
|
||||||
|
const date = value;
|
||||||
|
const isCustom = date.value === SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM;
|
||||||
|
queryUpdates.time_from = isCustom ? value.from?.unix() : value.from;
|
||||||
|
queryUpdates.time_to = isCustom ? value.to?.unix() : value.to;
|
||||||
|
}
|
||||||
|
|
||||||
const newQueryData = {
|
const newQueryData = {
|
||||||
...this.queryData,
|
...this.queryData,
|
||||||
search_filename_only: newFilters.search_filename_only,
|
...queryUpdates,
|
||||||
}
|
};
|
||||||
|
|
||||||
this.getSearchResult(newQueryData);
|
this.getSearchResult(newQueryData);
|
||||||
|
}
|
||||||
|
|
||||||
|
filterByCreator = (results) => {
|
||||||
|
const { filters } = this.state;
|
||||||
|
return results.filter(item => {
|
||||||
|
if (filters.creator_list && filters.creator_list.length > 0) {
|
||||||
|
if (!filters.creator_list.some(creator => creator.email === item.repo_owner_email)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
let isFilterControllerActive = false;
|
|
||||||
if (newFilters.creator_list.length > 0 || newFilters.date || newFilters.suffixes.length > 0) {
|
|
||||||
isFilterControllerActive = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ filters: newFilters, isFilterControllerActive }, () => this.forceUpdate());
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSelectTag = (tag) => {
|
handleSelectTag = (tag) => {
|
||||||
this.props.onSelectTag(tag);
|
this.props.onSelectTag(tag);
|
||||||
|
@ -53,6 +53,10 @@
|
|||||||
color: #ff9800;
|
color: #ff9800;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-container.show .input-icon .search-icon-right.search-filter-controller.active:hover {
|
||||||
|
background-color: rgba(255, 152, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
.search-icon-left {
|
.search-icon-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user