feat: allow file drop on path breadcrumb

This commit is contained in:
2025-09-20 23:54:27 +00:00
parent 0f5b1f79ff
commit 7eefe2b96e
5 changed files with 98 additions and 35 deletions

View File

@@ -5,6 +5,7 @@ import type {
} from "../functions"
import * as Err from "./error"
import type { FilePath, ReverseFilePath } from "./filesystem"
import { newDirectoryHandle } from "./filesystem"
type Directory = {
kind: "directory"
@@ -42,12 +43,20 @@ export async function fetch(
)
}
const path: ReverseFilePath = [{ id: directoryId, name: directory.name }]
const path: ReverseFilePath = [
{
handle: newDirectoryHandle(directoryId),
name: directory.name,
},
]
let parentDirId = directory.parentId
while (parentDirId) {
const parentDir = await ctx.db.get(parentDirId)
if (parentDir) {
path.push({ id: parentDir._id, name: parentDir.name })
path.push({
handle: newDirectoryHandle(parentDir._id),
name: parentDir.name,
})
parentDirId = parentDir.parentId
} else {
throw Err.create(Err.Code.Internal)

View File

@@ -1,12 +1,12 @@
import type { Id } from "../_generated/dataModel"
export type DirectoryPathComponent = {
id: Id<"directories">
handle: DirectoryHandle
name: string
}
export type FilePathComponent = {
id: Id<"files">
handle: FileHandle
name: string
}
@@ -15,3 +15,23 @@ export type PathComponent = FilePathComponent | DirectoryPathComponent
export type FilePath = [...DirectoryPathComponent[], PathComponent]
export type ReverseFilePath = [PathComponent, ...DirectoryPathComponent[]]
export type FileHandle = {
kind: "file"
id: Id<"files">
}
export type DirectoryHandle = {
kind: "directory"
id: Id<"directories">
}
export type FileSystemHandle = DirectoryHandle | FileHandle
export function newDirectoryHandle(id: Id<"directories">): DirectoryHandle {
return { kind: "directory", id }
}
export function newFileHandle(id: Id<"files">): FileHandle {
return { kind: "file", id }
}

View File

@@ -1,6 +1,10 @@
import { api } from "@fileone/convex/_generated/api"
import type { Doc, Id } from "@fileone/convex/_generated/dataModel"
import type { Doc } from "@fileone/convex/_generated/dataModel"
import type { DirectoryItem } from "@fileone/convex/model/directories"
import {
newDirectoryHandle,
newFileHandle,
} from "@fileone/convex/model/filesystem"
import { useMutation } from "@tanstack/react-query"
import { Link } from "@tanstack/react-router"
import {
@@ -13,7 +17,7 @@ import {
import { useMutation as useContextMutation } from "convex/react"
import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai"
import { CheckIcon, TextCursorInputIcon, TrashIcon, XIcon } from "lucide-react"
import { useContext, useEffect, useId, useRef, useState } from "react"
import { useContext, useEffect, useId, useRef } from "react"
import { toast } from "sonner"
import { DirectoryIcon } from "@/components/icons/directory-icon"
import { Checkbox } from "@/components/ui/checkbox"
@@ -402,7 +406,10 @@ function FileItemRow({
const setDragInfo = useSetAtom(dragInfoAtom)
const { isDraggedOver, dropHandlers } = useFileDrop({
item: row.original,
item:
row.original.kind === "directory"
? newDirectoryHandle(row.original.doc._id)
: null,
dragInfoAtom,
})
@@ -412,9 +419,10 @@ function FileItemRow({
"application/x-internal",
JSON.stringify(row.original),
)
const fileHandle = newFileHandle(row.original.doc._id)
setDragInfo({
source: row.original,
items: [row.original.doc._id],
source: fileHandle,
items: [fileHandle],
})
}
}

View File

@@ -1,5 +1,8 @@
import { api } from "@fileone/convex/_generated/api"
import type { PathComponent } from "@fileone/convex/model/filesystem"
import type {
DirectoryHandle,
PathComponent,
} from "@fileone/convex/model/filesystem"
import { useMutation } from "@tanstack/react-query"
import { Link } from "@tanstack/react-router"
import { useMutation as useConvexMutation } from "convex/react"
@@ -30,10 +33,17 @@ import {
BreadcrumbSeparator,
} from "../../components/ui/breadcrumb"
import { Button } from "../../components/ui/button"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "../../components/ui/tooltip"
import { useFileDrop } from "../../files/use-file-drop"
import { cn } from "../../lib/utils"
import { DirectoryPageContext } from "./context"
import { DirectoryContentTable } from "./directory-content-table"
import { RenameFileDialog } from "./rename-file-dialog"
import { newItemKindAtom, openedFileAtom } from "./state"
import { dragInfoAtom, newItemKindAtom, openedFileAtom } from "./state"
export function DirectoryPage() {
return (
@@ -57,12 +67,10 @@ export function DirectoryPage() {
function FilePathBreadcrumb() {
const { rootDirectory, directory } = useContext(DirectoryPageContext)
console.log(directory.path)
const breadcrumbItems: React.ReactNode[] = []
for (let i = 1; i < directory.path.length - 1; i++) {
breadcrumbItems.push(
<Fragment key={directory.path[i]!.id}>
<Fragment key={directory.path[i]!.handle.id}>
<BreadcrumbSeparator />
<FilePathBreadcrumbItem component={directory.path[i]!} />
</Fragment>,
@@ -90,14 +98,29 @@ function FilePathBreadcrumb() {
}
function FilePathBreadcrumbItem({ component }: { component: PathComponent }) {
const { isDraggedOver, dropHandlers } = useFileDrop({
item: component.handle as DirectoryHandle,
dragInfoAtom,
})
const dirName = component.name || "All Files"
return (
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link to={`/directories/${component.id}`}>
{component.name || "All Files"}
</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<Tooltip open={isDraggedOver}>
<TooltipTrigger asChild>
<BreadcrumbItem
className={cn({ "bg-muted": isDraggedOver })}
{...dropHandlers}
>
<BreadcrumbLink asChild>
<Link to={`/directories/${component.handle.id}`}>
{dirName}
</Link>
</BreadcrumbLink>
</BreadcrumbItem>
</TooltipTrigger>
<TooltipContent>Move to {dirName}</TooltipContent>
</Tooltip>
)
}

View File

@@ -1,6 +1,10 @@
import { api } from "@fileone/convex/_generated/api"
import type { Doc, Id } from "@fileone/convex/_generated/dataModel"
import type { DirectoryItem } from "@fileone/convex/model/directories"
import type {
DirectoryHandle,
FileHandle,
FileSystemHandle,
} from "@fileone/convex/model/filesystem"
import { useMutation } from "@tanstack/react-query"
import { useMutation as useContextMutation } from "convex/react"
import type { Atom } from "jotai"
@@ -9,14 +13,17 @@ import { useState } from "react"
import { toast } from "sonner"
export interface FileDragInfo {
source: DirectoryItem
items: Id<"files">[]
source: FileSystemHandle
items: FileHandle[]
}
export interface UseFileDropOptions {
item: DirectoryItem
item: DirectoryHandle | null
dragInfoAtom: Atom<FileDragInfo | null>
onDropSuccess?: (items: Id<"files">[], targetDirectory: Doc<"directories">) => void
onDropSuccess?: (
items: Id<"files">[],
targetDirectory: Doc<"directories">,
) => void
}
export interface UseFileDropReturn {
@@ -54,10 +61,10 @@ export function useFileDrop({
const handleDrop = (_e: React.DragEvent) => {
const dragInfo = store.get(dragInfoAtom)
if (dragInfo && item.kind === "directory") {
if (dragInfo && item) {
moveFiles({
targetDirectoryId: item.doc._id,
items: dragInfo.items,
targetDirectoryId: item.id,
items: dragInfo.items.map((item) => item.id),
})
}
setIsDraggedOver(false)
@@ -65,11 +72,7 @@ export function useFileDrop({
const handleDragOver = (e: React.DragEvent) => {
const dragInfo = store.get(dragInfoAtom)
if (
dragInfo &&
dragInfo.source !== item &&
item.kind === "directory"
) {
if (dragInfo && item) {
e.preventDefault()
e.dataTransfer.dropEffect = "move"
setIsDraggedOver(true)