Skip to content

Commit 71a6c60

Browse files
aqib-iopaulpopus
andauthored
fix(ui): preselect folder when bulk uploading from inside a folder (#16030)
### What? Preselect the current folder when triggering bulk upload from inside a folder view. ### Why? When bulk uploading from inside a folder, all files show "No Folder" instead of the current folder. Users have to manually assign each file to the folder, which is tedious and easy to miss. See #15647 ### How? - Added `folderID` to `BulkUploadContext` to pass folder state from the button to the drawer - In `ListBulkUploadButton`, read `folderID` from `useFolder()` and set it on the context when opening bulk upload - In `FormsManager`, inject the `folderID` into each form's initial state so the folder field is pre-populated Fixes #15647 --------- Co-authored-by: Paul Popus <paul@payloadcms.com>
1 parent 9391c20 commit 71a6c60

4 files changed

Lines changed: 99 additions & 24 deletions

File tree

packages/ui/src/elements/BulkUpload/FormsManager/index.tsx

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
105105
const {
106106
routes: { api },
107107
} = config
108+
const folderFieldName = config.folders ? config.folders.fieldName : undefined
108109
const { code } = useLocale()
109110
const { i18n, t } = useTranslation()
110111

@@ -130,6 +131,7 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
130131
const {
131132
collectionSlug,
132133
drawerSlug,
134+
folderID,
133135
initialFiles,
134136
initialForms,
135137
onSuccess,
@@ -218,13 +220,24 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
218220
schemaPath: collectionSlug,
219221
skipValidation: true,
220222
})
223+
224+
if (folderFieldName && formStateWithoutFiles?.[folderFieldName]) {
225+
formStateWithoutFiles[folderFieldName] = {
226+
...formStateWithoutFiles[folderFieldName],
227+
customComponents: {
228+
...formStateWithoutFiles[folderFieldName].customComponents,
229+
Field: undefined,
230+
},
231+
}
232+
}
233+
221234
initialStateRef.current = formStateWithoutFiles
222235
setHasInitializedState(true)
223236
} catch (_err) {
224237
// swallow error
225238
}
226239
},
227-
[getDocumentSlots, collectionSlug, getFormState, docPermissions, code],
240+
[getDocumentSlots, collectionSlug, getFormState, docPermissions, code, folderFieldName],
228241
)
229242

230243
const setActiveIndex: FormsManagerContext['setActiveIndex'] = React.useCallback(
@@ -252,6 +265,23 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
252265
[forms, activeIndex],
253266
)
254267

268+
const applyFolderToState = React.useCallback(
269+
(baseState: FormState | null): FormState | null => {
270+
if (folderID && folderFieldName && baseState?.[folderFieldName]) {
271+
return {
272+
...baseState,
273+
[folderFieldName]: {
274+
...baseState[folderFieldName],
275+
initialValue: folderID,
276+
value: folderID,
277+
},
278+
}
279+
}
280+
return baseState
281+
},
282+
[folderID, folderFieldName],
283+
)
284+
255285
const addFiles = React.useCallback(
256286
async (files: FileList) => {
257287
if (forms.length) {
@@ -272,12 +302,19 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
272302
type: 'ADD_FORMS',
273303
forms: Array.from(files).map((file) => ({
274304
file,
275-
initialState: initialStateRef.current,
305+
initialState: applyFolderToState(initialStateRef.current),
276306
})),
277307
})
278308
toggleLoadingOverlay({ isLoading: false, key: 'addingDocs' })
279309
},
280-
[initializeSharedFormState, hasInitializedState, toggleLoadingOverlay, activeIndex, forms],
310+
[
311+
initializeSharedFormState,
312+
hasInitializedState,
313+
toggleLoadingOverlay,
314+
activeIndex,
315+
forms,
316+
applyFolderToState,
317+
],
281318
)
282319

283320
const addFilesEffectEvent = useEffectEvent(addFiles)
@@ -293,7 +330,7 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
293330
type: 'ADD_FORMS',
294331
forms: initialForms.map((form) => ({
295332
...form,
296-
initialState: form?.initialState || initialStateRef.current,
333+
initialState: applyFolderToState(form?.initialState || initialStateRef.current),
297334
})),
298335
})
299336

