diff --git a/packages/convex/_generated/api.d.ts b/packages/convex/_generated/api.d.ts index e392b05..0b24462 100644 --- a/packages/convex/_generated/api.d.ts +++ b/packages/convex/_generated/api.d.ts @@ -18,6 +18,7 @@ import type * as functions from "../functions.js"; import type * as model_directories from "../model/directories.js"; import type * as model_error from "../model/error.js"; import type * as model_files from "../model/files.js"; +import type * as model_filesystem from "../model/filesystem.js"; import type * as model_user from "../model/user.js"; import type * as users from "../users.js"; @@ -35,6 +36,7 @@ declare const fullApi: ApiFromModules<{ "model/directories": typeof model_directories; "model/error": typeof model_error; "model/files": typeof model_files; + "model/filesystem": typeof model_filesystem; "model/user": typeof model_user; users: typeof users; }>; diff --git a/packages/convex/files.ts b/packages/convex/files.ts index 0f48fe2..ad186a6 100644 --- a/packages/convex/files.ts +++ b/packages/convex/files.ts @@ -54,10 +54,9 @@ export const fetchDirectory = authenticatedQuery({ export const fetchDirectoryContent = authenticatedQuery({ args: { directoryId: v.optional(v.id("directories")), - path: v.optional(v.string()), }, - handler: async (ctx, { directoryId, path }): Promise => { - return await Directories.fetchContent(ctx, { directoryId, path }) + handler: async (ctx, { directoryId }): Promise => { + return await Directories.fetchContent(ctx, { directoryId }) }, }) diff --git a/packages/convex/model/directories.ts b/packages/convex/model/directories.ts index bd52405..ba61e6f 100644 --- a/packages/convex/model/directories.ts +++ b/packages/convex/model/directories.ts @@ -1,10 +1,10 @@ import type { Doc, Id } from "@fileone/convex/_generated/dataModel" -import { joinPath, PATH_SEPARATOR } from "@fileone/path" import type { AuthenticatedMutationCtx, AuthenticatedQueryCtx, } from "../functions" import * as Err from "./error" +import type { FilePath, ReverseFilePath } from "./filesystem" type Directory = { kind: "directory" @@ -19,6 +19,8 @@ type File = { export type DirectoryItem = Directory | File export type DirectoryItemKind = DirectoryItem["kind"] +export type DirectoryInfo = Doc<"directories"> & { path: FilePath } + export async function fetchRoot(ctx: AuthenticatedQueryCtx) { return await ctx.db .query("directories") @@ -31,30 +33,36 @@ export async function fetchRoot(ctx: AuthenticatedQueryCtx) { export async function fetch( ctx: AuthenticatedQueryCtx, { directoryId }: { directoryId: Id<"directories"> }, -) { - return await ctx.db.get(directoryId) +): Promise { + const directory = await ctx.db.get(directoryId) + if (!directory) { + throw Err.create( + Err.Code.DirectoryNotFound, + `Directory ${directoryId} not found`, + ) + } + + const path: ReverseFilePath = [{ id: 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 }) + parentDirId = parentDir.parentId + } else { + throw Err.create(Err.Code.Internal) + } + } + + return { ...directory, path: path.reverse() as FilePath } } export async function fetchContent( ctx: AuthenticatedQueryCtx, - { - path, - directoryId, - }: { path?: string; directoryId?: Id<"directories"> } = {}, + { directoryId }: { directoryId?: Id<"directories"> } = {}, ): Promise { let dirId: Id<"directories"> | undefined - if (path) { - dirId = await ctx.db - .query("directories") - .withIndex("byPath", (q) => - q - .eq("userId", ctx.user._id) - .eq("path", path) - .eq("deletedAt", undefined), - ) - .first() - .then((dir) => dir?._id) - } else if (directoryId) { + if (directoryId) { dirId = directoryId } @@ -127,7 +135,6 @@ export async function create( userId: ctx.user._id, createdAt: now, updatedAt: now, - path: parentDir ? joinPath(parentDir.path, name) : joinPath("", name), }) } diff --git a/packages/convex/model/filesystem.ts b/packages/convex/model/filesystem.ts new file mode 100644 index 0000000..c281b2c --- /dev/null +++ b/packages/convex/model/filesystem.ts @@ -0,0 +1,17 @@ +import type { Id } from "../_generated/dataModel" + +export type DirectoryPathComponent = { + id: Id<"directories"> + name: string +} + +export type FilePathComponent = { + id: Id<"files"> + name: string +} + +export type PathComponent = FilePathComponent | DirectoryPathComponent + +export type FilePath = [...DirectoryPathComponent[], PathComponent] + +export type ReverseFilePath = [PathComponent, ...DirectoryPathComponent[]] diff --git a/packages/convex/model/user.ts b/packages/convex/model/user.ts index 5ffb316..632e6b1 100644 --- a/packages/convex/model/user.ts +++ b/packages/convex/model/user.ts @@ -46,7 +46,6 @@ export async function register(ctx: AuthenticatedMutationCtx) { }), ctx.db.insert("directories", { name: "", - path: "", userId: ctx.user._id, createdAt: now, updatedAt: now, diff --git a/packages/convex/schema.ts b/packages/convex/schema.ts index b1365a4..783d4b4 100644 --- a/packages/convex/schema.ts +++ b/packages/convex/schema.ts @@ -27,7 +27,6 @@ const schema = defineSchema({ ]), directories: defineTable({ name: v.string(), - path: v.string(), userId: v.id("users"), parentId: v.optional(v.id("directories")), createdAt: v.string(), @@ -41,8 +40,7 @@ const schema = defineSchema({ "parentId", "name", "deletedAt", - ]) - .index("byPath", ["userId", "path", "deletedAt"]), + ]), }) export default schema diff --git a/packages/web/src/directories/directory-page/context.ts b/packages/web/src/directories/directory-page/context.ts index 2a65743..e6bca7e 100644 --- a/packages/web/src/directories/directory-page/context.ts +++ b/packages/web/src/directories/directory-page/context.ts @@ -1,10 +1,13 @@ import type { Doc } from "@fileone/convex/_generated/dataModel" -import type { DirectoryItem } from "@fileone/convex/model/directories" +import type { + DirectoryInfo, + DirectoryItem, +} from "@fileone/convex/model/directories" import { createContext } from "react" type DirectoryPageContextType = { rootDirectory: Doc<"directories"> - directory: Doc<"directories"> + directory: DirectoryInfo directoryContent: DirectoryItem[] } diff --git a/packages/web/src/directories/directory-page/directory-page.tsx b/packages/web/src/directories/directory-page/directory-page.tsx index fa2a114..5f3f13d 100644 --- a/packages/web/src/directories/directory-page/directory-page.tsx +++ b/packages/web/src/directories/directory-page/directory-page.tsx @@ -1,5 +1,5 @@ import { api } from "@fileone/convex/_generated/api" -import { baseName, splitPath } from "@fileone/path" +import type { 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" @@ -10,7 +10,7 @@ import { PlusIcon, UploadCloudIcon, } from "lucide-react" -import { type ChangeEvent, Fragment, useContext, useRef } from "react" +import React, { type ChangeEvent, Fragment, useContext, useRef } from "react" import { toast } from "sonner" import { ImagePreviewDialog } from "@/components/image-preview-dialog" import { @@ -36,11 +36,10 @@ import { RenameFileDialog } from "./rename-file-dialog" import { newItemKindAtom, openedFileAtom } from "./state" export function DirectoryPage() { - const { directory } = useContext(DirectoryPageContext) return ( <>
- +
@@ -55,39 +54,53 @@ export function DirectoryPage() { ) } -function FilePathBreadcrumb({ path }: { path: string }) { - const { rootDirectory } = useContext(DirectoryPageContext) - const pathComponents = splitPath(path) - const base = baseName(path) +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( + + + + , + ) + } + return ( + {rootDirectory._id === directory._id ? ( + + All Files + + ) : ( + + )} + {breadcrumbItems} + - - - All Files - - + {directory.name}{" "} - {pathComponents.map((p) => ( - - - {p === base ? ( - {p} - ) : ( - - - {p} - - - )} - - ))} ) } +function FilePathBreadcrumbItem({ component }: { component: PathComponent }) { + return ( + + + + {component.name || "All Files"} + + + + ) +} + // tags: upload, uploadfile, uploadfilebutton, fileupload, fileuploadbutton function UploadFileButton() { const { directory } = useContext(DirectoryPageContext)