feat: enable multiple file selection from OneDrive

This commit is contained in:
Florian SCHNEIDER 2026-02-16 18:03:02 +01:00
parent ca2aaf0321
commit 18e784d88b
2 changed files with 137 additions and 16 deletions

View file

@ -14,7 +14,7 @@
import { onMount, tick, getContext, createEventDispatcher } from 'svelte';
import { createPicker, getAuthToken } from '$lib/utils/google-drive-picker';
import { pickAndDownloadFile } from '$lib/utils/onedrive-file-picker';
import { pickAndDownloadFiles } from '$lib/utils/onedrive-file-picker';
import { KokoroWorker } from '$lib/workers/KokoroWorker';
const dispatch = createEventDispatcher();
@ -1547,17 +1547,32 @@
}}
uploadOneDriveHandler={async (authorityType) => {
try {
const fileData = await pickAndDownloadFile(authorityType);
if (fileData) {
const file = new File([fileData.blob], fileData.name, {
type: fileData.blob.type || 'application/octet-stream'
});
await uploadFileHandler(file);
const filesData = await pickAndDownloadFiles(authorityType);
if (filesData && filesData.length > 0) {
if (filesData.length > 1) {
toast.success(
$i18n.t('Uploading {{count}} files from OneDrive...', {
count: filesData.length
})
);
}
for (const fileData of filesData) {
const file = new File([fileData.blob], fileData.name, {
type: fileData.blob.type || 'application/octet-stream'
});
await uploadFileHandler(file);
}
} else {
console.log('No file was selected from OneDrive');
console.log('No files were selected from OneDrive');
}
} catch (error) {
console.error('OneDrive Error:', error);
toast.error(
$i18n.t('OneDrive Error: {{error}}', {
error: error.message
})
);
}
}}
{onUpload}

View file

@ -179,6 +179,9 @@ interface PickerParams {
search: {
enabled: boolean;
};
selection?: {
mode?: 'single' | 'multiple' | 'pick';
};
typesAndSources: {
mode: string;
pivots: Record<string, boolean>;
@ -210,12 +213,18 @@ function getPickerParams(): PickerParams {
search: {
enabled: true
},
selection: {
mode: 'multiple'
},
typesAndSources: {
mode: 'files',
mode: 'all',
pivots: {
oneDrive: true,
recent: true,
myOrganization: config.getAuthorityType() === 'organizations'
shared: true,
sharedLibraries: true,
myOrganization: config.getAuthorityType() === 'organizations',
site: config.getAuthorityType() === 'organizations'
}
}
};
@ -235,10 +244,66 @@ interface OneDriveFileInfo {
driveId: string;
};
'@sharePoint.endpoint': string;
folder?: {
childCount?: number;
};
file?: {
mimeType?: string;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
}
// List all items in a folder recursively
async function listFolderContentsRecursive(
folderInfo: OneDriveFileInfo,
authorityType?: 'personal' | 'organizations',
currentPath: string = ''
): Promise<OneDriveFileInfo[]> {
const accessToken = await getToken(undefined, authorityType);
if (!accessToken) {
throw new Error('Unable to retrieve OneDrive access token.');
}
const childrenUrl = `${folderInfo['@sharePoint.endpoint']}/drives/${folderInfo.parentReference.driveId}/items/${folderInfo.id}/children`;
const response = await fetch(childrenUrl, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error(`Failed to list folder contents: ${response.status} ${response.statusText}`);
}
const data = await response.json();
const items: OneDriveFileInfo[] = data.value || [];
const allFiles: OneDriveFileInfo[] = [];
// Process all items
for (const item of items) {
// Inherit the @sharePoint.endpoint from parent folder if not present
if (!item['@sharePoint.endpoint']) {
item['@sharePoint.endpoint'] = folderInfo['@sharePoint.endpoint'];
}
if (item.folder) {
// Recursively get files from subfolder
const subPath = currentPath ? `${currentPath}/${item.name}` : item.name;
const subfolderFiles = await listFolderContentsRecursive(item, authorityType, subPath);
allFiles.push(...subfolderFiles);
} else if (item.file) {
// It's a file, add it to the list
allFiles.push(item);
}
// Skip items that are neither files nor folders (e.g., packages, OneNote notebooks)
}
return allFiles;
}
// Download file from OneDrive
async function downloadOneDriveFile(
fileInfo: OneDriveFileInfo,
@ -434,20 +499,61 @@ export async function openOneDrivePicker(
});
}
// Pick and download file from OneDrive
export async function pickAndDownloadFile(
// Pick and download multiple files from OneDrive (with folder support)
export async function pickAndDownloadFiles(
authorityType?: 'personal' | 'organizations'
): Promise<{ blob: Blob; name: string } | null> {
): Promise<{ blob: Blob; name: string }[] | null> {
const pickerResult = await openOneDrivePicker(authorityType);
if (!pickerResult || !pickerResult.items || pickerResult.items.length === 0) {
return null;
}
const selectedFile = pickerResult.items[0];
const blob = await downloadOneDriveFile(selectedFile, authorityType);
const allFiles: OneDriveFileInfo[] = [];
return { blob, name: selectedFile.name };
// First, expand folders to get all files
for (const item of pickerResult.items) {
if (item.folder) {
// It's a folder, get all files recursively
const folderFiles = await listFolderContentsRecursive(item, authorityType, item.name);
allFiles.push(...folderFiles);
} else {
// It's a file
allFiles.push(item);
}
}
// Check if any files were actually found
if (allFiles.length === 0) {
throw new Error('No files found in the selected items.');
}
// Download all files with error handling
const downloadPromises = allFiles.map(async (fileInfo) => {
const blob = await downloadOneDriveFile(fileInfo, authorityType);
return {
blob,
name: fileInfo.name
};
});
const results = await Promise.allSettled(downloadPromises);
const successful = results
.filter((r): r is PromiseFulfilledResult<{ blob: Blob; name: string }> => r.status === 'fulfilled')
.map((r) => r.value);
const failed = results.filter((r) => r.status === 'rejected');
if (failed.length > 0) {
console.warn(`Failed to download ${failed.length} out of ${allFiles.length} files from OneDrive`);
// If all downloads failed, throw error
if (successful.length === 0) {
throw new Error('Failed to download any files from OneDrive');
}
// If some succeeded, log warning but continue with successful ones
}
return successful.length > 0 ? successful : null;
}
export { downloadOneDriveFile };