diff --git a/frontend/src/metadata/metadata-view/_basic/constants/column/common.js b/frontend/src/metadata/metadata-view/_basic/constants/column/common.js index 19a22aebf3..27e2a58b60 100644 --- a/frontend/src/metadata/metadata-view/_basic/constants/column/common.js +++ b/frontend/src/metadata/metadata-view/_basic/constants/column/common.js @@ -10,6 +10,7 @@ export const NOT_DISPLAY_COLUMN_KEYS = [ PRIVATE_COLUMN_KEY.SUFFIX, PRIVATE_COLUMN_KEY.FILE_DETAILS, PRIVATE_COLUMN_KEY.LOCATION, + PRIVATE_COLUMN_KEY.IS_DIR, ]; export const VIEW_NOT_DISPLAY_COLUMN_KEYS = [ diff --git a/frontend/src/metadata/metadata-view/components/data-process-setter/filter-setter.jsx b/frontend/src/metadata/metadata-view/components/data-process-setter/filter-setter.jsx index c1c825141f..32e7f844bd 100644 --- a/frontend/src/metadata/metadata-view/components/data-process-setter/filter-setter.jsx +++ b/frontend/src/metadata/metadata-view/components/data-process-setter/filter-setter.jsx @@ -18,6 +18,7 @@ const FilterSetter = ({ filtersClassName, target, filterConjunction, + basicFilters, modifyFilters }) => { const [isShowSetter, setShowSetter] = useState(false); @@ -26,12 +27,15 @@ const FilterSetter = ({ return deepCopy(getValidFilters(propsFilters || [], columns)); }, [propsFilters, columns]); + const filtersCount = useMemo(() => { + return filters.length + basicFilters.length; + }, [filters, basicFilters]); + const message = useMemo(() => { - const filtersLength = filters.length; - if (filtersLength === 1) return isNeedSubmit ? gettext('1 preset filter') : gettext('1 filter'); - if (filtersLength > 1) return filtersLength + ' ' + (isNeedSubmit ? gettext('Preset filters') : gettext('Filters')); + if (filtersCount === 1) return isNeedSubmit ? gettext('1 preset filter') : gettext('1 filter'); + if (filtersCount > 1) return filtersCount + ' ' + (isNeedSubmit ? gettext('Preset filters') : gettext('Filters')); return isNeedSubmit ? gettext('Preset filter') : gettext('Filter'); - }, [isNeedSubmit, filters]); + }, [isNeedSubmit, filtersCount]); const onSetterToggle = useCallback(() => { setShowSetter(!isShowSetter); @@ -43,13 +47,13 @@ const FilterSetter = ({ }, [onSetterToggle]); const onChange = useCallback((update) => { - const { filters, filter_conjunction } = update || {}; + const { filters, filter_conjunction, basic_filters } = update || {}; const validFilters = getValidFilters(filters, columns); - modifyFilters(validFilters, filter_conjunction); + modifyFilters(validFilters, filter_conjunction, basic_filters); }, [columns, modifyFilters]); if (!columns) return null; - const className = classnames(wrapperClass, { 'active': filters.length > 0 }); + const className = classnames(wrapperClass, { 'active': filtersCount > 0 }); return ( <> { + + const options = useMemo(() => { + return OPTIONS.map(o => { + const { name } = o; + return { + value: o.value, + label: ( +
+
{name}
+
+ {value === o.value && ()} +
+
+ ) + }; + }); + }, [value]); + + const displayValue = useMemo(() => { + const selectedOption = OPTIONS.find(o => o.value === value) || OPTIONS[2]; + return { + label: ( +
+ {selectedOption.name} +
+ ) + }; + }, [value]); + + const onChange = useCallback((newValue) => { + if (newValue === value) return; + onChangeAPI(newValue); + }, [value, onChangeAPI]); + + return ( + + ); +}; + +FileOrFolderFilter.propTypes = { + readOnly: PropTypes.bool, + value: PropTypes.string, + onChange: PropTypes.func, +}; + +export default FileOrFolderFilter; diff --git a/frontend/src/metadata/metadata-view/components/popover/filter-popover/basic-filters/index.css b/frontend/src/metadata/metadata-view/components/popover/filter-popover/basic-filters/index.css new file mode 100644 index 0000000000..8d9b83451d --- /dev/null +++ b/frontend/src/metadata/metadata-view/components/popover/filter-popover/basic-filters/index.css @@ -0,0 +1,30 @@ +.sf-metadata-basic-filters-select { + width: 200px; +} + +.select-basic-filter-option { + width: 100%; + display: flex; + align-items: center; +} + +.select-basic-filter-option .select-basic-filter-option-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.select-basic-filter-option .select-basic-filter-option-check-icon { + display: inline-flex; + align-items: center; + height: 20px; + width: 20px; + text-align: center; + flex-shrink: 0; + margin-left: 10px; +} + +.sf-metadata-option.sf-metadata-option-active .sf-metadata-icon-check-mark { + fill: #fff; +} diff --git a/frontend/src/metadata/metadata-view/components/popover/filter-popover/basic-filters/index.js b/frontend/src/metadata/metadata-view/components/popover/filter-popover/basic-filters/index.js new file mode 100644 index 0000000000..7a11d58068 --- /dev/null +++ b/frontend/src/metadata/metadata-view/components/popover/filter-popover/basic-filters/index.js @@ -0,0 +1,44 @@ +import React, { useCallback } from 'react'; +import PropTypes from 'prop-types'; +import { FormGroup, Label } from 'reactstrap'; +import { gettext } from '../../../../utils'; +import { PRIVATE_COLUMN_KEY } from '../../../../_basic'; +import FileOrFolderFilter from './file-folder-filter'; + +import './index.css'; + +const BasicFilters = ({ readOnly, filters = [], onChange }) => { + + const onChangeFileOrFolderFilter = useCallback((newValue) => { + const filterIndex = filters.findIndex(filter => filter.column_key === PRIVATE_COLUMN_KEY.IS_DIR); + const filter = filters[filterIndex]; + const newFilters = filters.slice(0); + newFilters[filterIndex] = { ...filter, filter_term: newValue }; + onChange(newFilters); + }, [filters, onChange]); + + return ( + + +
+ {filters.map(filter => { + const { column_key, filter_term } = filter; + if (column_key === PRIVATE_COLUMN_KEY.IS_DIR) { + return ( + + ); + } + return null; + })} +
+
+ ); +}; + +BasicFilters.propTypes = { + readOnly: PropTypes.bool, + value: PropTypes.string, + onChange: PropTypes.func, +}; + +export default BasicFilters; diff --git a/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.css b/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.css index a8bf97c0de..0e27c4d838 100644 --- a/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.css +++ b/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.css @@ -2,3 +2,39 @@ max-width: none; min-width: 300px; } + +.sf-metadata-filter-popover .sf-metadata-filters { + display: flex; + flex-direction: column; + min-width: 400px; +} + +.sf-metadata-filter-popover .filter-group-name { + margin-bottom: 10px; +} + +.sf-metadata-filter-popover .filter-group-advanced { + flex: 1; + display: flex; + flex-direction: column; +} + +.sf-metadata-filter-popover .filter-group-advanced .filter-group-name { + padding: 0 16px; +} + +.sf-metadata-filter-popover .filter-group-advanced .filter-group-container { + flex: 1; + padding: 0 16px; +} + +.sf-metadata-filter-popover .add-item-btn.popover-add-tool { + height: 28px; + padding: 0 6px; + width: fit-content; + max-width: 100%; + margin-bottom: 8px; + border-radius: 3px; + font-weight: 400; + margin-left: -6px; +} diff --git a/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.js b/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.js index c62660f2f7..dc9fb2bde7 100644 --- a/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.js +++ b/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import isHotkey from 'is-hotkey'; -import { Button, UncontrolledPopover } from 'reactstrap'; +import { Button, FormGroup, Label, UncontrolledPopover } from 'reactstrap'; import { CustomizeAddTool } from '@seafile/sf-metadata-ui-component'; import { FILTER_COLUMN_OPTIONS, @@ -11,6 +11,7 @@ import { getEventClassName, gettext } from '../../../utils'; import { getFilterByColumn } from '../../../utils/filters-utils'; import FiltersList from './widgets'; import { EVENT_BUS_TYPE } from '../../../constants'; +import BasicFilters from './basic-filters'; import './index.css'; @@ -32,6 +33,7 @@ class FilterPopover extends Component { constructor(props) { super(props); this.state = { + basicFilters: props.basicFilters, filters: getValidFilters(props.filters, props.columns), filterConjunction: props.filterConjunction || 'And', }; @@ -97,7 +99,7 @@ class FilterPopover extends Component { this.update(filters); }; - updateFilterConjunction = (conjunction) => { + modifyFilterConjunction = (conjunction) => { if (this.props.isNeedSubmit) { const isSubmitDisabled = false; this.setState({ filterConjunction: conjunction, isSubmitDisabled }); @@ -130,8 +132,8 @@ class FilterPopover extends Component { }; onSubmitFilters = () => { - const { filters, filterConjunction } = this.state; - const update = { filters, filter_conjunction: filterConjunction }; + const { filters, filterConjunction, basicFilters } = this.state; + const update = { filters, filter_conjunction: filterConjunction, basic_filters: basicFilters }; this.props.update(update); this.props.hidePopover(); }; @@ -140,9 +142,21 @@ class FilterPopover extends Component { e.stopPropagation(); }; + onBasicFilterChange = (value) => { + if (this.props.isNeedSubmit) { + const isSubmitDisabled = false; + this.setState({ basicFilters: value, isSubmitDisabled }); + return; + } + this.setState({ basicFilters: value }, () => { + const update = { filters: this.state.filters, filter_conjunction: this.state.filterConjunction, basic_filters: value }; + this.props.update(update); + }); + }; + render() { const { readOnly, target, columns, placement } = this.props; - const { filters, filterConjunction } = this.state; + const { filters, filterConjunction, basicFilters } = this.state; const canAddFilter = columns.length > 0; return ( {({ scheduleUpdate }) => (
this.dtablePopoverRef = ref} onClick={this.onPopoverInsideClick} className={this.props.filtersClassName}> - - {!readOnly && ( - this.addFilter(scheduleUpdate) : () => {}} - footerName={gettext('Add filter')} - addIconClassName="popover-add-icon" - /> - )} + + + +
+ + {!readOnly && ( + this.addFilter(scheduleUpdate) : () => {}} + footerName={gettext('Add filter')} + addIconClassName="popover-add-icon" + /> + )} +
+
{!readOnly && this.props.isNeedSubmit && (
@@ -201,6 +221,7 @@ FilterPopover.propTypes = { filters: PropTypes.array, collaborators: PropTypes.array, isPre: PropTypes.bool, + basicFilters: PropTypes.array, hidePopover: PropTypes.func, update: PropTypes.func, }; diff --git a/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/index.css b/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/index.css index e4c25b69b9..7e1a03d31a 100644 --- a/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/index.css +++ b/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/index.css @@ -1,12 +1,11 @@ .sf-metadata-filters-list { min-height: 120px; max-height: 100%; - padding: 15px; } .sf-metadata-filters-list.empty-filters-container { min-height: 80px; - padding: 16px; + padding: 0; } .sf-metadata-filters-list.empty-filters-container .empty-filters-list { @@ -230,7 +229,7 @@ .sf-metadata-filters-list .multiple-check-icon .sf-metadata-icon-check-mark, .sf-metadata-filters-list .collaborator-check-icon .sf-metadata-icon-check-mark { - fill: #798d99; + fill: #798d99 !important; font-size: 12px; } diff --git a/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/index.js b/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/index.js index d393c9b55c..79e0abf500 100644 --- a/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/index.js +++ b/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/index.js @@ -2,7 +2,6 @@ import React, { Component } from 'react'; import classnames from 'classnames'; import PropTypes from 'prop-types'; import { - VIEW_NOT_DISPLAY_COLUMN_KEYS, FILTER_COLUMN_OPTIONS, ValidateFilter, getColumnByKey, @@ -20,7 +19,7 @@ const propTypes = { filterConjunction: PropTypes.string.isRequired, updateFilter: PropTypes.func.isRequired, deleteFilter: PropTypes.func.isRequired, - updateFilterConjunction: PropTypes.func, + modifyFilterConjunction: PropTypes.func, emptyPlaceholder: PropTypes.string, value: PropTypes.object, collaborators: PropTypes.array, @@ -53,7 +52,7 @@ class FiltersList extends Component { }; updateConjunction = (filterConjunction) => { - this.props.updateFilterConjunction(filterConjunction); + this.props.modifyFilterConjunction(filterConjunction); }; getConjunctionOptions = () => { @@ -66,8 +65,7 @@ class FiltersList extends Component { getFilterColumns = () => { const { columns } = this.props; return columns.filter(column => { - let { type, key } = column; - if (VIEW_NOT_DISPLAY_COLUMN_KEYS.includes(key)) return false; + let { type } = column; return Object.prototype.hasOwnProperty.call(FILTER_COLUMN_OPTIONS, type); }); }; diff --git a/frontend/src/metadata/metadata-view/components/table/container.js b/frontend/src/metadata/metadata-view/components/table/container.js index 38f60d278d..e45dd285bb 100644 --- a/frontend/src/metadata/metadata-view/components/table/container.js +++ b/frontend/src/metadata/metadata-view/components/table/container.js @@ -100,8 +100,8 @@ const Container = () => { return { rowIdsInOrder, upperRowIds, belowRowIds }; }, [metadata]); - const modifyFilters = useCallback((filters, filterConjunction) => { - store.modifyFilters(filterConjunction, filters); + const modifyFilters = useCallback((filters, filterConjunction, basicFilters) => { + store.modifyFilters(filterConjunction, filters, basicFilters); }, [store]); const modifySorts = useCallback((sorts) => { diff --git a/frontend/src/metadata/metadata-view/components/view-toolbar/index.js b/frontend/src/metadata/metadata-view/components/view-toolbar/index.js index af33c9e939..e823588ecf 100644 --- a/frontend/src/metadata/metadata-view/components/view-toolbar/index.js +++ b/frontend/src/metadata/metadata-view/components/view-toolbar/index.js @@ -9,11 +9,6 @@ const ViewToolBar = ({ viewId }) => { const [view, setView] = useState(null); const [collaborators, setCollaborators] = useState([]); - const availableColumns = useMemo(() => { - if (!view) return []; - return view.available_columns; - }, [view]); - const viewColumns = useMemo(() => { if (!view) return []; return view.columns; @@ -23,8 +18,8 @@ const ViewToolBar = ({ viewId }) => { window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE); }, []); - const modifyFilters = useCallback((filters, filterConjunction) => { - window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.MODIFY_FILTERS, filters, filterConjunction); + const modifyFilters = useCallback((filters, filterConjunction, basicFilters) => { + window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.MODIFY_FILTERS, filters, filterConjunction, basicFilters); }, []); const modifySorts = useCallback((sorts) => { @@ -83,8 +78,9 @@ const ViewToolBar = ({ viewId }) => { target="sf-metadata-filter-popover" readOnly={readOnly} filterConjunction={view.filter_conjunction} + basicFilters={view.basic_filters} filters={view.filters} - columns={availableColumns} + columns={viewColumns} modifyFilters={modifyFilters} collaborators={collaborators} /> diff --git a/frontend/src/metadata/metadata-view/model/metadata/view.js b/frontend/src/metadata/metadata-view/model/metadata/view.js index ed4846628a..96d95449d3 100644 --- a/frontend/src/metadata/metadata-view/model/metadata/view.js +++ b/frontend/src/metadata/metadata-view/model/metadata/view.js @@ -1,4 +1,4 @@ -import { getColumnByKey, VIEW_NOT_DISPLAY_COLUMN_KEYS } from '../../_basic'; +import { getColumnByKey, VIEW_NOT_DISPLAY_COLUMN_KEYS, PRIVATE_COLUMN_KEY, FILTER_PREDICATE_TYPE } from '../../_basic'; class View { constructor(object, columns) { @@ -10,6 +10,8 @@ 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 : [{ column_key: PRIVATE_COLUMN_KEY.IS_DIR, filter_predicate: FILTER_PREDICATE_TYPE.IS, filter_term: 'all' }]; + // sort this.sorts = object.sorts || []; diff --git a/frontend/src/metadata/metadata-view/store/index.js b/frontend/src/metadata/metadata-view/store/index.js index 2023be2846..ce207ef4fe 100644 --- a/frontend/src/metadata/metadata-view/store/index.js +++ b/frontend/src/metadata/metadata-view/store/index.js @@ -344,12 +344,13 @@ class Store { this.applyOperation(operation); } - modifyFilters(filterConjunction, filters) { + modifyFilters(filterConjunction, filters, basicFilters = []) { const type = OPERATION_TYPE.MODIFY_FILTERS; const operation = this.createOperation({ type, filter_conjunction: filterConjunction, filters, + basic_filters: basicFilters, repo_id: this.repoId, view_id: this.viewId, success_callback: () => { diff --git a/frontend/src/metadata/metadata-view/store/operations/apply.js b/frontend/src/metadata/metadata-view/store/operations/apply.js index 80e843038a..09f19bc97c 100644 --- a/frontend/src/metadata/metadata-view/store/operations/apply.js +++ b/frontend/src/metadata/metadata-view/store/operations/apply.js @@ -101,9 +101,10 @@ export default function apply(data, operation) { return data; } case OPERATION_TYPE.MODIFY_FILTERS: { - const { filter_conjunction, filters } = operation; + const { filter_conjunction, filters, basic_filters } = operation; data.view.filter_conjunction = filter_conjunction; data.view.filters = filters; + data.view.basic_filters = basic_filters; return data; } case OPERATION_TYPE.MODIFY_SORTS: { diff --git a/frontend/src/metadata/metadata-view/store/operations/constants.js b/frontend/src/metadata/metadata-view/store/operations/constants.js index fea2cfa805..bcd7901ec6 100644 --- a/frontend/src/metadata/metadata-view/store/operations/constants.js +++ b/frontend/src/metadata/metadata-view/store/operations/constants.js @@ -24,7 +24,7 @@ export const OPERATION_ATTRIBUTES = { [OPERATION_TYPE.MODIFY_RECORDS]: ['repo_id', 'row_ids', 'id_row_updates', 'id_original_row_updates', 'id_old_row_data', 'id_original_old_row_data', 'is_copy_paste', 'id_obj_id'], [OPERATION_TYPE.RESTORE_RECORDS]: ['repo_id', 'rows_data', 'original_rows', 'link_infos', 'upper_row_ids'], [OPERATION_TYPE.RELOAD_RECORDS]: ['repo_id', 'row_ids'], - [OPERATION_TYPE.MODIFY_FILTERS]: ['repo_id', 'view_id', 'filter_conjunction', 'filters'], + [OPERATION_TYPE.MODIFY_FILTERS]: ['repo_id', 'view_id', 'filter_conjunction', 'filters', 'basic_filters'], [OPERATION_TYPE.MODIFY_SORTS]: ['repo_id', 'view_id', 'sorts'], [OPERATION_TYPE.MODIFY_GROUPBYS]: ['repo_id', 'view_id', 'groupbys'], [OPERATION_TYPE.MODIFY_HIDDEN_COLUMNS]: ['repo_id', 'view_id', 'hidden_columns'], diff --git a/frontend/src/metadata/metadata-view/store/server-operator.js b/frontend/src/metadata/metadata-view/store/server-operator.js index 4659b68cce..bcc0c712a7 100644 --- a/frontend/src/metadata/metadata-view/store/server-operator.js +++ b/frontend/src/metadata/metadata-view/store/server-operator.js @@ -107,8 +107,8 @@ class ServerOperator { break; } case OPERATION_TYPE.MODIFY_FILTERS: { - const { repo_id, view_id, filter_conjunction, filters } = operation; - window.sfMetadataContext.modifyView(repo_id, view_id, { filters, filter_conjunction }).then(res => { + const { repo_id, view_id, filter_conjunction, filters, basic_filters } = operation; + window.sfMetadataContext.modifyView(repo_id, view_id, { filters, filter_conjunction, basic_filters }).then(res => { callback({ operation }); }).catch(error => { callback({ error: gettext('Failed to modify filter') });