upload queue
This commit is contained in:
@@ -29,8 +29,6 @@ const useDocumentUploads = ({
|
||||
currentFolderName,
|
||||
ensureFolderData,
|
||||
refreshCurrentFolder,
|
||||
notifyApiError,
|
||||
setStatusMessage,
|
||||
setLoading,
|
||||
shellRef,
|
||||
}) => {
|
||||
@@ -40,12 +38,13 @@ const useDocumentUploads = ({
|
||||
});
|
||||
const dragCounterRef = useRef(0);
|
||||
const folderPathCacheRef = useRef(new Map());
|
||||
const queueIdRef = useRef(0);
|
||||
const [uploadQueue, setUploadQueue] = useState([]);
|
||||
|
||||
const uploadFile = useCallback(
|
||||
async (file, targetFolderId) => {
|
||||
if (!file || file.size === 0) {
|
||||
setStatusMessage('Skipped empty file.', 'error');
|
||||
return null;
|
||||
return { document: null, duplicate: false, statusCode: null, conflictDocumentId: null };
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
@@ -54,29 +53,74 @@ const useDocumentUploads = ({
|
||||
formData.append('folder_id', targetFolderId);
|
||||
}
|
||||
|
||||
try {
|
||||
const { data, status } = await apiClient.post('/documents', formData);
|
||||
const duplicate = data?.reused || status === 200;
|
||||
setStatusMessage(
|
||||
duplicate
|
||||
? `${file.name} already exists; reused existing document.`
|
||||
: `Uploaded ${file.name}`,
|
||||
duplicate ? 'info' : 'success',
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
if (error.response?.status === 409) {
|
||||
setStatusMessage(`${file.name} already exists on the server.`, 'info');
|
||||
return null;
|
||||
try {
|
||||
const { data, status } = await apiClient.post('/documents', formData);
|
||||
const duplicate = data?.reused || status === 200;
|
||||
const document = data?.document ?? data ?? null;
|
||||
return {
|
||||
document,
|
||||
duplicate,
|
||||
statusCode: status ?? (duplicate ? 200 : 201),
|
||||
conflictDocumentId: null,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error.response?.status === 409) {
|
||||
const conflictId = error.response?.data?.details?.conflict_document_id ?? null;
|
||||
let conflictDocument = null;
|
||||
if (conflictId) {
|
||||
try {
|
||||
const { data } = await apiClient.get(`/documents/${conflictId}`);
|
||||
conflictDocument = data?.document ?? data ?? null;
|
||||
} catch (fetchError) {
|
||||
console.warn('[Uploads] failed to fetch conflict document', fetchError);
|
||||
}
|
||||
}
|
||||
return {
|
||||
document: conflictDocument,
|
||||
duplicate: true,
|
||||
statusCode: 409,
|
||||
conflictDocumentId: conflictId,
|
||||
};
|
||||
}
|
||||
const message = error.response?.data?.error || `Failed to upload ${file.name}.`;
|
||||
const wrapped = Object.assign(new Error(message), { response: error.response });
|
||||
throw wrapped;
|
||||
}
|
||||
const message = error.response?.data?.error || `Failed to upload ${file.name}.`;
|
||||
notifyApiError(error, message);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[apiClient, notifyApiError, setStatusMessage],
|
||||
},
|
||||
[apiClient],
|
||||
);
|
||||
|
||||
const appendQueueItems = useCallback((entries, targetFolderId) => {
|
||||
const baseId = Date.now();
|
||||
const items = entries.map(({ file }) => {
|
||||
queueIdRef.current += 1;
|
||||
return {
|
||||
id: `upload-${baseId}-${queueIdRef.current}`,
|
||||
name: file?.name || 'Unnamed file',
|
||||
size: file?.size ?? null,
|
||||
folderId: targetFolderId ?? selectedFolder ?? 'root',
|
||||
status: 'pending',
|
||||
error: null,
|
||||
code: null,
|
||||
document: null,
|
||||
conflictDocumentId: null,
|
||||
};
|
||||
});
|
||||
if (items.length) {
|
||||
setUploadQueue((current) => [...current, ...items]);
|
||||
}
|
||||
return items;
|
||||
}, [selectedFolder]);
|
||||
|
||||
const updateQueueItem = useCallback((id, patch) => {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
setUploadQueue((current) =>
|
||||
current.map((item) => (item.id === id ? { ...item, ...patch } : item)),
|
||||
);
|
||||
}, []);
|
||||
|
||||
const ensureFolderPathOnServer = useCallback(
|
||||
async (baseFolderId, segments) => {
|
||||
const trimmedSegments = segments.map((segment) => segment.trim()).filter(Boolean);
|
||||
@@ -226,13 +270,23 @@ const useDocumentUploads = ({
|
||||
|
||||
const uploadFileEntries = useCallback(
|
||||
async (entries, targetFolderId) => {
|
||||
if (!token) {
|
||||
setStatusMessage('Please log in before uploading.', 'error');
|
||||
if (!entries || !entries.length) {
|
||||
console.warn('[Uploads] No files to upload.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entries || !entries.length) {
|
||||
setStatusMessage('No files to upload.', 'info');
|
||||
const queueItems = appendQueueItems(entries, targetFolderId);
|
||||
|
||||
if (!token) {
|
||||
queueItems.forEach((item) => {
|
||||
const patch = {
|
||||
status: 'error',
|
||||
error: 'Please log in before uploading.',
|
||||
code: null,
|
||||
};
|
||||
updateQueueItem(item.id, patch);
|
||||
Object.assign(item, patch);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -244,7 +298,14 @@ const useDocumentUploads = ({
|
||||
const baseFolderId =
|
||||
targetFolderId && targetFolderId !== 'root' ? targetFolderId : null;
|
||||
|
||||
for (const { file, segments } of entries) {
|
||||
for (let index = 0; index < entries.length; index += 1) {
|
||||
const { file, segments } = entries[index];
|
||||
const queueItem = queueItems[index];
|
||||
if (queueItem) {
|
||||
const patch = { status: 'uploading', error: null, code: null };
|
||||
updateQueueItem(queueItem.id, patch);
|
||||
Object.assign(queueItem, patch);
|
||||
}
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const destinationId = segments.length
|
||||
? await ensureFolderPathOnServer(baseFolderId, segments)
|
||||
@@ -254,8 +315,34 @@ const useDocumentUploads = ({
|
||||
destinationId ??
|
||||
(targetFolderId && targetFolderId !== 'root' ? targetFolderId : 'root');
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await uploadFile(file, uploadTarget);
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { duplicate, statusCode, document, conflictDocumentId } = await uploadFile(
|
||||
file,
|
||||
uploadTarget,
|
||||
);
|
||||
if (queueItem) {
|
||||
const patch = {
|
||||
status: duplicate ? 'duplicate' : 'success',
|
||||
code: statusCode ?? null,
|
||||
document: document || queueItem.document,
|
||||
conflictDocumentId: conflictDocumentId ?? queueItem.conflictDocumentId,
|
||||
};
|
||||
updateQueueItem(queueItem.id, patch);
|
||||
Object.assign(queueItem, patch);
|
||||
}
|
||||
} catch (error) {
|
||||
if (queueItem) {
|
||||
const patch = {
|
||||
status: 'error',
|
||||
error: error.response?.data?.error || error.message || 'Upload failed.',
|
||||
code: error.response?.status ?? null,
|
||||
};
|
||||
updateQueueItem(queueItem.id, patch);
|
||||
Object.assign(queueItem, patch);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
await refreshCurrentFolder();
|
||||
@@ -268,7 +355,20 @@ const useDocumentUploads = ({
|
||||
await ensureFolderData(targetFolderId, { force: true, prefetchDepth: 1 });
|
||||
}
|
||||
} catch (error) {
|
||||
notifyApiError(error, 'Failed to upload files.');
|
||||
const message = error.message || 'Failed to upload files.';
|
||||
queueItems.forEach((item) => {
|
||||
if (item.status === 'success' || item.status === 'duplicate' || item.status === 'error') {
|
||||
return;
|
||||
}
|
||||
const patch = {
|
||||
status: 'error',
|
||||
error: message,
|
||||
code: error.response?.status ?? null,
|
||||
};
|
||||
updateQueueItem(item.id, patch);
|
||||
Object.assign(item, patch);
|
||||
});
|
||||
console.error('[Uploads] batch failed', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -280,9 +380,9 @@ const useDocumentUploads = ({
|
||||
refreshCurrentFolder,
|
||||
selectedFolder,
|
||||
ensureFolderData,
|
||||
notifyApiError,
|
||||
setStatusMessage,
|
||||
setLoading,
|
||||
appendQueueItems,
|
||||
updateQueueItem,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -292,14 +392,13 @@ const useDocumentUploads = ({
|
||||
try {
|
||||
extracted = await extractFilesFromDataTransfer(dataTransfer);
|
||||
} catch (error) {
|
||||
const message = error.message || 'Failed to process dropped files.';
|
||||
notifyApiError(error, message);
|
||||
console.error('[Uploads] Failed to process dropped files.', error);
|
||||
return;
|
||||
}
|
||||
|
||||
await uploadFileEntries(extracted, targetFolderId);
|
||||
},
|
||||
[extractFilesFromDataTransfer, uploadFileEntries, notifyApiError],
|
||||
[extractFilesFromDataTransfer, uploadFileEntries],
|
||||
);
|
||||
|
||||
const handleFileSelection = useCallback(
|
||||
@@ -326,6 +425,11 @@ const useDocumentUploads = ({
|
||||
const resetUploadsState = useCallback(() => {
|
||||
dragCounterRef.current = 0;
|
||||
setDropOverlayState({ active: false, folderName: DEFAULT_FOLDER_NAME });
|
||||
setUploadQueue([]);
|
||||
}, []);
|
||||
|
||||
const clearUploadQueue = useCallback(() => {
|
||||
setUploadQueue([]);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
@@ -337,6 +441,8 @@ const useDocumentUploads = ({
|
||||
uploadFile,
|
||||
extractFilesFromDataTransfer,
|
||||
resetUploadsState,
|
||||
uploadQueue,
|
||||
clearUploadQueue,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -534,6 +534,8 @@ const useDocumentsWorkspace = ({
|
||||
dropOverlayState,
|
||||
handleFileDrop,
|
||||
handleFileSelection,
|
||||
uploadQueue,
|
||||
clearUploadQueue,
|
||||
resetUploadsState,
|
||||
} = useDocumentUploads({
|
||||
apiClient: api,
|
||||
@@ -624,6 +626,7 @@ const useDocumentsWorkspace = ({
|
||||
assetManager.reset();
|
||||
resetPreviewState();
|
||||
resetUploadsState();
|
||||
clearUploadQueue();
|
||||
breadcrumbFetchRef.current = new Set();
|
||||
detailFolderFetchRef.current = new Set();
|
||||
bootstrapInitializedRef.current = false;
|
||||
@@ -654,6 +657,7 @@ const useDocumentsWorkspace = ({
|
||||
setActivePreviewId,
|
||||
resetPreviewState,
|
||||
resetUploadsState,
|
||||
clearUploadQueue,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1373,6 +1377,7 @@ const useDocumentsWorkspace = ({
|
||||
handleTenantSelect,
|
||||
openSettings,
|
||||
handleFileSelection,
|
||||
uploadQueue,
|
||||
});
|
||||
|
||||
|
||||
@@ -1478,6 +1483,8 @@ const useDocumentsWorkspace = ({
|
||||
openSettings,
|
||||
detailPanelOpen,
|
||||
openDetailPanel,
|
||||
uploadQueue,
|
||||
clearUploadQueue,
|
||||
}),
|
||||
[
|
||||
token,
|
||||
@@ -1528,6 +1535,8 @@ const useDocumentsWorkspace = ({
|
||||
openSettings,
|
||||
detailPanelOpen,
|
||||
openDetailPanel,
|
||||
uploadQueue,
|
||||
clearUploadQueue,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user