fix: dir content invalidation on move to trash

This commit is contained in:
2025-12-16 22:35:56 +00:00
parent 696ff7db15
commit 5484a08636
2 changed files with 110 additions and 55 deletions

View File

@@ -49,6 +49,7 @@ import type {
DirectoryItem,
FileInfo,
} from "@/vfs/vfs"
import { formatError } from "../../../lib/error"
export const Route = createFileRoute(
"/_authenticated/_sidebar-layout/directories/$directoryId",
@@ -86,11 +87,16 @@ 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,
error: directoryInfoError,
} = useQuery(useAtomValue(directoryInfoQueryAtom(directoryId)))
const {
data: directoryContent,
isLoading: isLoadingDirectoryContent,
error: directoryContentError,
} = useQuery(useAtomValue(directoryContentQueryAtom(directoryId)))
const directoryUrlById = useCallback(
(directoryId: string) => `/directories/${directoryId}`,
@@ -236,39 +242,31 @@ function DirectoryContentContextMenu({
}) {
const store = useStore()
const [target, setTarget] = useAtom(contextMenuTargetItemsAtom)
const setOptimisticDeletedItems = useSetAtom(optimisticDeletedItemsAtom)
const setBackgroundTaskProgress = useSetAtom(backgroundTaskProgressAtom)
const setCutItems = useSetAtom(cutItemsAtom)
const moveToTrashMutation = useAtomValue(moveToTrashMutationAtom)
const { mutate: moveToTrash } = useMutation({
...useAtomValue(moveToTrashMutationAtom),
onMutate: (items) => {
...moveToTrashMutation,
onMutate: (vars, ctx) => {
setBackgroundTaskProgress({
label: "Moving items to trash…",
})
setOptimisticDeletedItems(
(prev) => new Set([...prev, ...items.map((item) => item.id)]),
return (
moveToTrashMutation.onMutate?.(vars, ctx) ?? {
prevDirContentMap: new Map(),
}
)
},
onSuccess: (trashedItems) => {
onSuccess: (data, vars, result, ctx) => {
moveToTrashMutation.onSuccess?.(data, vars, result, ctx)
setBackgroundTaskProgress(null)
setOptimisticDeletedItems((prev) => {
const newSet = new Set(prev)
for (const item of trashedItems) {
newSet.delete(item.id)
}
return newSet
})
toast.success(`Moved ${trashedItems.length} items to trash`)
toast.success(`Moved ${data.length} items to trash`)
},
onError: (_err, items) => {
setOptimisticDeletedItems((prev) => {
const newSet = new Set(prev)
for (const item of items) {
newSet.delete(item.id)
}
return newSet
})
onError: (err, vars, mutateResult, context) => {
moveToTrashMutation.onError?.(err, vars, mutateResult, context)
toast.error(formatError(err))
setBackgroundTaskProgress(null)
},
})

View File

@@ -163,7 +163,7 @@ export const moveDirectoryItemsMutationAtom = atom((get) =>
if (item.parentId) {
const s = movedItems.get(item.parentId)
if (!s) {
movedItems.set(item.parentId, new Set())
movedItems.set(item.parentId, new Set(s))
} else {
s.add(item.id)
}
@@ -192,7 +192,6 @@ export const moveDirectoryItemsMutationAtom = atom((get) =>
typeof targetDirectory === "string"
? targetDirectory
: targetDirectory.id
console.log(dirId)
client.invalidateQueries(get(directoryContentQueryAtom(dirId)))
for (const item of items) {
if (item.parentId) {
@@ -238,36 +237,94 @@ export const moveToTrashMutationAtom = atom((get) =>
}
}
const fileDeleteParams = new URLSearchParams()
fileDeleteParams.set("id", fileIds.join(","))
fileDeleteParams.set("trash", "true")
const deleteFilesPromise = fetchApi(
"DELETE",
`/accounts/${account.id}/files?${fileDeleteParams.toString()}`,
{
returns: FileInfo.array(),
},
)
let deleteFilesPromise: Promise<FileInfo[]>
if (fileIds.length > 0) {
const fileDeleteParams = new URLSearchParams()
fileDeleteParams.set("id", fileIds.join(","))
fileDeleteParams.set("trash", "true")
deleteFilesPromise = fetchApi(
"DELETE",
`/accounts/${account.id}/files?${fileDeleteParams.toString()}`,
{
returns: FileInfo.array(),
},
).then(([_, result]) => result)
} else {
deleteFilesPromise = Promise.resolve([])
}
const directoryDeleteParams = new URLSearchParams()
directoryDeleteParams.set("id", directoryIds.join(","))
directoryDeleteParams.set("trash", "true")
const deleteDirectoriesPromise = fetchApi(
"DELETE",
`/accounts/${account.id}/directories?${directoryDeleteParams.toString()}`,
{
returns: DirectoryInfo.array(),
},
)
let deleteDirectoriesPromise: Promise<DirectoryInfo[]>
if (directoryIds.length > 0) {
const directoryDeleteParams = new URLSearchParams()
directoryDeleteParams.set("id", directoryIds.join(","))
directoryDeleteParams.set("trash", "true")
deleteDirectoriesPromise = fetchApi(
"DELETE",
`/accounts/${account.id}/directories?${directoryDeleteParams.toString()}`,
{
returns: DirectoryInfo.array(),
},
).then(([_, result]) => result)
} else {
deleteDirectoriesPromise = Promise.resolve([])
}
const [[, deletedFiles], [, deletedDirectories]] =
await Promise.all([
deleteFilesPromise,
deleteDirectoriesPromise,
])
const [deletedFiles, deletedDirectories] = await Promise.all([
deleteFilesPromise,
deleteDirectoriesPromise,
])
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,
)
},
)
}
},
}),
)