@@ -27,9 +27,8 @@ interface UseFileUploadsOptions {
2727/** Serializes captured upload batches and reconciles the browser once each batch settles. */
2828export function useFileUploads ( { workspaceId, canEdit, onStart } : UseFileUploadsOptions ) {
2929 const queue = useRef ( Promise . resolve ( ) )
30- const controller = useRef < AbortController | null > ( null )
31- const pendingBatches = useRef ( 0 )
32- const generation = useRef ( 0 )
30+ const batchesRef = useRef < Set < AbortController > | null > ( null )
31+ const batches = ( batchesRef . current ??= new Set < AbortController > ( ) )
3332 const mounted = useRef ( true )
3433 const [ progress , setProgress ] = useState ( IDLE_PROGRESS )
3534 const queryClient = useQueryClient ( )
@@ -41,35 +40,40 @@ export function useFileUploads({ workspaceId, canEdit, onStart }: UseFileUploads
4140 mounted . current = true
4241 return ( ) => {
4342 mounted . current = false
44- generation . current += 1
45- controller . current ?. abort ( )
43+ for ( const batch of batches ) batch . abort ( )
4644 }
47- } , [ ] )
45+ } , [ workspaceId , canEdit , batches ] )
4846
4947 const upload = useCallback (
50- ( selection : UploadSelection | Promise < UploadSelection > , targetFolderId : string | null ) => {
51- const selected = Promise . resolve ( selection )
52- // Attach rejection handling immediately while a preceding batch is still uploading.
53- const captured = selected . then (
54- ( value ) => ( { success : true as const , value } ) ,
55- ( error : unknown ) => ( { success : false as const , error } )
56- )
48+ (
49+ captureSelection : ( signal : AbortSignal ) => UploadSelection | Promise < UploadSelection > ,
50+ targetFolderId : string | null
51+ ) => {
5752 if ( ! workspaceId || ! canEdit || ! mounted . current ) return Promise . resolve ( )
58- if ( pendingBatches . current >= MAX_PENDING_UPLOAD_BATCHES ) {
53+ if ( batches . size >= MAX_PENDING_UPLOAD_BATCHES ) {
5954 toast . error ( 'The upload queue is full' , {
6055 description : 'Wait for an upload to finish before adding more files or folders.' ,
6156 } )
6257 return Promise . resolve ( )
6358 }
64- pendingBatches . current += 1
65- const batchGeneration = generation . current
59+ const abort = new AbortController ( )
60+ batches . add ( abort )
61+ let selected : Promise < UploadSelection >
62+ try {
63+ selected = Promise . resolve ( captureSelection ( abort . signal ) )
64+ } catch ( error ) {
65+ selected = Promise . reject ( error )
66+ }
67+ // Native drag entries must be captured before the drop event releases its data store.
68+ const captured = selected . then (
69+ ( value ) => ( { success : true as const , value } ) ,
70+ ( error : unknown ) => ( { success : false as const , error } )
71+ )
6672 const run = async ( ) => {
67- if ( ! mounted . current || batchGeneration !== generation . current ) {
68- pendingBatches . current -= 1
73+ if ( ! mounted . current || abort . signal . aborted ) {
74+ batches . delete ( abort )
6975 return
7076 }
71- const abort = new AbortController ( )
72- controller . current = abort
7377 let attempted = false
7478 try {
7579 onStart ( )
@@ -95,6 +99,7 @@ export function useFileUploads({ workspaceId, canEdit, onStart }: UseFileUploads
9599 paths : directories ,
96100 signal : abort . signal ,
97101 } )
102+ abort . signal . throwIfAborted ( )
98103 for ( const folder of prepared . folders )
99104 folderIds . set ( JSON . stringify ( folder . path ) , folder . id )
100105 } catch ( error ) {
@@ -125,7 +130,7 @@ export function useFileUploads({ workspaceId, canEdit, onStart }: UseFileUploads
125130 skipToast : true ,
126131 skipInvalidation : true ,
127132 onProgress : ( { percent } ) => {
128- if ( mounted . current )
133+ if ( mounted . current && ! abort . signal . aborted )
129134 setProgress ( {
130135 completed : index ,
131136 total : allowed . length ,
@@ -147,7 +152,7 @@ export function useFileUploads({ workspaceId, canEdit, onStart }: UseFileUploads
147152 } )
148153 }
149154 }
150- if ( mounted . current )
155+ if ( mounted . current && ! abort . signal . aborted )
151156 setProgress ( {
152157 completed : index + 1 ,
153158 total : allowed . length ,
@@ -172,22 +177,20 @@ export function useFileUploads({ workspaceId, canEdit, onStart }: UseFileUploads
172177 toast . error ( getErrorMessage ( error , 'Could not upload the selection' ) )
173178 }
174179 } finally {
175- pendingBatches . current -= 1
180+ batches . delete ( abort )
176181 if ( attempted ) invalidateWorkspaceFileBrowsers ( queryClient , workspaceId )
177- controller . current = null
178182 if ( mounted . current ) setProgress ( IDLE_PROGRESS )
179183 }
180184 }
181185 queue . current = queue . current . then ( run , run )
182186 return queue . current
183187 } ,
184- [ workspaceId , canEdit , onStart , prepareFolders , uploadFile , notifyLimit , queryClient ]
188+ [ workspaceId , canEdit , onStart , prepareFolders , uploadFile , notifyLimit , queryClient , batches ]
185189 )
186190
187191 const cancel = useCallback ( ( ) => {
188- generation . current += 1
189- controller . current ?. abort ( )
190- } , [ ] )
192+ for ( const batch of batches ) batch . abort ( )
193+ } , [ batches ] )
191194
192195 return { upload, cancel, progress, uploading : progress . preparing || progress . total > 0 }
193196}
0 commit comments