diff --git a/frontend/src/assets/icons/sort-ascending.svg b/frontend/src/assets/icons/sort-ascending.svg new file mode 100644 index 0000000000..b8721f7eb7 --- /dev/null +++ b/frontend/src/assets/icons/sort-ascending.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/sort-descending.svg b/frontend/src/assets/icons/sort-descending.svg new file mode 100644 index 0000000000..0564784841 --- /dev/null +++ b/frontend/src/assets/icons/sort-descending.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/metadata/metadata-view/_basic/constants/sort.js b/frontend/src/metadata/metadata-view/_basic/constants/sort.js index 998309d5ed..6451b49b21 100644 --- a/frontend/src/metadata/metadata-view/_basic/constants/sort.js +++ b/frontend/src/metadata/metadata-view/_basic/constants/sort.js @@ -19,6 +19,13 @@ const SORT_COLUMN_OPTIONS = [ CellType.RATE, ]; +const SHOW_DISABLED_SORT_COLUMNS = [ + CellType.LONG_TEXT, + CellType.GEOLOCATION, + CellType.CREATOR, + CellType.LAST_MODIFIER, +]; + const GALLERY_SORT_COLUMN_OPTIONS = [ CellType.CTIME, CellType.MTIME, @@ -38,6 +45,7 @@ const NUMBER_SORTER_COLUMN_TYPES = [CellType.NUMBER, CellType.RATE]; export { SORT_TYPE, SORT_COLUMN_OPTIONS, + SHOW_DISABLED_SORT_COLUMNS, GALLERY_SORT_COLUMN_OPTIONS, GALLERY_FIRST_SORT_COLUMN_OPTIONS, TEXT_SORTER_COLUMN_TYPES, diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/rate-editor/rate-item.js b/frontend/src/metadata/metadata-view/components/cell-editor/rate-editor/rate-item.js index e4007feb58..a49b2bd8d0 100644 --- a/frontend/src/metadata/metadata-view/components/cell-editor/rate-editor/rate-item.js +++ b/frontend/src/metadata/metadata-view/components/cell-editor/rate-editor/rate-item.js @@ -53,7 +53,7 @@ const RateItem = ({ {enterIndex !== -1 && ( - + {enterIndex} )} diff --git a/frontend/src/metadata/metadata-view/components/data-process-setter/sort-setter.js b/frontend/src/metadata/metadata-view/components/data-process-setter/sort-setter.js index 647b3d8515..1fae22e0c7 100644 --- a/frontend/src/metadata/metadata-view/components/data-process-setter/sort-setter.js +++ b/frontend/src/metadata/metadata-view/components/data-process-setter/sort-setter.js @@ -1,10 +1,11 @@ -import React, { useCallback, useState, useMemo } from 'react'; +import React, { useCallback, useState, useMemo, useEffect } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { IconBtn } from '@seafile/sf-metadata-ui-component'; import { getValidSorts, CommonlyUsedHotkey } from '../../_basic'; import { gettext } from '../../utils'; import { SortPopover } from '../popover'; +import { EVENT_BUS_TYPE } from '../../constants'; const SortSetter = ({ target, type, sorts: propsSorts, readOnly, columns, isNeedSubmit, wrapperClass, modifySorts }) => { const [isShowSetter, setShowSetter] = useState(false); @@ -20,6 +21,19 @@ const SortSetter = ({ target, type, sorts: propsSorts, readOnly, columns, isNeed return isNeedSubmit ? gettext('Preset sort') : gettext('Sort'); }, [isNeedSubmit, sorts]); + const displaySetter = useCallback(() => { + setShowSetter(true); + }, []); + + useEffect(() => { + const eventBus = window.sfMetadataContext.eventBus; + const unsubscribeDisplaySorts = eventBus.subscribe(EVENT_BUS_TYPE.DISPLAY_SORTS, displaySetter); + return () => { + unsubscribeDisplaySorts(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const onSetterToggle = useCallback(() => { setShowSetter(!isShowSetter); }, [isShowSetter]); diff --git a/frontend/src/metadata/metadata-view/components/popover/filter-popover/advanced-filters/filter-item/index.js b/frontend/src/metadata/metadata-view/components/popover/filter-popover/advanced-filters/filter-item/index.js index fb1f1bb0eb..32366c5ffb 100644 --- a/frontend/src/metadata/metadata-view/components/popover/filter-popover/advanced-filters/filter-item/index.js +++ b/frontend/src/metadata/metadata-view/components/popover/filter-popover/advanced-filters/filter-item/index.js @@ -534,6 +534,7 @@ class FilterItem extends React.Component { target={this.invalidFilterTip} placement='bottom' fade={false} + className="sf-metadata-tooltip" > {gettext('Invalid filter')} @@ -608,7 +609,7 @@ class FilterItem extends React.Component { {/* {showToolTip && (
- + {gettext('If there are multiple items in the cell, a random one will be chosen and be compared with the filter value.')}
diff --git a/frontend/src/metadata/metadata-view/components/popover/options-popover/options-footer.js b/frontend/src/metadata/metadata-view/components/popover/options-popover/options-footer.js index 1f57334978..b4fe2e8ce4 100644 --- a/frontend/src/metadata/metadata-view/components/popover/options-popover/options-footer.js +++ b/frontend/src/metadata/metadata-view/components/popover/options-popover/options-footer.js @@ -84,7 +84,7 @@ export default class OptionFooter extends React.Component { return (
- + {gettext('Use the import/export function to transfer options quickly. (The export is in JSON format.) By pasting cells, copied from a text column, an Excel or a TXT file, you can also add options quickly.')} { fade={false} delay={{ show: 0, hide: 0 }} modifiers={{ preventOverflow: { boundariesElement: document.body } }} + className="sf-metadata-tooltip" > {isDir ? gettext('Open folder') : gettext('Open file')} diff --git a/frontend/src/metadata/metadata-view/components/view/table/table-main/records/records-header/cell/dropdown-menu/dropdown-item.js b/frontend/src/metadata/metadata-view/components/view/table/table-main/records/records-header/cell/dropdown-menu/dropdown-item.js index 4ce2033f0d..8a93ff0553 100644 --- a/frontend/src/metadata/metadata-view/components/view/table/table-main/records/records-header/cell/dropdown-menu/dropdown-item.js +++ b/frontend/src/metadata/metadata-view/components/view/table/table-main/records/records-header/cell/dropdown-menu/dropdown-item.js @@ -41,7 +41,7 @@ const ColumnDropdownItem = ({ disabled, iconName, target, title, tip, className, {title} {isShowToolTip && ( - + {tip} )} diff --git a/frontend/src/metadata/metadata-view/components/view/table/table-main/records/records-header/cell/dropdown-menu/index.js b/frontend/src/metadata/metadata-view/components/view/table/table-main/records/records-header/cell/dropdown-menu/index.js index 8c1d81058c..e6ed75d98f 100644 --- a/frontend/src/metadata/metadata-view/components/view/table/table-main/records/records-header/cell/dropdown-menu/index.js +++ b/frontend/src/metadata/metadata-view/components/view/table/table-main/records/records-header/cell/dropdown-menu/index.js @@ -5,12 +5,15 @@ import classnames from 'classnames'; import { ModalPortal, Icon } from '@seafile/sf-metadata-ui-component'; import { isMobile, gettext } from '../../../../../../../../utils'; import DropdownItem from './dropdown-item'; -import { CellType, DEFAULT_DATE_FORMAT, getDateDisplayString } from '../../../../../../../../_basic'; +import { CellType, DEFAULT_DATE_FORMAT, SORT_COLUMN_OPTIONS, SHOW_DISABLED_SORT_COLUMNS, SORT_TYPE, + getDateDisplayString +} from '../../../../../../../../_basic'; import { RenamePopover, OptionsPopover } from '../../../../../../../popover'; +import { EVENT_BUS_TYPE } from '../../../../../../../../constants'; import './index.css'; -const HeaderDropdownMenu = ({ column, renameColumn, modifyColumnData, deleteColumn }) => { +const HeaderDropdownMenu = ({ column, view, renameColumn, modifyColumnData, deleteColumn }) => { const menuRef = createRef(); const dropdownDomRef = createRef(); const [isMenuShow, setMenuShow] = useState(false); @@ -153,11 +156,38 @@ const HeaderDropdownMenu = ({ column, renameColumn, modifyColumnData, deleteColu ); }, [today, column, isMenuShow, isSubMenuShow, onChangeDateFormat, openSubMenu]); + const modifySort = useCallback((type, event) => { + const canModifyView = window.sfMetadataContext.canModifyView(); + if (!canModifyView) { + event.stopPropagation(); + return; + } + const sorts = view.sorts.slice(0); + const { key } = column; + const sortIndex = sorts.findIndex(sort => sort.column_key === key); + const sort = sorts[sortIndex]; + const newSort = { column_key: column.key, sort_type: type }; + const eventBus = window.sfMetadataContext.eventBus; + if (!sort) { + sorts.push(newSort); + eventBus.dispatch(EVENT_BUS_TYPE.MODIFY_SORTS, sorts, true); + return; + } + if (sort && sort.sort_type !== type) { + sorts.splice(sortIndex, 1, newSort); + eventBus.dispatch(EVENT_BUS_TYPE.MODIFY_SORTS, sorts, true); + return; + } + eventBus.dispatch(EVENT_BUS_TYPE.DISPLAY_SORTS); + }, [view, column]); + const renderDropdownMenu = useCallback(() => { const { type } = column; const canModifyColumnData = window.sfMetadataContext.canModifyColumnData(column); const canDeleteColumn = window.sfMetadataContext.canDeleteColumn(column); const canRenameColumn = window.sfMetadataContext.canRenameColumn(column); + const canModifyView = window.sfMetadataContext.canModifyView(); + return (
@@ -216,6 +246,28 @@ const HeaderDropdownMenu = ({ column, renameColumn, modifyColumnData, deleteColu onChange={openRenamePopover} onMouseEnter={hideSubMenu} /> + {(SORT_COLUMN_OPTIONS.includes(column.type) || SHOW_DISABLED_SORT_COLUMNS.includes(column.type)) && ( + <> + modifySort(SORT_TYPE.UP)} + onMouseEnter={hideSubMenu} + /> + modifySort(SORT_TYPE.DOWN)} + onMouseEnter={hideSubMenu} + /> + + )} ); - }, [column, openRenamePopover, hideSubMenu, renderDateFormat, openOptionPopover, menuRef, dropdownDomRef, onDelete]); + }, [column, openRenamePopover, hideSubMenu, renderDateFormat, openOptionPopover, menuRef, dropdownDomRef, modifySort, onDelete]); return ( <> diff --git a/frontend/src/metadata/metadata-view/components/view/table/table-main/records/records-header/cell/index.js b/frontend/src/metadata/metadata-view/components/view/table/table-main/records/records-header/cell/index.js index ef9e4f300e..ddae5c9926 100644 --- a/frontend/src/metadata/metadata-view/components/view/table/table-main/records/records-header/cell/index.js +++ b/frontend/src/metadata/metadata-view/components/view/table/table-main/records/records-header/cell/index.js @@ -89,6 +89,7 @@ const Cell = ({ isHideTriangle, column, style: propsStyle, + view, renameColumn, deleteColumn, modifyColumnData, @@ -99,8 +100,8 @@ const Cell = ({ const canEditColumnInfo = useMemo(() => { if (isHideTriangle) return false; - return window.sfMetadataContext.canModifyColumn(column); - }, [isHideTriangle, column]); + return window.sfMetadataContext.canModify(); + }, [isHideTriangle]); const style = useMemo(() => { const { left, width } = column; @@ -159,7 +160,7 @@ const Cell = ({ - + {gettext(headerIconTooltip)}
@@ -169,6 +170,7 @@ const Cell = ({ {canEditColumnInfo && ( { + if (this.permission === 'r') return false; + return true; + }; + canModifyRow = (row) => { if (this.permission === 'r') return false; return true; diff --git a/frontend/src/metadata/metadata-view/hooks/metadata.js b/frontend/src/metadata/metadata-view/hooks/metadata.js index 8c50ed2a7c..2cb1de1701 100644 --- a/frontend/src/metadata/metadata-view/hooks/metadata.js +++ b/frontend/src/metadata/metadata-view/hooks/metadata.js @@ -49,8 +49,8 @@ export const MetadataProvider = ({ window.sfMetadataStore.modifyFilters(filterConjunction, filters, basicFilters); }, []); - const modifySorts = useCallback((sorts) => { - window.sfMetadataStore.modifySorts(sorts); + const modifySorts = useCallback((sorts, displaySorts = false) => { + window.sfMetadataStore.modifySorts(sorts, displaySorts); }, []); const modifyGroupbys = useCallback((groupbys) => { diff --git a/frontend/src/metadata/metadata-view/store/index.js b/frontend/src/metadata/metadata-view/store/index.js index ce207ef4fe..48cf1c9445 100644 --- a/frontend/src/metadata/metadata-view/store/index.js +++ b/frontend/src/metadata/metadata-view/store/index.js @@ -360,7 +360,7 @@ class Store { this.applyOperation(operation); } - modifySorts(sorts) { + modifySorts(sorts, displaySorts = false) { const type = OPERATION_TYPE.MODIFY_SORTS; const operation = this.createOperation({ type, @@ -369,6 +369,7 @@ class Store { view_id: this.viewId, success_callback: () => { this.context.eventBus.dispatch(EVENT_BUS_TYPE.RELOAD_DATA); + displaySorts && this.context.eventBus.dispatch(EVENT_BUS_TYPE.DISPLAY_SORTS); } }); this.applyOperation(operation);