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 (