mirror of
https://github.com/get-drexa/drive.git
synced 2025-12-01 05:51:39 +00:00
feat: impl multi file deletion support
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
import type { Id } from "@fileone/convex/_generated/dataModel"
|
||||
import { v } from "convex/values"
|
||||
import type { Id } from "./_generated/dataModel"
|
||||
import { authenticatedMutation, authenticatedQuery } from "./functions"
|
||||
import type { DirectoryItem } from "./model/directories"
|
||||
import * as Directories from "./model/directories"
|
||||
import * as Err from "./model/error"
|
||||
import * as Files from "./model/files"
|
||||
import type { FileSystemItem } from "./model/filesystem"
|
||||
|
||||
export const generateUploadUrl = authenticatedMutation({
|
||||
handler: async (ctx) => {
|
||||
@@ -55,7 +54,7 @@ export const fetchDirectoryContent = authenticatedQuery({
|
||||
args: {
|
||||
directoryId: v.optional(v.id("directories")),
|
||||
},
|
||||
handler: async (ctx, { directoryId }): Promise<DirectoryItem[]> => {
|
||||
handler: async (ctx, { directoryId }): Promise<FileSystemItem[]> => {
|
||||
return await Directories.fetchContent(ctx, { directoryId })
|
||||
},
|
||||
})
|
||||
@@ -107,37 +106,3 @@ export const renameFile = authenticatedMutation({
|
||||
await Files.renameFile(ctx, { directoryId, itemId, newName })
|
||||
},
|
||||
})
|
||||
|
||||
export const moveToTrash = authenticatedMutation({
|
||||
args: {
|
||||
kind: v.union(v.literal("file"), v.literal("directory")),
|
||||
itemId: v.union(v.id("files"), v.id("directories")),
|
||||
},
|
||||
handler: async (ctx, { itemId, kind }) => {
|
||||
switch (kind) {
|
||||
case "file": {
|
||||
const file = await ctx.db.get(itemId as Id<"files">)
|
||||
if (!file || file.userId !== ctx.user._id) {
|
||||
throw new Error("File not found or access denied")
|
||||
}
|
||||
await ctx.db.patch(itemId, {
|
||||
deletedAt: new Date().toISOString(),
|
||||
})
|
||||
break
|
||||
}
|
||||
case "directory": {
|
||||
const directory = await ctx.db.get(itemId as Id<"directories">)
|
||||
if (!directory || directory.userId !== ctx.user._id) {
|
||||
throw new Error("Directory not found or access denied")
|
||||
}
|
||||
await Directories.moveToTrashRecursive(
|
||||
ctx,
|
||||
itemId as Id<"directories">,
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return itemId
|
||||
},
|
||||
})
|
||||
|
||||
@@ -4,18 +4,12 @@ import * as Directories from "./model/directories"
|
||||
import * as Err from "./model/error"
|
||||
import * as Files from "./model/files"
|
||||
import type { DirectoryHandle, FileHandle } from "./model/filesystem"
|
||||
|
||||
const VDirectoryHandle = v.object({
|
||||
kind: v.literal("directory"),
|
||||
id: v.id("directories"),
|
||||
})
|
||||
|
||||
const VFileHandle = v.object({
|
||||
kind: v.literal("file"),
|
||||
id: v.id("files"),
|
||||
})
|
||||
|
||||
const VFileSystemHandle = v.union(VFileHandle, VDirectoryHandle)
|
||||
import {
|
||||
type FileSystemHandle,
|
||||
FileType,
|
||||
VDirectoryHandle,
|
||||
VFileSystemHandle,
|
||||
} from "./model/filesystem"
|
||||
|
||||
export const moveItems = authenticatedMutation({
|
||||
args: {
|
||||
@@ -38,10 +32,10 @@ export const moveItems = authenticatedMutation({
|
||||
const fileHandles: FileHandle[] = []
|
||||
for (const item of items) {
|
||||
switch (item.kind) {
|
||||
case "directory":
|
||||
case FileType.Directory:
|
||||
directoryHandles.push(item)
|
||||
break
|
||||
case "file":
|
||||
case FileType.File:
|
||||
fileHandles.push(item)
|
||||
break
|
||||
}
|
||||
@@ -64,3 +58,45 @@ export const moveItems = authenticatedMutation({
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const moveToTrash = authenticatedMutation({
|
||||
args: {
|
||||
handles: v.array(VFileSystemHandle),
|
||||
},
|
||||
handler: async (ctx, { handles }) => {
|
||||
// biome-ignore lint/suspicious/useIterableCallbackReturn: switch statement is exhaustive
|
||||
const promises = handles.map((handle) => {
|
||||
switch (handle.kind) {
|
||||
case FileType.File:
|
||||
return ctx.db
|
||||
.patch(handle.id, {
|
||||
deletedAt: new Date().toISOString(),
|
||||
})
|
||||
.then(() => handle)
|
||||
case FileType.Directory:
|
||||
return Directories.moveToTrashRecursive(ctx, handle).then(
|
||||
() => handle,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const results = await Promise.allSettled(promises)
|
||||
const errors: Err.ApplicationErrorData[] = []
|
||||
const okHandles: FileSystemHandle[] = []
|
||||
for (const result of results) {
|
||||
switch (result.status) {
|
||||
case "fulfilled":
|
||||
okHandles.push(result.value)
|
||||
break
|
||||
case "rejected":
|
||||
errors.push(Err.createJson(Err.Code.Internal))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
deleted: okHandles,
|
||||
errors,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -4,21 +4,14 @@ import type {
|
||||
AuthenticatedQueryCtx,
|
||||
} from "../functions"
|
||||
import * as Err from "./error"
|
||||
import type { DirectoryHandle, FilePath, ReverseFilePath } from "./filesystem"
|
||||
import { newDirectoryHandle } from "./filesystem"
|
||||
|
||||
type Directory = {
|
||||
kind: "directory"
|
||||
doc: Doc<"directories">
|
||||
}
|
||||
|
||||
type File = {
|
||||
kind: "file"
|
||||
doc: Doc<"files">
|
||||
}
|
||||
|
||||
export type DirectoryItem = Directory | File
|
||||
export type DirectoryItemKind = DirectoryItem["kind"]
|
||||
import {
|
||||
type DirectoryHandle,
|
||||
type FilePath,
|
||||
type FileSystemItem,
|
||||
FileType,
|
||||
newDirectoryHandle,
|
||||
type ReverseFilePath,
|
||||
} from "./filesystem"
|
||||
|
||||
export type DirectoryInfo = Doc<"directories"> & { path: FilePath }
|
||||
|
||||
@@ -83,7 +76,7 @@ export async function fetch(
|
||||
export async function fetchContent(
|
||||
ctx: AuthenticatedQueryCtx,
|
||||
{ directoryId }: { directoryId?: Id<"directories"> } = {},
|
||||
): Promise<DirectoryItem[]> {
|
||||
): Promise<FileSystemItem[]> {
|
||||
let dirId: Id<"directories"> | undefined
|
||||
if (directoryId) {
|
||||
dirId = directoryId
|
||||
@@ -110,12 +103,12 @@ export async function fetchContent(
|
||||
.collect(),
|
||||
])
|
||||
|
||||
const items: DirectoryItem[] = []
|
||||
const items: FileSystemItem[] = []
|
||||
for (const directory of directories) {
|
||||
items.push({ kind: "directory", doc: directory })
|
||||
items.push({ kind: FileType.Directory, doc: directory })
|
||||
}
|
||||
for (const file of files) {
|
||||
items.push({ kind: "file", doc: file })
|
||||
items.push({ kind: FileType.File, doc: file })
|
||||
}
|
||||
|
||||
return items
|
||||
@@ -206,7 +199,7 @@ export async function move(
|
||||
),
|
||||
)
|
||||
} else {
|
||||
okDirectories.push(sourceDirectories[i])
|
||||
okDirectories.push(sourceDirectories[i]!)
|
||||
}
|
||||
} else if (result.status === "rejected") {
|
||||
errors.push(Err.createJson(Err.Code.Internal))
|
||||
@@ -244,14 +237,14 @@ export async function move(
|
||||
|
||||
export async function moveToTrashRecursive(
|
||||
ctx: AuthenticatedMutationCtx,
|
||||
directoryId: Id<"directories">,
|
||||
handle: DirectoryHandle,
|
||||
): Promise<void> {
|
||||
const now = new Date().toISOString()
|
||||
|
||||
const filesToDelete: Id<"files">[] = []
|
||||
const directoriesToDelete: Id<"directories">[] = []
|
||||
|
||||
const directoryQueue: Id<"directories">[] = [directoryId]
|
||||
const directoryQueue: Id<"directories">[] = [handle.id]
|
||||
|
||||
while (directoryQueue.length > 0) {
|
||||
const currentDirectoryId = directoryQueue.shift()!
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
import type { Id } from "../_generated/dataModel"
|
||||
import { v } from "convex/values"
|
||||
import type { Doc, Id } from "../_generated/dataModel"
|
||||
|
||||
export enum FileType {
|
||||
File = "File",
|
||||
Directory = "Directory",
|
||||
}
|
||||
|
||||
export type Directory = {
|
||||
kind: FileType.Directory
|
||||
doc: Doc<"directories">
|
||||
}
|
||||
export type File = {
|
||||
kind: FileType.File
|
||||
doc: Doc<"files">
|
||||
}
|
||||
export type FileSystemItem = Directory | File
|
||||
|
||||
export type DirectoryPathComponent = {
|
||||
handle: DirectoryHandle
|
||||
name: string
|
||||
@@ -14,31 +25,28 @@ export type FilePathComponent = {
|
||||
handle: FileHandle
|
||||
name: string
|
||||
}
|
||||
|
||||
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"
|
||||
kind: FileType.Directory
|
||||
id: Id<"directories">
|
||||
}
|
||||
|
||||
export type FileHandle = {
|
||||
kind: FileType.File
|
||||
id: Id<"files">
|
||||
}
|
||||
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 }
|
||||
export function newFileSystemHandle(item: FileSystemItem): FileSystemHandle {
|
||||
console.log("item", item)
|
||||
switch (item.kind) {
|
||||
case FileType.File:
|
||||
return { kind: item.kind, id: item.doc._id }
|
||||
case FileType.Directory:
|
||||
return { kind: item.kind, id: item.doc._id }
|
||||
}
|
||||
}
|
||||
|
||||
export function isSameHandle(
|
||||
@@ -47,3 +55,21 @@ export function isSameHandle(
|
||||
): boolean {
|
||||
return handle1.kind === handle2.kind && handle1.id === handle2.id
|
||||
}
|
||||
|
||||
export function newDirectoryHandle(id: Id<"directories">): DirectoryHandle {
|
||||
return { kind: FileType.Directory, id }
|
||||
}
|
||||
|
||||
export function newFileHandle(id: Id<"files">): FileHandle {
|
||||
return { kind: FileType.File, id }
|
||||
}
|
||||
|
||||
export const VDirectoryHandle = v.object({
|
||||
kind: v.literal(FileType.Directory),
|
||||
id: v.id("directories"),
|
||||
})
|
||||
export const VFileHandle = v.object({
|
||||
kind: v.literal(FileType.File),
|
||||
id: v.id("files"),
|
||||
})
|
||||
export const VFileSystemHandle = v.union(VFileHandle, VDirectoryHandle)
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import type { Doc } from "@fileone/convex/_generated/dataModel"
|
||||
import type {
|
||||
DirectoryInfo,
|
||||
DirectoryItem,
|
||||
} from "@fileone/convex/model/directories"
|
||||
import type { DirectoryInfo } from "@fileone/convex/model/directories"
|
||||
import type { FileSystemItem } from "@fileone/convex/model/filesystem"
|
||||
import { createContext } from "react"
|
||||
|
||||
type DirectoryPageContextType = {
|
||||
rootDirectory: Doc<"directories">
|
||||
directory: DirectoryInfo
|
||||
directoryContent: DirectoryItem[]
|
||||
directoryContent: FileSystemItem[]
|
||||
}
|
||||
|
||||
export const DirectoryPageContext = createContext<DirectoryPageContextType>(
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
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 DirectoryHandle,
|
||||
type FileHandle,
|
||||
type FileSystemHandle,
|
||||
type FileSystemItem,
|
||||
FileType,
|
||||
isSameHandle,
|
||||
newDirectoryHandle,
|
||||
newFileHandle,
|
||||
newFileSystemHandle,
|
||||
} from "@fileone/convex/model/filesystem"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { Link, useNavigate } from "@tanstack/react-router"
|
||||
@@ -19,8 +23,8 @@ import {
|
||||
} from "@tanstack/react-table"
|
||||
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 } from "react"
|
||||
import { TextCursorInputIcon, TrashIcon } from "lucide-react"
|
||||
import { useContext, useEffect, useRef } from "react"
|
||||
import { toast } from "sonner"
|
||||
import { DirectoryIcon } from "@/components/icons/directory-icon"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
@@ -43,17 +47,13 @@ import {
|
||||
keyboardModifierAtom,
|
||||
} from "@/lib/keyboard"
|
||||
import { TextFileIcon } from "../../components/icons/text-file-icon"
|
||||
import { Button } from "../../components/ui/button"
|
||||
import { LoadingSpinner } from "../../components/ui/loading-spinner"
|
||||
import { useFileDrop } from "../../files/use-file-drop"
|
||||
import { withDefaultOnError } from "../../lib/error"
|
||||
import { cn } from "../../lib/utils"
|
||||
import { DirectoryPageContext } from "./context"
|
||||
import {
|
||||
contextMenuTargeItemAtom,
|
||||
contextMenuTargeItemsAtom,
|
||||
dragInfoAtom,
|
||||
itemBeingRenamedAtom,
|
||||
newFileTypeAtom,
|
||||
openedFileAtom,
|
||||
optimisticDeletedItemsAtom,
|
||||
} from "./state"
|
||||
@@ -68,7 +68,7 @@ function formatFileSize(bytes: number): string {
|
||||
return `${parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`
|
||||
}
|
||||
|
||||
const columns: ColumnDef<DirectoryItem>[] = [
|
||||
const columns: ColumnDef<FileSystemItem>[] = [
|
||||
{
|
||||
id: "select",
|
||||
header: ({ table }) => (
|
||||
@@ -99,9 +99,9 @@ const columns: ColumnDef<DirectoryItem>[] = [
|
||||
accessorKey: "doc.name",
|
||||
cell: ({ row }) => {
|
||||
switch (row.original.kind) {
|
||||
case "file":
|
||||
case FileType.File:
|
||||
return <FileNameCell file={row.original.doc} />
|
||||
case "directory":
|
||||
case FileType.Directory:
|
||||
return <DirectoryNameCell directory={row.original.doc} />
|
||||
}
|
||||
},
|
||||
@@ -112,9 +112,9 @@ const columns: ColumnDef<DirectoryItem>[] = [
|
||||
accessorKey: "size",
|
||||
cell: ({ row }) => {
|
||||
switch (row.original.kind) {
|
||||
case "file":
|
||||
case FileType.File:
|
||||
return <div>{formatFileSize(row.original.doc.size)}</div>
|
||||
case "directory":
|
||||
case FileType.Directory:
|
||||
return <div className="font-mono">-</div>
|
||||
}
|
||||
},
|
||||
@@ -148,31 +148,44 @@ export function DirectoryContentTableContextMenu({
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const store = useStore()
|
||||
const target = useAtomValue(contextMenuTargeItemAtom)
|
||||
const [target, setTarget] = useAtom(contextMenuTargeItemsAtom)
|
||||
const setOptimisticDeletedItems = useSetAtom(optimisticDeletedItemsAtom)
|
||||
const moveToTrashMutation = useContextMutation(api.files.moveToTrash)
|
||||
const moveToTrashMutation = useContextMutation(api.filesystem.moveToTrash)
|
||||
const setItemBeingRenamed = useSetAtom(itemBeingRenamedAtom)
|
||||
const setContextMenuTargetItem = useSetAtom(contextMenuTargeItemAtom)
|
||||
const { mutate: moveToTrash } = useMutation({
|
||||
mutationFn: moveToTrashMutation,
|
||||
onMutate: ({ itemId }) => {
|
||||
setOptimisticDeletedItems((prev) => new Set([...prev, itemId]))
|
||||
onMutate: ({ handles }) => {
|
||||
setOptimisticDeletedItems(
|
||||
(prev) =>
|
||||
new Set([...prev, ...handles.map((handle) => handle.id)]),
|
||||
)
|
||||
},
|
||||
onSuccess: (itemId) => {
|
||||
onSuccess: ({ deleted, errors }, { handles }) => {
|
||||
setOptimisticDeletedItems((prev) => {
|
||||
const newSet = new Set(prev)
|
||||
newSet.delete(itemId)
|
||||
for (const handle of handles) {
|
||||
newSet.delete(handle.id)
|
||||
}
|
||||
return newSet
|
||||
})
|
||||
toast.success("Moved to trash")
|
||||
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 handleRename = () => {
|
||||
const selectedItem = store.get(contextMenuTargeItemAtom)
|
||||
if (selectedItem) {
|
||||
const selectedItems = store.get(contextMenuTargeItemsAtom)
|
||||
if (selectedItems.length === 1) {
|
||||
// biome-ignore lint/style/noNonNullAssertion: length is checked
|
||||
const selectedItem = selectedItems[0]!
|
||||
setItemBeingRenamed({
|
||||
kind: selectedItem.kind,
|
||||
originalItem: selectedItem,
|
||||
name: selectedItem.doc.name,
|
||||
})
|
||||
@@ -180,11 +193,10 @@ export function DirectoryContentTableContextMenu({
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
const selectedItem = store.get(contextMenuTargeItemAtom)
|
||||
if (selectedItem) {
|
||||
const selectedItems = store.get(contextMenuTargeItemsAtom)
|
||||
if (selectedItems.length > 0) {
|
||||
moveToTrash({
|
||||
kind: selectedItem.kind,
|
||||
itemId: selectedItem.doc._id,
|
||||
handles: selectedItems.map(newFileSystemHandle),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -193,7 +205,7 @@ export function DirectoryContentTableContextMenu({
|
||||
<ContextMenu
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setContextMenuTargetItem(null)
|
||||
setTarget([])
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -217,7 +229,7 @@ export function DirectoryContentTableContextMenu({
|
||||
export function DirectoryContentTableContent() {
|
||||
const { directoryContent } = useContext(DirectoryPageContext)
|
||||
const optimisticDeletedItems = useAtomValue(optimisticDeletedItemsAtom)
|
||||
const setContextMenuTargetItem = useSetAtom(contextMenuTargeItemAtom)
|
||||
const setContextMenuTargetItem = useSetAtom(contextMenuTargeItemsAtom)
|
||||
const store = useStore()
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -247,23 +259,26 @@ export function DirectoryContentTableContent() {
|
||||
)
|
||||
|
||||
const handleRowContextMenu = (
|
||||
row: Row<DirectoryItem>,
|
||||
row: Row<FileSystemItem>,
|
||||
_event: React.MouseEvent,
|
||||
) => {
|
||||
const target = store.get(contextMenuTargeItemAtom)
|
||||
if (target === row.original) {
|
||||
setContextMenuTargetItem(null)
|
||||
const target = store.get(contextMenuTargeItemsAtom)
|
||||
if (target.length > 0) {
|
||||
setContextMenuTargetItem([])
|
||||
} else if (row.getIsSelected()) {
|
||||
setContextMenuTargetItem(
|
||||
table.getSelectedRowModel().rows.map((row) => row.original),
|
||||
)
|
||||
} else {
|
||||
selectRow(row)
|
||||
setContextMenuTargetItem(row.original)
|
||||
setContextMenuTargetItem([row.original])
|
||||
}
|
||||
}
|
||||
|
||||
const selectRow = (row: Row<DirectoryItem>) => {
|
||||
const selectRow = (row: Row<FileSystemItem>) => {
|
||||
const keyboardModifiers = store.get(keyboardModifierAtom)
|
||||
const isMultiSelectMode = isControlOrCommandKeyActive(keyboardModifiers)
|
||||
const isRowSelected = row.getIsSelected()
|
||||
console.log({ isMultiSelectMode, isRowSelected })
|
||||
if (isRowSelected && isMultiSelectMode) {
|
||||
row.toggleSelected(false)
|
||||
} else if (isRowSelected && !isMultiSelectMode) {
|
||||
@@ -282,8 +297,8 @@ export function DirectoryContentTableContent() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleRowDoubleClick = (row: Row<DirectoryItem>) => {
|
||||
if (row.original.kind === "directory") {
|
||||
const handleRowDoubleClick = (row: Row<FileSystemItem>) => {
|
||||
if (row.original.kind === FileType.Directory) {
|
||||
navigate({
|
||||
to: `/directories/${row.original.doc._id}`,
|
||||
})
|
||||
@@ -355,8 +370,8 @@ function FileItemRow({
|
||||
onContextMenu,
|
||||
onDoubleClick,
|
||||
}: {
|
||||
table: TableType<DirectoryItem>
|
||||
row: Row<DirectoryItem>
|
||||
table: TableType<FileSystemItem>
|
||||
row: Row<FileSystemItem>
|
||||
onClick: () => void
|
||||
onContextMenu: (e: React.MouseEvent) => void
|
||||
onDoubleClick: () => void
|
||||
@@ -366,19 +381,19 @@ function FileItemRow({
|
||||
|
||||
const { isDraggedOver, dropHandlers } = useFileDrop({
|
||||
destItem:
|
||||
row.original.kind === "directory"
|
||||
row.original.kind === FileType.Directory
|
||||
? newDirectoryHandle(row.original.doc._id)
|
||||
: null,
|
||||
dragInfoAtom,
|
||||
})
|
||||
|
||||
const handleDragStart = (e: React.DragEvent) => {
|
||||
let source: FileSystemHandle
|
||||
const handleDragStart = (_e: React.DragEvent) => {
|
||||
let source: DirectoryHandle | FileHandle
|
||||
switch (row.original.kind) {
|
||||
case "file":
|
||||
case FileType.File:
|
||||
source = newFileHandle(row.original.doc._id)
|
||||
break
|
||||
case "directory":
|
||||
case FileType.Directory:
|
||||
source = newDirectoryHandle(row.original.doc._id)
|
||||
break
|
||||
}
|
||||
@@ -386,15 +401,9 @@ function FileItemRow({
|
||||
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)
|
||||
}
|
||||
})
|
||||
draggedItems = table
|
||||
.getSelectedRowModel()
|
||||
.rows.map((row) => newFileSystemHandle(row.original))
|
||||
if (!draggedItems.some((item) => isSameHandle(item, source))) {
|
||||
draggedItems.push(source)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import type { Doc, Id } from "@fileone/convex/_generated/dataModel"
|
||||
import type {
|
||||
DirectoryItem,
|
||||
DirectoryItemKind,
|
||||
} from "@fileone/convex/model/directories"
|
||||
import type { FileType } from "@fileone/convex/model/filesystem"
|
||||
import type { DirectoryItemKind } from "@fileone/convex/model/directories"
|
||||
import type { FileSystemItem, FileType } from "@fileone/convex/model/filesystem"
|
||||
import type { RowSelectionState } from "@tanstack/react-table"
|
||||
import { atom } from "jotai"
|
||||
import type { FileDragInfo } from "../../files/use-file-drop"
|
||||
|
||||
export const contextMenuTargeItemAtom = atom<DirectoryItem | null>(null)
|
||||
export const contextMenuTargeItemsAtom = atom<FileSystemItem[]>([])
|
||||
export const optimisticDeletedItemsAtom = atom(
|
||||
new Set<Id<"files"> | Id<"directories">>(),
|
||||
)
|
||||
@@ -18,8 +15,7 @@ export const selectedFileRowsAtom = atom<RowSelectionState>({})
|
||||
export const newFileTypeAtom = atom<FileType | null>(null)
|
||||
|
||||
export const itemBeingRenamedAtom = atom<{
|
||||
kind: DirectoryItemKind
|
||||
originalItem: DirectoryItem
|
||||
originalItem: FileSystemItem
|
||||
name: string
|
||||
} | null>(null)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user