feat: impl multi file/dir moving

Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
2025-09-21 17:03:50 +00:00
parent a331276c43
commit 29eab87c71
10 changed files with 147 additions and 53 deletions

View File

@@ -6,7 +6,8 @@
"scripts": {
"dev": "bun --hot src/server.tsx",
"build": "bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='\"production\"' --env='BUN_PUBLIC_*'",
"start": "NODE_ENV=production bun src/index.tsx"
"start": "NODE_ENV=production bun src/index.tsx",
"format": "biome format --write"
},
"dependencies": {
"@convex-dev/workos": "^0.0.1",

View File

@@ -2,6 +2,7 @@ import { api } from "@fileone/convex/_generated/api"
import type { Doc } from "@fileone/convex/_generated/dataModel"
import type { DirectoryItem } from "@fileone/convex/model/directories"
import {
type FileSystemHandle,
newDirectoryHandle,
newFileHandle,
} from "@fileone/convex/model/filesystem"
@@ -12,6 +13,7 @@ import {
flexRender,
getCoreRowModel,
type Row,
type Table as TableType,
useReactTable,
} from "@tanstack/react-table"
import { useMutation as useContextMutation } from "convex/react"
@@ -302,6 +304,7 @@ export function DirectoryContentTableContent() {
table.getRowModel().rows.map((row) => (
<FileItemRow
key={row.id}
table={table}
row={row}
onClick={() => selectRow(row)}
onContextMenu={(e) =>
@@ -439,11 +442,13 @@ function NewItemRow() {
}
function FileItemRow({
table,
row,
onClick,
onContextMenu,
onDoubleClick,
}: {
table: TableType<DirectoryItem>
row: Row<DirectoryItem>
onClick: () => void
onContextMenu: (e: React.MouseEvent) => void
@@ -461,17 +466,30 @@ function FileItemRow({
})
const handleDragStart = (e: React.DragEvent) => {
if (row.original.kind === "file") {
e.dataTransfer.setData(
"application/x-internal",
JSON.stringify(row.original),
)
const fileHandle = newFileHandle(row.original.doc._id)
setDragInfo({
source: fileHandle,
items: [fileHandle],
})
let source: FileSystemHandle
switch (row.original.kind) {
case "file":
source = newFileHandle(row.original.doc._id)
break
case "directory":
source = newDirectoryHandle(row.original.doc._id)
break
}
// biome-ignore lint/suspicious/useIterableCallbackReturn: the switch statement is exhaustive
const draggedItems = table.getSelectedRowModel().rows.map((row) => {
switch (row.original.kind) {
case "file":
return newFileHandle(row.original.doc._id)
case "directory":
return newDirectoryHandle(row.original.doc._id)
}
})
setDragInfo({
source,
items: draggedItems,
})
}
const handleDragEnd = () => {

View File

@@ -2,7 +2,6 @@ import { api } from "@fileone/convex/_generated/api"
import type { Doc, Id } from "@fileone/convex/_generated/dataModel"
import type {
DirectoryHandle,
FileHandle,
FileSystemHandle,
} from "@fileone/convex/model/filesystem"
import { useMutation } from "@tanstack/react-query"
@@ -14,7 +13,7 @@ import { toast } from "sonner"
export interface FileDragInfo {
source: FileSystemHandle
items: FileHandle[]
items: FileSystemHandle[]
}
export interface UseFileDropOptions {
@@ -38,33 +37,31 @@ export interface UseFileDropReturn {
export function useFileDrop({
item,
dragInfoAtom,
onDropSuccess,
}: UseFileDropOptions): UseFileDropReturn {
const [isDraggedOver, setIsDraggedOver] = useState(false)
const store = useStore()
const { mutate: moveFiles } = useMutation({
mutationFn: useContextMutation(api.files.moveFiles),
const { mutate: moveDroppedItems } = useMutation({
mutationFn: useContextMutation(api.filesystem.moveItems),
onSuccess: ({
items,
targetDirectory,
}: {
items: Id<"files">[]
items: FileSystemHandle[]
targetDirectory: Doc<"directories">
}) => {
toast.success(
`${items.length} files moved to ${targetDirectory.name}`,
`${items.length} items moved to ${targetDirectory.name}`,
)
onDropSuccess?.(items, targetDirectory)
},
})
const handleDrop = (_e: React.DragEvent) => {
const dragInfo = store.get(dragInfoAtom)
if (dragInfo && item) {
moveFiles({
targetDirectoryId: item.id,
items: dragInfo.items.map((item) => item.id),
moveDroppedItems({
targetDirectory: item,
items: dragInfo.items,
})
}
setIsDraggedOver(false)