diff --git a/frontend/src/css/grid-view.css b/frontend/src/css/grid-view.css index 10b0e5f8e3..d96ca46c35 100644 --- a/frontend/src/css/grid-view.css +++ b/frontend/src/css/grid-view.css @@ -86,6 +86,10 @@ background: #f8f8f8; } +.grid-drop-show * { + pointer-events: none; +} + .selection-box { position: absolute; background-color: rgba(0, 120, 215, 0.3); diff --git a/frontend/src/css/lib-content-view.css b/frontend/src/css/lib-content-view.css index 10ffdb8c2a..ed44774ddf 100644 --- a/frontend/src/css/lib-content-view.css +++ b/frontend/src/css/lib-content-view.css @@ -124,7 +124,24 @@ } .tree-node-drop { - background-color: #FFEFB2; + border-radius: 4px; + background-color: rgb(200, 220, 240); +} + +.tree-node-sort { + position: relative; +} + +.tree-node-sort::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 2px; + background-color: rgb(200, 220, 240); + border-radius: 2px; + z-index: 1; } .tree-node-icon { @@ -305,7 +322,7 @@ } .dir-view-path .path-item-drop { - background-color: #FFEFB2; + background-color: rgb(200, 220, 240); } .dir-view-path .path-split { diff --git a/frontend/src/metadata/api.js b/frontend/src/metadata/api.js index 3ec6478020..0aa2c5e76f 100644 --- a/frontend/src/metadata/api.js +++ b/frontend/src/metadata/api.js @@ -200,13 +200,14 @@ class MetadataManagerAPI { return this.req.delete(url, { data: params }); }; - moveView = (repoID, source_view_id, source_folder_id, target_view_id, target_folder_id) => { + moveView = (repoID, source_view_id, source_folder_id, target_view_id, target_folder_id, is_above_folder) => { const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/move-views/'; const params = { source_view_id, source_folder_id, target_view_id, target_folder_id, + is_above_folder, }; return this._sendPostRequest(url, params, { headers: { 'Content-type': 'application/json' } }); }; diff --git a/frontend/src/metadata/hooks/metadata.js b/frontend/src/metadata/hooks/metadata.js index 8bf2bd22f3..e637d0b84c 100644 --- a/frontend/src/metadata/hooks/metadata.js +++ b/frontend/src/metadata/hooks/metadata.js @@ -290,7 +290,7 @@ export const MetadataProvider = ({ repoID, currentPath, repoInfo, selectMetadata }); }, [repoID, idViewMap, addViewIntoMap]); - const moveView = useCallback(({ sourceViewId, sourceFolderId, targetViewId, targetFolderId }) => { + const moveView = useCallback(({ sourceViewId, sourceFolderId, targetViewId, targetFolderId, isAboveFolder }) => { if ( (!sourceViewId && !sourceFolderId) // must drag view or folder || (!targetViewId && !targetFolderId) // must move above to view/folder or move view into folder @@ -299,7 +299,7 @@ export const MetadataProvider = ({ repoID, currentPath, repoInfo, selectMetadata ) { return; } - metadataAPI.moveView(repoID, sourceViewId, sourceFolderId, targetViewId, targetFolderId).then(res => { + metadataAPI.moveView(repoID, sourceViewId, sourceFolderId, targetViewId, targetFolderId, isAboveFolder).then(res => { let newNavigation = [...navigation]; // remove folder/view from old position @@ -336,7 +336,7 @@ export const MetadataProvider = ({ repoID, currentPath, repoInfo, selectMetadata // insert folder/view into new position let updatedTargetNavList = newNavigation; - if (targetFolderId && sourceViewId) { + if (targetFolderId && sourceViewId && !isAboveFolder) { // move view into folder let targetFolder = newNavigation.find((folder) => folder._id === targetFolderId); if (!Array.isArray(targetFolder.children)) { @@ -347,15 +347,15 @@ export const MetadataProvider = ({ repoID, currentPath, repoInfo, selectMetadata let targetNavIndex = -1; if (targetViewId) { - // move folder/view above to view + // move folder/view above view targetNavIndex = updatedTargetNavList.findIndex((nav) => nav._id === targetViewId); - } else if (!sourceViewId && targetFolderId) { - // move folder above to folder + } else if (targetFolderId) { + // move view/folder above folder targetNavIndex = updatedTargetNavList.findIndex((nav) => nav._id === targetFolderId); } if (targetNavIndex > -1) { - updatedTargetNavList.splice(targetNavIndex, 0, movedNav); // move above to the target folder/view + updatedTargetNavList.splice(targetNavIndex, 0, movedNav); // move above target folder/view } else { updatedTargetNavList.push(movedNav); // move into navigation or folder } diff --git a/frontend/src/metadata/metadata-tree-view/folder.js b/frontend/src/metadata/metadata-tree-view/folder.js index 9c3046c16f..d9450b9777 100644 --- a/frontend/src/metadata/metadata-tree-view/folder.js +++ b/frontend/src/metadata/metadata-tree-view/folder.js @@ -29,6 +29,7 @@ const ViewsFolder = ({ const [isRenaming, setRenaming] = useState(false); const [newView, setNewView] = useState(null); const [isDropShow, setDropShow] = useState(false); + const [isSortShow, setSortShow] = useState(false); const canUpdate = useMemo(() => { if (userPerm !== 'rw' && userPerm !== 'admin') return false; @@ -40,6 +41,10 @@ const ViewsFolder = ({ return true; }, [canUpdate]); + const isValid = useCallback((event) => { + return event.dataTransfer.types.includes(METADATA_VIEWS_DRAG_DATA_KEY); + }, []); + const folderMoreOperationMenus = useMemo(() => { let menus = []; if (canUpdate) { @@ -123,6 +128,7 @@ const ViewsFolder = ({ }, [prepareAddView, folderId, deleteFolder]); const onDragStart = useCallback((event) => { + event.stopPropagation(); if (!canDrop) return false; const dragData = JSON.stringify({ type: METADATA_VIEWS_KEY, folder_id: folderId, mode: VIEWS_TYPE_FOLDER }); event.dataTransfer.effectAllowed = 'move'; @@ -131,31 +137,50 @@ const ViewsFolder = ({ }, [canDrop, folderId, setDragMode]); const onDragEnter = useCallback((event) => { - if (!canDrop) { + if (!canDrop || !isValid(event)) return false; + + const dragMode = getDragMode(); + if (!canDrop || folderId && dragMode === VIEWS_TYPE_FOLDER) { // not allowed drag folder into folder + setSortShow(true); return false; } - if (!canDrop) { - return false; + + const targetRect = event.target.getBoundingClientRect(); + const pointerPosition = event.clientY - targetRect.top; + if (pointerPosition <= 4) { + setSortShow(true); + } else { + setDropShow(true); } - setDropShow(true); - }, [canDrop]); + }, [canDrop, folderId, getDragMode, isValid]); const onDragLeave = useCallback(() => { if (!canDrop) return false; setDropShow(false); + setSortShow(false); }, [canDrop]); const onDragMove = useCallback((event) => { - if (!canDrop) return false; + if (!canDrop || !isValid(event)) return false; event.preventDefault(); event.dataTransfer.dropEffect = 'move'; - }, [canDrop]); + const targetRect = event.target.getBoundingClientRect(); + const pointerPosition = event.clientY - targetRect.top; + if (pointerPosition <= 4) { + setSortShow(true); + setDropShow(false); + } else { + setDropShow(true); + setSortShow(false); + } + }, [canDrop, isValid]); const onDrop = useCallback((event) => { if (!canDrop) return false; event.stopPropagation(); setDropShow(false); + setSortShow(false); let dragData = event.dataTransfer.getData(METADATA_VIEWS_DRAG_DATA_KEY); if (!dragData) return; @@ -166,8 +191,8 @@ const ViewsFolder = ({ if ((dragMode === VIEWS_TYPE_VIEW && !sourceViewId)) { return; } - moveView({ sourceViewId, sourceFolderId, targetFolderId: folderId }); - }, [canDrop, folderId, getDragMode, moveView]); + moveView({ sourceViewId, sourceFolderId, targetFolderId: folderId, isAboveFolder: isSortShow }); + }, [canDrop, folderId, getDragMode, moveView, isSortShow]); const onConfirmRename = useCallback((name) => { const foldersNames = getFoldersNames(); @@ -240,7 +265,7 @@ const ViewsFolder = ({ return (
{ + return event.dataTransfer.types.includes(METADATA_VIEWS_DRAG_DATA_KEY); + }, []); + const onDragStart = useCallback((event) => { - if (!canDrop) return false; + event.stopPropagation(); + if (!canDrop || freeze) return false; const dragData = JSON.stringify({ type: METADATA_VIEWS_KEY, mode: VIEWS_TYPE_VIEW, @@ -142,33 +147,33 @@ const ViewItem = ({ event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData(METADATA_VIEWS_DRAG_DATA_KEY, dragData); setDragMode(VIEWS_TYPE_VIEW); - }, [canDrop, viewId, folderId, setDragMode]); + }, [canDrop, viewId, folderId, setDragMode, freeze]); const onDragEnter = useCallback((event) => { const dragMode = getDragMode(); - if (!canDrop || folderId && dragMode === VIEWS_TYPE_FOLDER) { + if (!canDrop || folderId && dragMode === VIEWS_TYPE_FOLDER || freeze || !isValid(event)) { // not allowed drag folder into folder return false; } - setDropShow(true); - }, [canDrop, folderId, getDragMode]); + setSortShow(true); + }, [canDrop, folderId, getDragMode, freeze, isValid]); const onDragLeave = useCallback(() => { if (!canDrop) return false; - setDropShow(false); + setSortShow(false); }, [canDrop]); const onDragMove = useCallback((event) => { - if (!canDrop) return false; + if (!canDrop || freeze) return false; event.preventDefault(); event.dataTransfer.dropEffect = 'move'; - }, [canDrop]); + }, [canDrop, freeze]); const onDrop = useCallback((event) => { const dragMode = getDragMode(); - if (!canDrop || (folderId && dragMode === VIEWS_TYPE_FOLDER)) return false; + if (!canDrop || (folderId && dragMode === VIEWS_TYPE_FOLDER) || freeze) return false; event.stopPropagation(); - setDropShow(false); + setSortShow(false); let dragData = event.dataTransfer.getData(METADATA_VIEWS_DRAG_DATA_KEY); if (!dragData) return; @@ -178,7 +183,7 @@ const ViewItem = ({ return; } moveView({ sourceViewId, sourceFolderId, targetViewId: viewId, targetFolderId: folderId }); - }, [canDrop, folderId, viewId, getDragMode, moveView]); + }, [canDrop, folderId, viewId, getDragMode, moveView, freeze]); const handleSubmit = useCallback((name) => { const { isValid, message } = validateName(name, otherViewsName); @@ -194,9 +199,9 @@ const ViewItem = ({ }, [viewName, otherViewsName, renameView]); return ( -
+
.sf-metadata-result-table-cell.column::before { - left: -1px; + left: -2px; } .sf-metadata-record-header-cell .rdg-dropping-position-right > .sf-metadata-result-table-cell.column::before { - right: -1px; + right: -2px; } .sf-metadata-record-header-cell .rdg-dropping-position-none > .sf-metadata-result-table-cell.column::before { diff --git a/seahub/repo_metadata/apis.py b/seahub/repo_metadata/apis.py index 0afc16be54..69f8f18f43 100644 --- a/seahub/repo_metadata/apis.py +++ b/seahub/repo_metadata/apis.py @@ -1152,6 +1152,7 @@ class MetadataViewsMoveView(APIView): source_folder_id = request.data.get('source_folder_id') target_view_id = request.data.get('target_view_id') target_folder_id = request.data.get('target_folder_id') + is_above_folder = request.data.get('is_above_folder', False) # must drag view or folder if not source_view_id and not source_folder_id: @@ -1204,7 +1205,7 @@ class MetadataViewsMoveView(APIView): return api_error(status.HTTP_400_BAD_REQUEST, 'target_folder_id %s does not exists.' % target_folder_id) try: - results = RepoMetadataViews.objects.move_view(repo_id, source_view_id, source_folder_id, target_view_id, target_folder_id) + results = RepoMetadataViews.objects.move_view(repo_id, source_view_id, source_folder_id, target_view_id, target_folder_id, is_above_folder) if not results: return api_error(status.HTTP_400_BAD_REQUEST, 'move view or folder failed') except Exception as e: diff --git a/seahub/repo_metadata/models.py b/seahub/repo_metadata/models.py index 705937f2fe..278d6e00d7 100644 --- a/seahub/repo_metadata/models.py +++ b/seahub/repo_metadata/models.py @@ -303,7 +303,7 @@ class RepoMetadataViewsManager(models.Manager): metadata_views.save() return json.loads(metadata_views.details) - def move_view(self, repo_id, source_view_id, source_folder_id, target_view_id, target_folder_id): + def move_view(self, repo_id, source_view_id, source_folder_id, target_view_id, target_folder_id, is_above_folder): metadata_views = self.filter(repo_id=repo_id).first() view_details = json.loads(metadata_views.details) navigation = view_details.get('navigation', []) @@ -337,7 +337,7 @@ class RepoMetadataViewsManager(models.Manager): # find drop target updated_target_nav_list = navigation - if target_folder_id and source_view_id: + if target_folder_id and source_view_id and not is_above_folder: target_folder = next((folder for folder in navigation if folder.get('_id') == target_folder_id), None) if target_folder: updated_target_nav_list = target_folder.get('children', []) @@ -350,10 +350,10 @@ class RepoMetadataViewsManager(models.Manager): # drop drag source to the target position target_nav = None if target_view_id: - # move folder/view above to view + # move folder/view above view target_nav = next((nav for nav in updated_target_nav_list if nav.get('_id') == target_view_id), None) - elif not source_view_id and target_folder_id: - # move folder above to folder + elif target_folder_id: + # move folder/view above folder target_nav = next((nav for nav in updated_target_nav_list if nav.get('_id') == target_folder_id), None) insert_index = -1