mirror of
https://github.com/get-drexa/drive.git
synced 2025-11-30 21:41:39 +00:00
Compare commits
2 Commits
ad99bca7fd
...
5cc13a34b2
| Author | SHA1 | Date | |
|---|---|---|---|
|
5cc13a34b2
|
|||
|
879287f8bf
|
@@ -1,15 +1,25 @@
|
|||||||
import { api } from "@fileone/convex/api"
|
import { api } from "@fileone/convex/api"
|
||||||
import { Link, useLocation } from "@tanstack/react-router"
|
import { newDirectoryHandle } from "@fileone/convex/filesystem"
|
||||||
import { useQuery as useConvexQuery } from "convex/react"
|
import { useMutation } from "@tanstack/react-query"
|
||||||
import { useAtomValue } from "jotai"
|
import { Link, useLocation, useParams } from "@tanstack/react-router"
|
||||||
import {
|
import {
|
||||||
|
useMutation as useConvexMutation,
|
||||||
|
useQuery as useConvexQuery,
|
||||||
|
} from "convex/react"
|
||||||
|
import { useAtomValue, useSetAtom, useStore } from "jotai"
|
||||||
|
import {
|
||||||
|
CircleXIcon,
|
||||||
ClockIcon,
|
ClockIcon,
|
||||||
FilesIcon,
|
FilesIcon,
|
||||||
|
FolderInputIcon,
|
||||||
LogOutIcon,
|
LogOutIcon,
|
||||||
|
ScissorsIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
User2Icon,
|
User2Icon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -26,7 +36,10 @@ import {
|
|||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from "@/components/ui/sidebar"
|
} from "@/components/ui/sidebar"
|
||||||
|
import { formatError } from "@/lib/error"
|
||||||
|
import { Button } from "../components/ui/button"
|
||||||
import { LoadingSpinner } from "../components/ui/loading-spinner"
|
import { LoadingSpinner } from "../components/ui/loading-spinner"
|
||||||
|
import { clearCutItemsAtom, cutHandlesAtom } from "../files/store"
|
||||||
import { backgroundTaskProgressAtom } from "./state"
|
import { backgroundTaskProgressAtom } from "./state"
|
||||||
|
|
||||||
export function DashboardSidebar() {
|
export function DashboardSidebar() {
|
||||||
@@ -46,6 +59,7 @@ export function DashboardSidebar() {
|
|||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
|
<CutItemsCard />
|
||||||
<BackgroundTaskProgressItem />
|
<BackgroundTaskProgressItem />
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
@@ -134,6 +148,93 @@ function BackgroundTaskProgressItem() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the number of cut items and allows the user to perform actions on them, such as moving them to a target directory.
|
||||||
|
* Visible when there are cut items.
|
||||||
|
*/
|
||||||
|
function CutItemsCard() {
|
||||||
|
const { directoryId } = useParams({ strict: false })
|
||||||
|
const cutHandles = useAtomValue(cutHandlesAtom)
|
||||||
|
const clearCutItems = useSetAtom(clearCutItemsAtom)
|
||||||
|
const setCutHandles = useSetAtom(cutHandlesAtom)
|
||||||
|
const setBackgroundTaskProgress = useSetAtom(backgroundTaskProgressAtom)
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
const _moveItems = useConvexMutation(api.filesystem.moveItems)
|
||||||
|
const { mutate: moveItems } = useMutation({
|
||||||
|
mutationFn: _moveItems,
|
||||||
|
onMutate: () => {
|
||||||
|
setBackgroundTaskProgress({
|
||||||
|
label: "Moving items…",
|
||||||
|
})
|
||||||
|
const cutHandles = store.get(cutHandlesAtom)
|
||||||
|
clearCutItems()
|
||||||
|
return { cutHandles }
|
||||||
|
},
|
||||||
|
onError: (error, _variables, context) => {
|
||||||
|
if (context?.cutHandles) {
|
||||||
|
setCutHandles(context.cutHandles)
|
||||||
|
}
|
||||||
|
toast.error("Failed to move items", {
|
||||||
|
description: formatError(error),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Items moved")
|
||||||
|
},
|
||||||
|
onSettled: () => {
|
||||||
|
setBackgroundTaskProgress(null)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (cutHandles.length === 0) return null
|
||||||
|
|
||||||
|
const moveCutItems = () => {
|
||||||
|
if (directoryId) {
|
||||||
|
moveItems({
|
||||||
|
targetDirectory: newDirectoryHandle(directoryId),
|
||||||
|
items: cutHandles,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<Card className="p-0 gap-0 rounded-md overflow-clip">
|
||||||
|
<CardHeader className="px-3.5 py-1.5! gap-0 border-b border-b-primary-foreground/10 bg-primary text-primary-foreground">
|
||||||
|
<CardTitle className="p-0 m-0 text-xs uppercase">
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<ScissorsIcon size={16} /> {cutHandles.length} Cut
|
||||||
|
Items
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardFooter className="p-1 flex flex-col">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="w-full justify-start transition-none"
|
||||||
|
disabled={!directoryId}
|
||||||
|
onClick={moveCutItems}
|
||||||
|
>
|
||||||
|
<FolderInputIcon size={16} />
|
||||||
|
Move items here
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="w-full justify-start transition-none"
|
||||||
|
onClick={() => clearCutItems()}
|
||||||
|
>
|
||||||
|
<CircleXIcon size={16} />
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function UserMenu() {
|
function UserMenu() {
|
||||||
function handleSignOut() {}
|
function handleSignOut() {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { FileSystemHandle } from "@fileone/convex/filesystem"
|
||||||
import { atom } from "jotai"
|
import { atom } from "jotai"
|
||||||
import { atomFamily } from "jotai/utils"
|
import { atomFamily } from "jotai/utils"
|
||||||
|
|
||||||
@@ -92,3 +93,8 @@ export const hasFileUploadsErrorAtom = atom((get) => {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const cutHandlesAtom = atom<FileSystemHandle[]>([])
|
||||||
|
export const clearCutItemsAtom = atom(null, (_, set) => {
|
||||||
|
set(cutHandlesAtom, [])
|
||||||
|
})
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { atom, useAtom, useAtomValue, useSetAtom, useStore } from "jotai"
|
|||||||
import {
|
import {
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
|
ScissorsIcon,
|
||||||
TextCursorInputIcon,
|
TextCursorInputIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
@@ -46,7 +47,7 @@ import { NewDirectoryDialog } from "@/directories/directory-page/new-directory-d
|
|||||||
import { RenameFileDialog } from "@/directories/directory-page/rename-file-dialog"
|
import { RenameFileDialog } from "@/directories/directory-page/rename-file-dialog"
|
||||||
import { DirectoryPathBreadcrumb } from "@/directories/directory-path-breadcrumb"
|
import { DirectoryPathBreadcrumb } from "@/directories/directory-path-breadcrumb"
|
||||||
import { FilePreviewDialog } from "@/files/file-preview-dialog"
|
import { FilePreviewDialog } from "@/files/file-preview-dialog"
|
||||||
import { inProgressFileUploadCountAtom } from "@/files/store"
|
import { cutHandlesAtom, inProgressFileUploadCountAtom } from "@/files/store"
|
||||||
import { UploadFileDialog } from "@/files/upload-file-dialog"
|
import { UploadFileDialog } from "@/files/upload-file-dialog"
|
||||||
import type { FileDragInfo } from "@/files/use-file-drop"
|
import type { FileDragInfo } from "@/files/use-file-drop"
|
||||||
|
|
||||||
@@ -250,6 +251,7 @@ function DirectoryContentContextMenu({
|
|||||||
const [target, setTarget] = useAtom(contextMenuTargetItemsAtom)
|
const [target, setTarget] = useAtom(contextMenuTargetItemsAtom)
|
||||||
const setOptimisticDeletedItems = useSetAtom(optimisticDeletedItemsAtom)
|
const setOptimisticDeletedItems = useSetAtom(optimisticDeletedItemsAtom)
|
||||||
const setBackgroundTaskProgress = useSetAtom(backgroundTaskProgressAtom)
|
const setBackgroundTaskProgress = useSetAtom(backgroundTaskProgressAtom)
|
||||||
|
const setCutHandles = useSetAtom(cutHandlesAtom)
|
||||||
const moveToTrashMutation = useContextMutation(api.filesystem.moveToTrash)
|
const moveToTrashMutation = useContextMutation(api.filesystem.moveToTrash)
|
||||||
|
|
||||||
const { mutate: moveToTrash } = useMutation({
|
const { mutate: moveToTrash } = useMutation({
|
||||||
@@ -293,6 +295,13 @@ function DirectoryContentContextMenu({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleCut = () => {
|
||||||
|
const selectedItems = store.get(contextMenuTargetItemsAtom)
|
||||||
|
if (selectedItems.length > 0) {
|
||||||
|
setCutHandles(selectedItems.map(newFileSystemHandle))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
const selectedItems = store.get(contextMenuTargetItemsAtom)
|
const selectedItems = store.get(contextMenuTargetItemsAtom)
|
||||||
if (selectedItems.length > 0) {
|
if (selectedItems.length > 0) {
|
||||||
@@ -314,6 +323,10 @@ function DirectoryContentContextMenu({
|
|||||||
{target.length > 0 && (
|
{target.length > 0 && (
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
<RenameMenuItem />
|
<RenameMenuItem />
|
||||||
|
<ContextMenuItem onClick={handleCut}>
|
||||||
|
<ScissorsIcon />
|
||||||
|
Cut
|
||||||
|
</ContextMenuItem>
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ export type DeleteResult = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function newFileSystemHandle(item: FileSystemItem): FileSystemHandle {
|
export function newFileSystemHandle(item: FileSystemItem): FileSystemHandle {
|
||||||
console.log("item", item)
|
|
||||||
switch (item.kind) {
|
switch (item.kind) {
|
||||||
case FileType.File:
|
case FileType.File:
|
||||||
return { kind: item.kind, id: item.doc._id }
|
return { kind: item.kind, id: item.doc._id }
|
||||||
|
|||||||
Reference in New Issue
Block a user