From e6613488fce6aa3fed6db42fc5d3da72d75343f0 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Tue, 2 Dec 2025 10:34:03 +0700 Subject: [PATCH] allow selecting folders (#6074) for google drive #5532 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .changeset/lazy-games-pump.md | 5 ++ .../src/GooglePicker/GooglePickerView.tsx | 6 ++ .../src/GooglePicker/googlePicker.ts | 90 +++++++++++++++++-- packages/@uppy/status-bar/src/locale.ts | 1 + 4 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 .changeset/lazy-games-pump.md diff --git a/.changeset/lazy-games-pump.md b/.changeset/lazy-games-pump.md new file mode 100644 index 000000000..9f325670e --- /dev/null +++ b/.changeset/lazy-games-pump.md @@ -0,0 +1,5 @@ +--- +"@uppy/provider-views": patch +--- + +Allow selecting folders with Google Drive Picker. They will be recursively resolved. diff --git a/packages/@uppy/provider-views/src/GooglePicker/GooglePickerView.tsx b/packages/@uppy/provider-views/src/GooglePicker/GooglePickerView.tsx index 373ed8173..c18a06557 100644 --- a/packages/@uppy/provider-views/src/GooglePicker/GooglePickerView.tsx +++ b/packages/@uppy/provider-views/src/GooglePicker/GooglePickerView.tsx @@ -105,6 +105,11 @@ export default function GooglePickerView({ appId, onFilesPicked, signal, + onLoadingChange: (isLoading: boolean) => setLoading(isLoading), + onError: (err: unknown) => { + uppy.log(err) + uppy.info(i18n('failedToAddFiles'), 'error') + }, }) } else { // photos @@ -175,6 +180,7 @@ export default function GooglePickerView({ pickerType, setAccessToken, uppy, + i18n, ], ) diff --git a/packages/@uppy/provider-views/src/GooglePicker/googlePicker.ts b/packages/@uppy/provider-views/src/GooglePicker/googlePicker.ts index 6bbd5a350..b32c200c8 100644 --- a/packages/@uppy/provider-views/src/GooglePicker/googlePicker.ts +++ b/packages/@uppy/provider-views/src/GooglePicker/googlePicker.ts @@ -209,12 +209,16 @@ export async function showDrivePicker({ appId, onFilesPicked, signal, + onLoadingChange, + onError, }: { token: string apiKey: string appId: string onFilesPicked: (files: PickedItem[], accessToken: string) => void signal: AbortSignal | undefined + onLoadingChange: (loading: boolean) => void + onError: (err: unknown) => void }): Promise { // google drive picker will crash hard if given an invalid token, so we need to check it first // https://github.com/transloadit/uppy/pull/5443#pullrequestreview-2452439265 @@ -222,18 +226,88 @@ export async function showDrivePicker({ throw new InvalidTokenError() } - const onPicked = (picked: google.picker.ResponseObject) => { - if (picked.action === google.picker.Action.PICKED) { - // console.log('Picker response', JSON.stringify(picked, null, 2)); - onFilesPicked( - picked.docs.map((doc) => ({ + async function listFilesInDriveFolder({ + doc, + token, + signal, + }: { + doc: PickedItemBase + token: string + signal?: AbortSignal + }): Promise { + if (doc.mimeType !== 'application/vnd.google-apps.folder') { + return [ + { platform: 'drive', id: doc.id, name: doc.name, mimeType: doc.mimeType, - })), - token, + }, + ] + } + + const headers = getAuthHeader(token) + const items: PickedDriveItem[] = [] + let pageToken: string | undefined + + do { + const params = new URLSearchParams({ + q: `'${doc.id.replace(/'/g, "\\'")}' in parents and trashed = false`, + fields: 'nextPageToken, files(id, name, mimeType)', + pageSize: '1000', + ...(pageToken && { pageToken }), + }) + const res = await fetch( + `https://www.googleapis.com/drive/v3/files?${params.toString()}`, + { headers, signal }, ) + + if (!res.ok) { + throw new Error( + `Failed to list folder contents for '${doc.name}' (${doc.id}): ${res.status} ${res.statusText}`, + ) + } + const json: { nextPageToken?: string; files: PickedItemBase[] } = + await res.json() + pageToken = json.nextPageToken + + for (const file of json.files) { + items.push( + ...(await listFilesInDriveFolder({ doc: file, token, signal })), + ) + } + } while (pageToken) + + return items + } + + const onPicked = async (picked: google.picker.ResponseObject) => { + if (picked.action !== google.picker.Action.PICKED) return + + try { + onLoadingChange(true) + + // console.log('Picker response', JSON.stringify(picked, null, 2)); + const results: PickedDriveItem[] = [] + for (const doc of picked.docs) { + if (doc.mimeType === 'application/vnd.google-apps.folder') { + results.push( + ...(await listFilesInDriveFolder({ doc, token, signal })), + ) + } else { + results.push({ + platform: 'drive', + id: doc.id, + name: doc.name, + mimeType: doc.mimeType, + }) + } + } + onFilesPicked(results, token) + } catch (err) { + onError(err) + } finally { + onLoadingChange(false) } } @@ -248,7 +322,7 @@ export async function showDrivePicker({ .setIncludeFolders(true) // Note: setEnableDrives doesn't seem to work // .setEnableDrives(true) - .setSelectFolderEnabled(false) + .setSelectFolderEnabled(true) .setMode(google.picker.DocsViewMode.LIST), ) // NOTE: photos is broken and results in an error being returned from Google diff --git a/packages/@uppy/status-bar/src/locale.ts b/packages/@uppy/status-bar/src/locale.ts index 4ff9a3e79..e026aa988 100644 --- a/packages/@uppy/status-bar/src/locale.ts +++ b/packages/@uppy/status-bar/src/locale.ts @@ -46,5 +46,6 @@ export default { 1: '%{smart_count} more files added', }, showErrorDetails: 'Show error details', + failedToAddFiles: 'Failed to add files', }, }