1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-28 08:06:56 +00:00

Feature/filter by file type (#6852)

* update basic filters, add file type filter

* feat: optimize code

* feat: optimize code

* feat: optimize code

* feat: optimize code

---------

Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
Aries
2024-09-29 17:57:50 +08:00
committed by GitHub
parent 2a4949f518
commit 77b4664920
10 changed files with 141 additions and 24 deletions

View File

@@ -7,6 +7,7 @@ import { FilterPopover } from '../popover';
import { getValidFilters } from '../../utils/filter'; import { getValidFilters } from '../../utils/filter';
import { gettext } from '../../../utils/constants'; import { gettext } from '../../../utils/constants';
import { isEnter, isSpace } from '../../utils/hotkey'; import { isEnter, isSpace } from '../../utils/hotkey';
import { VIEW_TYPE } from '../../constants';
const FilterSetter = ({ const FilterSetter = ({
readOnly, readOnly,
@@ -20,7 +21,8 @@ const FilterSetter = ({
target, target,
filterConjunction, filterConjunction,
basicFilters, basicFilters,
modifyFilters modifyFilters,
viewType,
}) => { }) => {
const [isShowSetter, setShowSetter] = useState(false); const [isShowSetter, setShowSetter] = useState(false);
@@ -84,6 +86,7 @@ const FilterSetter = ({
hidePopover={onSetterToggle} hidePopover={onSetterToggle}
update={onChange} update={onChange}
isPre={isPre} isPre={isPre}
viewType={viewType}
/> />
} }
</> </>
@@ -104,12 +107,14 @@ FilterSetter.propTypes = {
collaborators: PropTypes.array, collaborators: PropTypes.array,
isPre: PropTypes.bool, isPre: PropTypes.bool,
basicFilters: PropTypes.array, basicFilters: PropTypes.array,
viewType: PropTypes.string,
}; };
FilterSetter.defaultProps = { FilterSetter.defaultProps = {
target: 'sf-metadata-filter-popover', target: 'sf-metadata-filter-popover',
isNeedSubmit: false, isNeedSubmit: false,
basicFilters: [], basicFilters: [],
viewType: VIEW_TYPE.TABLE,
}; };
export default FilterSetter; export default FilterSetter;

View File

@@ -9,7 +9,7 @@ const OPTIONS = [
{ value: 'all', name: gettext('Pictures and videos') }, { value: 'all', name: gettext('Pictures and videos') },
]; ];
const FileTypeFilter = ({ readOnly, value = 'picture', onChange: onChangeAPI }) => { const GalleryFileTypeFilter = ({ readOnly, value = 'picture', onChange: onChangeAPI }) => {
const options = useMemo(() => { const options = useMemo(() => {
return OPTIONS.map(o => { return OPTIONS.map(o => {
@@ -60,10 +60,10 @@ const FileTypeFilter = ({ readOnly, value = 'picture', onChange: onChangeAPI })
); );
}; };
FileTypeFilter.propTypes = { GalleryFileTypeFilter.propTypes = {
readOnly: PropTypes.bool, readOnly: PropTypes.bool,
value: PropTypes.string, value: PropTypes.string,
onChange: PropTypes.func, onChange: PropTypes.func,
}; };
export default FileTypeFilter; export default GalleryFileTypeFilter;

View File

@@ -25,6 +25,10 @@
margin-right: 8px; margin-right: 8px;
} }
.sf-metadata-basic-filters-select .selected-option .custom-select-dropdown-icon {
margin-left: 0;
}
.select-basic-filter-option { .select-basic-filter-option {
width: 100%; width: 100%;
display: flex; display: flex;
@@ -52,10 +56,22 @@
fill: #fff; fill: #fff;
} }
.sf-metadata-basic-filters-select .sf-metadata-option-group {
margin-left: 6px;
}
.filter-group-basic .sf-metadata-filters-list { .filter-group-basic .sf-metadata-filters-list {
min-height: unset; min-height: unset;
display: flex;
}
.sf-metadata-table-view-basic-filter-file-type-select .sf-metadata-option {
padding: 0.25rem 1rem;
}
.sf-metadata-table-view-basic-filter-file-type-select .select-basic-filter-option .select-basic-filter-option-checkbox {
display: inline-flex;
align-items: center;
flex-shrink: 0;
}
.sf-metadata-table-view-basic-filter-file-type-select .select-basic-filter-option .select-basic-filter-option-checkbox,
.sf-metadata-table-view-basic-filter-file-type-select .select-basic-filter-option .select-basic-filter-option-checkbox input {
cursor: inherit;
} }

View File

@@ -2,13 +2,14 @@ import React, { useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FormGroup, Label } from 'reactstrap'; import { FormGroup, Label } from 'reactstrap';
import FileOrFolderFilter from './file-folder-filter'; import FileOrFolderFilter from './file-folder-filter';
import FileTypeFilter from './file-type-filter'; import TableFileTypeFilter from './table-file-type-filter';
import GalleryFileTypeFilter from './gallery-file-type-filter';
import { gettext } from '../../../../../utils/constants'; import { gettext } from '../../../../../utils/constants';
import { PRIVATE_COLUMN_KEY } from '../../../../constants'; import { PRIVATE_COLUMN_KEY, VIEW_TYPE } from '../../../../constants';
import './index.css'; import './index.css';
const BasicFilters = ({ readOnly, filters = [], onChange }) => { const BasicFilters = ({ readOnly, filters = [], onChange, viewType }) => {
const onChangeFileOrFolderFilter = useCallback((newValue) => { const onChangeFileOrFolderFilter = useCallback((newValue) => {
const filterIndex = filters.findIndex(filter => filter.column_key === PRIVATE_COLUMN_KEY.IS_DIR); const filterIndex = filters.findIndex(filter => filter.column_key === PRIVATE_COLUMN_KEY.IS_DIR);
@@ -31,7 +32,7 @@ const BasicFilters = ({ readOnly, filters = [], onChange }) => {
<Label className="filter-group-name">{gettext('Basic')}</Label> <Label className="filter-group-name">{gettext('Basic')}</Label>
<div className="filter-group-container"> <div className="filter-group-container">
<div className="sf-metadata-filters-list"> <div className="sf-metadata-filters-list">
{filters.map((filter, index) => { {filters.map((filter) => {
const { column_key, filter_term } = filter; const { column_key, filter_term } = filter;
if (column_key === PRIVATE_COLUMN_KEY.IS_DIR) { if (column_key === PRIVATE_COLUMN_KEY.IS_DIR) {
return ( return (
@@ -39,9 +40,8 @@ const BasicFilters = ({ readOnly, filters = [], onChange }) => {
); );
} }
if (column_key === PRIVATE_COLUMN_KEY.FILE_TYPE) { if (column_key === PRIVATE_COLUMN_KEY.FILE_TYPE) {
return ( const FileTypeFilter = viewType === VIEW_TYPE.GALLERY ? GalleryFileTypeFilter : TableFileTypeFilter;
<FileTypeFilter key={column_key} readOnly={readOnly} value={filter_term} onChange={onChangeFileTypeFilter} /> return (<FileTypeFilter key={column_key} readOnly={readOnly} value={filter_term} onChange={onChangeFileTypeFilter} />);
);
} }
return null; return null;
})} })}
@@ -56,6 +56,7 @@ BasicFilters.propTypes = {
filters: PropTypes.array, filters: PropTypes.array,
columns: PropTypes.array, columns: PropTypes.array,
onChange: PropTypes.func, onChange: PropTypes.func,
viewType: PropTypes.oneOf(Object.values(VIEW_TYPE)),
}; };
export default BasicFilters; export default BasicFilters;

View File

@@ -0,0 +1,77 @@
import React, { useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import { CustomizeSelect } from '@seafile/sf-metadata-ui-component';
import { gettext } from '../../../../../utils/constants';
import { getFileTypeColumnOptions } from '../../../../utils/column';
const TableFileTypeFilter = ({ readOnly, value, onChange: onChangeAPI }) => {
const OPTIONS = useMemo(() => {
const optionsMap = getFileTypeColumnOptions();
let options = [];
for (const [key, option] of Object.entries(optionsMap)) {
options.push({ value: key, name: option.name });
}
return options;
}, []);
const options = useMemo(() => {
return OPTIONS.map(o => {
const { name } = o;
return {
value: o.value,
label: (
<div className="select-basic-filter-option">
<div className="select-basic-filter-option-checkbox mr-2">
<input type="checkbox" checked={value.includes(o.value)} readOnly />
</div>
<div className="select-basic-filter-option-name" title={name} aria-label={name}>{name}</div>
</div>
)
};
});
}, [OPTIONS, value]);
const displayValue = useMemo(() => {
const selectedOptions = OPTIONS.filter(o => value.includes(o.value));
return {
label: (
<div className="select-basic-filter-display-name">
{selectedOptions.length > 0 ? selectedOptions.map(o => o.name).join(', ') : gettext('File type')}
</div>
)
};
}, [OPTIONS, value]);
const onChange = useCallback((newValue) => {
if (value.includes(newValue)) {
onChangeAPI(value.filter(v => v !== newValue));
} else {
onChangeAPI([...value, newValue]);
}
}, [value, onChangeAPI]);
return (
<CustomizeSelect
readOnly={readOnly}
className="sf-metadata-basic-filters-select sf-metadata-table-view-basic-filter-file-type-select ml-4"
value={displayValue}
options={options}
onSelectOption={onChange}
supportMultipleSelect={true}
component={{
DropDownIcon: (
<i className="sf3-font sf3-font-down"></i>
)
}}
/>
);
};
TableFileTypeFilter.propTypes = {
readOnly: PropTypes.bool,
value: PropTypes.array,
onChange: PropTypes.func,
};
export default TableFileTypeFilter;

View File

@@ -153,7 +153,7 @@ class FilterPopover extends Component {
}; };
render() { render() {
const { readOnly, target, columns, placement } = this.props; const { readOnly, target, columns, placement, viewType } = this.props;
const { filters, filterConjunction, basicFilters } = this.state; const { filters, filterConjunction, basicFilters } = this.state;
const canAddFilter = columns.length > 0; const canAddFilter = columns.length > 0;
return ( return (
@@ -168,7 +168,7 @@ class FilterPopover extends Component {
> >
{({ scheduleUpdate }) => ( {({ scheduleUpdate }) => (
<div ref={ref => this.dtablePopoverRef = ref} onClick={this.onPopoverInsideClick} className={this.props.filtersClassName}> <div ref={ref => this.dtablePopoverRef = ref} onClick={this.onPopoverInsideClick} className={this.props.filtersClassName}>
<BasicFilters filters={basicFilters} onChange={this.onBasicFilterChange} /> <BasicFilters filters={basicFilters} onChange={this.onBasicFilterChange} viewType={viewType}/>
<FormGroup className="filter-group-advanced filter-group mb-0"> <FormGroup className="filter-group-advanced filter-group mb-0">
<Label className="filter-group-name">{gettext('Advanced')}</Label> <Label className="filter-group-name">{gettext('Advanced')}</Label>
<div className="filter-group-container"> <div className="filter-group-container">
@@ -222,6 +222,7 @@ FilterPopover.propTypes = {
basicFilters: PropTypes.array, basicFilters: PropTypes.array,
hidePopover: PropTypes.func, hidePopover: PropTypes.func,
update: PropTypes.func, update: PropTypes.func,
viewType: PropTypes.string,
}; };
export default FilterPopover; export default FilterPopover;

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { GalleryGroupBySetter, GallerySliderSetter, FilterSetter, GroupbySetter, SortSetter, HideColumnSetter } from '../data-process-setter'; import { GalleryGroupBySetter, GallerySliderSetter, FilterSetter, GroupbySetter, SortSetter, HideColumnSetter } from '../data-process-setter';
import { EVENT_BUS_TYPE, VIEW_TYPE } from '../../constants'; import { EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY, VIEW_TYPE } from '../../constants';
import './index.css'; import './index.css';
@@ -14,6 +14,10 @@ const ViewToolBar = ({ viewId }) => {
return view.columns; return view.columns;
}, [view]); }, [view]);
const filterColumns = useMemo(() => {
return viewColumns.filter(c => c.key !== PRIVATE_COLUMN_KEY.FILE_TYPE);
}, [viewColumns]);
const onHeaderClick = useCallback(() => { const onHeaderClick = useCallback(() => {
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE); window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
}, []); }, []);
@@ -86,9 +90,10 @@ const ViewToolBar = ({ viewId }) => {
filterConjunction={view.filter_conjunction} filterConjunction={view.filter_conjunction}
basicFilters={view.basic_filters} basicFilters={view.basic_filters}
filters={view.filters} filters={view.filters}
columns={viewColumns} columns={filterColumns}
modifyFilters={modifyFilters} modifyFilters={modifyFilters}
collaborators={collaborators} collaborators={collaborators}
viewType={viewType}
/> />
<SortSetter <SortSetter
isNeedSubmit={true} isNeedSubmit={true}

View File

@@ -19,7 +19,11 @@ export const VIEW_TYPE_DEFAULT_BASIC_FILTER = {
column_key: PRIVATE_COLUMN_KEY.IS_DIR, column_key: PRIVATE_COLUMN_KEY.IS_DIR,
filter_predicate: FILTER_PREDICATE_TYPE.IS, filter_predicate: FILTER_PREDICATE_TYPE.IS,
filter_term: 'file' filter_term: 'file'
} }, {
column_key: PRIVATE_COLUMN_KEY.FILE_TYPE,
filter_predicate: FILTER_PREDICATE_TYPE.IS_ANY_OF,
filter_term: []
},
], ],
[VIEW_TYPE.GALLERY]: [ [VIEW_TYPE.GALLERY]: [
{ {

View File

@@ -15,7 +15,11 @@ class View {
this.filters = object.filters || []; this.filters = object.filters || [];
this.filter_conjunction = object.filter_conjunction || 'Or'; this.filter_conjunction = object.filter_conjunction || 'Or';
this.basic_filters = object.basic_filters && object.basic_filters.length > 0 ? object.basic_filters : VIEW_TYPE_DEFAULT_BASIC_FILTER[this.type]; const defaultBasicFilters = VIEW_TYPE_DEFAULT_BASIC_FILTER[this.type];
this.basic_filters = object.basic_filters && object.basic_filters.length > 0 ? object.basic_filters : defaultBasicFilters;
if (this.basic_filters.length !== defaultBasicFilters.length) {
this.basic_filters = [...this.basic_filters, ...defaultBasicFilters.slice(this.basic_filters.length)];
}
// sort // sort
this.sorts = object.sorts && object.sorts.length > 0 ? object.sorts : VIEW_TYPE_DEFAULT_SORTS[this.type]; this.sorts = object.sorts && object.sorts.length > 0 ? object.sorts : VIEW_TYPE_DEFAULT_SORTS[this.type];

View File

@@ -262,9 +262,8 @@ export const getNormalizedColumnType = (key, type) => {
} }
}; };
const getFileTypeColumnData = (column) => { export const getFileTypeColumnOptions = () => {
const { data } = column; return {
const _OPTIONS = {
[PREDEFINED_FILE_TYPE_OPTION_KEY.PICTURE]: { name: gettext('Picture'), color: '#FFFCB5', textColor: '#202428' }, [PREDEFINED_FILE_TYPE_OPTION_KEY.PICTURE]: { name: gettext('Picture'), color: '#FFFCB5', textColor: '#202428' },
[PREDEFINED_FILE_TYPE_OPTION_KEY.DOCUMENT]: { name: gettext('Document'), color: '#B7CEF9', textColor: '#202428' }, [PREDEFINED_FILE_TYPE_OPTION_KEY.DOCUMENT]: { name: gettext('Document'), color: '#B7CEF9', textColor: '#202428' },
[PREDEFINED_FILE_TYPE_OPTION_KEY.VIDEO]: { name: gettext('Video'), color: '#9860E5', textColor: '#FFFFFF', borderColor: '#844BD2' }, [PREDEFINED_FILE_TYPE_OPTION_KEY.VIDEO]: { name: gettext('Video'), color: '#9860E5', textColor: '#FFFFFF', borderColor: '#844BD2' },
@@ -272,6 +271,11 @@ const getFileTypeColumnData = (column) => {
[PREDEFINED_FILE_TYPE_OPTION_KEY.CODE]: { name: gettext('Code'), color: '#4ad8fb', textColor: '#FFFFFF', borderColor: '#4283e5' }, [PREDEFINED_FILE_TYPE_OPTION_KEY.CODE]: { name: gettext('Code'), color: '#4ad8fb', textColor: '#FFFFFF', borderColor: '#4283e5' },
[PREDEFINED_FILE_TYPE_OPTION_KEY.COMPRESSED]: { name: gettext('Compressed'), color: '#4a9afb', textColor: '#FFFFFF', borderColor: '#da42e5' }, [PREDEFINED_FILE_TYPE_OPTION_KEY.COMPRESSED]: { name: gettext('Compressed'), color: '#4a9afb', textColor: '#FFFFFF', borderColor: '#da42e5' },
}; };
};
const getFileTypeColumnData = (column) => {
const { data } = column;
const _OPTIONS = getFileTypeColumnOptions();
let newData = { ...data }; let newData = { ...data };
newData.options = Array.isArray(data.options) ? data.options.map(o => { newData.options = Array.isArray(data.options) ? data.options.map(o => {
return { ...o, ..._OPTIONS[o.id] }; return { ...o, ..._OPTIONS[o.id] };