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:
@@ -86,6 +86,10 @@
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.grid-drop-show * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.selection-box {
|
||||
position: absolute;
|
||||
background-color: rgba(0, 120, 215, 0.3);
|
||||
|
@@ -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 {
|
||||
|
@@ -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' } });
|
||||
};
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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}
|
||||
|
@@ -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}
|
||||
|
@@ -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 {
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user