diff --git a/frontend/src/components/dialog/lib-settings.js b/frontend/src/components/dialog/lib-settings.js index 28af43a8a0..be16e82d8b 100644 --- a/frontend/src/components/dialog/lib-settings.js +++ b/frontend/src/components/dialog/lib-settings.js @@ -40,7 +40,7 @@ const LibSettingsDialog = ({ repoID, currentRepoInfo, toggleDialog, tab, showMig const { encrypted, is_admin } = currentRepoInfo; const { enableMetadataManagement } = window.app.pageOptions; const { updateEnableFaceRecognition } = useMetadata(); - const { enableMetadata, updateEnableMetadata, enableTags, tagsLang, updateEnableTags, enableOCR, updateEnableOCR, enableFaceRecognition } = useMetadataStatus(); + const { enableMetadata, updateEnableMetadata, enableTags, tagsLang, updateEnableTags, enableOCR, updateEnableOCR, enableFaceRecognition, globalHiddenColumns, modifyGlobalHiddenColumns } = useMetadataStatus(); const enableHistorySetting = is_admin; // repo owner, admin of the department which the repo belongs to, and ... const enableAutoDelSetting = is_admin && enableRepoAutoDel; const enableExtendedPropertiesSetting = !encrypted && is_admin && enableMetadataManagement; @@ -204,6 +204,8 @@ const LibSettingsDialog = ({ repoID, currentRepoInfo, toggleDialog, tab, showMig diff --git a/frontend/src/hooks/metadata-status.js b/frontend/src/hooks/metadata-status.js index 592e22a18e..e1ac66b493 100644 --- a/frontend/src/hooks/metadata-status.js +++ b/frontend/src/hooks/metadata-status.js @@ -3,12 +3,15 @@ import metadataAPI from '../metadata/api'; import { Utils } from '../utils/utils'; import toaster from '../components/toast'; import Loading from '../components/loading'; +import { PRIVATE_FILE_TYPE } from '../constants'; +import { EVENT_BUS_TYPE } from '../metadata/constants'; import { enableSeafileAI } from '../utils/constants'; + // This hook provides content related to seahub interaction, such as whether to enable extended attributes const MetadataStatusContext = React.createContext(null); -export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, statusCallback, children }) => { +export const MetadataStatusProvider = ({ repoID, repoInfo, currentPath, hideMetadataView, statusCallback, children }) => { const enableMetadataManagement = useMemo(() => { if (repoInfo?.encrypted) return false; return window.app.pageOptions.enableMetadataManagement; @@ -23,6 +26,7 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, sta const [detailsSettings, setDetailsSettings] = useState({}); const [isBeingBuilt, setIsBeingBuilt] = useState(false); const [enableFaceRecognition, setEnableFaceRecognition] = useState(false); + const [globalHiddenColumns, setGlobalHiddenColumns] = useState([]); const cancelMetadataURL = useCallback((isSetRoot = false) => { // If attribute extension is turned off, unmark the URL @@ -57,6 +61,7 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, sta details_settings: detailsSettings, ocr_enabled: enableOCR, face_recognition_enabled: enableFaceRecognition, + global_hidden_columns: globalHiddenColumns, } = res.data; if (!enableMetadata) { cancelMetadataURL(); @@ -67,6 +72,10 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, sta setEnableOCR(enableSeafileAI && enableOCR); setEnableFaceRecognition(enableSeafileAI && enableFaceRecognition); setEnableMetadata(enableMetadata); + const parsedGlobalHiddenColumns = typeof globalHiddenColumns === 'string' + ? JSON.parse(globalHiddenColumns) + : (globalHiddenColumns || []); + setGlobalHiddenColumns(parsedGlobalHiddenColumns); setLoading(false); }).catch(error => { const errorMsg = Utils.getErrorMsg(error, true); @@ -126,6 +135,19 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, sta }); }, [repoID, detailsSettings]); + const modifyGlobalHiddenColumns = useCallback((columns) => { + metadataAPI.modifyGlobalHiddenColumns(repoID, columns).then(res => { + setGlobalHiddenColumns(columns); + const isView = currentPath.startsWith('/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES); + if (isView) { + window.sfMetadataContext && window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_TABLE_CHANGED); + } + }).catch(error => { + toaster.danger(Utils.getErrorMsg(error)); + setGlobalHiddenColumns(globalHiddenColumns); + }); + }, [repoID, currentPath, globalHiddenColumns]); + if (isLoading) { return (
@@ -151,6 +173,8 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, sta updateEnableOCR, enableFaceRecognition, updateEnableFaceRecognition, + globalHiddenColumns, + modifyGlobalHiddenColumns, }} > {!isLoading && ( diff --git a/frontend/src/metadata/api.js b/frontend/src/metadata/api.js index 190f19e840..8ec74bc61b 100644 --- a/frontend/src/metadata/api.js +++ b/frontend/src/metadata/api.js @@ -71,6 +71,12 @@ class MetadataManagerAPI { return this.req.put(url, data); } + modifyGlobalHiddenColumns(repoID, globalHiddenColumns) { + const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/global-hidden-columns/'; + const data = { global_hidden_columns: globalHiddenColumns }; + return this.req.put(url, data); + } + getMetadata(repoID, params) { const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/records/'; return this.req.get(url, { params: params }); diff --git a/frontend/src/metadata/components/data-process-setter/hide-column-setter.js b/frontend/src/metadata/components/data-process-setter/hide-column-setter.js index 955c29bae3..1107be54d0 100644 --- a/frontend/src/metadata/components/data-process-setter/hide-column-setter.js +++ b/frontend/src/metadata/components/data-process-setter/hide-column-setter.js @@ -6,11 +6,14 @@ import { HideColumnPopover } from '../popover'; import { gettext } from '../../../utils/constants'; import { isEnter, isSpace } from '../../../utils/hotkey'; import { TABLE_NOT_DISPLAY_COLUMN_KEYS } from '../../constants'; +import { useMetadataStatus } from '../../../hooks'; const HideColumnSetter = ({ readOnly, columns, wrapperClass, target, hiddenColumns, modifyHiddenColumns, modifyColumnOrder }) => { const [isShowSetter, setShowSetter] = useState(false); - const validColumns = useMemo(() => columns.filter(column => !TABLE_NOT_DISPLAY_COLUMN_KEYS.includes(column.key)), [columns]); + const { globalHiddenColumns } = useMetadataStatus(); + + const validColumns = useMemo(() => columns.filter(column => !TABLE_NOT_DISPLAY_COLUMN_KEYS.includes(column.key) && !globalHiddenColumns.includes(column.key)), [columns, globalHiddenColumns]); const validHiddenColumns = useMemo(() => { return hiddenColumns.filter(key => columns.find(column => column.key === key)); diff --git a/frontend/src/metadata/components/dialog/metadata-status-manage-dialog/index.css b/frontend/src/metadata/components/dialog/metadata-status-manage-dialog/index.css index c75135e7ac..34e82def93 100644 --- a/frontend/src/metadata/components/dialog/metadata-status-manage-dialog/index.css +++ b/frontend/src/metadata/components/dialog/metadata-status-manage-dialog/index.css @@ -14,3 +14,29 @@ .metadata-status-management-dialog .tip { font-size: 12px; } + +.metadata-status-management-dialog .metadata-status-hide-columns-container .tip { + margin: 4px 0; +} + +.metadata-status-management-dialog .metadata-status-hide-properties-button { + width: fit-content; + display: flex; + flex-direction: row; + align-items: center; + padding: 4px 8px; + margin-top: 4px; + color: #666; + background-color: #f5f5f5; + border-radius: 3px; + cursor: pointer; +} + +.metadata-status-management-dialog .metadata-status-hide-properties-button.disabled { + cursor: not-allowed; +} + +.metadata-status-management-dialog .metadata-status-hide-properties-button:not(.disabled):hover { + color: #212529; + background-color: #e9e9e9; +} diff --git a/frontend/src/metadata/components/dialog/metadata-status-manage-dialog/index.js b/frontend/src/metadata/components/dialog/metadata-status-manage-dialog/index.js index 138c06bbc9..8bc426c282 100644 --- a/frontend/src/metadata/components/dialog/metadata-status-manage-dialog/index.js +++ b/frontend/src/metadata/components/dialog/metadata-status-manage-dialog/index.js @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { ModalBody, ModalFooter, Button } from 'reactstrap'; @@ -8,13 +8,78 @@ import TurnOffConfirmDialog from '../turn-off-confirm-dialog'; import metadataAPI from '../../../api'; import { Utils } from '../../../../utils/utils'; import { gettext } from '../../../../utils/constants'; +import Icon from '../../../../components/icon'; +import { HideColumnPopover } from '../../popover'; +import { CellType, PRIVATE_COLUMN_KEY } from '../../../constants'; +import { getColumnDisplayName } from '../../../utils/column'; import './index.css'; -const MetadataStatusManagementDialog = ({ value: oldValue, repoID, toggleDialog: toggle, submit }) => { +const GLOBAL_CONFIGURABLE_COLUMNS = [ + { + key: PRIVATE_COLUMN_KEY.PARENT_DIR, + name: '_parent_dir', + type: CellType.TEXT, + }, + { + key: PRIVATE_COLUMN_KEY.FILE_CREATOR, + name: '_file_creator', + type: CellType.TEXT, + }, + { + key: PRIVATE_COLUMN_KEY.FILE_CTIME, + name: '_file_ctime', + type: CellType.DATE, + }, + { + key: PRIVATE_COLUMN_KEY.FILE_MODIFIER, + name: '_file_modifier', + type: CellType.TEXT, + }, + { + key: PRIVATE_COLUMN_KEY.FILE_MTIME, + name: '_file_mtime', + type: CellType.DATE, + }, + { + key: PRIVATE_COLUMN_KEY.FILE_TYPE, + name: '_file_type', + type: CellType.SINGLE_SELECT, + }, + { + key: PRIVATE_COLUMN_KEY.LOCATION, + name: '_location', + type: CellType.GEOLOCATION, + }, + { + key: PRIVATE_COLUMN_KEY.SIZE, + name: '_size', + type: CellType.NUMBER, + }, + { + key: PRIVATE_COLUMN_KEY.FILE_DESCRIPTION, + name: '_description', + type: CellType.LONG_TEXT, + } +]; + +const MetadataStatusManagementDialog = ({ value: oldValue, repoID, hiddenColumns: oldHiddenColumns, toggleDialog: toggle, submit, modifyHiddenColumns }) => { const [value, setValue] = useState(oldValue); const [submitting, setSubmitting] = useState(false); const [showTurnOffConfirmDialog, setShowTurnOffConfirmDialog] = useState(false); + const [isHiddenColumnsVisible, setHiddenColumnsVisible] = useState(false); + const [hiddenColumns, setHiddenColumns] = useState(oldHiddenColumns || []); + + const hideColumnBtnRef = useRef(null); + + const columns = useMemo(() => { + return GLOBAL_CONFIGURABLE_COLUMNS.map(column => { + return { + ...column, + name: getColumnDisplayName(column.key, column.name), + }; + }); + }, []); const onToggle = useCallback(() => { if (submitting) return; @@ -59,6 +124,31 @@ const MetadataStatusManagementDialog = ({ value: oldValue, repoID, toggleDialog: setValue(nextValue); }, [value]); + const hidePopover = useCallback(() => { + setHiddenColumnsVisible(false); + }, []); + + const showPopover = useCallback(() => { + setHiddenColumnsVisible(true); + }, []); + + const onClickHideColumns = useCallback(() => { + if (!oldValue) return; + isHiddenColumnsVisible ? hidePopover() : showPopover(); + }, [oldValue, isHiddenColumnsVisible, hidePopover, showPopover]); + + const onHiddenColumnsChange = useCallback((columns) => { + setHiddenColumns(columns); + }, []); + + useEffect(() => { + if (!isHiddenColumnsVisible && (oldHiddenColumns !== hiddenColumns)) { + modifyHiddenColumns(hiddenColumns); + } + }, [isHiddenColumnsVisible, oldHiddenColumns, hiddenColumns, modifyHiddenColumns]); + + const count = hiddenColumns.length; + const text = gettext('Hide properties'); return ( <> {!showTurnOffConfirmDialog && ( @@ -76,6 +166,35 @@ const MetadataStatusManagementDialog = ({ value: oldValue, repoID, toggleDialog:

{gettext('After enable extended properties for files, you can add different properties to files, like collaborators, file expiring time, file description. You can also create different views for files based extended properties.')}

+ {value && ( +
+ {gettext('Global hidden properties')} +

+ {gettext('Global hidden properties will not be displayed in all views.')} +

+
+ + {count > 0 ? `${count} ${gettext('Hidden properties')}` : text } +
+ {isHiddenColumnsVisible && ( + + )} +
+ )} diff --git a/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/hide-column.js b/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/hide-column.js index 2ca34ed1b4..2da3b39470 100644 --- a/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/hide-column.js +++ b/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/hide-column.js @@ -15,6 +15,7 @@ const HideColumnItem = ({ dragOverColumnKey, updateDraggingKey, updateDragOverKey, + canReorder = true, onChange, onMove, }) => { @@ -83,7 +84,7 @@ const HideColumnItem = ({ onDragLeave={onDragLeave} onDragEnd={onDragEnd} > - {!readOnly && ( + {!readOnly && canReorder && (
@@ -114,6 +115,7 @@ HideColumnItem.propTypes = { dragOverColumnKey: PropTypes.string, updateDraggingKey: PropTypes.func, updateDragOverKey: PropTypes.func, + canReorder: PropTypes.bool, onChange: PropTypes.func.isRequired, onMove: PropTypes.func, }; diff --git a/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/index.js b/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/index.js index c4609101a0..9a67184086 100644 --- a/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/index.js +++ b/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/index.js @@ -4,7 +4,7 @@ import classnames from 'classnames'; import HideColumn from './hide-column'; import { gettext } from '../../../../../utils/constants'; -const HiddenColumns = ({ readOnly, columns, hiddenColumns, onChange, modifyColumnOrder }) => { +const HiddenColumns = ({ readOnly, columns, hiddenColumns, onChange, canReorder, modifyColumnOrder }) => { const [draggingColumnKey, setDraggingCellKey] = useState(null); const [dragOverColumnKey, setDragOverCellKey] = useState(null); @@ -39,6 +39,7 @@ const HiddenColumns = ({ readOnly, columns, hiddenColumns, onChange, modifyColum draggingColumnKey={draggingColumnKey} draggingColumnIndex={draggingColumnIndex} dragOverColumnKey={dragOverColumnKey} + canReorder={canReorder} onChange={onChange} onMove={modifyColumnOrder} updateDraggingKey={updateDraggingKey} @@ -55,6 +56,7 @@ HiddenColumns.propTypes = { hiddenColumns: PropTypes.array, columns: PropTypes.array, onChange: PropTypes.func, + canReorder: PropTypes.bool, modifyColumnOrder: PropTypes.func, }; diff --git a/frontend/src/metadata/components/popover/hidden-column-popover/index.js b/frontend/src/metadata/components/popover/hidden-column-popover/index.js index c2ede85e91..d802205138 100644 --- a/frontend/src/metadata/components/popover/hidden-column-popover/index.js +++ b/frontend/src/metadata/components/popover/hidden-column-popover/index.js @@ -10,7 +10,7 @@ import { getEventClassName } from '../../../../utils/dom'; import './index.css'; -const HideColumnPopover = ({ hidePopover, onChange, readOnly, target, placement, columns, hiddenColumns: oldHiddenColumns, modifyColumnOrder }) => { +const HideColumnPopover = ({ hidePopover, onChange, readOnly, target, placement, columns, hiddenColumns: oldHiddenColumns, canReorder, modifyColumnOrder }) => { const [searchValue, setSearchValue] = useState(''); const [hiddenColumns, setHiddenColumns] = useState(oldHiddenColumns); const displayColumns = useMemo(() => { @@ -105,7 +105,7 @@ const HideColumnPopover = ({ hidePopover, onChange, readOnly, target, placement,
- + {!readOnly && !searchValue && (
{gettext('Hide all')}
@@ -126,6 +126,7 @@ HideColumnPopover.propTypes = { columns: PropTypes.array.isRequired, onChange: PropTypes.func.isRequired, hidePopover: PropTypes.func.isRequired, + canReorder: PropTypes.bool, modifyColumnOrder: PropTypes.func, }; diff --git a/frontend/src/metadata/components/view-toolbar/gallery-view-toolbar/index.js b/frontend/src/metadata/components/view-toolbar/gallery-view-toolbar/index.js index cc2af08084..dd4e51e5a2 100644 --- a/frontend/src/metadata/components/view-toolbar/gallery-view-toolbar/index.js +++ b/frontend/src/metadata/components/view-toolbar/gallery-view-toolbar/index.js @@ -3,16 +3,18 @@ import PropTypes from 'prop-types'; import { GalleryGroupBySetter, FilterSetter, SortSetter } from '../../data-process-setter'; import { PRIVATE_COLUMN_KEY } from '../../../constants'; import { gettext } from '../../../../utils/constants'; +import { useMetadataStatus } from '../../../../hooks'; const GalleryViewToolbar = ({ readOnly, isCustomPermission, view, collaborators, modifyFilters, modifySorts, onToggleDetail, }) => { + const { globalHiddenColumns } = useMetadataStatus(); const viewType = useMemo(() => view.type, [view]); const viewColumns = useMemo(() => { if (!view) return []; - return view.columns; - }, [view]); + return view.columns.filter(column => !globalHiddenColumns.includes(column.key)); + }, [view, globalHiddenColumns]); const filterColumns = useMemo(() => { return viewColumns.filter(c => c.key !== PRIVATE_COLUMN_KEY.FILE_TYPE); diff --git a/frontend/src/metadata/components/view-toolbar/table-view-toolbar/index.js b/frontend/src/metadata/components/view-toolbar/table-view-toolbar/index.js index f1dfdff109..1c47cca546 100644 --- a/frontend/src/metadata/components/view-toolbar/table-view-toolbar/index.js +++ b/frontend/src/metadata/components/view-toolbar/table-view-toolbar/index.js @@ -2,16 +2,18 @@ import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; import { FilterSetter, GroupbySetter, SortSetter, HideColumnSetter } from '../../data-process-setter'; import { PRIVATE_COLUMN_KEY } from '../../../constants'; +import { useMetadataStatus } from '../../../../hooks'; const TableViewToolbar = ({ readOnly, view, collaborators, modifyFilters, modifySorts, modifyGroupbys, modifyHiddenColumns, modifyColumnOrder }) => { + const { globalHiddenColumns } = useMetadataStatus(); const viewType = useMemo(() => view.type, [view]); const viewColumns = useMemo(() => { if (!view) return []; - return view.columns; - }, [view]); + return view.columns.filter(column => !globalHiddenColumns.includes(column.key)); + }, [view, globalHiddenColumns]); const filterColumns = useMemo(() => { return viewColumns.filter(c => c.key !== PRIVATE_COLUMN_KEY.FILE_TYPE); diff --git a/frontend/src/metadata/utils/group/index.js b/frontend/src/metadata/utils/group/index.js index 21b7175b50..bfbcabd857 100644 --- a/frontend/src/metadata/utils/group/index.js +++ b/frontend/src/metadata/utils/group/index.js @@ -2,6 +2,7 @@ import { isDateColumn } from '../column'; import { CellType, DISPLAY_GROUP_DATE_GRANULARITY, GROUP_DATE_GRANULARITY, SORT_TYPE, SUPPORT_GROUP_COLUMN_TYPES, GROUPBY_DATE_GRANULARITY_LIST, GROUP_GEOLOCATION_GRANULARITY, DISPLAY_GROUP_GEOLOCATION_GRANULARITY, + PRIVATE_COLUMN_KEY, } from '../../constants'; const GROUPBY_GEOLOCATION_GRANULARITY_LIST = [ @@ -84,7 +85,7 @@ export const getGroupbyGranularityByColumn = (column) => { export const generateDefaultGroupby = (columns) => { const dateColumn = columns.find(column => column.type === CellType.DATE) || columns.find(column => isDateColumn(column)); - let groupby = { column_key: null, sort_type: SORT_TYPE.UP }; + let groupby = { column_key: columns[0].key, sort_type: SORT_TYPE.UP }; if (dateColumn) { groupby.column_key = dateColumn.key; groupby.count_type = getDefaultCountType(dateColumn); diff --git a/frontend/src/metadata/views/kanban/settings/index.js b/frontend/src/metadata/views/kanban/settings/index.js index bd92ac264b..777e1e7c21 100644 --- a/frontend/src/metadata/views/kanban/settings/index.js +++ b/frontend/src/metadata/views/kanban/settings/index.js @@ -8,6 +8,7 @@ import FieldDisplaySettings from '../../../components/data-process-setter/field- import { gettext } from '../../../../utils/constants'; import { CellType, COLUMNS_ICON_CONFIG, KANBAN_SETTINGS_KEYS } from '../../../constants'; import { getColumnByKey } from '../../../utils/column'; +import { useMetadataStatus } from '../../../../hooks'; import './index.css'; @@ -18,8 +19,10 @@ const Settings = ({ modifySettings, onClose }) => { + const { globalHiddenColumns } = useMetadataStatus(); + const validColumns = useMemo(() => columns.filter(column => !globalHiddenColumns.includes(column.key)), [columns, globalHiddenColumns]); const groupByColumnOptions = useMemo(() => { - return columns + return validColumns .filter(col => col.type === CellType.SINGLE_SELECT || col.type === CellType.COLLABORATOR) .map(col => ({ value: col.key, @@ -30,34 +33,35 @@ const Settings = ({ ) })); - }, [columns]); + }, [validColumns]); const titleColumnOptions = useMemo(() => { - return columns.map(col => ({ - value: col.key, - label: ( - <> - - {col.name} - - ) - })); - }, [columns]); + return validColumns + .map(col => ({ + value: col.key, + label: ( + <> + + {col.name} + + ) + })); + }, [validColumns]); const displayColumns = useMemo(() => { - const displayColumnsConfig = settings[KANBAN_SETTINGS_KEYS.COLUMNS]; + const displayColumnsConfig = settings[KANBAN_SETTINGS_KEYS.COLUMNS].filter(column => !globalHiddenColumns.includes(column.key)); const titleColumnKey = settings[KANBAN_SETTINGS_KEYS.TITLE_COLUMN_KEY]; - const validColumns = columns.filter(item => item.key !== titleColumnKey); - if (!displayColumnsConfig) return validColumns.map(column => ({ ...column, shown: false })); + const filteredColumns = validColumns.filter(item => item.key !== titleColumnKey); + if (!displayColumnsConfig) return filteredColumns.map(column => ({ ...column, shown: false })); const validDisplayColumnsConfig = displayColumnsConfig.map(columnConfig => { const column = columnsMap[columnConfig.key]; if (column) return { ...column, shown: columnConfig.shown }; return null; }).filter(column => column && column.key !== titleColumnKey); - const addedColumns = validColumns + const addedColumns = filteredColumns .filter(column => !getColumnByKey(validDisplayColumnsConfig, column.key)) .map(column => ({ ...column, shown: false })); return [...validDisplayColumnsConfig, ...addedColumns]; - }, [columns, columnsMap, settings]); + }, [validColumns, columnsMap, settings, globalHiddenColumns]); const displayColumnsConfig = useMemo(() => displayColumns.map(column => ({ key: column.key, shown: column.shown })), [displayColumns]); diff --git a/frontend/src/metadata/views/table/table-main/index.js b/frontend/src/metadata/views/table/table-main/index.js index 820262eba8..dbd28178b5 100644 --- a/frontend/src/metadata/views/table/table-main/index.js +++ b/frontend/src/metadata/views/table/table-main/index.js @@ -4,6 +4,7 @@ import classnames from 'classnames'; import Records from './records'; import GridUtils from '../utils/grid-utils'; import { GROUP_VIEW_OFFSET, TABLE_NOT_DISPLAY_COLUMN_KEYS } from '../../../constants'; +import { useMetadataStatus } from '../../../../hooks'; import './index.css'; @@ -12,6 +13,7 @@ const TableMain = ({ modifyColumnData, updateFileTags, ...props }) => { + const { globalHiddenColumns } = useMetadataStatus(); const gridUtils = useMemo(() => { return new GridUtils(metadata, { @@ -35,8 +37,8 @@ const TableMain = ({ const columns = useMemo(() => { const { columns, hidden_columns } = metadata.view; - return columns.filter(column => !hidden_columns.includes(column.key) && !TABLE_NOT_DISPLAY_COLUMN_KEYS.includes(column.key)); - }, [metadata]); + return columns.filter(column => !globalHiddenColumns.includes(column.key) && !hidden_columns.includes(column.key) && !TABLE_NOT_DISPLAY_COLUMN_KEYS.includes(column.key)); + }, [metadata, globalHiddenColumns]); const getCopiedRecordsAndColumnsFromRange = useCallback(({ type, copied, isGroupView }) => { return gridUtils.getCopiedContent({ type, copied, isGroupView, columns }); diff --git a/frontend/src/metadata/views/table/table-main/records-header/cell/index.js b/frontend/src/metadata/views/table/table-main/records-header/cell/index.js index 3822561816..8bbeeb2ce3 100644 --- a/frontend/src/metadata/views/table/table-main/records-header/cell/index.js +++ b/frontend/src/metadata/views/table/table-main/records-header/cell/index.js @@ -48,7 +48,8 @@ const Cell = ({ value.left = left + groupOffsetLeft; } return value; - }, [frozen, groupOffsetLeft, column, height, propsStyle]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [frozen, groupOffsetLeft, column, column.left, height, propsStyle]); const getWidthFromMouseEvent = useCallback((e) => { let right = e.pageX || (e.touches && e.touches[0] && e.touches[0].pageX) || (e.changedTouches && e.changedTouches[e.changedTouches.length - 1].pageX); diff --git a/frontend/src/metadata/views/table/table-main/records-header/insert-column/index.js b/frontend/src/metadata/views/table/table-main/records-header/insert-column/index.js index fd7d0cacf1..05c5e2a1ba 100644 --- a/frontend/src/metadata/views/table/table-main/records-header/insert-column/index.js +++ b/frontend/src/metadata/views/table/table-main/records-header/insert-column/index.js @@ -24,7 +24,8 @@ const InsertColumn = ({ lastColumn, height, groupOffsetLeft, insertColumn: inser left: lastColumn.left + lastColumn.width + groupOffsetLeft, position: 'absolute', }; - }, [lastColumn, height, groupOffsetLeft]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [lastColumn, lastColumn.left, height, groupOffsetLeft]); const toggleAddColumn = useCallback(() => { setColumnMenuOpen(!isColumnMenuOpen); diff --git a/frontend/src/metadata/views/table/table-main/records/record/cell/index.js b/frontend/src/metadata/views/table/table-main/records/record/cell/index.js index d48ebc0709..7ca223b2a5 100644 --- a/frontend/src/metadata/views/table/table-main/records/record/cell/index.js +++ b/frontend/src/metadata/views/table/table-main/records/record/cell/index.js @@ -61,7 +61,8 @@ const Cell = React.memo(({ value['backgroundColor'] = bgColor; } return value; - }, [frozen, height, column, bgColor]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [frozen, height, column, column.left, bgColor]); const onCellClick = useCallback((event) => { const cell = { idx: column.idx, groupRecordIndex, rowIdx: recordIndex }; diff --git a/frontend/src/pages/lib-content-view/lib-content-view.js b/frontend/src/pages/lib-content-view/lib-content-view.js index 08b0a9c545..8fda49e297 100644 --- a/frontend/src/pages/lib-content-view/lib-content-view.js +++ b/frontend/src/pages/lib-content-view/lib-content-view.js @@ -2363,8 +2363,8 @@ class LibContentView extends React.Component { return ( - - + +
diff --git a/seahub/repo_metadata/apis.py b/seahub/repo_metadata/apis.py index a5b84e4545..8381f4913d 100644 --- a/seahub/repo_metadata/apis.py +++ b/seahub/repo_metadata/apis.py @@ -57,12 +57,14 @@ class MetadataManage(APIView): details_settings = '{}' is_ocr_enabled = False face_recognition_enabled = False + global_hidden_columns = [] try: record = RepoMetadata.objects.filter(repo_id=repo_id).first() if record and record.enabled: is_enabled = True details_settings = record.details_settings + global_hidden_columns = json.loads(record.global_hidden_columns) if record.global_hidden_columns else [] if not details_settings: details_settings = '{}' if record.tags_enabled: @@ -72,6 +74,8 @@ class MetadataManage(APIView): is_ocr_enabled = True if record.face_recognition_enabled: face_recognition_enabled = True + if not global_hidden_columns: + global_hidden_columns = [] except Exception as e: logger.error(e) error_msg = 'Internal Server Error' @@ -84,6 +88,7 @@ class MetadataManage(APIView): 'face_recognition_enabled': face_recognition_enabled, 'tags_lang': tags_lang, 'details_settings': details_settings, + 'global_hidden_columns': global_hidden_columns, }) def put(self, request, repo_id): @@ -221,6 +226,36 @@ class MetadataDetailsSettingsView(APIView): return Response({'success': True}) +class MetadataGlobalHiddenColumnsView(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated,) + throttle_classes = (UserRateThrottle,) + + def put(self, request, repo_id): + global_hidden_columns = request.data.get('global_hidden_columns', []) + if not isinstance(global_hidden_columns, list): + error_msg = 'global_hidden_columns must be a list.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + metadata = RepoMetadata.objects.filter(repo_id=repo_id).first() + if not metadata or not metadata.enabled: + error_msg = f'The metadata module is disabled for repo {repo_id}.' + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + + if not is_repo_admin(request.user.username, repo_id): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + try: + metadata.global_hidden_columns = json.dumps(global_hidden_columns) + metadata.save() + except Exception as e: + logger.exception(e) + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error') + return Response({'success': True}) + + class MetadataOCRManageView(APIView): authentication_classes = (TokenAuthentication, SessionAuthentication) permission_classes = (IsAuthenticated, ) diff --git a/seahub/repo_metadata/models.py b/seahub/repo_metadata/models.py index 84fa2b9fab..cb8e7dfb30 100644 --- a/seahub/repo_metadata/models.py +++ b/seahub/repo_metadata/models.py @@ -72,6 +72,7 @@ class RepoMetadata(models.Model): last_face_cluster_time = models.DateTimeField(db_index=True, blank=True, null=True) details_settings = models.TextField() ocr_enabled = models.BooleanField(db_index=True) + global_hidden_columns = models.TextField() objects = RepoMetadataManager() diff --git a/seahub/repo_metadata/urls.py b/seahub/repo_metadata/urls.py index 979802026e..05488ac1d2 100644 --- a/seahub/repo_metadata/urls.py +++ b/seahub/repo_metadata/urls.py @@ -3,7 +3,7 @@ from .apis import MetadataRecognizeFaces, MetadataRecords, MetadataManage, Metad MetadataFolders, MetadataViews, MetadataViewsMoveView, MetadataViewsDetailView, MetadataViewsDuplicateView, FacesRecords, \ FaceRecognitionManage, FacesRecord, MetadataExtractFileDetails, PeoplePhotos, MetadataTagsStatusManage, MetadataTags, \ MetadataTagsLinks, MetadataFileTags, MetadataTagFiles, MetadataMergeTags, MetadataTagsFiles, MetadataDetailsSettingsView, \ - MetadataOCRManageView, PeopleCoverPhoto, MetadataMigrateTags, MetadataExportTags, MetadataImportTags + MetadataOCRManageView, PeopleCoverPhoto, MetadataMigrateTags, MetadataExportTags, MetadataImportTags, MetadataGlobalHiddenColumnsView urlpatterns = [ re_path(r'^$', MetadataManage.as_view(), name='api-v2.1-metadata'), @@ -32,6 +32,9 @@ urlpatterns = [ # details settings re_path(r'^details-settings/', MetadataDetailsSettingsView.as_view(), name='api-v2.1-metadata-details-settings'), + # global hidden columns + re_path(r'^global-hidden-columns/$', MetadataGlobalHiddenColumnsView.as_view(), name='api-v2.1-metadata-global-hidden-columns'), + # ocr re_path(r'^ocr/', MetadataOCRManageView.as_view(), name='api-v2.1-metadata-ocr'),