mirror of
https://github.com/supabase/supabase.git
synced 2026-10-14 05:39:11 +03:00
expand drag and drop
This commit is contained in:
1 parent
561ca8d78a
commit
d0d22edce9
3 files changed
+150
-30
No files matched your search
@@ -220,6 +220,18 @@ const FileExplorerColumn = ({
|
||||
|
||||
// We're over column background (not over a specific item)
|
||||
if (isInternalFileDrag) {
|
||||
// Check if this is a drag from the same column (same location)
|
||||
try {
|
||||
const draggedItem = JSON.parse(event.dataTransfer.getData('application/json'))
|
||||
if (draggedItem && draggedItem.columnIndex === index) {
|
||||
// Same column drag - don't highlight or allow drop
|
||||
console.log('Same column drag detected, ignoring')
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
// Can't parse data during dragover, continue with normal logic
|
||||
}
|
||||
|
||||
// Internal file drag over column background - show blue ring to indicate valid drop zone
|
||||
event.preventDefault()
|
||||
if (event.type === 'dragover') {
|
||||
@@ -241,21 +253,10 @@ const FileExplorerColumn = ({
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// External file drag - only show overlay for empty columns
|
||||
const hasVisibleItems =
|
||||
column.items.length > 0 && column.status !== STORAGE_ROW_STATUS.LOADING
|
||||
|
||||
if (!hasVisibleItems) {
|
||||
// Column is empty - show overlay for external file uploads
|
||||
event.preventDefault()
|
||||
if (event.type === 'dragover' && !isDraggedOver) {
|
||||
setIsDraggedOver(true)
|
||||
}
|
||||
} else {
|
||||
// Column has items - don't show overlay for external files
|
||||
if (isDraggedOver) {
|
||||
setIsDraggedOver(false)
|
||||
}
|
||||
// External file drag - show overlay for file uploads (both empty and non-empty columns)
|
||||
event.preventDefault()
|
||||
if (event.type === 'dragover' && !isDraggedOver) {
|
||||
setIsDraggedOver(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -287,6 +288,12 @@ const FileExplorerColumn = ({
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this is a drop to the same column (same location)
|
||||
if (draggedItem.columnIndex === index) {
|
||||
console.log('Same column drop detected, ignoring move operation')
|
||||
return
|
||||
}
|
||||
|
||||
// This is a column background drop
|
||||
// Use the pre-calculated path from drag over
|
||||
const targetDirectory = moveToPath
|
||||
@@ -311,12 +318,8 @@ const FileExplorerColumn = ({
|
||||
target.closest('[data-item-type]') || target.closest('.storage-row')
|
||||
)
|
||||
|
||||
// Set the selected items for moving (required by moveFiles)
|
||||
snap.setSelectedItemsToMove([draggedItem])
|
||||
|
||||
// Trigger the move operation directly without opening the modal
|
||||
// moveFiles expects the directory path, not the full file path
|
||||
snap.moveFiles(targetDirectory)
|
||||
// Use the new drag & drop function that doesn't interfere with the modal
|
||||
snap.moveFilesDragAndDrop([draggedItem], targetDirectory)
|
||||
|
||||
// Reset drag states after successful drop
|
||||
setIsDraggedOver(false)
|
||||
@@ -350,7 +353,7 @@ const FileExplorerColumn = ({
|
||||
fullWidth ? 'w-full' : 'w-64 border-r border-overlay',
|
||||
snap.view === STORAGE_VIEWS.LIST && 'h-full',
|
||||
'hide-scrollbar relative flex flex-shrink-0 flex-col overflow-auto',
|
||||
(isDraggedOver || isInternalDragOver) && 'ring-2 ring-blue-500 ring-opacity-50'
|
||||
(isDraggedOver || isInternalDragOver) && 'bg-selection/10'
|
||||
)}
|
||||
onContextMenu={displayMenu}
|
||||
onDragOver={onDragOver}
|
||||
@@ -433,6 +436,67 @@ const FileExplorerColumn = ({
|
||||
onLoadNextPage={() => onColumnLoadMore(index, column)}
|
||||
/>
|
||||
|
||||
{/* List View Background Drag & Drop (separate from content) */}
|
||||
{snap.view === STORAGE_VIEWS.LIST && (
|
||||
<div
|
||||
className={cn(
|
||||
'absolute inset-0 pointer-events-none',
|
||||
isInternalDragOver && 'pointer-events-auto'
|
||||
)}
|
||||
onDragOver={(event) => {
|
||||
// Handle list view background drag over for internal file moves
|
||||
if (event.dataTransfer.types.includes('application/json')) {
|
||||
event.preventDefault()
|
||||
if (!isInternalDragOver) {
|
||||
setIsInternalDragOver(true)
|
||||
// For list view, the target is the current folder level
|
||||
const currentPath = snap.openedFolders.map((folder) => folder.name).join('/')
|
||||
setMoveToPath(currentPath)
|
||||
console.log('List view background drag over - Target path:', currentPath)
|
||||
}
|
||||
}
|
||||
}}
|
||||
onDrop={(event) => {
|
||||
// Handle list view background drop for internal file moves
|
||||
if (event.dataTransfer.types.includes('application/json')) {
|
||||
event.preventDefault()
|
||||
try {
|
||||
const draggedItem = JSON.parse(event.dataTransfer.getData('application/json'))
|
||||
if (draggedItem && draggedItem.type === STORAGE_ROW_TYPES.FILE) {
|
||||
// Check if this is a drop to the same location (same opened folders)
|
||||
const draggedItemPath = snap.openedFolders
|
||||
.slice(0, draggedItem.columnIndex)
|
||||
.map((folder) => folder.name)
|
||||
.join('/')
|
||||
const currentPath = snap.openedFolders.map((folder) => folder.name).join('/')
|
||||
|
||||
if (draggedItemPath === currentPath) {
|
||||
console.log('Same location drop detected in list view, ignoring move operation')
|
||||
return
|
||||
}
|
||||
|
||||
const targetDirectory = moveToPath
|
||||
console.log('List view background drop - Target directory:', targetDirectory)
|
||||
|
||||
// Use the new drag & drop function that doesn't interfere with the modal
|
||||
snap.moveFilesDragAndDrop([draggedItem], targetDirectory)
|
||||
|
||||
// Reset drag states
|
||||
setIsDraggedOver(false)
|
||||
setIsInternalDragOver(false)
|
||||
setMoveToPath('')
|
||||
|
||||
console.log('List view file move completed')
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to parse dragged item in list view:', error)
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Drag drop upload CTA for when column is empty */}
|
||||
{!(snap.isSearching && itemSearchString.length > 0) &&
|
||||
column.items.length === 0 &&
|
||||
|
||||
@@ -211,14 +211,22 @@ const FileExplorerRow: ItemRenderer<StorageItem, FileExplorerRowProps> = ({
|
||||
try {
|
||||
const draggedItem = JSON.parse(event.dataTransfer.getData('application/json'))
|
||||
if (draggedItem && draggedItem.type === STORAGE_ROW_TYPES.FILE) {
|
||||
// Calculate the target directory path - need the full path to the folder
|
||||
// The folder might be nested within other folders, so we need to construct the complete path
|
||||
// Check if this is a drop to the same location (same opened folders)
|
||||
const draggedItemPath = snap.openedFolders
|
||||
.slice(0, draggedItem.columnIndex)
|
||||
.map((folder) => folder.name)
|
||||
.join('/')
|
||||
const targetDirectory = snap.openedFolders
|
||||
.slice(0, columnIndex)
|
||||
.map((folder) => folder.name)
|
||||
.concat(item.name)
|
||||
.join('/')
|
||||
|
||||
if (draggedItemPath === targetDirectory) {
|
||||
console.log('Same location drop detected on folder, ignoring move operation')
|
||||
return
|
||||
}
|
||||
|
||||
console.log(
|
||||
'Folder drop - Target directory:',
|
||||
targetDirectory,
|
||||
@@ -230,12 +238,8 @@ const FileExplorerRow: ItemRenderer<StorageItem, FileExplorerRowProps> = ({
|
||||
snap.openedFolders.map((f) => f.name)
|
||||
)
|
||||
|
||||
// Set the selected items for moving (required by moveFiles)
|
||||
snap.setSelectedItemsToMove([draggedItem])
|
||||
|
||||
// Trigger the move operation directly without opening the modal
|
||||
// moveFiles expects the directory path, not the full file path
|
||||
snap.moveFiles(targetDirectory)
|
||||
// Use the new drag & drop function that doesn't interfere with the modal
|
||||
snap.moveFilesDragAndDrop([draggedItem], targetDirectory)
|
||||
|
||||
// Reset drag over state
|
||||
setIsFolderDragOver(false)
|
||||
|
||||
@@ -1404,6 +1404,58 @@ function createStorageExplorerState({
|
||||
state.setSelectedItemsToMove([])
|
||||
},
|
||||
|
||||
// New function for drag & drop operations that doesn't interfere with the modal
|
||||
moveFilesDragAndDrop: async (items: StorageItemWithColumn[], newPathToFile: string) => {
|
||||
const newPaths = compact(newPathToFile.split('/'))
|
||||
const formattedNewPathToFile = newPaths.join('/')
|
||||
let numberOfFilesMovedFail = 0
|
||||
|
||||
const toastId = toast(`Moving ${items.length} file${items.length > 1 ? 's' : ''}...`, {
|
||||
description: STORAGE_PROGRESS_INFO_TEXT,
|
||||
duration: Infinity,
|
||||
})
|
||||
|
||||
await Promise.all(
|
||||
items.map(async (item) => {
|
||||
const pathToFile = state.openedFolders
|
||||
.slice(0, item.columnIndex)
|
||||
.map((folder) => folder.name)
|
||||
.join('/')
|
||||
|
||||
const fromPath = pathToFile.length > 0 ? `${pathToFile}/${item.name}` : item.name
|
||||
const toPath =
|
||||
newPathToFile.length > 0 ? `${formattedNewPathToFile}/${item.name}` : item.name
|
||||
|
||||
try {
|
||||
await moveStorageObject({
|
||||
projectRef: state.projectRef,
|
||||
bucketId: state.selectedBucket.id,
|
||||
from: fromPath,
|
||||
to: toPath,
|
||||
})
|
||||
} catch (error: any) {
|
||||
numberOfFilesMovedFail += 1
|
||||
toast.error(error.message)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if (numberOfFilesMovedFail === items.length) {
|
||||
toast.error('Failed to move files')
|
||||
} else {
|
||||
toast(
|
||||
`Successfully moved ${
|
||||
items.length - numberOfFilesMovedFail
|
||||
} files to ${formattedNewPathToFile.length > 0 ? formattedNewPathToFile : 'the root of your bucket'}`
|
||||
)
|
||||
}
|
||||
|
||||
toast.dismiss(toastId)
|
||||
|
||||
// TODO: invalidate the file preview cache when moving files
|
||||
await state.refetchAllOpenedFolders()
|
||||
},
|
||||
|
||||
deleteFiles: async ({
|
||||
files,
|
||||
isDeleteFolder = false,
|
||||
|
||||
Reference in new issue
Block a user