packages/ui/src/elements/BulkUpload/index.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export function BulkUploadDrawer() {
7676
const {
7777
drawerSlug,
7878
onCancel,
79+
setFolderID,
7980
setInitialFiles,
8081
setInitialForms,
8182
setOnCancel,
@@ -109,6 +110,7 @@ export function BulkUploadDrawer() {
109110
}
110111

111112
// Reset everything to defaults
113+
setFolderID(undefined)
112114
setInitialFiles(undefined)
113115
setInitialForms(undefined)
114116
setOnCancel(() => () => null)
@@ -140,6 +142,7 @@ export function BulkUploadDrawer() {
140142
export type BulkUploadContext = {
141143
collectionSlug: CollectionSlug
142144
drawerSlug: string
145+
folderID?: number | string
143146
initialFiles: FileList
144147
/**
145148
* Like initialFiles, but allows manually providing initial form state or the form ID for each file
@@ -164,6 +167,7 @@ export type BulkUploadContext = {
164167
*/
165168
selectableCollections?: null | string[]
166169
setCollectionSlug: (slug: string) => void
170+
setFolderID: (folderID: number | string | undefined) => void
167171
setInitialFiles: (files: FileList) => void
168172
setInitialForms: (
169173
forms: ((forms: InitialForms | undefined) => InitialForms | undefined) | InitialForms,
@@ -184,13 +188,15 @@ export type BulkUploadContext = {
184188
const Context = React.createContext<BulkUploadContext>({
185189
collectionSlug: '',
186190
drawerSlug: '',
191+
folderID: undefined,
187192
initialFiles: undefined,
188193
initialForms: [],
189194
maxFiles: undefined,
190195
onCancel: () => null,
191196
onSuccess: () => null,
192197
selectableCollections: null,
193198
setCollectionSlug: () => null,
199+
setFolderID: () => null,
194200
setInitialFiles: () => null,
195201
setInitialForms: () => null,
196202
setMaxFiles: () => null,
@@ -209,6 +215,7 @@ export function BulkUploadProvider({
209215
}) {
210216
const [selectableCollections, setSelectableCollections] = React.useState<null | string[]>(null)
211217
const [collection, setCollection] = React.useState<string>()
218+
const [folderID, setFolderID] = React.useState<number | string | undefined>(undefined)
212219
const [onSuccessFunction, setOnSuccessFunction] = React.useState<BulkUploadContext['onSuccess']>()
213220
const [onCancelFunction, setOnCancelFunction] = React.useState<BulkUploadContext['onCancel']>()
214221
const [initialFiles, setInitialFiles] = React.useState<FileList>(undefined)
@@ -230,6 +237,7 @@ export function BulkUploadProvider({
230237
value={{
231238
collectionSlug: collection,
232239
drawerSlug,
240+
folderID,
233241
initialFiles,
234242
initialForms,
235243
maxFiles,
@@ -245,6 +253,7 @@ export function BulkUploadProvider({
245253
},
246254
selectableCollections,
247255
setCollectionSlug: setCollection,
256+
setFolderID,
248257
setInitialFiles,
249258
setInitialForms,
250259
setMaxFiles,

packages/ui/src/elements/ListHeader/TitleActions/ListBulkUploadButton.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation.js'
66
import React from 'react'
77

88
import { useBulkUpload } from '../../../elements/BulkUpload/index.js'
9+
import { useFolder } from '../../../providers/Folders/index.js'
910
import { useTranslation } from '../../../providers/Translation/index.js'
1011
import { Button } from '../../Button/index.js'
1112

@@ -27,7 +28,13 @@ export function ListBulkUploadButton({
2728
*/
2829
openBulkUpload?: () => void
2930
}) {
30-
const { drawerSlug: bulkUploadDrawerSlug, setCollectionSlug, setOnSuccess } = useBulkUpload()
31+
const {
32+
drawerSlug: bulkUploadDrawerSlug,
33+
setCollectionSlug,
34+
setFolderID,
35+
setOnSuccess,
36+
} = useBulkUpload()
37+
const { folderID } = useFolder()
3138
const { t } = useTranslation()
3239
const { openModal } = useModal()
3340
const router = useRouter()
@@ -37,6 +44,7 @@ export function ListBulkUploadButton({
3744
openBulkUploadFromProps()
3845
} else {
3946
setCollectionSlug(collectionSlug)
47+
setFolderID(folderID)
4048
openModal(bulkUploadDrawerSlug)
4149
setOnSuccess(() => {
4250
if (typeof onBulkUploadSuccess === 'function') {
@@ -50,8 +58,10 @@ export function ListBulkUploadButton({
5058
router,
5159
collectionSlug,
5260
bulkUploadDrawerSlug,
61+
folderID,
5362
openModal,
5463
setCollectionSlug,
64+
setFolderID,
5565
setOnSuccess,
5666
onBulkUploadSuccess,
5767
openBulkUploadFromProps,

test/folders/e2e.spec.ts

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,29 @@ import * as path from 'path'
55
import { formatAdminURL } from 'payload/shared'
66
import { fileURLToPath } from 'url'
77

8+
import { applyBrowseByFolderTypeFilter } from '../__helpers/e2e/folders/applyBrowseByFolderTypeFilter.js'
9+
import { clickFolderCard } from '../__helpers/e2e/folders/clickFolderCard.js'
10+
import { createFolder } from '../__helpers/e2e/folders/createFolder.js'
11+
import { createFolderDoc } from '../__helpers/e2e/folders/createFolderDoc.js'
12+
import { createFolderFromDoc } from '../__helpers/e2e/folders/createFolderFromDoc.js'
13+
import { expectNoResultsAndCreateFolderButton } from '../__helpers/e2e/folders/expectNoResultsAndCreateFolderButton.js'
14+
import { selectFolderAndConfirmMove } from '../__helpers/e2e/folders/selectFolderAndConfirmMove.js'
15+
import { selectFolderAndConfirmMoveFromList } from '../__helpers/e2e/folders/selectFolderAndConfirmMoveFromList.js'
816
import {
917
closeAllToasts,
1018
ensureCompilationIsDone,
1119
getRoutes,
1220
initPageConsoleErrorCatch,
1321
saveDocAndAssert,
1422
} from '../__helpers/e2e/helpers.js'
15-
import { AdminUrlUtil } from '../__helpers/shared/adminUrlUtil.js'
1623
import {
1724
getSelectInputOptions,
1825
getSelectInputValue,
1926
openSelectMenu,
2027
} from '../__helpers/e2e/selectInput.js'
21-
import { applyBrowseByFolderTypeFilter } from '../__helpers/e2e/folders/applyBrowseByFolderTypeFilter.js'
22-
import { clickFolderCard } from '../__helpers/e2e/folders/clickFolderCard.js'
23-
import { createFolder } from '../__helpers/e2e/folders/createFolder.js'
24-
import { createFolderDoc } from '../__helpers/e2e/folders/createFolderDoc.js'
25-
import { createFolderFromDoc } from '../__helpers/e2e/folders/createFolderFromDoc.js'
26-
import { expectNoResultsAndCreateFolderButton } from '../__helpers/e2e/folders/expectNoResultsAndCreateFolderButton.js'
27-
import { selectFolderAndConfirmMove } from '../__helpers/e2e/folders/selectFolderAndConfirmMove.js'
28-
import { selectFolderAndConfirmMoveFromList } from '../__helpers/e2e/folders/selectFolderAndConfirmMoveFromList.js'
29-
import { initPayloadE2ENoConfig } from '../__helpers/shared/initPayloadE2ENoConfig.js'
28+
import { AdminUrlUtil } from '../__helpers/shared/adminUrlUtil.js'
3029
import { reInitializeDB } from '../__helpers/shared/clearAndSeed/reInitializeDB.js'
30+
import { initPayloadE2ENoConfig } from '../__helpers/shared/initPayloadE2ENoConfig.js'
3131
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
3232
import { omittedFromBrowseBySlug, postSlug } from './shared.js'
3333

@@ -395,6 +395,33 @@ test.describe('Folders', () => {
395395
await expect(testFolderCard).toBeHidden()
396396
await expect(searchFolderCard).toBeVisible()
397397
})
398+
399+
test('should preselect folder when bulk uploading from inside a folder', async () => {
400+
const mediaURL = new AdminUrlUtil(serverURL, 'media')
401+
await page.goto(mediaURL.byFolder)
402+
403+
await createFolder({ folderName: 'Bulk Upload Folder', folderType: ['Media'], page })
404+
await clickFolderCard({ folderName: 'Bulk Upload Folder', page, doubleClick: true })
405+
406+
const bulkUploadButton = page.locator('.list-header__title-actions button', {
407+
hasText: 'Bulk Upload',
408+
})
409+
await expect(bulkUploadButton).toBeVisible()
410+
await bulkUploadButton.click()
411+
412+
const bulkUploadDrawer = page.locator('dialog#media-bulk-upload-drawer-slug-1')
413+
await expect(bulkUploadDrawer).toBeVisible()
414+
415+
await bulkUploadDrawer
416+
.locator('.dropzone input[type="file"]')
417+
.setInputFiles(path.resolve(dirname, '../uploads/image.png'))
418+
419+
const folderField = bulkUploadDrawer.locator('.render-fields #field-folder')
420+
await expect(folderField).toBeVisible()
421+
await expect(folderField.locator('.relationship--single-value__text')).toHaveText(
422+
'Bulk Upload Folder',
423+
)
424+
})
398425
})
399426

400427
test.describe('Collection view actions', () => {
@@ -463,15 +490,7 @@ test.describe('Folders', () => {
463490

464491
test('should create folder from By Folder view', async () => {
465492
await page.goto(postURL.byFolder)
466-
const createButton = page.locator('.create-new-doc-in-folder__button', {
467-
hasText: 'Create folder',
468-
})
469-
await createButton.click()
470-
await createFolderDoc({
471-
page,
472-
folderName: 'New Folder From Collection',
473-
folderType: ['Posts'],
474-
})
493+
await createFolder({ folderName: 'New Folder From Collection', folderType: ['Posts'], page })
475494
})
476495
})
477496

0 commit comments

Comments
 (0)