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, 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,11 +87,16 @@ 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}`,
@@ -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
})
}, },
}) })

View File

@@ -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,
)
},
)
}
},
}), }),
) )