diff --git a/apps/drive-web/src/files/file-grid.tsx b/apps/drive-web/src/files/file-grid.tsx index 13a9666..81fc0ce 100644 --- a/apps/drive-web/src/files/file-grid.tsx +++ b/apps/drive-web/src/files/file-grid.tsx @@ -1,19 +1,84 @@ -import type { Doc } from "@fileone/convex/dataModel" -import { TextFileIcon } from "../components/icons/text-file-icon" -import { MiddleTruncatedText } from "../components/ui/middle-truncated-text" +import type { Doc, Id } from "@fileone/convex/dataModel" +import { memo, useCallback } from "react" +import { TextFileIcon } from "@/components/icons/text-file-icon" +import { MiddleTruncatedText } from "@/components/ui/middle-truncated-text" +import { cn } from "@/lib/utils" + +export type FileGridSelection = Set> + +export function FileGrid({ + files, + selectedFiles = new Set(), + onSelectionChange, + onContextMenu, +}: { + files: Doc<"files">[] + selectedFiles?: FileGridSelection + onSelectionChange?: (selection: FileGridSelection) => void + onContextMenu?: (file: Doc<"files">, event: React.MouseEvent) => void +}) { + const onItemSelect = useCallback( + (file: Doc<"files">) => { + onSelectionChange?.(new Set([file._id])) + }, + [onSelectionChange], + ) + + const onItemContextMenu = useCallback( + (file: Doc<"files">, event: React.MouseEvent) => { + onContextMenu?.(file, event) + onSelectionChange?.(new Set([file._id])) + }, + [onContextMenu, onSelectionChange], + ) -export function FileGrid({ files }: { files: Doc<"files">[] }) { return ( -
+
{files.map((file) => ( -
- - {file.name} -
+ file={file} + onSelect={onItemSelect} + onContextMenu={onItemContextMenu} + /> ))}
) } + +const FileGridItem = memo(function FileGridItem({ + selected, + file, + onSelect, + onContextMenu, +}: { + selected: boolean + file: Doc<"files"> + onSelect?: (file: Doc<"files">) => void + onContextMenu?: (file: Doc<"files">, event: React.MouseEvent) => void +}) { + return ( + + ) +}) + +export { FileGridItem } diff --git a/apps/drive-web/src/routes/_authenticated/_sidebar-layout/recent.tsx b/apps/drive-web/src/routes/_authenticated/_sidebar-layout/recent.tsx index 012ab84..fa1d528 100644 --- a/apps/drive-web/src/routes/_authenticated/_sidebar-layout/recent.tsx +++ b/apps/drive-web/src/routes/_authenticated/_sidebar-layout/recent.tsx @@ -1,22 +1,119 @@ import { api } from "@fileone/convex/api" -import { createFileRoute } from "@tanstack/react-router" -import { useQuery as useConvexQuery } from "convex/react" +import type { Doc } from "@fileone/convex/dataModel" +import { newFileHandle } from "@fileone/convex/filesystem" +import { useMutation } from "@tanstack/react-query" +import { createFileRoute, Link } from "@tanstack/react-router" +import { + useMutation as useConvexMutation, + useQuery as useConvexQuery, +} from "convex/react" +import { atom, useAtom, useAtomValue, useSetAtom } from "jotai" +import { FolderInputIcon, TrashIcon } from "lucide-react" +import { useCallback } from "react" +import { toast } from "sonner" +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuTrigger, +} from "@/components/ui/context-menu" +import { backgroundTaskProgressAtom } from "@/dashboard/state" +import type { FileGridSelection } from "@/files/file-grid" import { FileGrid } from "@/files/file-grid" +import { formatError } from "@/lib/error" export const Route = createFileRoute("/_authenticated/_sidebar-layout/recent")({ component: RouteComponent, }) +const selectedFilesAtom = atom(new Set() as FileGridSelection) +const contextMenuTargetItem = atom | null>(null) + function RouteComponent() { - const recentFiles = useConvexQuery(api.filesystem.fetchRecentFiles, { - limit: 100, - }) - - console.log("recentFiles", recentFiles) - return (
- + + +
) } + +function RecentFilesGrid() { + const recentFiles = useConvexQuery(api.filesystem.fetchRecentFiles, { + limit: 100, + }) + const [selectedFiles, setSelectedFiles] = useAtom(selectedFilesAtom) + const setContextMenuTargetItem = useSetAtom(contextMenuTargetItem) + + const handleContextMenu = useCallback( + (file: Doc<"files">, _event: React.MouseEvent) => { + setContextMenuTargetItem(file) + }, + [setContextMenuTargetItem], + ) + + return ( + + ) +} + +function RecentFilesContextMenu({ children }: { children: React.ReactNode }) { + const targetItem = useAtomValue(contextMenuTargetItem) + const setBackgroundTaskProgress = useSetAtom(backgroundTaskProgressAtom) + + const { mutate: moveToTrash } = useMutation({ + mutationFn: useConvexMutation(api.filesystem.moveToTrash), + onMutate: () => { + setBackgroundTaskProgress({ + label: "Moving to trash…", + }) + }, + onSuccess: () => { + setBackgroundTaskProgress(null) + toast.success("Moved to trash") + }, + onError: (error) => { + toast.error("Failed to move to trash", { + description: formatError(error), + }) + }, + }) + + return ( + + +
{children}
+
+ {targetItem && ( + + + + + Open in directory + + + { + moveToTrash({ + handles: [newFileHandle(targetItem._id)], + }) + }} + > + + Move to trash + + + )} +
+ ) +}