mirror of
https://github.com/get-drexa/drive.git
synced 2025-12-01 05:51:39 +00:00
Compare commits
5 Commits
3209ce1cd2
...
d2c09f5d0f
| Author | SHA1 | Date | |
|---|---|---|---|
|
d2c09f5d0f
|
|||
|
952a0e41b4
|
|||
|
8f194eec55
|
|||
|
a8c7a8f60b
|
|||
|
7fe5184e81
|
21
apps/drive-web/src/components/ui/middle-truncated-text.tsx
Normal file
21
apps/drive-web/src/components/ui/middle-truncated-text.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function MiddleTruncatedText({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
children: string
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
const LAST_PART_LENGTH = 3
|
||||||
|
const lastPart = children.slice(children.length - LAST_PART_LENGTH)
|
||||||
|
const firstPart = children.slice(0, children.length - LAST_PART_LENGTH)
|
||||||
|
return (
|
||||||
|
<p className={cn("max-w-full flex", className)}>
|
||||||
|
<span className="flex-1 truncate">{firstPart}</span>
|
||||||
|
<span className="w-min">{lastPart}</span>
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { MiddleTruncatedText }
|
||||||
@@ -3,8 +3,8 @@ import { Link, useLocation } from "@tanstack/react-router"
|
|||||||
import { useQuery as useConvexQuery } from "convex/react"
|
import { useQuery as useConvexQuery } from "convex/react"
|
||||||
import { useAtomValue } from "jotai"
|
import { useAtomValue } from "jotai"
|
||||||
import {
|
import {
|
||||||
|
ClockIcon,
|
||||||
FilesIcon,
|
FilesIcon,
|
||||||
HomeIcon,
|
|
||||||
LogOutIcon,
|
LogOutIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
@@ -66,10 +66,10 @@ function MainSidebarMenu() {
|
|||||||
return (
|
return (
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton asChild isActive={isActive("/")}>
|
<SidebarMenuButton asChild isActive={isActive("/recent")}>
|
||||||
<Link to="/">
|
<Link to="/recent">
|
||||||
<HomeIcon />
|
<ClockIcon />
|
||||||
<span>Home</span>
|
<span>Recent</span>
|
||||||
</Link>
|
</Link>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
|
|||||||
84
apps/drive-web/src/files/file-grid.tsx
Normal file
84
apps/drive-web/src/files/file-grid.tsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
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<Id<"files">>
|
||||||
|
|
||||||
|
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],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid auto-cols-max grid-flow-col gap-3">
|
||||||
|
{files.map((file) => (
|
||||||
|
<FileGridItem
|
||||||
|
selected={selectedFiles.has(file._id)}
|
||||||
|
key={file._id}
|
||||||
|
file={file}
|
||||||
|
onSelect={onItemSelect}
|
||||||
|
onContextMenu={onItemContextMenu}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
key={file._id}
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col gap-2 items-center justify-center w-24 p-[calc(var(--spacing)*1+1px)] rounded-md",
|
||||||
|
{ "bg-muted border border-border p-1": selected },
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
onSelect?.(file)
|
||||||
|
}}
|
||||||
|
onContextMenu={(event) => {
|
||||||
|
onContextMenu?.(file, event)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextFileIcon className="size-10" />
|
||||||
|
<MiddleTruncatedText className="text-sm">
|
||||||
|
{file.name}
|
||||||
|
</MiddleTruncatedText>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export { FileGridItem }
|
||||||
@@ -15,6 +15,7 @@ import { Route as AuthenticatedRouteImport } from './routes/_authenticated'
|
|||||||
import { Route as AuthenticatedIndexRouteImport } from './routes/_authenticated/index'
|
import { Route as AuthenticatedIndexRouteImport } from './routes/_authenticated/index'
|
||||||
import { Route as LoginCallbackRouteImport } from './routes/login_.callback'
|
import { Route as LoginCallbackRouteImport } from './routes/login_.callback'
|
||||||
import { Route as AuthenticatedSidebarLayoutRouteImport } from './routes/_authenticated/_sidebar-layout'
|
import { Route as AuthenticatedSidebarLayoutRouteImport } from './routes/_authenticated/_sidebar-layout'
|
||||||
|
import { Route as AuthenticatedSidebarLayoutRecentRouteImport } from './routes/_authenticated/_sidebar-layout/recent'
|
||||||
import { Route as AuthenticatedSidebarLayoutHomeRouteImport } from './routes/_authenticated/_sidebar-layout/home'
|
import { Route as AuthenticatedSidebarLayoutHomeRouteImport } from './routes/_authenticated/_sidebar-layout/home'
|
||||||
import { Route as AuthenticatedSidebarLayoutDirectoriesDirectoryIdRouteImport } from './routes/_authenticated/_sidebar-layout/directories.$directoryId'
|
import { Route as AuthenticatedSidebarLayoutDirectoriesDirectoryIdRouteImport } from './routes/_authenticated/_sidebar-layout/directories.$directoryId'
|
||||||
import { Route as AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRouteImport } from './routes/_authenticated/_sidebar-layout/trash.directories.$directoryId'
|
import { Route as AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRouteImport } from './routes/_authenticated/_sidebar-layout/trash.directories.$directoryId'
|
||||||
@@ -48,6 +49,12 @@ const AuthenticatedSidebarLayoutRoute =
|
|||||||
id: '/_sidebar-layout',
|
id: '/_sidebar-layout',
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
getParentRoute: () => AuthenticatedRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AuthenticatedSidebarLayoutRecentRoute =
|
||||||
|
AuthenticatedSidebarLayoutRecentRouteImport.update({
|
||||||
|
id: '/recent',
|
||||||
|
path: '/recent',
|
||||||
|
getParentRoute: () => AuthenticatedSidebarLayoutRoute,
|
||||||
|
} as any)
|
||||||
const AuthenticatedSidebarLayoutHomeRoute =
|
const AuthenticatedSidebarLayoutHomeRoute =
|
||||||
AuthenticatedSidebarLayoutHomeRouteImport.update({
|
AuthenticatedSidebarLayoutHomeRouteImport.update({
|
||||||
id: '/home',
|
id: '/home',
|
||||||
@@ -73,6 +80,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/login/callback': typeof LoginCallbackRoute
|
'/login/callback': typeof LoginCallbackRoute
|
||||||
'/': typeof AuthenticatedIndexRoute
|
'/': typeof AuthenticatedIndexRoute
|
||||||
'/home': typeof AuthenticatedSidebarLayoutHomeRoute
|
'/home': typeof AuthenticatedSidebarLayoutHomeRoute
|
||||||
|
'/recent': typeof AuthenticatedSidebarLayoutRecentRoute
|
||||||
'/directories/$directoryId': typeof AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute
|
'/directories/$directoryId': typeof AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute
|
||||||
'/trash/directories/$directoryId': typeof AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute
|
'/trash/directories/$directoryId': typeof AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute
|
||||||
}
|
}
|
||||||
@@ -82,6 +90,7 @@ export interface FileRoutesByTo {
|
|||||||
'/login/callback': typeof LoginCallbackRoute
|
'/login/callback': typeof LoginCallbackRoute
|
||||||
'/': typeof AuthenticatedIndexRoute
|
'/': typeof AuthenticatedIndexRoute
|
||||||
'/home': typeof AuthenticatedSidebarLayoutHomeRoute
|
'/home': typeof AuthenticatedSidebarLayoutHomeRoute
|
||||||
|
'/recent': typeof AuthenticatedSidebarLayoutRecentRoute
|
||||||
'/directories/$directoryId': typeof AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute
|
'/directories/$directoryId': typeof AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute
|
||||||
'/trash/directories/$directoryId': typeof AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute
|
'/trash/directories/$directoryId': typeof AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute
|
||||||
}
|
}
|
||||||
@@ -94,6 +103,7 @@ export interface FileRoutesById {
|
|||||||
'/login_/callback': typeof LoginCallbackRoute
|
'/login_/callback': typeof LoginCallbackRoute
|
||||||
'/_authenticated/': typeof AuthenticatedIndexRoute
|
'/_authenticated/': typeof AuthenticatedIndexRoute
|
||||||
'/_authenticated/_sidebar-layout/home': typeof AuthenticatedSidebarLayoutHomeRoute
|
'/_authenticated/_sidebar-layout/home': typeof AuthenticatedSidebarLayoutHomeRoute
|
||||||
|
'/_authenticated/_sidebar-layout/recent': typeof AuthenticatedSidebarLayoutRecentRoute
|
||||||
'/_authenticated/_sidebar-layout/directories/$directoryId': typeof AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute
|
'/_authenticated/_sidebar-layout/directories/$directoryId': typeof AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute
|
||||||
'/_authenticated/_sidebar-layout/trash/directories/$directoryId': typeof AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute
|
'/_authenticated/_sidebar-layout/trash/directories/$directoryId': typeof AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute
|
||||||
}
|
}
|
||||||
@@ -105,6 +115,7 @@ export interface FileRouteTypes {
|
|||||||
| '/login/callback'
|
| '/login/callback'
|
||||||
| '/'
|
| '/'
|
||||||
| '/home'
|
| '/home'
|
||||||
|
| '/recent'
|
||||||
| '/directories/$directoryId'
|
| '/directories/$directoryId'
|
||||||
| '/trash/directories/$directoryId'
|
| '/trash/directories/$directoryId'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
@@ -114,6 +125,7 @@ export interface FileRouteTypes {
|
|||||||
| '/login/callback'
|
| '/login/callback'
|
||||||
| '/'
|
| '/'
|
||||||
| '/home'
|
| '/home'
|
||||||
|
| '/recent'
|
||||||
| '/directories/$directoryId'
|
| '/directories/$directoryId'
|
||||||
| '/trash/directories/$directoryId'
|
| '/trash/directories/$directoryId'
|
||||||
id:
|
id:
|
||||||
@@ -125,6 +137,7 @@ export interface FileRouteTypes {
|
|||||||
| '/login_/callback'
|
| '/login_/callback'
|
||||||
| '/_authenticated/'
|
| '/_authenticated/'
|
||||||
| '/_authenticated/_sidebar-layout/home'
|
| '/_authenticated/_sidebar-layout/home'
|
||||||
|
| '/_authenticated/_sidebar-layout/recent'
|
||||||
| '/_authenticated/_sidebar-layout/directories/$directoryId'
|
| '/_authenticated/_sidebar-layout/directories/$directoryId'
|
||||||
| '/_authenticated/_sidebar-layout/trash/directories/$directoryId'
|
| '/_authenticated/_sidebar-layout/trash/directories/$directoryId'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
@@ -180,6 +193,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AuthenticatedSidebarLayoutRouteImport
|
preLoaderRoute: typeof AuthenticatedSidebarLayoutRouteImport
|
||||||
parentRoute: typeof AuthenticatedRoute
|
parentRoute: typeof AuthenticatedRoute
|
||||||
}
|
}
|
||||||
|
'/_authenticated/_sidebar-layout/recent': {
|
||||||
|
id: '/_authenticated/_sidebar-layout/recent'
|
||||||
|
path: '/recent'
|
||||||
|
fullPath: '/recent'
|
||||||
|
preLoaderRoute: typeof AuthenticatedSidebarLayoutRecentRouteImport
|
||||||
|
parentRoute: typeof AuthenticatedSidebarLayoutRoute
|
||||||
|
}
|
||||||
'/_authenticated/_sidebar-layout/home': {
|
'/_authenticated/_sidebar-layout/home': {
|
||||||
id: '/_authenticated/_sidebar-layout/home'
|
id: '/_authenticated/_sidebar-layout/home'
|
||||||
path: '/home'
|
path: '/home'
|
||||||
@@ -206,6 +226,7 @@ declare module '@tanstack/react-router' {
|
|||||||
|
|
||||||
interface AuthenticatedSidebarLayoutRouteChildren {
|
interface AuthenticatedSidebarLayoutRouteChildren {
|
||||||
AuthenticatedSidebarLayoutHomeRoute: typeof AuthenticatedSidebarLayoutHomeRoute
|
AuthenticatedSidebarLayoutHomeRoute: typeof AuthenticatedSidebarLayoutHomeRoute
|
||||||
|
AuthenticatedSidebarLayoutRecentRoute: typeof AuthenticatedSidebarLayoutRecentRoute
|
||||||
AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute: typeof AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute
|
AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute: typeof AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute
|
||||||
AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute: typeof AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute
|
AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute: typeof AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute
|
||||||
}
|
}
|
||||||
@@ -213,6 +234,8 @@ interface AuthenticatedSidebarLayoutRouteChildren {
|
|||||||
const AuthenticatedSidebarLayoutRouteChildren: AuthenticatedSidebarLayoutRouteChildren =
|
const AuthenticatedSidebarLayoutRouteChildren: AuthenticatedSidebarLayoutRouteChildren =
|
||||||
{
|
{
|
||||||
AuthenticatedSidebarLayoutHomeRoute: AuthenticatedSidebarLayoutHomeRoute,
|
AuthenticatedSidebarLayoutHomeRoute: AuthenticatedSidebarLayoutHomeRoute,
|
||||||
|
AuthenticatedSidebarLayoutRecentRoute:
|
||||||
|
AuthenticatedSidebarLayoutRecentRoute,
|
||||||
AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute:
|
AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute:
|
||||||
AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute,
|
AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute,
|
||||||
AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute:
|
AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute:
|
||||||
|
|||||||
@@ -196,7 +196,6 @@ function _DirectoryContentTable() {
|
|||||||
const { mutate: openFile } = useMutation({
|
const { mutate: openFile } = useMutation({
|
||||||
mutationFn: useConvexMutation(api.filesystem.openFile),
|
mutationFn: useConvexMutation(api.filesystem.openFile),
|
||||||
onSuccess: (openedFile: OpenedFile) => {
|
onSuccess: (openedFile: OpenedFile) => {
|
||||||
console.log("openedFile", openedFile)
|
|
||||||
setOpenedFile(openedFile)
|
setOpenedFile(openedFile)
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import { api } from "@fileone/convex/api"
|
||||||
|
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<Doc<"files"> | null>(null)
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return (
|
||||||
|
<main className="p-4">
|
||||||
|
<RecentFilesContextMenu>
|
||||||
|
<RecentFilesGrid />
|
||||||
|
</RecentFilesContextMenu>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<FileGrid
|
||||||
|
files={recentFiles ?? []}
|
||||||
|
selectedFiles={selectedFiles}
|
||||||
|
onSelectionChange={setSelectedFiles}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<ContextMenu>
|
||||||
|
<ContextMenuTrigger asChild>
|
||||||
|
<div>{children}</div>
|
||||||
|
</ContextMenuTrigger>
|
||||||
|
{targetItem && (
|
||||||
|
<ContextMenuContent>
|
||||||
|
<ContextMenuItem>
|
||||||
|
<Link
|
||||||
|
to={`/directories/${targetItem.directoryId}`}
|
||||||
|
className="flex flex-row items-center gap-2"
|
||||||
|
>
|
||||||
|
<FolderInputIcon />
|
||||||
|
Open in directory
|
||||||
|
</Link>
|
||||||
|
</ContextMenuItem>
|
||||||
|
<ContextMenuItem
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => {
|
||||||
|
moveToTrash({
|
||||||
|
handles: [newFileHandle(targetItem._id)],
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TrashIcon />
|
||||||
|
Move to trash
|
||||||
|
</ContextMenuItem>
|
||||||
|
</ContextMenuContent>
|
||||||
|
)}
|
||||||
|
</ContextMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -36,7 +36,6 @@ import { DirectoryPageContext } from "@/directories/directory-page/context"
|
|||||||
import { DirectoryContentTable } from "@/directories/directory-page/directory-content-table"
|
import { DirectoryContentTable } from "@/directories/directory-page/directory-content-table"
|
||||||
import { DirectoryPageSkeleton } from "@/directories/directory-page/directory-page-skeleton"
|
import { DirectoryPageSkeleton } from "@/directories/directory-page/directory-page-skeleton"
|
||||||
import { DirectoryPathBreadcrumb } from "@/directories/directory-path-breadcrumb"
|
import { DirectoryPathBreadcrumb } from "@/directories/directory-path-breadcrumb"
|
||||||
import { FilePreviewDialog } from "@/files/file-preview-dialog"
|
|
||||||
import type { FileDragInfo } from "@/files/use-file-drop"
|
import type { FileDragInfo } from "@/files/use-file-drop"
|
||||||
import { backgroundTaskProgressAtom } from "../../../dashboard/state"
|
import { backgroundTaskProgressAtom } from "../../../dashboard/state"
|
||||||
|
|
||||||
@@ -136,18 +135,6 @@ function RouteComponent() {
|
|||||||
|
|
||||||
<DeleteConfirmationDialog />
|
<DeleteConfirmationDialog />
|
||||||
<EmptyTrashConfirmationDialog />
|
<EmptyTrashConfirmationDialog />
|
||||||
|
|
||||||
<WithAtom atom={openedFileAtom}>
|
|
||||||
{(openedFile, setOpenedFile) => {
|
|
||||||
if (!openedFile) return null
|
|
||||||
return (
|
|
||||||
<FilePreviewDialog
|
|
||||||
file={openedFile}
|
|
||||||
onClose={() => setOpenedFile(null)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</WithAtom>
|
|
||||||
</DirectoryPageContext>
|
</DirectoryPageContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ export const Route = createFileRoute("/_authenticated/")({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return <Navigate replace to="/home" />
|
return <Navigate replace to="/recent" />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,3 +188,12 @@ export const openFile = authenticatedMutation({
|
|||||||
return await FileSystem.openFile(ctx, { fileId })
|
return await FileSystem.openFile(ctx, { fileId })
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const fetchRecentFiles = authenticatedQuery({
|
||||||
|
args: {
|
||||||
|
limit: v.number(),
|
||||||
|
},
|
||||||
|
handler: async (ctx, { limit }) => {
|
||||||
|
return await FileSystem.fetchRecentFiles(ctx, { limit })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|||||||
@@ -265,3 +265,19 @@ export async function openFile(
|
|||||||
shareToken: newFileShare.shareToken,
|
shareToken: newFileShare.shareToken,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchRecentFiles(
|
||||||
|
ctx: AuthenticatedQueryCtx,
|
||||||
|
{ limit }: { limit: number },
|
||||||
|
) {
|
||||||
|
return await ctx.db
|
||||||
|
.query("files")
|
||||||
|
.withIndex("byLastAccessedAt", (q) =>
|
||||||
|
q
|
||||||
|
.eq("userId", ctx.user._id)
|
||||||
|
.eq("deletedAt", undefined)
|
||||||
|
.gte("lastAccessedAt", 0),
|
||||||
|
)
|
||||||
|
.order("desc")
|
||||||
|
.take(limit)
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const schema = defineSchema({
|
|||||||
.index("byDirectoryId", ["userId", "directoryId", "deletedAt"])
|
.index("byDirectoryId", ["userId", "directoryId", "deletedAt"])
|
||||||
.index("byUserId", ["userId", "deletedAt"])
|
.index("byUserId", ["userId", "deletedAt"])
|
||||||
.index("byDeletedAt", ["deletedAt"])
|
.index("byDeletedAt", ["deletedAt"])
|
||||||
.index("byLastAccessedAt", ["userId", "lastAccessedAt"])
|
.index("byLastAccessedAt", ["userId", "deletedAt", "lastAccessedAt"])
|
||||||
.index("uniqueFileInDirectory", [
|
.index("uniqueFileInDirectory", [
|
||||||
"userId",
|
"userId",
|
||||||
"directoryId",
|
"directoryId",
|
||||||
|
|||||||
Reference in New Issue
Block a user