Files
bee-dashboard/src/modules/filemanager/hooks/useBulkActions.ts
T
Bálint Ujvári 308ec3dcc0 fix: use upload and download abort signals (#212)
* fix: use upload and download abort signals

* fix: progress trackers

chore: update fm-lib and bee-js

* chore: bump-up fm-lib version
2026-03-20 16:02:17 +01:00

217 lines
6.3 KiB
TypeScript

import { PostageBatch } from '@ethersphere/bee-js'
import type { FileInfo } from '@solarpunkltd/file-manager-lib'
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
import { Context as FMContext } from '../../../providers/FileManager'
import { Context as SettingsContext } from '../../../providers/Settings'
import { uuidV4 } from '../../../utils'
import { DownloadProgress, TrackDownloadProps } from '../constants/transfers'
import { getUsableStamps } from '../utils/bee'
import { formatBytes, getFileId, safeSetState } from '../utils/common'
import { FileInfoWithUUID, startDownloadingQueue } from '../utils/download'
import { FileOperation, performBulkFileOperation } from '../utils/fileOperations'
interface BulkOptions {
listToRender: FileInfo[]
trackDownload: (props: TrackDownloadProps) => (dp: DownloadProgress) => void
setErrorMessage?: (error: string) => void
}
export function useBulkActions({ listToRender, setErrorMessage, trackDownload }: BulkOptions) {
const { fm, adminDrive, drives, refreshStamp, setShowError } = useContext(FMContext)
const { beeApi } = useContext(SettingsContext)
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
const [driveStamps, setDriveStamps] = useState<PostageBatch[] | undefined>(undefined)
const allIds = useMemo(() => listToRender.map(getFileId), [listToRender])
const selectedCount = useMemo(() => allIds.filter(id => selectedIds.has(id)).length, [allIds, selectedIds])
const allChecked = useMemo(() => allIds.length > 0 && selectedCount === allIds.length, [allIds.length, selectedCount])
const someChecked = useMemo(() => selectedCount > 0 && !allChecked, [selectedCount, allChecked])
const isMountedRef = useRef(true)
const fileInputRef = useRef<HTMLInputElement | null>(null)
const selectedFiles = useMemo(
() => listToRender.filter(fi => selectedIds.has(getFileId(fi))),
[listToRender, selectedIds],
)
useEffect(() => {
isMountedRef.current = true
const getStamps = async () => {
const stamps = await getUsableStamps(beeApi)
const stampList = stamps.filter(s => drives.some(d => d.batchId.toString() === s.batchID.toString()))
safeSetState(isMountedRef, setDriveStamps)(stampList)
}
getStamps()
return () => {
isMountedRef.current = false
}
}, [beeApi, drives])
const toggleOne = useCallback((fi: FileInfo, checked: boolean) => {
const id = getFileId(fi)
setSelectedIds(prev => {
const next = new Set(prev)
if (checked) {
next.add(id)
} else {
next.delete(id)
}
return next
})
}, [])
const selectAll = useCallback(() => setSelectedIds(new Set(allIds)), [allIds])
const clearAll = useCallback(() => setSelectedIds(new Set()), [])
const bulkUploadFromPicker = useCallback(() => {
fileInputRef.current?.click()
}, [])
const bulkDownload = useCallback(
async (list: FileInfo[]) => {
if (!fm || !list?.length) return
const trackers = new Array<(progress: DownloadProgress) => void>(list.length)
const infoListWitIDs: FileInfoWithUUID[] = new Array<FileInfoWithUUID>(list.length)
for (let i = 0; i < list.length; i++) {
const fi = list[i]
const rawSize = fi.customMetadata?.size as string | number | undefined
const prettySize = formatBytes(rawSize)
const expected = rawSize ? Number(rawSize) : undefined
const driveName = drives.find(d => d.id.toString() === fi.driveId.toString())?.name
const uuid = uuidV4()
infoListWitIDs[i] = { uuid, info: fi }
trackers[i] = trackDownload({
uuid,
name: fi.name,
size: prettySize,
expectedSize: expected,
driveName,
})
}
await startDownloadingQueue(fm, infoListWitIDs, trackers)
},
[fm, trackDownload, drives],
)
const bulkTrash = useCallback(
async (list: FileInfo[]) => {
if (!fm || !list?.length) return
await performBulkFileOperation({
fm,
files: list,
operation: FileOperation.Trash,
stamps: driveStamps || [],
onError: error => {
setErrorMessage?.(error)
setShowError(true)
},
onFileComplete: file => {
refreshStamp(file.batchId.toString())
},
})
clearAll()
},
[fm, driveStamps, clearAll, refreshStamp, setErrorMessage, setShowError],
)
const bulkRestore = useCallback(
async (list: FileInfo[]) => {
if (!fm || !list?.length) return
await performBulkFileOperation({
fm,
files: list,
operation: FileOperation.Recover,
stamps: driveStamps || [],
onError: error => {
setErrorMessage?.(error)
setShowError(true)
},
onFileComplete: file => {
refreshStamp(file.batchId.toString())
},
})
clearAll()
},
[fm, driveStamps, refreshStamp, clearAll, setErrorMessage, setShowError],
)
const bulkForget = useCallback(
async (list: FileInfo[]) => {
if (!fm || !fm.adminStamp || !adminDrive || !list?.length) return
await performBulkFileOperation({
fm,
files: list,
operation: FileOperation.Forget,
stamps: driveStamps || [],
adminStamp: fm.adminStamp,
adminDrive: adminDrive || undefined,
onError: error => {
setErrorMessage?.(error)
setShowError(true)
},
onFileComplete: () => {
if (fm.adminStamp) {
refreshStamp(fm.adminStamp.batchID.toString())
}
},
})
clearAll()
},
[fm, adminDrive, driveStamps, clearAll, refreshStamp, setErrorMessage, setShowError],
)
return useMemo(
() => ({
// selection
selectedIds,
setSelectedIds,
selectedFiles,
selectedCount,
allChecked,
someChecked,
toggleOne,
selectAll,
clearAll,
// file input (for bulk upload)
fileInputRef,
bulkUploadFromPicker,
// actions
bulkDownload,
bulkTrash,
bulkRestore,
bulkForget,
}),
[
selectedIds,
selectedFiles,
selectedCount,
allChecked,
someChecked,
toggleOne,
selectAll,
clearAll,
bulkUploadFromPicker,
bulkDownload,
bulkTrash,
bulkRestore,
bulkForget,
],
)
}