mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-28 16:17:02 +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:
@@ -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;
|
||||||
|
@@ -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;
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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;
|
@@ -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;
|
||||||
|
@@ -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}
|
||||||
|
@@ -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]: [
|
||||||
{
|
{
|
||||||
|
@@ -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];
|
||||||
|
@@ -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] };
|
||||||
|
Reference in New Issue
Block a user