mirror of
https://github.com/get-drexa/drive.git
synced 2026-02-02 13:11:18 +00:00
impl: dir content pagination
This commit is contained in:
@@ -3,7 +3,6 @@ import type { DirectoryContent, DirectoryInfoWithPath } from "@/vfs/vfs"
|
||||
|
||||
type DirectoryPageContextType = {
|
||||
directory: DirectoryInfoWithPath
|
||||
directoryContent: DirectoryContent
|
||||
}
|
||||
|
||||
export const DirectoryPageContext = createContext<DirectoryPageContextType>(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Link, useNavigate } from "@tanstack/react-router"
|
||||
import { useInfiniteQuery } from "@tanstack/react-query"
|
||||
import { Link, useNavigate, useSearch } from "@tanstack/react-router"
|
||||
import {
|
||||
type ColumnDef,
|
||||
flexRender,
|
||||
@@ -8,7 +9,7 @@ import {
|
||||
type Table as TableType,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table"
|
||||
import { type PrimitiveAtom, useSetAtom, useStore } from "jotai"
|
||||
import { type PrimitiveAtom, useAtomValue, useSetAtom, useStore } from "jotai"
|
||||
import { useContext, useEffect, useMemo, useRef } from "react"
|
||||
import { DirectoryIcon } from "@/components/icons/directory-icon"
|
||||
import { TextFileIcon } from "@/components/icons/text-file-icon"
|
||||
@@ -28,12 +29,13 @@ import {
|
||||
} from "@/lib/keyboard"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { DirectoryInfo, DirectoryItem, FileInfo } from "@/vfs/vfs"
|
||||
import { directoryContentQueryAtom } from "../../vfs/api"
|
||||
import { DirectoryPageContext } from "./context"
|
||||
import { DirectoryContentTableSkeleton } from "./directory-content-table-skeleton"
|
||||
|
||||
type DirectoryContentTableItemIdFilter = Set<string>
|
||||
|
||||
type DirectoryContentTableProps = {
|
||||
hiddenItems: DirectoryContentTableItemIdFilter
|
||||
directoryUrlFn: (directory: DirectoryInfo) => string
|
||||
fileDragInfoAtom: PrimitiveAtom<FileDragInfo | null>
|
||||
onContextMenu: (
|
||||
@@ -138,26 +140,40 @@ function useTableColumns(
|
||||
}
|
||||
|
||||
export function DirectoryContentTable({
|
||||
hiddenItems,
|
||||
directoryUrlFn,
|
||||
onContextMenu,
|
||||
fileDragInfoAtom,
|
||||
onOpenFile,
|
||||
}: DirectoryContentTableProps) {
|
||||
const { directoryContent } = useContext(DirectoryPageContext)
|
||||
const { directory } = useContext(DirectoryPageContext)
|
||||
const search = useSearch({
|
||||
from: "/_authenticated/_sidebar-layout/directories/$directoryId",
|
||||
})
|
||||
|
||||
const directoryContentQuery = useAtomValue(
|
||||
directoryContentQueryAtom({
|
||||
directoryId: directory.id,
|
||||
orderBy: search.orderBy,
|
||||
direction: search.direction,
|
||||
limit: 100,
|
||||
}),
|
||||
)
|
||||
const { data: directoryContent, isLoading: isLoadingDirectoryContent } =
|
||||
useInfiniteQuery(directoryContentQuery)
|
||||
|
||||
const store = useStore()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const table = useReactTable({
|
||||
data: directoryContent || [],
|
||||
data: useMemo(
|
||||
() => directoryContent?.pages.flatMap((page) => page.items) || [],
|
||||
[directoryContent],
|
||||
),
|
||||
columns: useTableColumns(onOpenFile, directoryUrlFn),
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
enableRowSelection: true,
|
||||
enableGlobalFilter: true,
|
||||
state: {
|
||||
globalFilter: hiddenItems,
|
||||
},
|
||||
globalFilterFn: (
|
||||
row,
|
||||
_columnId,
|
||||
@@ -180,6 +196,10 @@ export function DirectoryContentTable({
|
||||
[table.setRowSelection],
|
||||
)
|
||||
|
||||
if (isLoadingDirectoryContent) {
|
||||
return <DirectoryContentTableSkeleton />
|
||||
}
|
||||
|
||||
const handleRowContextMenu = (
|
||||
row: Row<DirectoryItem>,
|
||||
_event: React.MouseEvent,
|
||||
|
||||
@@ -32,3 +32,9 @@ if (import.meta.hot) {
|
||||
// The hot module reloading API is not available in production.
|
||||
createRoot(elem).render(app)
|
||||
}
|
||||
|
||||
declare module "@tanstack/react-router" {
|
||||
interface Register {
|
||||
router: typeof router
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query"
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import type { Row, Table } from "@tanstack/react-table"
|
||||
import { type } from "arktype"
|
||||
import { atom, useAtom, useAtomValue, useSetAtom, useStore } from "jotai"
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
@@ -38,8 +39,10 @@ import { FilePreviewDialog } from "@/files/file-preview-dialog"
|
||||
import { cutItemsAtom, inProgressFileUploadCountAtom } from "@/files/store"
|
||||
import { UploadFileDialog } from "@/files/upload-file-dialog"
|
||||
import type { FileDragInfo } from "@/files/use-file-drop"
|
||||
import { formatError } from "@/lib/error"
|
||||
import {
|
||||
directoryContentQueryAtom,
|
||||
DIRECTORY_CONTENT_ORDER_BY,
|
||||
DIRECTORY_CONTENT_ORDER_DIRECTION,
|
||||
directoryInfoQueryAtom,
|
||||
moveToTrashMutationAtom,
|
||||
} from "@/vfs/api"
|
||||
@@ -49,11 +52,20 @@ import type {
|
||||
DirectoryItem,
|
||||
FileInfo,
|
||||
} from "@/vfs/vfs"
|
||||
import { formatError } from "../../../lib/error"
|
||||
|
||||
const DirectoryContentPageParams = type({
|
||||
orderBy: type
|
||||
.valueOf(DIRECTORY_CONTENT_ORDER_BY)
|
||||
.default(DIRECTORY_CONTENT_ORDER_BY.name),
|
||||
direction: type
|
||||
.valueOf(DIRECTORY_CONTENT_ORDER_DIRECTION)
|
||||
.default(DIRECTORY_CONTENT_ORDER_DIRECTION.asc),
|
||||
})
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticated/_sidebar-layout/directories/$directoryId",
|
||||
)({
|
||||
validateSearch: DirectoryContentPageParams,
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
@@ -87,37 +99,54 @@ const itemBeingRenamedAtom = atom<{
|
||||
// MARK: page entry
|
||||
function RouteComponent() {
|
||||
const { directoryId } = Route.useParams()
|
||||
const {
|
||||
data: directoryInfo,
|
||||
isLoading: isLoadingDirectoryInfo,
|
||||
error: directoryInfoError,
|
||||
} = useQuery(useAtomValue(directoryInfoQueryAtom(directoryId)))
|
||||
const {
|
||||
data: directoryContent,
|
||||
isLoading: isLoadingDirectoryContent,
|
||||
error: directoryContentError,
|
||||
} = useQuery(useAtomValue(directoryContentQueryAtom(directoryId)))
|
||||
const { data: directoryInfo, isLoading: isLoadingDirectoryInfo } = useQuery(
|
||||
useAtomValue(directoryInfoQueryAtom(directoryId)),
|
||||
)
|
||||
|
||||
const setOpenedFile = useSetAtom(openedFileAtom)
|
||||
const setContextMenuTargetItems = useSetAtom(contextMenuTargetItemsAtom)
|
||||
|
||||
const directoryUrlById = useCallback(
|
||||
(directoryId: string) => `/directories/${directoryId}`,
|
||||
[],
|
||||
)
|
||||
|
||||
console.log({ directoryInfoError, directoryContentError })
|
||||
const onTableOpenFile = useCallback(
|
||||
(file: FileInfo) => {
|
||||
setOpenedFile(file)
|
||||
},
|
||||
[setOpenedFile],
|
||||
)
|
||||
|
||||
if (isLoadingDirectoryInfo || isLoadingDirectoryContent) {
|
||||
const directoryUrlFn = useCallback(
|
||||
(directory: DirectoryInfo) => `/directories/${directory.id}`,
|
||||
[],
|
||||
)
|
||||
|
||||
const handleContextMenuRequest = useCallback(
|
||||
(row: Row<DirectoryItem>, table: Table<DirectoryItem>) => {
|
||||
if (row.getIsSelected()) {
|
||||
setContextMenuTargetItems(
|
||||
table.getSelectedRowModel().rows.map((row) => row.original),
|
||||
)
|
||||
} else {
|
||||
setContextMenuTargetItems([row.original])
|
||||
}
|
||||
},
|
||||
[setContextMenuTargetItems],
|
||||
)
|
||||
|
||||
if (isLoadingDirectoryInfo) {
|
||||
return <DirectoryPageSkeleton />
|
||||
}
|
||||
|
||||
if (!directoryInfo || !directoryContent) {
|
||||
if (!directoryInfo) {
|
||||
// TODO: handle empty state/error
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<DirectoryPageContext
|
||||
value={{ directory: directoryInfo, directoryContent }}
|
||||
>
|
||||
<DirectoryPageContext value={{ directory: directoryInfo }}>
|
||||
<header className="flex py-2 shrink-0 items-center gap-2 border-b px-4 w-full">
|
||||
<DirectoryPathBreadcrumb
|
||||
directory={directoryInfo}
|
||||
@@ -134,7 +163,12 @@ 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">
|
||||
<_DirectoryContentTable />
|
||||
<DirectoryContentTable
|
||||
directoryUrlFn={directoryUrlFn}
|
||||
fileDragInfoAtom={fileDragInfoAtom}
|
||||
onContextMenu={handleContextMenuRequest}
|
||||
onOpenFile={onTableOpenFile}
|
||||
/>
|
||||
</div>
|
||||
</DirectoryContentContextMenu>
|
||||
|
||||
@@ -191,46 +225,6 @@ function RouteComponent() {
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: directory table
|
||||
|
||||
function _DirectoryContentTable() {
|
||||
const optimisticDeletedItems = useAtomValue(optimisticDeletedItemsAtom)
|
||||
const setOpenedFile = useSetAtom(openedFileAtom)
|
||||
const setContextMenuTargetItems = useSetAtom(contextMenuTargetItemsAtom)
|
||||
|
||||
const onTableOpenFile = (file: FileInfo) => {
|
||||
setOpenedFile(file)
|
||||
}
|
||||
|
||||
const directoryUrlFn = useCallback(
|
||||
(directory: DirectoryInfo) => `/directories/${directory.id}`,
|
||||
[],
|
||||
)
|
||||
|
||||
const handleContextMenuRequest = (
|
||||
row: Row<DirectoryItem>,
|
||||
table: Table<DirectoryItem>,
|
||||
) => {
|
||||
if (row.getIsSelected()) {
|
||||
setContextMenuTargetItems(
|
||||
table.getSelectedRowModel().rows.map((row) => row.original),
|
||||
)
|
||||
} else {
|
||||
setContextMenuTargetItems([row.original])
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<DirectoryContentTable
|
||||
hiddenItems={optimisticDeletedItems}
|
||||
directoryUrlFn={directoryUrlFn}
|
||||
fileDragInfoAtom={fileDragInfoAtom}
|
||||
onContextMenu={handleContextMenuRequest}
|
||||
onOpenFile={onTableOpenFile}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// ==================================
|
||||
// MARK: ctx menu
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { createFileRoute, useNavigate } from "@tanstack/react-router"
|
||||
import { useSetAtom } from "jotai"
|
||||
import { GalleryVerticalEnd } from "lucide-react"
|
||||
import { loginMutation } from "@/auth/api"
|
||||
import { Button } from "@/components/ui/button"
|
||||
@@ -20,7 +19,6 @@ import {
|
||||
} from "@/components/ui/field"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { currentAccountAtom } from "../account/account"
|
||||
|
||||
export const Route = createFileRoute("/login")({
|
||||
component: RouteComponent,
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { mutationOptions, queryOptions, skipToken } from "@tanstack/react-query"
|
||||
import {
|
||||
type InfiniteData,
|
||||
infiniteQueryOptions,
|
||||
mutationOptions,
|
||||
queryOptions,
|
||||
skipToken,
|
||||
} from "@tanstack/react-query"
|
||||
import { type } from "arktype"
|
||||
import { atom } from "jotai"
|
||||
import { atomFamily } from "jotai/utils"
|
||||
@@ -12,6 +18,11 @@ import {
|
||||
FileInfo,
|
||||
} from "./vfs"
|
||||
|
||||
const DirectoryContentResponse = type({
|
||||
items: DirectoryContent,
|
||||
"nextCursor?": "string",
|
||||
})
|
||||
|
||||
/**
|
||||
* This atom derives the file url for a given file.
|
||||
* It is recommended to use {@link useFileUrl} instead of using this atom directly.
|
||||
@@ -58,27 +69,63 @@ export const directoryInfoQueryAtom = atomFamily((directoryId: string) =>
|
||||
}),
|
||||
)
|
||||
|
||||
export const directoryContentQueryAtom = atomFamily((directoryId: string) =>
|
||||
atom((get) => {
|
||||
const account = get(currentAccountAtom)
|
||||
return queryOptions({
|
||||
queryKey: [
|
||||
"accounts",
|
||||
account?.id,
|
||||
"directories",
|
||||
directoryId,
|
||||
"content",
|
||||
],
|
||||
queryFn: account
|
||||
? () =>
|
||||
fetchApi(
|
||||
"GET",
|
||||
`/accounts/${account.id}/directories/${directoryId}/content`,
|
||||
{ returns: DirectoryContent },
|
||||
).then(([_, result]) => result)
|
||||
: skipToken,
|
||||
})
|
||||
}),
|
||||
export const DIRECTORY_CONTENT_ORDER_BY = {
|
||||
name: "name",
|
||||
createdAt: "createdAt",
|
||||
updatedAt: "updatedAt",
|
||||
} as const
|
||||
export type DirectoryContentOrderBy =
|
||||
(typeof DIRECTORY_CONTENT_ORDER_BY)[keyof typeof DIRECTORY_CONTENT_ORDER_BY]
|
||||
|
||||
export const DIRECTORY_CONTENT_ORDER_DIRECTION = {
|
||||
asc: "asc",
|
||||
desc: "desc",
|
||||
} as const
|
||||
type DirectoryContentOrderDirection =
|
||||
(typeof DIRECTORY_CONTENT_ORDER_DIRECTION)[keyof typeof DIRECTORY_CONTENT_ORDER_DIRECTION]
|
||||
|
||||
type DirectoryContentQueryParams = {
|
||||
directoryId: string
|
||||
orderBy: DirectoryContentOrderBy
|
||||
direction: DirectoryContentOrderDirection
|
||||
limit: number
|
||||
}
|
||||
|
||||
const directoryContentQueryKey = (
|
||||
accountId: string | undefined,
|
||||
directoryId: string,
|
||||
) => ["accounts", accountId, "directories", directoryId, "content"]
|
||||
export const directoryContentQueryAtom = atomFamily(
|
||||
({ directoryId, orderBy, direction, limit }: DirectoryContentQueryParams) =>
|
||||
atom((get) => {
|
||||
const account = get(currentAccountAtom)
|
||||
return infiniteQueryOptions({
|
||||
queryKey: directoryContentQueryKey(account?.id, directoryId),
|
||||
initialPageParam: {
|
||||
orderBy,
|
||||
direction,
|
||||
limit,
|
||||
cursor: "",
|
||||
},
|
||||
queryFn: ({ pageParam }) =>
|
||||
account
|
||||
? fetchApi(
|
||||
"GET",
|
||||
`/accounts/${account.id}/directories/${directoryId}/content?orderBy=${pageParam.orderBy}&dir=${pageParam.direction}&limit=${pageParam.limit}${pageParam.cursor ? `&cursor=${pageParam.cursor}` : ""}`,
|
||||
{ returns: DirectoryContentResponse },
|
||||
).then(([_, result]) => result)
|
||||
: Promise.reject(new Error("No account selected")),
|
||||
getNextPageParam: (lastPage, _pages, lastPageParam) => ({
|
||||
...lastPageParam,
|
||||
cursor: lastPage.nextCursor ?? "",
|
||||
}),
|
||||
})
|
||||
}),
|
||||
(paramsA, paramsB) =>
|
||||
paramsA.directoryId === paramsB.directoryId &&
|
||||
paramsA.orderBy === paramsB.orderBy &&
|
||||
paramsA.direction === paramsB.direction &&
|
||||
paramsA.limit === paramsB.limit,
|
||||
)
|
||||
|
||||
export const createDirectoryMutationAtom = atom((get) => {
|
||||
@@ -103,13 +150,6 @@ export const createDirectoryMutationAtom = atom((get) => {
|
||||
get(directoryInfoQueryAtom(data.id)).queryKey,
|
||||
data,
|
||||
)
|
||||
const parent = data.path.at(-2)
|
||||
if (parent) {
|
||||
client.setQueryData(
|
||||
get(directoryContentQueryAtom(parent.id)).queryKey,
|
||||
(prev) => (prev ? [...prev, data] : [data]),
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -157,13 +197,18 @@ export const moveDirectoryItemsMutationAtom = atom((get) =>
|
||||
return result
|
||||
},
|
||||
onMutate: ({ items }, { client }) => {
|
||||
const account = get(currentAccountAtom)
|
||||
if (!account) {
|
||||
return null
|
||||
}
|
||||
|
||||
const movedItems = new Map<string, Set<string>>()
|
||||
|
||||
for (const item of items) {
|
||||
if (item.parentId) {
|
||||
const s = movedItems.get(item.parentId)
|
||||
if (!s) {
|
||||
movedItems.set(item.parentId, new Set(s))
|
||||
movedItems.set(item.parentId, new Set([item.id]))
|
||||
} else {
|
||||
s.add(item.id)
|
||||
}
|
||||
@@ -172,45 +217,67 @@ export const moveDirectoryItemsMutationAtom = atom((get) =>
|
||||
|
||||
const prevDirContentMap = new Map<
|
||||
string,
|
||||
DirectoryItem[] | undefined
|
||||
InfiniteData<typeof DirectoryContentResponse.infer> | undefined
|
||||
>()
|
||||
|
||||
movedItems.forEach((s, parentId) => {
|
||||
const query = get(directoryContentQueryAtom(parentId))
|
||||
const prevDirContent = client.getQueryData(query.queryKey)
|
||||
client.setQueryData(
|
||||
query.queryKey,
|
||||
(prev) => prev?.filter((it) => !s.has(it.id)) ?? prev,
|
||||
)
|
||||
const key = directoryContentQueryKey(account.id, parentId)
|
||||
const prevDirContent =
|
||||
client.getQueryData<
|
||||
InfiniteData<typeof DirectoryContentResponse.infer>
|
||||
>(key)
|
||||
client.setQueryData<
|
||||
InfiniteData<typeof DirectoryContentResponse.infer>
|
||||
>(key, (prev) => {
|
||||
if (!prev) return prev
|
||||
return {
|
||||
...prev,
|
||||
pages: prev.pages.map((page) => ({
|
||||
...page,
|
||||
items: page.items.filter((it) => !s.has(it.id)),
|
||||
})),
|
||||
}
|
||||
})
|
||||
prevDirContentMap.set(parentId, prevDirContent)
|
||||
})
|
||||
|
||||
return { prevDirContentMap }
|
||||
},
|
||||
onSuccess: (_data, { targetDirectory, items }, _result, { client }) => {
|
||||
const account = get(currentAccountAtom)
|
||||
if (!account) return
|
||||
|
||||
const dirId =
|
||||
typeof targetDirectory === "string"
|
||||
? targetDirectory
|
||||
: targetDirectory.id
|
||||
client.invalidateQueries(get(directoryContentQueryAtom(dirId)))
|
||||
client.invalidateQueries({
|
||||
queryKey: directoryContentQueryKey(account.id, dirId),
|
||||
})
|
||||
for (const item of items) {
|
||||
if (item.parentId) {
|
||||
client.invalidateQueries(
|
||||
get(directoryContentQueryAtom(item.parentId)),
|
||||
)
|
||||
client.invalidateQueries({
|
||||
queryKey: directoryContentQueryKey(
|
||||
account.id,
|
||||
item.parentId,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (_error, _vars, context, { client }) => {
|
||||
if (context) {
|
||||
context.prevDirContentMap.forEach(
|
||||
(prevDirContent, parentId) => {
|
||||
client.setQueryData(
|
||||
get(directoryContentQueryAtom(parentId)).queryKey,
|
||||
prevDirContent,
|
||||
)
|
||||
},
|
||||
)
|
||||
const account = get(currentAccountAtom)
|
||||
if (account) {
|
||||
context.prevDirContentMap.forEach(
|
||||
(prevDirContent, parentId) => {
|
||||
client.setQueryData(
|
||||
directoryContentQueryKey(account.id, parentId),
|
||||
prevDirContent,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
@@ -277,12 +344,17 @@ export const moveToTrashMutationAtom = atom((get) =>
|
||||
return [...deletedFiles, ...deletedDirectories]
|
||||
},
|
||||
onMutate: (items, { client }) => {
|
||||
const account = get(currentAccountAtom)
|
||||
if (!account) {
|
||||
return null
|
||||
}
|
||||
|
||||
const trashedItems = new Map<string, Set<string>>()
|
||||
for (const item of items) {
|
||||
if (item.parentId) {
|
||||
const s = trashedItems.get(item.parentId)
|
||||
if (!s) {
|
||||
trashedItems.set(item.parentId, new Set(s))
|
||||
trashedItems.set(item.parentId, new Set([item.id]))
|
||||
} else {
|
||||
s.add(item.id)
|
||||
}
|
||||
@@ -291,38 +363,58 @@ export const moveToTrashMutationAtom = atom((get) =>
|
||||
|
||||
const prevDirContentMap = new Map<
|
||||
string,
|
||||
DirectoryItem[] | undefined
|
||||
InfiniteData<typeof DirectoryContentResponse.infer> | undefined
|
||||
>()
|
||||
trashedItems.forEach((s, parentId) => {
|
||||
const query = get(directoryContentQueryAtom(parentId))
|
||||
const prevDirContent = client.getQueryData(query.queryKey)
|
||||
client.setQueryData(
|
||||
query.queryKey,
|
||||
(prev) => prev?.filter((it) => !s.has(it.id)) ?? prev,
|
||||
)
|
||||
const key = directoryContentQueryKey(account.id, parentId)
|
||||
const prevDirContent =
|
||||
client.getQueryData<
|
||||
InfiniteData<typeof DirectoryContentResponse.infer>
|
||||
>(key)
|
||||
client.setQueryData<
|
||||
InfiniteData<typeof DirectoryContentResponse.infer>
|
||||
>(key, (prev) => {
|
||||
if (!prev) return prev
|
||||
return {
|
||||
...prev,
|
||||
pages: prev.pages.map((page) => ({
|
||||
...page,
|
||||
items: page.items.filter((it) => !s.has(it.id)),
|
||||
})),
|
||||
}
|
||||
})
|
||||
prevDirContentMap.set(parentId, prevDirContent)
|
||||
})
|
||||
return { prevDirContentMap }
|
||||
},
|
||||
onSuccess: (_data, items, _result, { client }) => {
|
||||
for (const item of items) {
|
||||
if (item.parentId) {
|
||||
client.invalidateQueries(
|
||||
get(directoryContentQueryAtom(item.parentId)),
|
||||
)
|
||||
const account = get(currentAccountAtom)
|
||||
if (account) {
|
||||
for (const item of items) {
|
||||
if (item.parentId) {
|
||||
client.invalidateQueries({
|
||||
queryKey: directoryContentQueryKey(
|
||||
account.id,
|
||||
item.parentId,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (_error, items, context, { client }) => {
|
||||
onError: (_error, _items, context, { client }) => {
|
||||
if (context) {
|
||||
context.prevDirContentMap.forEach(
|
||||
(prevDirContent, parentId) => {
|
||||
client.setQueryData(
|
||||
get(directoryContentQueryAtom(parentId)).queryKey,
|
||||
prevDirContent,
|
||||
)
|
||||
},
|
||||
)
|
||||
const account = get(currentAccountAtom)
|
||||
if (account) {
|
||||
context.prevDirContentMap.forEach(
|
||||
(prevDirContent, parentId) => {
|
||||
client.setQueryData(
|
||||
directoryContentQueryKey(account.id, parentId),
|
||||
prevDirContent,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import path from "node:path"
|
||||
import tailwindcss from "@tailwindcss/vite"
|
||||
import { TanStackRouterVite } from "@tanstack/router-plugin/vite"
|
||||
import { tanstackRouter } from "@tanstack/router-plugin/vite"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import { defineConfig } from "vite"
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [TanStackRouterVite(), react(), tailwindcss()],
|
||||
plugins: [
|
||||
tanstackRouter({ target: "react", autoCodeSplitting: true }),
|
||||
react(),
|
||||
tailwindcss(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
|
||||
Reference in New Issue
Block a user