1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-17 07:41:26 +00:00

Optimize/drag and drop UI (#7432)

* update the sorting effect in views tree

* update table header sort ui

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
Aries
2025-02-07 18:50:08 +08:00
committed by GitHub
parent db1ea4f07d
commit 5b5423c63f
9 changed files with 98 additions and 45 deletions

View File

@@ -86,6 +86,10 @@
background: #f8f8f8;
}
.grid-drop-show * {
pointer-events: none;
}
.selection-box {
position: absolute;
background-color: rgba(0, 120, 215, 0.3);

View File

@@ -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 {

View File

@@ -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' } });
};

View File

@@ -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
}

View File

@@ -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 (
<div className="tree-node views-folder-wrapper">
<div
className={classnames('tree-node-inner views-folder-main text-nowrap', { 'tree-node-inner-hover': highlight, 'tree-node-drop': isDropShow })}
className={classnames('tree-node-inner views-folder-main text-nowrap', { 'tree-node-inner-hover': highlight, 'tree-node-drop': isDropShow, 'tree-node-sort': isSortShow })}
type="dir"
title={folderName}
onMouseEnter={onMouseEnter}

View File

@@ -31,7 +31,7 @@ const ViewItem = ({
const { _id: viewId, name: viewName } = view;
const [highlight, setHighlight] = useState(false);
const [freeze, setFreeze] = useState(false);
const [isDropShow, setDropShow] = useState(false);
const [isSortShow, setSortShow] = useState(false);
const [isRenaming, setRenaming] = useState(false);
const { idViewMap, moveView } = useMetadata();
@@ -131,8 +131,13 @@ const ViewItem = ({
});
}, [isSelected, onUpdate, viewId, viewName]);
const isValid = useCallback((event) => {
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 (
<div className="tree-node">
<div className={classnames('tree-node', { 'tree-node-sort': isSortShow })}>
<div
className={classnames('tree-node-inner text-nowrap', { 'tree-node-inner-hover': highlight, 'tree-node-hight-light': isSelected, 'tree-node-drop': isDropShow })}
className={classnames('tree-node-inner text-nowrap', { 'tree-node-inner-hover': highlight, 'tree-node-hight-light': isSelected })}
title={viewName}
onMouseEnter={onMouseEnter}
onMouseOver={onMouseOver}

View File

@@ -26,18 +26,18 @@
position: absolute;
top: 10%;
height: 80%;
width: 1px;
background-color: #2d7ff9;
border-radius: 50%;
width: 2px;
background-color: rgb(200, 220, 240);
border-radius: 2px;
z-index: 1;
}
.sf-metadata-record-header-cell .rdg-dropping-position-left > .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 {

View File

@@ -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:

View File

@@ -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