mirror of
https://github.com/get-drexa/drive.git
synced 2026-02-02 17:21:17 +00:00
refactor: initial frontend wiring for new api
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
import type { Doc } from "@fileone/convex/dataModel"
|
||||
import type { FileSystemItem } from "@fileone/convex/filesystem"
|
||||
import type { DirectoryInfo } from "@fileone/convex/types"
|
||||
import { createContext } from "react"
|
||||
import type { DirectoryContent, DirectoryInfoWithPath } from "@/vfs/vfs"
|
||||
|
||||
type DirectoryPageContextType = {
|
||||
rootDirectory: Doc<"directories">
|
||||
directory: DirectoryInfo
|
||||
directoryContent: FileSystemItem[]
|
||||
directory: DirectoryInfoWithPath
|
||||
directoryContent: DirectoryContent
|
||||
}
|
||||
|
||||
export const DirectoryPageContext = createContext<DirectoryPageContextType>(
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
import { api } from "@fileone/convex/api"
|
||||
import { newFileSystemHandle } from "@fileone/convex/filesystem"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { useMutation as useContextMutation } from "convex/react"
|
||||
import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai"
|
||||
import { TextCursorInputIcon, TrashIcon } from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuTrigger,
|
||||
} from "@/components/ui/context-menu"
|
||||
import {
|
||||
contextMenuTargeItemsAtom,
|
||||
itemBeingRenamedAtom,
|
||||
optimisticDeletedItemsAtom,
|
||||
} from "./state"
|
||||
|
||||
export function DirectoryContentContextMenu({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const store = useStore()
|
||||
const [target, setTarget] = useAtom(contextMenuTargeItemsAtom)
|
||||
const setOptimisticDeletedItems = useSetAtom(optimisticDeletedItemsAtom)
|
||||
const moveToTrashMutation = useContextMutation(api.filesystem.moveToTrash)
|
||||
const { mutate: moveToTrash } = useMutation({
|
||||
mutationFn: moveToTrashMutation,
|
||||
onMutate: ({ handles }) => {
|
||||
setOptimisticDeletedItems(
|
||||
(prev) =>
|
||||
new Set([...prev, ...handles.map((handle) => handle.id)]),
|
||||
)
|
||||
},
|
||||
onSuccess: ({ deleted, errors }, { handles }) => {
|
||||
setOptimisticDeletedItems((prev) => {
|
||||
const newSet = new Set(prev)
|
||||
for (const handle of handles) {
|
||||
newSet.delete(handle.id)
|
||||
}
|
||||
return newSet
|
||||
})
|
||||
if (errors.length === 0 && deleted.length === handles.length) {
|
||||
toast.success(`Moved ${handles.length} items to trash`)
|
||||
} else if (errors.length === handles.length) {
|
||||
toast.error("Failed to move to trash")
|
||||
} else {
|
||||
toast.info(
|
||||
`Moved ${deleted.length} items to trash; failed to move ${errors.length} items`,
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const handleDelete = () => {
|
||||
const selectedItems = store.get(contextMenuTargeItemsAtom)
|
||||
if (selectedItems.length > 0) {
|
||||
moveToTrash({
|
||||
handles: selectedItems.map(newFileSystemHandle),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextMenu
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setTarget([])
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ContextMenuTrigger asChild>{children}</ContextMenuTrigger>
|
||||
{target && (
|
||||
<ContextMenuContent>
|
||||
<RenameMenuItem />
|
||||
<ContextMenuItem onClick={handleDelete}>
|
||||
<TrashIcon />
|
||||
Move to trash
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
)}
|
||||
</ContextMenu>
|
||||
)
|
||||
}
|
||||
|
||||
function RenameMenuItem() {
|
||||
const store = useStore()
|
||||
const target = useAtomValue(contextMenuTargeItemsAtom)
|
||||
const setItemBeingRenamed = useSetAtom(itemBeingRenamedAtom)
|
||||
|
||||
const handleRename = () => {
|
||||
const selectedItems = store.get(contextMenuTargeItemsAtom)
|
||||
if (selectedItems.length === 1) {
|
||||
// biome-ignore lint/style/noNonNullAssertion: length is checked
|
||||
const selectedItem = selectedItems[0]!
|
||||
setItemBeingRenamed({
|
||||
originalItem: selectedItem,
|
||||
name: selectedItem.doc.name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Only render if exactly one item is selected
|
||||
if (target.length !== 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextMenuItem onClick={handleRename}>
|
||||
<TextCursorInputIcon />
|
||||
Rename
|
||||
</ContextMenuItem>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +1,3 @@
|
||||
import type { Doc } from "@fileone/convex/dataModel"
|
||||
import {
|
||||
type DirectoryHandle,
|
||||
type FileHandle,
|
||||
type FileSystemHandle,
|
||||
type FileSystemItem,
|
||||
FileType,
|
||||
isSameHandle,
|
||||
newDirectoryHandle,
|
||||
newFileHandle,
|
||||
newFileSystemHandle,
|
||||
} from "@fileone/convex/filesystem"
|
||||
import { Link, useNavigate } from "@tanstack/react-router"
|
||||
import {
|
||||
type ColumnDef,
|
||||
@@ -23,6 +11,7 @@ import {
|
||||
import { type PrimitiveAtom, useSetAtom, useStore } from "jotai"
|
||||
import { useContext, useEffect, useMemo, useRef } from "react"
|
||||
import { DirectoryIcon } from "@/components/icons/directory-icon"
|
||||
import { TextFileIcon } from "@/components/icons/text-file-icon"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import {
|
||||
Table,
|
||||
@@ -32,26 +21,26 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table"
|
||||
import { type FileDragInfo, useFileDrop } from "@/files/use-file-drop"
|
||||
import {
|
||||
isControlOrCommandKeyActive,
|
||||
keyboardModifierAtom,
|
||||
} from "@/lib/keyboard"
|
||||
import { TextFileIcon } from "../../components/icons/text-file-icon"
|
||||
import { type FileDragInfo, useFileDrop } from "../../files/use-file-drop"
|
||||
import { cn } from "../../lib/utils"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { DirectoryInfo, DirectoryItem, FileInfo } from "@/vfs/vfs"
|
||||
import { DirectoryPageContext } from "./context"
|
||||
|
||||
type DirectoryContentTableItemIdFilter = Set<FileSystemItem["doc"]["_id"]>
|
||||
type DirectoryContentTableItemIdFilter = Set<string>
|
||||
|
||||
type DirectoryContentTableProps = {
|
||||
hiddenItems: DirectoryContentTableItemIdFilter
|
||||
directoryUrlFn: (directory: Doc<"directories">) => string
|
||||
directoryUrlFn: (directory: DirectoryInfo) => string
|
||||
fileDragInfoAtom: PrimitiveAtom<FileDragInfo | null>
|
||||
onContextMenu: (
|
||||
row: Row<FileSystemItem>,
|
||||
table: TableType<FileSystemItem>,
|
||||
row: Row<DirectoryItem>,
|
||||
table: TableType<DirectoryItem>,
|
||||
) => void
|
||||
onOpenFile: (file: Doc<"files">) => void
|
||||
onOpenFile: (file: FileInfo) => void
|
||||
}
|
||||
|
||||
function formatFileSize(bytes: number): string {
|
||||
@@ -65,9 +54,9 @@ function formatFileSize(bytes: number): string {
|
||||
}
|
||||
|
||||
function useTableColumns(
|
||||
onOpenFile: (file: Doc<"files">) => void,
|
||||
directoryUrlFn: (directory: Doc<"directories">) => string,
|
||||
): ColumnDef<FileSystemItem>[] {
|
||||
onOpenFile: (file: FileInfo) => void,
|
||||
directoryUrlFn: (directory: DirectoryInfo) => string,
|
||||
): ColumnDef<DirectoryItem>[] {
|
||||
return useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -100,17 +89,17 @@ function useTableColumns(
|
||||
accessorKey: "doc.name",
|
||||
cell: ({ row }) => {
|
||||
switch (row.original.kind) {
|
||||
case FileType.File:
|
||||
case "file":
|
||||
return (
|
||||
<FileNameCell
|
||||
file={row.original.doc}
|
||||
file={row.original}
|
||||
onOpenFile={onOpenFile}
|
||||
/>
|
||||
)
|
||||
case FileType.Directory:
|
||||
case "directory":
|
||||
return (
|
||||
<DirectoryNameCell
|
||||
directory={row.original.doc}
|
||||
directory={row.original}
|
||||
directoryUrlFn={directoryUrlFn}
|
||||
/>
|
||||
)
|
||||
@@ -123,13 +112,11 @@ function useTableColumns(
|
||||
accessorKey: "size",
|
||||
cell: ({ row }) => {
|
||||
switch (row.original.kind) {
|
||||
case FileType.File:
|
||||
case "file":
|
||||
return (
|
||||
<div>
|
||||
{formatFileSize(row.original.doc.size)}
|
||||
</div>
|
||||
<div>{formatFileSize(row.original.size)}</div>
|
||||
)
|
||||
case FileType.Directory:
|
||||
case "directory":
|
||||
return <div className="font-mono">-</div>
|
||||
}
|
||||
},
|
||||
@@ -140,9 +127,7 @@ function useTableColumns(
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<div>
|
||||
{new Date(
|
||||
row.original.doc.createdAt,
|
||||
).toLocaleString()}
|
||||
{new Date(row.original.createdAt).toLocaleString()}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
@@ -178,8 +163,8 @@ export function DirectoryContentTable({
|
||||
_columnId,
|
||||
filterValue: DirectoryContentTableItemIdFilter,
|
||||
_addMeta,
|
||||
) => !filterValue.has(row.original.doc._id),
|
||||
getRowId: (row) => row.doc._id,
|
||||
) => !filterValue.has(row.original.id),
|
||||
getRowId: (row) => row.id,
|
||||
})
|
||||
|
||||
useEffect(
|
||||
@@ -196,7 +181,7 @@ export function DirectoryContentTable({
|
||||
)
|
||||
|
||||
const handleRowContextMenu = (
|
||||
row: Row<FileSystemItem>,
|
||||
row: Row<DirectoryItem>,
|
||||
_event: React.MouseEvent,
|
||||
) => {
|
||||
if (!row.getIsSelected()) {
|
||||
@@ -205,7 +190,7 @@ export function DirectoryContentTable({
|
||||
onContextMenu(row, table)
|
||||
}
|
||||
|
||||
const selectRow = (row: Row<FileSystemItem>) => {
|
||||
const selectRow = (row: Row<DirectoryItem>) => {
|
||||
const keyboardModifiers = store.get(keyboardModifierAtom)
|
||||
const isMultiSelectMode = isControlOrCommandKeyActive(keyboardModifiers)
|
||||
const isRowSelected = row.getIsSelected()
|
||||
@@ -227,10 +212,10 @@ export function DirectoryContentTable({
|
||||
}
|
||||
}
|
||||
|
||||
const handleRowDoubleClick = (row: Row<FileSystemItem>) => {
|
||||
if (row.original.kind === FileType.Directory) {
|
||||
const handleRowDoubleClick = (row: Row<DirectoryItem>) => {
|
||||
if (row.original.kind === "directory") {
|
||||
navigate({
|
||||
to: `/directories/${row.original.doc._id}`,
|
||||
to: `/directories/${row.original.id}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -302,8 +287,8 @@ function FileItemRow({
|
||||
onDoubleClick,
|
||||
fileDragInfoAtom,
|
||||
}: {
|
||||
table: TableType<FileSystemItem>
|
||||
row: Row<FileSystemItem>
|
||||
table: TableType<DirectoryItem>
|
||||
row: Row<DirectoryItem>
|
||||
onClick: () => void
|
||||
onContextMenu: (e: React.MouseEvent) => void
|
||||
onDoubleClick: () => void
|
||||
@@ -313,39 +298,24 @@ function FileItemRow({
|
||||
const setFileDragInfo = useSetAtom(fileDragInfoAtom)
|
||||
|
||||
const { isDraggedOver, dropHandlers } = useFileDrop({
|
||||
destItem:
|
||||
row.original.kind === FileType.Directory
|
||||
? newDirectoryHandle(row.original.doc._id)
|
||||
: null,
|
||||
destDir: row.original,
|
||||
dragInfoAtom: fileDragInfoAtom,
|
||||
})
|
||||
|
||||
const handleDragStart = (_e: React.DragEvent) => {
|
||||
let source: DirectoryHandle | FileHandle
|
||||
switch (row.original.kind) {
|
||||
case FileType.File:
|
||||
source = newFileHandle(row.original.doc._id)
|
||||
break
|
||||
case FileType.Directory:
|
||||
source = newDirectoryHandle(row.original.doc._id)
|
||||
break
|
||||
}
|
||||
|
||||
let draggedItems: FileSystemHandle[]
|
||||
let draggedItems: DirectoryItem[]
|
||||
// drag all selections, but only if the currently dragged row is also selected
|
||||
if (row.getIsSelected()) {
|
||||
draggedItems = table
|
||||
.getSelectedRowModel()
|
||||
.rows.map((row) => newFileSystemHandle(row.original))
|
||||
if (!draggedItems.some((item) => isSameHandle(item, source))) {
|
||||
draggedItems.push(source)
|
||||
draggedItems = [...table.getSelectedRowModel().rows]
|
||||
if (!draggedItems.some((item) => item.id === row.original.id)) {
|
||||
draggedItems.push(row.original)
|
||||
}
|
||||
} else {
|
||||
draggedItems = [source]
|
||||
draggedItems = [row.original]
|
||||
}
|
||||
|
||||
setFileDragInfo({
|
||||
source,
|
||||
source: row.original,
|
||||
items: draggedItems,
|
||||
})
|
||||
}
|
||||
@@ -385,8 +355,8 @@ function DirectoryNameCell({
|
||||
directory,
|
||||
directoryUrlFn,
|
||||
}: {
|
||||
directory: Doc<"directories">
|
||||
directoryUrlFn: (directory: Doc<"directories">) => string
|
||||
directory: DirectoryInfo
|
||||
directoryUrlFn: (directory: DirectoryInfo) => string
|
||||
}) {
|
||||
return (
|
||||
<div className="flex w-full items-center gap-2">
|
||||
@@ -402,8 +372,8 @@ function FileNameCell({
|
||||
file,
|
||||
onOpenFile,
|
||||
}: {
|
||||
file: Doc<"files">
|
||||
onOpenFile: (file: Doc<"files">) => void
|
||||
file: FileInfo
|
||||
onOpenFile: (file: FileInfo) => void
|
||||
}) {
|
||||
return (
|
||||
<div className="flex w-full items-center gap-2">
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { api } from "@fileone/convex/api"
|
||||
import type { Id } from "@fileone/convex/dataModel"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { useMutation as useContextMutation } from "convex/react"
|
||||
import { useAtomValue } from "jotai"
|
||||
import { useId } from "react"
|
||||
import { toast } from "sonner"
|
||||
import { Button } from "@/components/ui/button"
|
||||
@@ -14,21 +12,26 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { createDirectoryMutationAtom } from "@/vfs/api"
|
||||
import type { DirectoryInfo } from "@/vfs/vfs"
|
||||
|
||||
export function NewDirectoryDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
directoryId,
|
||||
parentDirectory,
|
||||
}: {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
directoryId: Id<"directories">
|
||||
parentDirectory: DirectoryInfo
|
||||
}) {
|
||||
const formId = useId()
|
||||
|
||||
const createDirectoryMutation = useAtomValue(createDirectoryMutationAtom)
|
||||
|
||||
const { mutate: createDirectory, isPending: isCreating } = useMutation({
|
||||
mutationFn: useContextMutation(api.files.createDirectory),
|
||||
onSuccess: () => {
|
||||
...createDirectoryMutation,
|
||||
onSuccess: (data, vars, result, context) => {
|
||||
createDirectoryMutation.onSuccess?.(data, vars, result, context)
|
||||
onOpenChange(false)
|
||||
toast.success("Directory created successfully")
|
||||
},
|
||||
@@ -41,7 +44,7 @@ export function NewDirectoryDialog({
|
||||
const name = formData.get("directoryName") as string
|
||||
|
||||
if (name) {
|
||||
createDirectory({ name, directoryId })
|
||||
createDirectory({ name, parentId: parentDirectory.id })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { api } from "@fileone/convex/api"
|
||||
import { type FileSystemItem, FileType } from "@fileone/convex/filesystem"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { useMutation as useContextMutation } from "convex/react"
|
||||
import { useAtomValue } from "jotai"
|
||||
import { useId } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
@@ -13,9 +11,11 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { renameDirectoryMutationAtom, renameFileMutationAtom } from "@/vfs/api"
|
||||
import type { DirectoryItem } from "@/vfs/vfs"
|
||||
|
||||
type RenameFileDialogProps = {
|
||||
item: FileSystemItem
|
||||
item: DirectoryItem
|
||||
onRenameSuccess: () => void
|
||||
onClose: () => void
|
||||
}
|
||||
@@ -27,13 +27,22 @@ export function RenameFileDialog({
|
||||
}: RenameFileDialogProps) {
|
||||
const formId = useId()
|
||||
|
||||
const { mutate: renameFile, isPending: isRenaming } = useMutation({
|
||||
mutationFn: useContextMutation(api.files.renameFile),
|
||||
onSuccess: () => {
|
||||
onRenameSuccess()
|
||||
},
|
||||
const renameFileMutation = useAtomValue(renameFileMutationAtom)
|
||||
const renameDirectoryMutation = useAtomValue(renameDirectoryMutationAtom)
|
||||
|
||||
const { mutate: renameFile, isPending: isRenamingFile } = useMutation({
|
||||
...renameFileMutation,
|
||||
onSuccess: onRenameSuccess,
|
||||
})
|
||||
|
||||
const { mutate: renameDirectory, isPending: isRenamingDirectory } =
|
||||
useMutation({
|
||||
...renameDirectoryMutation,
|
||||
onSuccess: onRenameSuccess,
|
||||
})
|
||||
|
||||
const isRenaming = isRenamingFile || isRenamingDirectory
|
||||
|
||||
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
|
||||
@@ -42,14 +51,11 @@ export function RenameFileDialog({
|
||||
|
||||
if (newName) {
|
||||
switch (item.kind) {
|
||||
case FileType.File:
|
||||
renameFile({
|
||||
directoryId: item.doc.directoryId,
|
||||
itemId: item.doc._id,
|
||||
newName,
|
||||
})
|
||||
case "file":
|
||||
renameFile(item)
|
||||
break
|
||||
default:
|
||||
case "directory":
|
||||
renameDirectory(item)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -70,7 +76,7 @@ export function RenameFileDialog({
|
||||
</DialogHeader>
|
||||
|
||||
<form id={formId} onSubmit={onSubmit}>
|
||||
<RenameFileInput initialValue={item.doc.name} />
|
||||
<RenameFileInput initialValue={item.name} />
|
||||
</form>
|
||||
|
||||
<DialogFooter>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { Doc, Id } from "@fileone/convex/dataModel"
|
||||
import type { FileSystemItem } from "@fileone/convex/filesystem"
|
||||
import type { RowSelectionState } from "@tanstack/react-table"
|
||||
import { atom } from "jotai"
|
||||
import type { FileDragInfo } from "../../files/use-file-drop"
|
||||
|
||||
export const contextMenuTargeItemsAtom = atom<FileSystemItem[]>([])
|
||||
export const optimisticDeletedItemsAtom = atom(
|
||||
new Set<Id<"files"> | Id<"directories">>(),
|
||||
)
|
||||
|
||||
export const selectedFileRowsAtom = atom<RowSelectionState>({})
|
||||
|
||||
export const itemBeingRenamedAtom = atom<{
|
||||
originalItem: FileSystemItem
|
||||
name: string
|
||||
} | null>(null)
|
||||
|
||||
export const openedFileAtom = atom<Doc<"files"> | null>(null)
|
||||
|
||||
export const dragInfoAtom = atom<FileDragInfo | null>(null)
|
||||
@@ -1,9 +1,3 @@
|
||||
import type { Id } from "@fileone/convex/dataModel"
|
||||
import type {
|
||||
DirectoryHandle,
|
||||
DirectoryPathComponent,
|
||||
} from "@fileone/convex/filesystem"
|
||||
import type { DirectoryInfo } from "@fileone/convex/types"
|
||||
import { Link } from "@tanstack/react-router"
|
||||
import type { PrimitiveAtom } from "jotai"
|
||||
import { atom } from "jotai"
|
||||
@@ -24,6 +18,8 @@ import {
|
||||
import type { FileDragInfo } from "@/files/use-file-drop"
|
||||
import { useFileDrop } from "@/files/use-file-drop"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { DirectoryInfoWithPath } from "@/vfs/vfs"
|
||||
import type { PathSegment } from "../lib/path"
|
||||
|
||||
/**
|
||||
* This is a placeholder file drag info atom that always stores null and is never mutated.
|
||||
@@ -36,15 +32,28 @@ export function DirectoryPathBreadcrumb({
|
||||
directoryUrlFn,
|
||||
fileDragInfoAtom = nullFileDragInfoAtom,
|
||||
}: {
|
||||
directory: DirectoryInfo
|
||||
directory: DirectoryInfoWithPath
|
||||
rootLabel: string
|
||||
directoryUrlFn: (directory: Id<"directories">) => string
|
||||
directoryUrlFn: (directoryId: string) => string
|
||||
fileDragInfoAtom?: PrimitiveAtom<FileDragInfo | null>
|
||||
}) {
|
||||
if (directory.path.length === 1) {
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>{rootLabel}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
)
|
||||
}
|
||||
|
||||
const breadcrumbItems: React.ReactNode[] = [
|
||||
<FilePathBreadcrumbItem
|
||||
key={directory.path[0].handle.id}
|
||||
component={directory.path[0]}
|
||||
key={directory.path[0].id}
|
||||
segment={directory.path[0]}
|
||||
rootLabel={rootLabel}
|
||||
directoryUrlFn={directoryUrlFn}
|
||||
fileDragInfoAtom={fileDragInfoAtom}
|
||||
@@ -52,10 +61,10 @@ export function DirectoryPathBreadcrumb({
|
||||
]
|
||||
for (let i = 1; i < directory.path.length - 1; i++) {
|
||||
breadcrumbItems.push(
|
||||
<Fragment key={directory.path[i]?.handle.id}>
|
||||
<Fragment key={directory.path[i]!.id}>
|
||||
<BreadcrumbSeparator />
|
||||
<FilePathBreadcrumbItem
|
||||
component={directory.path[i]!}
|
||||
segment={directory.path[i]!}
|
||||
rootLabel={rootLabel}
|
||||
directoryUrlFn={directoryUrlFn}
|
||||
fileDragInfoAtom={fileDragInfoAtom}
|
||||
@@ -78,22 +87,22 @@ export function DirectoryPathBreadcrumb({
|
||||
}
|
||||
|
||||
function FilePathBreadcrumbItem({
|
||||
component,
|
||||
segment,
|
||||
rootLabel,
|
||||
directoryUrlFn,
|
||||
fileDragInfoAtom,
|
||||
}: {
|
||||
component: DirectoryPathComponent
|
||||
segment: PathSegment
|
||||
rootLabel: string
|
||||
directoryUrlFn: (directory: Id<"directories">) => string
|
||||
directoryUrlFn: (directoryId: string) => string
|
||||
fileDragInfoAtom: PrimitiveAtom<FileDragInfo | null>
|
||||
}) {
|
||||
const { isDraggedOver, dropHandlers } = useFileDrop({
|
||||
destItem: component.handle as DirectoryHandle,
|
||||
destDir: segment.id,
|
||||
dragInfoAtom: fileDragInfoAtom,
|
||||
})
|
||||
|
||||
const dirName = component.name || rootLabel
|
||||
const dirName = segment.name || rootLabel
|
||||
|
||||
return (
|
||||
<Tooltip open={isDraggedOver}>
|
||||
@@ -103,9 +112,7 @@ function FilePathBreadcrumbItem({
|
||||
{...dropHandlers}
|
||||
>
|
||||
<BreadcrumbLink asChild>
|
||||
<Link to={directoryUrlFn(component.handle.id)}>
|
||||
{dirName}
|
||||
</Link>
|
||||
<Link to={directoryUrlFn(segment.id)}>{dirName}</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</TooltipTrigger>
|
||||
|
||||
Reference in New Issue
Block a user