mirror of
https://github.com/get-drexa/drive.git
synced 2026-02-02 11:51:17 +00:00
fix: dir content invalidation on move to trash
This commit is contained in:
@@ -49,6 +49,7 @@ import type {
|
|||||||
DirectoryItem,
|
DirectoryItem,
|
||||||
FileInfo,
|
FileInfo,
|
||||||
} from "@/vfs/vfs"
|
} from "@/vfs/vfs"
|
||||||
|
import { formatError } from "../../../lib/error"
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticated/_sidebar-layout/directories/$directoryId",
|
"/_authenticated/_sidebar-layout/directories/$directoryId",
|
||||||
@@ -86,17 +87,22 @@ const itemBeingRenamedAtom = atom<{
|
|||||||
// MARK: page entry
|
// MARK: page entry
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const { directoryId } = Route.useParams()
|
const { directoryId } = Route.useParams()
|
||||||
const { data: directoryInfo, isLoading: isLoadingDirectoryInfo, error: directoryInfoError } = useQuery(
|
const {
|
||||||
useAtomValue(directoryInfoQueryAtom(directoryId)),
|
data: directoryInfo,
|
||||||
)
|
isLoading: isLoadingDirectoryInfo,
|
||||||
const { data: directoryContent, isLoading: isLoadingDirectoryContent, error: directoryContentError } =
|
error: directoryInfoError,
|
||||||
useQuery(useAtomValue(directoryContentQueryAtom(directoryId)))
|
} = useQuery(useAtomValue(directoryInfoQueryAtom(directoryId)))
|
||||||
|
const {
|
||||||
|
data: directoryContent,
|
||||||
|
isLoading: isLoadingDirectoryContent,
|
||||||
|
error: directoryContentError,
|
||||||
|
} = useQuery(useAtomValue(directoryContentQueryAtom(directoryId)))
|
||||||
|
|
||||||
const directoryUrlById = useCallback(
|
const directoryUrlById = useCallback(
|
||||||
(directoryId: string) => `/directories/${directoryId}`,
|
(directoryId: string) => `/directories/${directoryId}`,
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log({ directoryInfoError, directoryContentError })
|
console.log({ directoryInfoError, directoryContentError })
|
||||||
|
|
||||||
if (isLoadingDirectoryInfo || isLoadingDirectoryContent) {
|
if (isLoadingDirectoryInfo || isLoadingDirectoryContent) {
|
||||||
@@ -236,39 +242,31 @@ function DirectoryContentContextMenu({
|
|||||||
}) {
|
}) {
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const [target, setTarget] = useAtom(contextMenuTargetItemsAtom)
|
const [target, setTarget] = useAtom(contextMenuTargetItemsAtom)
|
||||||
const setOptimisticDeletedItems = useSetAtom(optimisticDeletedItemsAtom)
|
|
||||||
const setBackgroundTaskProgress = useSetAtom(backgroundTaskProgressAtom)
|
const setBackgroundTaskProgress = useSetAtom(backgroundTaskProgressAtom)
|
||||||
const setCutItems = useSetAtom(cutItemsAtom)
|
const setCutItems = useSetAtom(cutItemsAtom)
|
||||||
|
|
||||||
|
const moveToTrashMutation = useAtomValue(moveToTrashMutationAtom)
|
||||||
const { mutate: moveToTrash } = useMutation({
|
const { mutate: moveToTrash } = useMutation({
|
||||||
...useAtomValue(moveToTrashMutationAtom),
|
...moveToTrashMutation,
|
||||||
onMutate: (items) => {
|
onMutate: (vars, ctx) => {
|
||||||
setBackgroundTaskProgress({
|
setBackgroundTaskProgress({
|
||||||
label: "Moving items to trash…",
|
label: "Moving items to trash…",
|
||||||
})
|
})
|
||||||
setOptimisticDeletedItems(
|
return (
|
||||||
(prev) => new Set([...prev, ...items.map((item) => item.id)]),
|
moveToTrashMutation.onMutate?.(vars, ctx) ?? {
|
||||||
|
prevDirContentMap: new Map(),
|
||||||
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onSuccess: (trashedItems) => {
|
onSuccess: (data, vars, result, ctx) => {
|
||||||
|
moveToTrashMutation.onSuccess?.(data, vars, result, ctx)
|
||||||
setBackgroundTaskProgress(null)
|
setBackgroundTaskProgress(null)
|
||||||
setOptimisticDeletedItems((prev) => {
|
toast.success(`Moved ${data.length} items to trash`)
|
||||||
const newSet = new Set(prev)
|
|
||||||
for (const item of trashedItems) {
|
|
||||||
newSet.delete(item.id)
|
|
||||||
}
|
|
||||||
return newSet
|
|
||||||
})
|
|
||||||
toast.success(`Moved ${trashedItems.length} items to trash`)
|
|
||||||
},
|
},
|
||||||
onError: (_err, items) => {
|
onError: (err, vars, mutateResult, context) => {
|
||||||
setOptimisticDeletedItems((prev) => {
|
moveToTrashMutation.onError?.(err, vars, mutateResult, context)
|
||||||
const newSet = new Set(prev)
|
toast.error(formatError(err))
|
||||||
for (const item of items) {
|
setBackgroundTaskProgress(null)
|
||||||
newSet.delete(item.id)
|
|
||||||
}
|
|
||||||
return newSet
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ export const moveDirectoryItemsMutationAtom = atom((get) =>
|
|||||||
if (item.parentId) {
|
if (item.parentId) {
|
||||||
const s = movedItems.get(item.parentId)
|
const s = movedItems.get(item.parentId)
|
||||||
if (!s) {
|
if (!s) {
|
||||||
movedItems.set(item.parentId, new Set())
|
movedItems.set(item.parentId, new Set(s))
|
||||||
} else {
|
} else {
|
||||||
s.add(item.id)
|
s.add(item.id)
|
||||||
}
|
}
|
||||||
@@ -192,7 +192,6 @@ export const moveDirectoryItemsMutationAtom = atom((get) =>
|
|||||||
typeof targetDirectory === "string"
|
typeof targetDirectory === "string"
|
||||||
? targetDirectory
|
? targetDirectory
|
||||||
: targetDirectory.id
|
: targetDirectory.id
|
||||||
console.log(dirId)
|
|
||||||
client.invalidateQueries(get(directoryContentQueryAtom(dirId)))
|
client.invalidateQueries(get(directoryContentQueryAtom(dirId)))
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.parentId) {
|
if (item.parentId) {
|
||||||
@@ -238,36 +237,94 @@ export const moveToTrashMutationAtom = atom((get) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileDeleteParams = new URLSearchParams()
|
let deleteFilesPromise: Promise<FileInfo[]>
|
||||||
fileDeleteParams.set("id", fileIds.join(","))
|
if (fileIds.length > 0) {
|
||||||
fileDeleteParams.set("trash", "true")
|
const fileDeleteParams = new URLSearchParams()
|
||||||
const deleteFilesPromise = fetchApi(
|
fileDeleteParams.set("id", fileIds.join(","))
|
||||||
"DELETE",
|
fileDeleteParams.set("trash", "true")
|
||||||
`/accounts/${account.id}/files?${fileDeleteParams.toString()}`,
|
deleteFilesPromise = fetchApi(
|
||||||
{
|
"DELETE",
|
||||||
returns: FileInfo.array(),
|
`/accounts/${account.id}/files?${fileDeleteParams.toString()}`,
|
||||||
},
|
{
|
||||||
)
|
returns: FileInfo.array(),
|
||||||
|
},
|
||||||
|
).then(([_, result]) => result)
|
||||||
|
} else {
|
||||||
|
deleteFilesPromise = Promise.resolve([])
|
||||||
|
}
|
||||||
|
|
||||||
const directoryDeleteParams = new URLSearchParams()
|
let deleteDirectoriesPromise: Promise<DirectoryInfo[]>
|
||||||
directoryDeleteParams.set("id", directoryIds.join(","))
|
if (directoryIds.length > 0) {
|
||||||
directoryDeleteParams.set("trash", "true")
|
const directoryDeleteParams = new URLSearchParams()
|
||||||
const deleteDirectoriesPromise = fetchApi(
|
directoryDeleteParams.set("id", directoryIds.join(","))
|
||||||
"DELETE",
|
directoryDeleteParams.set("trash", "true")
|
||||||
`/accounts/${account.id}/directories?${directoryDeleteParams.toString()}`,
|
deleteDirectoriesPromise = fetchApi(
|
||||||
{
|
"DELETE",
|
||||||
returns: DirectoryInfo.array(),
|
`/accounts/${account.id}/directories?${directoryDeleteParams.toString()}`,
|
||||||
},
|
{
|
||||||
)
|
returns: DirectoryInfo.array(),
|
||||||
|
},
|
||||||
|
).then(([_, result]) => result)
|
||||||
|
} else {
|
||||||
|
deleteDirectoriesPromise = Promise.resolve([])
|
||||||
|
}
|
||||||
|
|
||||||
const [[, deletedFiles], [, deletedDirectories]] =
|
const [deletedFiles, deletedDirectories] = await Promise.all([
|
||||||
await Promise.all([
|
deleteFilesPromise,
|
||||||
deleteFilesPromise,
|
deleteDirectoriesPromise,
|
||||||
deleteDirectoriesPromise,
|
])
|
||||||
])
|
|
||||||
|
|
||||||
return [...deletedFiles, ...deletedDirectories]
|
return [...deletedFiles, ...deletedDirectories]
|
||||||
},
|
},
|
||||||
|
onMutate: (items, { client }) => {
|
||||||
|
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))
|
||||||
|
} else {
|
||||||
|
s.add(item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevDirContentMap = new Map<
|
||||||
|
string,
|
||||||
|
DirectoryItem[] | 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,
|
||||||
|
)
|
||||||
|
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)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (_error, items, context, { client }) => {
|
||||||
|
if (context) {
|
||||||
|
context.prevDirContentMap.forEach(
|
||||||
|
(prevDirContent, parentId) => {
|
||||||
|
client.setQueryData(
|
||||||
|
get(directoryContentQueryAtom(parentId)).queryKey,
|
||||||
|
prevDirContent,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user