mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-27 23:56:18 +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 { gettext } from '../../../utils/constants';
|
||||
import { isEnter, isSpace } from '../../utils/hotkey';
|
||||
import { VIEW_TYPE } from '../../constants';
|
||||
|
||||
const FilterSetter = ({
|
||||
readOnly,
|
||||
@@ -20,7 +21,8 @@ const FilterSetter = ({
|
||||
target,
|
||||
filterConjunction,
|
||||
basicFilters,
|
||||
modifyFilters
|
||||
modifyFilters,
|
||||
viewType,
|
||||
}) => {
|
||||
const [isShowSetter, setShowSetter] = useState(false);
|
||||
|
||||
@@ -84,6 +86,7 @@ const FilterSetter = ({
|
||||
hidePopover={onSetterToggle}
|
||||
update={onChange}
|
||||
isPre={isPre}
|
||||
viewType={viewType}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
@@ -104,12 +107,14 @@ FilterSetter.propTypes = {
|
||||
collaborators: PropTypes.array,
|
||||
isPre: PropTypes.bool,
|
||||
basicFilters: PropTypes.array,
|
||||
viewType: PropTypes.string,
|
||||
};
|
||||
|
||||
FilterSetter.defaultProps = {
|
||||
target: 'sf-metadata-filter-popover',
|
||||
isNeedSubmit: false,
|
||||
basicFilters: [],
|
||||
viewType: VIEW_TYPE.TABLE,
|
||||
};
|
||||
|
||||
export default FilterSetter;
|
||||
|
@@ -9,7 +9,7 @@ const OPTIONS = [
|
||||
{ value: 'all', name: gettext('Pictures and videos') },
|
||||
];
|
||||
|
||||
const FileTypeFilter = ({ readOnly, value = 'picture', onChange: onChangeAPI }) => {
|
||||
const GalleryFileTypeFilter = ({ readOnly, value = 'picture', onChange: onChangeAPI }) => {
|
||||
|
||||
const options = useMemo(() => {
|
||||
return OPTIONS.map(o => {
|
||||
@@ -60,10 +60,10 @@ const FileTypeFilter = ({ readOnly, value = 'picture', onChange: onChangeAPI })
|
||||
);
|
||||
};
|
||||
|
||||
FileTypeFilter.propTypes = {
|
||||
GalleryFileTypeFilter.propTypes = {
|
||||
readOnly: PropTypes.bool,
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
export default FileTypeFilter;
|
||||
export default GalleryFileTypeFilter;
|
@@ -25,6 +25,10 @@
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.sf-metadata-basic-filters-select .selected-option .custom-select-dropdown-icon {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.select-basic-filter-option {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@@ -52,10 +56,22 @@
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.sf-metadata-basic-filters-select .sf-metadata-option-group {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.filter-group-basic .sf-metadata-filters-list {
|
||||
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 { FormGroup, Label } from 'reactstrap';
|
||||
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 { PRIVATE_COLUMN_KEY } from '../../../../constants';
|
||||
import { PRIVATE_COLUMN_KEY, VIEW_TYPE } from '../../../../constants';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const BasicFilters = ({ readOnly, filters = [], onChange }) => {
|
||||
const BasicFilters = ({ readOnly, filters = [], onChange, viewType }) => {
|
||||
|
||||
const onChangeFileOrFolderFilter = useCallback((newValue) => {
|
||||
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>
|
||||
<div className="filter-group-container">
|
||||
<div className="sf-metadata-filters-list">
|
||||
{filters.map((filter, index) => {
|
||||
{filters.map((filter) => {
|
||||
const { column_key, filter_term } = filter;
|
||||
if (column_key === PRIVATE_COLUMN_KEY.IS_DIR) {
|
||||
return (
|
||||
@@ -39,9 +40,8 @@ const BasicFilters = ({ readOnly, filters = [], onChange }) => {
|
||||
);
|
||||
}
|
||||
if (column_key === PRIVATE_COLUMN_KEY.FILE_TYPE) {
|
||||
return (
|
||||
<FileTypeFilter key={column_key} readOnly={readOnly} value={filter_term} onChange={onChangeFileTypeFilter} />
|
||||
);
|
||||
const FileTypeFilter = viewType === VIEW_TYPE.GALLERY ? GalleryFileTypeFilter : TableFileTypeFilter;
|
||||
return (<FileTypeFilter key={column_key} readOnly={readOnly} value={filter_term} onChange={onChangeFileTypeFilter} />);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
@@ -56,6 +56,7 @@ BasicFilters.propTypes = {
|
||||
filters: PropTypes.array,
|
||||
columns: PropTypes.array,
|
||||
onChange: PropTypes.func,
|
||||
viewType: PropTypes.oneOf(Object.values(VIEW_TYPE)),
|
||||
};
|
||||
|
||||
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() {
|
||||
const { readOnly, target, columns, placement } = this.props;
|
||||
const { readOnly, target, columns, placement, viewType } = this.props;
|
||||
const { filters, filterConjunction, basicFilters } = this.state;
|
||||
const canAddFilter = columns.length > 0;
|
||||
return (
|
||||
@@ -168,7 +168,7 @@ class FilterPopover extends Component {
|
||||
>
|
||||
{({ scheduleUpdate }) => (
|
||||
<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">
|
||||
<Label className="filter-group-name">{gettext('Advanced')}</Label>
|
||||
<div className="filter-group-container">
|
||||
@@ -222,6 +222,7 @@ FilterPopover.propTypes = {
|
||||
basicFilters: PropTypes.array,
|
||||
hidePopover: PropTypes.func,
|
||||
update: PropTypes.func,
|
||||
viewType: PropTypes.string,
|
||||
};
|
||||
|
||||
export default FilterPopover;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
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';
|
||||
|
||||
@@ -14,6 +14,10 @@ const ViewToolBar = ({ viewId }) => {
|
||||
return view.columns;
|
||||
}, [view]);
|
||||
|
||||
const filterColumns = useMemo(() => {
|
||||
return viewColumns.filter(c => c.key !== PRIVATE_COLUMN_KEY.FILE_TYPE);
|
||||
}, [viewColumns]);
|
||||
|
||||
const onHeaderClick = useCallback(() => {
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
|
||||
}, []);
|
||||
@@ -86,9 +90,10 @@ const ViewToolBar = ({ viewId }) => {
|
||||
filterConjunction={view.filter_conjunction}
|
||||
basicFilters={view.basic_filters}
|
||||
filters={view.filters}
|
||||
columns={viewColumns}
|
||||
columns={filterColumns}
|
||||
modifyFilters={modifyFilters}
|
||||
collaborators={collaborators}
|
||||
viewType={viewType}
|
||||
/>
|
||||
<SortSetter
|
||||
isNeedSubmit={true}
|
||||
|
@@ -19,7 +19,11 @@ export const VIEW_TYPE_DEFAULT_BASIC_FILTER = {
|
||||
column_key: PRIVATE_COLUMN_KEY.IS_DIR,
|
||||
filter_predicate: FILTER_PREDICATE_TYPE.IS,
|
||||
filter_term: 'file'
|
||||
}
|
||||
}, {
|
||||
column_key: PRIVATE_COLUMN_KEY.FILE_TYPE,
|
||||
filter_predicate: FILTER_PREDICATE_TYPE.IS_ANY_OF,
|
||||
filter_term: []
|
||||
},
|
||||
],
|
||||
[VIEW_TYPE.GALLERY]: [
|
||||
{
|
||||
|
@@ -15,7 +15,11 @@ class View {
|
||||
this.filters = object.filters || [];
|
||||
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
|
||||
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) => {
|
||||
const { data } = column;
|
||||
const _OPTIONS = {
|
||||
export const getFileTypeColumnOptions = () => {
|
||||
return {
|
||||
[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.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.COMPRESSED]: { name: gettext('Compressed'), color: '#4a9afb', textColor: '#FFFFFF', borderColor: '#da42e5' },
|
||||
};
|
||||
};
|
||||
|
||||
const getFileTypeColumnData = (column) => {
|
||||
const { data } = column;
|
||||
const _OPTIONS = getFileTypeColumnOptions();
|
||||
let newData = { ...data };
|
||||
newData.options = Array.isArray(data.options) ? data.options.map(o => {
|
||||
return { ...o, ..._OPTIONS[o.id] };
|
||||
|
Reference in New Issue
Block a user