diff --git a/packages/convex/model/directories.ts b/packages/convex/model/directories.ts index b89b6b1..147fad8 100644 --- a/packages/convex/model/directories.ts +++ b/packages/convex/model/directories.ts @@ -194,8 +194,6 @@ export async function move( ), ) - console.log(sourceDirectories) - const errors: Err.ApplicationErrorData[] = [] const okDirectories: DirectoryHandle[] = [] conflictCheckResults.forEach((result, i) => { @@ -215,11 +213,22 @@ export async function move( } }) - const results = await Promise.allSettled( - okDirectories.map((handle) => - ctx.db.patch(handle.id, { parentId: targetDirectory.id }), - ), - ) + const ignoredHandles = new Set() + + const promises: Promise[] = [] + for (const handle of okDirectories) { + if (handle.id === targetDirectory.id) { + // if the directory that needs to be moved is the same as the dest directory + // it is silently ignored + ignoredHandles.add(handle) + } else { + promises.push( + ctx.db.patch(handle.id, { parentId: targetDirectory.id }), + ) + } + } + + const results = await Promise.allSettled(promises) for (const updateResult of results) { if (updateResult.status === "rejected") { @@ -227,7 +236,10 @@ export async function move( } } - return { moved: okDirectories, errors } + return { + moved: okDirectories.filter((handle) => !ignoredHandles.has(handle)), + errors, + } } export async function moveToTrashRecursive( diff --git a/packages/convex/model/error.ts b/packages/convex/model/error.ts index 5555354..c67ef2e 100644 --- a/packages/convex/model/error.ts +++ b/packages/convex/model/error.ts @@ -1,6 +1,6 @@ import { ConvexError } from "convex/values" - export enum Code { +export enum Code { Conflict = "Conflict", DirectoryExists = "DirectoryExists", DirectoryNotFound = "DirectoryNotFound", diff --git a/packages/convex/model/filesystem.ts b/packages/convex/model/filesystem.ts index ae10f2b..3eadf1c 100644 --- a/packages/convex/model/filesystem.ts +++ b/packages/convex/model/filesystem.ts @@ -35,3 +35,10 @@ export function newDirectoryHandle(id: Id<"directories">): DirectoryHandle { export function newFileHandle(id: Id<"files">): FileHandle { return { kind: "file", id } } + +export function isSameHandle( + handle1: FileSystemHandle, + handle2: FileSystemHandle, +): boolean { + return handle1.kind === handle2.kind && handle1.id === handle2.id +} diff --git a/packages/web/src/directories/directory-page/directory-content-table.tsx b/packages/web/src/directories/directory-page/directory-content-table.tsx index a54b40b..2a3f5dc 100644 --- a/packages/web/src/directories/directory-page/directory-content-table.tsx +++ b/packages/web/src/directories/directory-page/directory-content-table.tsx @@ -3,6 +3,7 @@ import type { Doc } from "@fileone/convex/_generated/dataModel" import type { DirectoryItem } from "@fileone/convex/model/directories" import { type FileSystemHandle, + isSameHandle, newDirectoryHandle, newFileHandle, } from "@fileone/convex/model/filesystem" @@ -458,7 +459,7 @@ function FileItemRow({ const setDragInfo = useSetAtom(dragInfoAtom) const { isDraggedOver, dropHandlers } = useFileDrop({ - item: + destItem: row.original.kind === "directory" ? newDirectoryHandle(row.original.doc._id) : null, @@ -476,16 +477,24 @@ function FileItemRow({ 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) + let draggedItems: FileSystemHandle[] + // drag all selections, but only if the currently dragged row is also selected + if (row.getIsSelected()) { + // biome-ignore lint/suspicious/useIterableCallbackReturn: the switch statement is exhaustive + 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) + } + }) + if (!draggedItems.some((item) => isSameHandle(item, source))) { + draggedItems.push(source) } - }) - draggedItems.push(source) + } else { + draggedItems = [source] + } setDragInfo({ source, diff --git a/packages/web/src/directories/directory-page/directory-page.tsx b/packages/web/src/directories/directory-page/directory-page.tsx index bcd9c44..42a662b 100644 --- a/packages/web/src/directories/directory-page/directory-page.tsx +++ b/packages/web/src/directories/directory-page/directory-page.tsx @@ -99,7 +99,7 @@ function FilePathBreadcrumb() { function FilePathBreadcrumbItem({ component }: { component: PathComponent }) { const { isDraggedOver, dropHandlers } = useFileDrop({ - item: component.handle as DirectoryHandle, + destItem: component.handle as DirectoryHandle, dragInfoAtom, }) diff --git a/packages/web/src/files/use-file-drop.ts b/packages/web/src/files/use-file-drop.ts index 4353ca2..1906b35 100644 --- a/packages/web/src/files/use-file-drop.ts +++ b/packages/web/src/files/use-file-drop.ts @@ -1,14 +1,15 @@ import { api } from "@fileone/convex/_generated/api" import type { Doc, Id } from "@fileone/convex/_generated/dataModel" import * as Err from "@fileone/convex/model/error" -import type { - DirectoryHandle, - FileSystemHandle, +import { + type DirectoryHandle, + type FileSystemHandle, + isSameHandle, } from "@fileone/convex/model/filesystem" import { useMutation } from "@tanstack/react-query" import { useMutation as useContextMutation } from "convex/react" -import type { Atom } from "jotai" -import { useStore } from "jotai" +import type { PrimitiveAtom } from "jotai" +import { useSetAtom, useStore } from "jotai" import { useState } from "react" import { toast } from "sonner" @@ -18,8 +19,8 @@ export interface FileDragInfo { } export interface UseFileDropOptions { - item: DirectoryHandle | null - dragInfoAtom: Atom + destItem: DirectoryHandle | null + dragInfoAtom: PrimitiveAtom onDropSuccess?: ( items: Id<"files">[], targetDirectory: Doc<"directories">, @@ -36,10 +37,11 @@ export interface UseFileDropReturn { } export function useFileDrop({ - item, + destItem, dragInfoAtom, }: UseFileDropOptions): UseFileDropReturn { const [isDraggedOver, setIsDraggedOver] = useState(false) + const setDragInfo = useSetAtom(dragInfoAtom) const store = useStore() const { mutate: moveDroppedItems } = useMutation({ @@ -69,19 +71,25 @@ export function useFileDrop({ const handleDrop = (_e: React.DragEvent) => { const dragInfo = store.get(dragInfoAtom) - console.log("dragInfo", dragInfo) - if (dragInfo && item) { - moveDroppedItems({ - targetDirectory: item, - items: dragInfo.items, - }) + console.log("handleDrop", { dragInfo, destItem }) + if (dragInfo && destItem) { + const items = dragInfo.items.filter( + (item) => !isSameHandle(item, destItem), + ) + if (items.length > 0) { + moveDroppedItems({ + targetDirectory: destItem, + items, + }) + } } setIsDraggedOver(false) + setDragInfo(null) } const handleDragOver = (e: React.DragEvent) => { const dragInfo = store.get(dragInfoAtom) - if (dragInfo && item) { + if (dragInfo && destItem) { e.preventDefault() e.dataTransfer.dropEffect = "move" setIsDraggedOver(true)