mirror of
https://github.com/get-drexa/drive.git
synced 2025-11-30 21:41:39 +00:00
Compare commits
2 Commits
1ae649850a
...
c0f852ad35
| Author | SHA1 | Date | |
|---|---|---|---|
|
c0f852ad35
|
|||
|
efd4eefa49
|
@@ -1,15 +1,64 @@
|
||||
import { type PrimitiveAtom, useAtom } from "jotai"
|
||||
import {
|
||||
type Atom,
|
||||
type ExtractAtomArgs,
|
||||
type ExtractAtomResult,
|
||||
type ExtractAtomValue,
|
||||
type PrimitiveAtom,
|
||||
type SetStateAction,
|
||||
useAtom,
|
||||
type WritableAtom,
|
||||
} from "jotai"
|
||||
import type * as React from "react"
|
||||
|
||||
export function WithAtom<Value>({
|
||||
type SetAtom<Args extends unknown[], Result> = (...args: Args) => Result
|
||||
|
||||
export function WithAtom<Value, Args extends unknown[], Result>(props: {
|
||||
atom: WritableAtom<Value, Args, Result>
|
||||
children: (
|
||||
value: Awaited<Value>,
|
||||
setAtom: SetAtom<Args, Result>,
|
||||
) => React.ReactNode
|
||||
}): React.ReactNode
|
||||
export function WithAtom<Value>(props: {
|
||||
atom: PrimitiveAtom<Value>
|
||||
children: (
|
||||
value: Awaited<Value>,
|
||||
setAtom: SetAtom<[SetStateAction<Value>], void>,
|
||||
) => React.ReactNode
|
||||
}): React.ReactNode
|
||||
export function WithAtom<Value>(props: {
|
||||
atom: Atom<Value>
|
||||
children: (value: Awaited<Value>, setAtom: never) => React.ReactNode
|
||||
}): React.ReactNode
|
||||
export function WithAtom<
|
||||
AtomType extends WritableAtom<unknown, never[], unknown>,
|
||||
>(props: {
|
||||
atom: AtomType
|
||||
children: (
|
||||
value: Awaited<ExtractAtomValue<AtomType>>,
|
||||
setAtom: SetAtom<
|
||||
ExtractAtomArgs<AtomType>,
|
||||
ExtractAtomResult<AtomType>
|
||||
>,
|
||||
) => React.ReactNode
|
||||
}): React.ReactNode
|
||||
export function WithAtom<AtomType extends Atom<unknown>>(props: {
|
||||
atom: AtomType
|
||||
children: (
|
||||
value: Awaited<ExtractAtomValue<AtomType>>,
|
||||
setAtom: never,
|
||||
) => React.ReactNode
|
||||
}): React.ReactNode
|
||||
export function WithAtom<Value, Args extends unknown[], Result>({
|
||||
atom,
|
||||
children,
|
||||
}: {
|
||||
atom: PrimitiveAtom<Value>
|
||||
atom: Atom<Value> | WritableAtom<Value, Args, Result>
|
||||
children: (
|
||||
value: Value,
|
||||
setValue: (value: Value | ((current: Value) => Value)) => void,
|
||||
value: Awaited<Value>,
|
||||
setAtom: SetAtom<Args, Result> | never,
|
||||
) => React.ReactNode
|
||||
}) {
|
||||
const [value, setValue] = useAtom(atom)
|
||||
return children(value, setValue)
|
||||
const [value, setAtom] = useAtom(atom as WritableAtom<Value, Args, Result>)
|
||||
return children(value, setAtom)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
type ColumnDef,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
type Row,
|
||||
type Table as TableType,
|
||||
useReactTable,
|
||||
@@ -40,8 +41,10 @@ import { type FileDragInfo, useFileDrop } from "../../files/use-file-drop"
|
||||
import { cn } from "../../lib/utils"
|
||||
import { DirectoryPageContext } from "./context"
|
||||
|
||||
type DirectoryContentTableItemIdFilter = Set<FileSystemItem["doc"]["_id"]>
|
||||
|
||||
type DirectoryContentTableProps = {
|
||||
filterFn: (item: FileSystemItem) => boolean
|
||||
hiddenItems: DirectoryContentTableItemIdFilter
|
||||
directoryUrlFn: (directory: Doc<"directories">) => string
|
||||
fileDragInfoAtom: PrimitiveAtom<FileDragInfo | null>
|
||||
onContextMenu: (
|
||||
@@ -150,7 +153,7 @@ function useTableColumns(
|
||||
}
|
||||
|
||||
export function DirectoryContentTable({
|
||||
filterFn,
|
||||
hiddenItems,
|
||||
directoryUrlFn,
|
||||
onContextMenu,
|
||||
fileDragInfoAtom,
|
||||
@@ -164,10 +167,18 @@ export function DirectoryContentTable({
|
||||
data: directoryContent || [],
|
||||
columns: useTableColumns(onOpenFile, directoryUrlFn),
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
enableRowSelection: true,
|
||||
enableGlobalFilter: true,
|
||||
globalFilterFn: (row, _columnId, _filterValue, _addMeta) =>
|
||||
filterFn(row.original),
|
||||
state: {
|
||||
globalFilter: hiddenItems,
|
||||
},
|
||||
globalFilterFn: (
|
||||
row,
|
||||
_columnId,
|
||||
filterValue: DirectoryContentTableItemIdFilter,
|
||||
_addMeta,
|
||||
) => !filterValue.has(row.original.doc._id),
|
||||
getRowId: (row) => row.doc._id,
|
||||
})
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
import type { DirectoryInfo } from "@fileone/convex/types"
|
||||
import { Link } from "@tanstack/react-router"
|
||||
import type { PrimitiveAtom } from "jotai"
|
||||
import { atom } from "jotai"
|
||||
import { Fragment } from "react"
|
||||
import {
|
||||
Breadcrumb,
|
||||
@@ -24,16 +25,21 @@ import type { FileDragInfo } from "@/files/use-file-drop"
|
||||
import { useFileDrop } from "@/files/use-file-drop"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
/**
|
||||
* This is a placeholder file drag info atom that always stores null and is never mutated.
|
||||
*/
|
||||
const nullFileDragInfoAtom = atom<FileDragInfo | null>(null)
|
||||
|
||||
export function DirectoryPathBreadcrumb({
|
||||
directory,
|
||||
rootLabel,
|
||||
directoryUrlFn,
|
||||
fileDragInfoAtom,
|
||||
fileDragInfoAtom = nullFileDragInfoAtom,
|
||||
}: {
|
||||
directory: DirectoryInfo
|
||||
rootLabel: string
|
||||
directoryUrlFn: (directory: Id<"directories">) => string
|
||||
fileDragInfoAtom: PrimitiveAtom<FileDragInfo | null>
|
||||
fileDragInfoAtom?: PrimitiveAtom<FileDragInfo | null>
|
||||
}) {
|
||||
const breadcrumbItems: React.ReactNode[] = [
|
||||
<FilePathBreadcrumbItem
|
||||
|
||||
@@ -2,7 +2,6 @@ import { api } from "@fileone/convex/api"
|
||||
import type { Doc, Id } from "@fileone/convex/dataModel"
|
||||
import {
|
||||
type FileSystemItem,
|
||||
FileType,
|
||||
newFileSystemHandle,
|
||||
} from "@fileone/convex/filesystem"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
@@ -37,6 +36,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { WithAtom } from "@/components/with-atom"
|
||||
import { backgroundTaskProgressAtom } from "@/dashboard/state"
|
||||
import { DirectoryPageContext } from "@/directories/directory-page/context"
|
||||
import { DirectoryContentTable } from "@/directories/directory-page/directory-content-table"
|
||||
import { DirectoryPageSkeleton } from "@/directories/directory-page/directory-page-skeleton"
|
||||
@@ -83,6 +83,16 @@ const itemBeingRenamedAtom = atom<{
|
||||
name: string
|
||||
} | null>(null)
|
||||
|
||||
const tableFilterAtom = atom((get) => {
|
||||
const optimisticDeletedItems = get(optimisticDeletedItemsAtom)
|
||||
console.log("optimisticDeletedItems", optimisticDeletedItems)
|
||||
return (item: FileSystemItem) => {
|
||||
const test = !optimisticDeletedItems.has(item.doc._id)
|
||||
console.log("test", test)
|
||||
return test
|
||||
}
|
||||
})
|
||||
|
||||
// MARK: page entry
|
||||
function RouteComponent() {
|
||||
const { directoryId } = Route.useParams()
|
||||
@@ -161,13 +171,17 @@ function RouteComponent() {
|
||||
{/* DirectoryContentContextMenu must wrap div instead of DirectoryContentTable, otherwise radix will throw "event.preventDefault is not a function" error, idk why */}
|
||||
<DirectoryContentContextMenu>
|
||||
<div className="w-full">
|
||||
<WithAtom atom={optimisticDeletedItemsAtom}>
|
||||
{(optimisticDeletedItems) => (
|
||||
<DirectoryContentTable
|
||||
filterFn={tableFilter}
|
||||
hiddenItems={optimisticDeletedItems}
|
||||
directoryUrlFn={directoryUrlFn}
|
||||
fileDragInfoAtom={fileDragInfoAtom}
|
||||
onContextMenu={handleContextMenuRequest}
|
||||
onOpenFile={openFile}
|
||||
/>
|
||||
)}
|
||||
</WithAtom>
|
||||
</div>
|
||||
</DirectoryContentContextMenu>
|
||||
|
||||
@@ -225,8 +239,9 @@ function RouteComponent() {
|
||||
}
|
||||
|
||||
// ==================================
|
||||
// MARK: DirectoryContentContextMenu
|
||||
// MARK: ctx menu
|
||||
|
||||
// tags: ctxmenu contextmenu directorycontextmenu
|
||||
function DirectoryContentContextMenu({
|
||||
children,
|
||||
}: {
|
||||
@@ -235,17 +250,22 @@ function DirectoryContentContextMenu({
|
||||
const store = useStore()
|
||||
const [target, setTarget] = useAtom(contextMenuTargetItemsAtom)
|
||||
const setOptimisticDeletedItems = useSetAtom(optimisticDeletedItemsAtom)
|
||||
const setBackgroundTaskProgress = useSetAtom(backgroundTaskProgressAtom)
|
||||
const moveToTrashMutation = useContextMutation(api.filesystem.moveToTrash)
|
||||
|
||||
const { mutate: moveToTrash } = useMutation({
|
||||
mutationFn: moveToTrashMutation,
|
||||
onMutate: ({ handles }) => {
|
||||
setBackgroundTaskProgress({
|
||||
label: "Moving items to trash…",
|
||||
})
|
||||
setOptimisticDeletedItems(
|
||||
(prev) =>
|
||||
new Set([...prev, ...handles.map((handle) => handle.id)]),
|
||||
)
|
||||
},
|
||||
onSuccess: ({ deleted, errors }, { handles }) => {
|
||||
setBackgroundTaskProgress(null)
|
||||
setOptimisticDeletedItems((prev) => {
|
||||
const newSet = new Set(prev)
|
||||
for (const handle of handles) {
|
||||
@@ -263,6 +283,15 @@ function DirectoryContentContextMenu({
|
||||
)
|
||||
}
|
||||
},
|
||||
onError: (_err, { handles }) => {
|
||||
setOptimisticDeletedItems((prev) => {
|
||||
const newSet = new Set(prev)
|
||||
for (const handle of handles) {
|
||||
newSet.delete(handle.id)
|
||||
}
|
||||
return newSet
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const handleDelete = () => {
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "convex/react"
|
||||
import { atom, useAtom, useSetAtom, useStore } from "jotai"
|
||||
import { ShredderIcon, TrashIcon, UndoIcon } from "lucide-react"
|
||||
import { useCallback, useContext, useEffect } from "react"
|
||||
import { useCallback, useContext } from "react"
|
||||
import { toast } from "sonner"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
@@ -109,6 +109,7 @@ function RouteComponent() {
|
||||
>
|
||||
<header className="flex py-2 shrink-0 items-center gap-2 border-b px-4 w-full">
|
||||
<DirectoryPathBreadcrumb
|
||||
directory={directory}
|
||||
rootLabel="Trash"
|
||||
directoryUrlFn={directoryUrlById}
|
||||
/>
|
||||
@@ -119,13 +120,17 @@ function RouteComponent() {
|
||||
|
||||
<TableContextMenu>
|
||||
<div className="w-full">
|
||||
<WithAtom atom={optimisticRemovedItemsAtom}>
|
||||
{(optimisticRemovedItems) => (
|
||||
<DirectoryContentTable
|
||||
filterFn={() => true}
|
||||
hiddenItems={optimisticRemovedItems}
|
||||
directoryUrlFn={directoryUrlFn}
|
||||
fileDragInfoAtom={fileDragInfoAtom}
|
||||
onContextMenu={handleContextMenuRequest}
|
||||
onOpenFile={setOpenedFile}
|
||||
/>
|
||||
)}
|
||||
</WithAtom>
|
||||
</div>
|
||||
</TableContextMenu>
|
||||
|
||||
@@ -173,14 +178,19 @@ function RestoreContextMenuItem() {
|
||||
const store = useStore()
|
||||
const setOptimisticRemovedItems = useSetAtom(optimisticRemovedItemsAtom)
|
||||
const restoreItemsMutation = useConvexMutation(api.filesystem.restoreItems)
|
||||
const { mutate: restoreItems, isPending: isRestoring } = useMutation({
|
||||
|
||||
const { mutate: restoreItems } = useMutation({
|
||||
mutationFn: restoreItemsMutation,
|
||||
onMutate: ({ handles }) => {
|
||||
setBackgroundTaskProgress({
|
||||
label: "Restoring items…",
|
||||
})
|
||||
setOptimisticRemovedItems(
|
||||
new Set(handles.map((handle) => handle.id)),
|
||||
)
|
||||
},
|
||||
onSuccess: ({ restored, errors }) => {
|
||||
setBackgroundTaskProgress(null)
|
||||
if (errors.length === 0) {
|
||||
if (restored.files > 0 && restored.directories > 0) {
|
||||
toast.success(
|
||||
@@ -199,19 +209,18 @@ function RestoreContextMenuItem() {
|
||||
)
|
||||
}
|
||||
},
|
||||
onError: (_err, { handles }) => {
|
||||
setOptimisticRemovedItems((prev) => {
|
||||
const newSet = new Set(prev)
|
||||
for (const handle of handles) {
|
||||
newSet.delete(handle.id)
|
||||
}
|
||||
return newSet
|
||||
})
|
||||
},
|
||||
})
|
||||
const setBackgroundTaskProgress = useSetAtom(backgroundTaskProgressAtom)
|
||||
|
||||
useEffect(() => {
|
||||
if (isRestoring) {
|
||||
setBackgroundTaskProgress({
|
||||
label: "Restoring items…",
|
||||
})
|
||||
} else {
|
||||
setBackgroundTaskProgress(null)
|
||||
}
|
||||
}, [isRestoring, setBackgroundTaskProgress])
|
||||
|
||||
const onClick = () => {
|
||||
const targetItems = store.get(contextMenuTargetItemsAtom)
|
||||
restoreItems({
|
||||
|
||||
Reference in New Issue
Block a user