From 3dfcdd84cf07b11d2033103aaef0e6ae8daf18f5 Mon Sep 17 00:00:00 2001 From: kenneth Date: Fri, 26 Sep 2025 23:04:02 +0000 Subject: [PATCH] feat: new directory dialog repalces the new item table row --- packages/convex/model/filesystem.ts | 5 + packages/web/src/components/with-atom.tsx | 15 +++ .../directory-content-table.tsx | 109 +----------------- .../directory-page/directory-page.tsx | 34 ++++-- .../directory-page/new-directory-dialog.tsx | 72 ++++++++++++ .../src/directories/directory-page/state.ts | 3 +- 6 files changed, 119 insertions(+), 119 deletions(-) create mode 100644 packages/web/src/components/with-atom.tsx create mode 100644 packages/web/src/directories/directory-page/new-directory-dialog.tsx diff --git a/packages/convex/model/filesystem.ts b/packages/convex/model/filesystem.ts index 3eadf1c..b965019 100644 --- a/packages/convex/model/filesystem.ts +++ b/packages/convex/model/filesystem.ts @@ -1,5 +1,10 @@ import type { Id } from "../_generated/dataModel" +export enum FileType { + File = "File", + Directory = "Directory", +} + export type DirectoryPathComponent = { handle: DirectoryHandle name: string diff --git a/packages/web/src/components/with-atom.tsx b/packages/web/src/components/with-atom.tsx new file mode 100644 index 0000000..daf7d75 --- /dev/null +++ b/packages/web/src/components/with-atom.tsx @@ -0,0 +1,15 @@ +import { type PrimitiveAtom, useAtom } from "jotai" + +export function WithAtom({ + atom, + children, +}: { + atom: PrimitiveAtom + children: ( + value: Value, + setValue: (value: Value) => void, + ) => React.ReactNode +}) { + const [value, setValue] = useAtom(atom) + return children(value, setValue) +} 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 3c34e98..332dc79 100644 --- a/packages/web/src/directories/directory-page/directory-content-table.tsx +++ b/packages/web/src/directories/directory-page/directory-content-table.tsx @@ -53,7 +53,7 @@ import { contextMenuTargeItemAtom, dragInfoAtom, itemBeingRenamedAtom, - newItemKindAtom, + newFileTypeAtom, openedFileAtom, optimisticDeletedItemsAtom, } from "./state" @@ -332,7 +332,6 @@ export function DirectoryContentTableContent() { ) : ( )} - @@ -340,10 +339,6 @@ export function DirectoryContentTableContent() { } function NoResultsRow() { - const newItemKind = useAtomValue(newItemKindAtom) - if (newItemKind) { - return null - } return ( @@ -353,108 +348,6 @@ function NoResultsRow() { ) } -function NewItemRow() { - const { directory } = useContext(DirectoryPageContext) - const inputRef = useRef(null) - const newItemFormId = useId() - const [newItemKind, setNewItemKind] = useAtom(newItemKindAtom) - const { mutate: createDirectory, isPending } = useMutation({ - mutationFn: useContextMutation(api.files.createDirectory), - onSuccess: () => { - setNewItemKind(null) - }, - onError: withDefaultOnError(() => { - setTimeout(() => { - inputRef.current?.focus() - }, 1) - }), - }) - - // Auto-focus the input when newItemKind changes to a truthy value - useEffect(() => { - if (newItemKind && inputRef.current) { - // Use requestAnimationFrame to ensure the component is fully rendered - // and the dropdown has completed its close cycle - requestAnimationFrame(() => { - if (inputRef.current) { - inputRef.current.focus() - inputRef.current.select() // Also select the default text for better UX - } - }) - } - }, [newItemKind]) - - if (!newItemKind) { - return null - } - - const onSubmit = (event: React.FormEvent) => { - event.preventDefault() - - const formData = new FormData(event.currentTarget) - const itemName = formData.get("itemName") as string - - if (itemName) { - createDirectory({ name: itemName, directoryId: directory._id }) - } else { - toast.error("Please enter a name.") - } - } - - const clearNewItemKind = () => { - // setItemBeingAdded(null) - setNewItemKind(null) - } - - return ( - - - -
- {isPending ? ( - - ) : ( - - )} -
- -
-
-
- - - {!isPending ? ( - <> - - - - ) : null} - -
- ) -} - function FileItemRow({ table, row, diff --git a/packages/web/src/directories/directory-page/directory-page.tsx b/packages/web/src/directories/directory-page/directory-page.tsx index 42a662b..d0e8c6e 100644 --- a/packages/web/src/directories/directory-page/directory-page.tsx +++ b/packages/web/src/directories/directory-page/directory-page.tsx @@ -1,7 +1,8 @@ import { api } from "@fileone/convex/_generated/api" -import type { - DirectoryHandle, - PathComponent, +import { + type DirectoryHandle, + FileType, + type PathComponent, } from "@fileone/convex/model/filesystem" import { useMutation } from "@tanstack/react-query" import { Link } from "@tanstack/react-router" @@ -38,14 +39,17 @@ import { TooltipContent, TooltipTrigger, } from "../../components/ui/tooltip" +import { WithAtom } from "../../components/with-atom" import { useFileDrop } from "../../files/use-file-drop" import { cn } from "../../lib/utils" import { DirectoryPageContext } from "./context" import { DirectoryContentTable } from "./directory-content-table" +import { NewDirectoryDialog } from "./new-directory-dialog" import { RenameFileDialog } from "./rename-file-dialog" -import { dragInfoAtom, newItemKindAtom, openedFileAtom } from "./state" +import { dragInfoAtom, newFileTypeAtom, openedFileAtom } from "./state" export function DirectoryPage() { + const { directory } = useContext(DirectoryPageContext) return ( <>
@@ -60,6 +64,19 @@ export function DirectoryPage() { + + {(newFileType, setNewFileType) => ( + { + if (!open) { + setNewFileType(null) + } + }} + /> + )} + ) } @@ -194,16 +211,15 @@ function UploadFileButton() { } function NewDirectoryItemDropdown() { - const setNewItemKind = useSetAtom(newItemKindAtom) - const newItemKind = useAtomValue(newItemKindAtom) + const [newFileType, setNewFileType] = useAtom(newFileTypeAtom) const addNewDirectory = () => { - setNewItemKind("directory") + setNewFileType(FileType.Directory) } const handleCloseAutoFocus = (event: Event) => { // If we just created a new item, prevent the dropdown from restoring focus to the trigger - if (newItemKind) { + if (newFileType) { event.preventDefault() } } @@ -236,8 +252,6 @@ function PreviewDialog() { if (!openedFile) return null - console.log("openedFile", openedFile) - switch (openedFile.mimeType) { case "image/jpeg": case "image/png": diff --git a/packages/web/src/directories/directory-page/new-directory-dialog.tsx b/packages/web/src/directories/directory-page/new-directory-dialog.tsx new file mode 100644 index 0000000..a68f13c --- /dev/null +++ b/packages/web/src/directories/directory-page/new-directory-dialog.tsx @@ -0,0 +1,72 @@ +import { api } from "@fileone/convex/_generated/api" +import type { Id } from "@fileone/convex/_generated/dataModel" +import { useMutation } from "@tanstack/react-query" +import { useMutation as useContextMutation } from "convex/react" +import { useId } from "react" +import { toast } from "sonner" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { Input } from "@/components/ui/input" + +export function NewDirectoryDialog({ + open, + onOpenChange, + directoryId, +}: { + open: boolean + onOpenChange: (open: boolean) => void + directoryId: Id<"directories"> +}) { + const formId = useId() + + const { mutate: createDirectory, isPending: isCreating } = useMutation({ + mutationFn: useContextMutation(api.files.createDirectory), + onSuccess: () => { + onOpenChange(false) + toast.success("Directory created successfully") + }, + }) + + const onSubmit = (event: React.FormEvent) => { + event.preventDefault() + + const formData = new FormData(event.currentTarget) + const name = formData.get("directoryName") as string + + if (name) { + createDirectory({ name, directoryId }) + } + } + + return ( + + + + New Directory + + +
+ +
+ + + + + + + +
+
+ ) +} diff --git a/packages/web/src/directories/directory-page/state.ts b/packages/web/src/directories/directory-page/state.ts index c823c8c..0178b47 100644 --- a/packages/web/src/directories/directory-page/state.ts +++ b/packages/web/src/directories/directory-page/state.ts @@ -3,6 +3,7 @@ import type { DirectoryItem, DirectoryItemKind, } from "@fileone/convex/model/directories" +import type { 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" @@ -14,7 +15,7 @@ export const optimisticDeletedItemsAtom = atom( export const selectedFileRowsAtom = atom({}) -export const newItemKindAtom = atom(null) +export const newFileTypeAtom = atom(null) export const itemBeingRenamedAtom = atom<{ kind: DirectoryItemKind