Skip to content

fix(ui): upload edits handling for bulk uploads #12001

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 7, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export function AddingFilesView() {
hasPublishPermission,
hasSavePermission,
hasSubmitted,
resetUploadEdits,
updateUploadEdits,
} = useFormsManager()
const activeForm = forms[activeIndex]
const { getEntityConfig } = useConfig()
Expand Down Expand Up @@ -67,7 +69,12 @@ export function AddingFilesView() {
versionCount={0}
>
<ActionsBar collectionConfig={collectionConfig} />
<EditForm submitted={hasSubmitted} />
<EditForm
resetUploadEdits={resetUploadEdits}
submitted={hasSubmitted}
updateUploadEdits={updateUploadEdits}
uploadEdits={activeForm?.uploadEdits}
/>
</DocumentInfoProvider>
) : null}
</div>
Expand Down
18 changes: 12 additions & 6 deletions packages/ui/src/elements/BulkUpload/EditForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ import { useEditDepth } from '../../../providers/EditDepth/index.js'
import { OperationProvider } from '../../../providers/Operation/index.js'
import { useRouteTransition } from '../../../providers/RouteTransition/index.js'
import { useServerFunctions } from '../../../providers/ServerFunctions/index.js'
import { useUploadEdits } from '../../../providers/UploadEdits/index.js'
import { abortAndIgnore, handleAbortRef } from '../../../utilities/abortAndIgnore.js'
import { useDocumentDrawerContext } from '../../DocumentDrawer/Provider.js'
import { DocumentFields } from '../../DocumentFields/index.js'
import { Upload } from '../../Upload/index.js'
import { Upload_v4 } from '../../Upload/index.js'
import { useFormsManager } from '../FormsManager/index.js'
import { BulkUploadProvider } from '../index.js'
import './index.scss'
Expand All @@ -31,7 +30,12 @@ const baseClass = 'collection-edit'
// When rendered within a drawer, props are empty
// This is solely to support custom edit views which get server-rendered

export function EditForm({ submitted }: EditFormProps) {
export function EditForm({
resetUploadEdits,
submitted,
updateUploadEdits,
uploadEdits,
}: EditFormProps) {
const {
action,
collectionSlug: docSlug,
Expand Down Expand Up @@ -62,7 +66,6 @@ export function EditForm({ submitted }: EditFormProps) {
const depth = useEditDepth()
const params = useSearchParams()
const { reportUpdate } = useDocumentEvents()
const { resetUploadEdits } = useUploadEdits()
const { startRouteTransition } = useRouteTransition()

const locale = params.get('locale')
Expand Down Expand Up @@ -161,10 +164,13 @@ export function EditForm({ submitted }: EditFormProps) {
BeforeFields={
<React.Fragment>
{CustomUpload || (
<Upload
<Upload_v4
collectionSlug={collectionConfig.slug}
initialState={initialState}
resetUploadEdits={resetUploadEdits}
updateUploadEdits={updateUploadEdits}
uploadConfig={collectionConfig.upload}
uploadEdits={uploadEdits}
/>
)}
</React.Fragment>
Expand All @@ -185,7 +191,7 @@ function GetFieldProxy() {
const { getFields } = useForm()
const { getFormDataRef } = useFormsManager()

React.useEffect(() => {
useEffect(() => {
getFormDataRef.current = getFields
}, [getFields, getFormDataRef])

Expand Down
4 changes: 3 additions & 1 deletion packages/ui/src/elements/BulkUpload/EditForm/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { UploadProps_v4 } from '../../Upload/index.js'

export type EditFormProps = {
readonly submitted?: boolean
}
} & Pick<UploadProps_v4, 'resetUploadEdits' | 'updateUploadEdits' | 'uploadEdits'>
51 changes: 49 additions & 2 deletions packages/ui/src/elements/BulkUpload/FormsManager/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
'use client'

import type { Data, DocumentSlots, FormState, SanitizedDocumentPermissions } from 'payload'
import type {
Data,
DocumentSlots,
FormState,
SanitizedDocumentPermissions,
UploadEdits,
} from 'payload'

import { useModal } from '@faceless-ui/modal'
import { isImage } from 'payload/shared'
Expand Down Expand Up @@ -41,6 +47,7 @@ type FormsManagerContext = {
readonly hasSubmitted: boolean
readonly isInitializing: boolean
readonly removeFile: (index: number) => void
readonly resetUploadEdits?: () => void
readonly saveAllDocs: ({ overrides }?: { overrides?: Record<string, unknown> }) => Promise<void>
readonly setActiveIndex: (index: number) => void
readonly setFormTotalErrorCount: ({
Expand All @@ -52,6 +59,7 @@ type FormsManagerContext = {
}) => void
readonly thumbnailUrls: string[]
readonly totalErrorCount?: number
readonly updateUploadEdits: (args: UploadEdits) => void
}

const Context = React.createContext<FormsManagerContext>({
Expand All @@ -73,6 +81,7 @@ const Context = React.createContext<FormsManagerContext>({
setFormTotalErrorCount: () => {},
thumbnailUrls: [],
totalErrorCount: 0,
updateUploadEdits: () => {},
})

const initialState: State = {
Expand Down Expand Up @@ -242,6 +251,7 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
return {
errorCount: form.errorCount,
formState: currentFormsData,
uploadEdits: form.uploadEdits,
}
}
return form
Expand Down Expand Up @@ -295,6 +305,7 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
currentForms[activeIndex] = {
errorCount: currentForms[activeIndex].errorCount,
formState: currentFormsData,
uploadEdits: currentForms[activeIndex].uploadEdits,
}
const newDocs = []

Expand All @@ -306,7 +317,16 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {

setLoadingText(t('general:uploadingBulk', { current: i + 1, total: currentForms.length }))

const req = await fetch(actionURL, {
const actionURLWithParams = `${actionURL}${qs.stringify(
{
uploadEdits: form?.uploadEdits || undefined,
},
{
addQueryPrefix: true,
},
)}`

const req = await fetch(actionURLWithParams, {
body: await createFormData(
form.formState,
overrides,
Expand Down Expand Up @@ -478,6 +498,31 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
[collectionSlug, docPermissions, forms, getFormState, hasSubmitted],
)

const updateUploadEdits = React.useCallback<FormsManagerContext['updateUploadEdits']>(
(uploadEdits) => {
dispatch({
type: 'UPDATE_FORM',
errorCount: forms[activeIndex].errorCount,
formState: forms[activeIndex].formState,
index: activeIndex,
uploadEdits,
})
},
[activeIndex, forms],
)

const resetUploadEdits = React.useCallback<FormsManagerContext['resetUploadEdits']>(() => {
dispatch({
type: 'REPLACE',
state: {
forms: forms.map((form) => ({
...form,
uploadEdits: {},
})),
},
})
}, [forms])

React.useEffect(() => {
if (!collectionSlug) {
return
Expand Down Expand Up @@ -529,11 +574,13 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
hasSubmitted,
isInitializing,
removeFile,
resetUploadEdits,
saveAllDocs,
setActiveIndex,
setFormTotalErrorCount,
thumbnailUrls: renderedThumbnails,
totalErrorCount,
updateUploadEdits,
}}
>
{isUploading && (
Expand Down
9 changes: 8 additions & 1 deletion packages/ui/src/elements/BulkUpload/FormsManager/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { FormState } from 'payload'
import type { FormState, UploadEdits } from 'payload'

export type State = {
activeIndex: number
forms: {
errorCount: number
formState: FormState
uploadEdits?: UploadEdits
}[]
totalErrorCount: number
}
Expand All @@ -21,6 +22,7 @@ type Action =
index: number
type: 'UPDATE_FORM'
updatedFields?: Record<string, unknown>
uploadEdits?: UploadEdits
}
| {
files: FileList
Expand Down Expand Up @@ -55,6 +57,7 @@ export function formsManagementReducer(state: State, action: Action): State {
value: action.files[i],
},
},
uploadEdits: {},
}
}

Expand Down Expand Up @@ -117,6 +120,10 @@ export function formsManagementReducer(state: State, action: Action): State {
...updatedForms[action.index].formState,
...action.formState,
},
uploadEdits: {
...updatedForms[action.index].uploadEdits,
...action.uploadEdits,
},
}

return {
Expand Down
32 changes: 29 additions & 3 deletions packages/ui/src/elements/Upload/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import { Drawer, DrawerToggler } from '../Drawer/index.js'
import { Dropzone } from '../Dropzone/index.js'
import { EditUpload } from '../EditUpload/index.js'
import { FileDetails } from '../FileDetails/index.js'
import './index.scss'
import { PreviewSizes } from '../PreviewSizes/index.js'
import { Thumbnail } from '../Thumbnail/index.js'
import './index.scss'

const baseClass = 'file-field'
export const editDrawerSlug = 'edit-upload'
Expand Down Expand Up @@ -91,7 +91,34 @@ export type UploadProps = {
}

export const Upload: React.FC<UploadProps> = (props) => {
const { collectionSlug, customActions, initialState, onChange, uploadConfig } = props
const { resetUploadEdits, updateUploadEdits, uploadEdits } = useUploadEdits()
return (
<Upload_v4
{...props}
resetUploadEdits={resetUploadEdits}
updateUploadEdits={updateUploadEdits}
uploadEdits={uploadEdits}
/>
)
}

export type UploadProps_v4 = {
readonly resetUploadEdits?: () => void
readonly updateUploadEdits?: (args: UploadEdits) => void
readonly uploadEdits?: UploadEdits
} & UploadProps

export const Upload_v4: React.FC<UploadProps_v4> = (props) => {
const {
collectionSlug,
customActions,
initialState,
onChange,
resetUploadEdits,
updateUploadEdits,
uploadConfig,
uploadEdits,
} = props

const {
config: {
Expand All @@ -102,7 +129,6 @@ export const Upload: React.FC<UploadProps> = (props) => {

const { t } = useTranslation()
const { setModified } = useForm()
const { resetUploadEdits, updateUploadEdits, uploadEdits } = useUploadEdits()
const { id, docPermissions, savedDocumentData, setUploadStatus } = useDocumentInfo()
const isFormSubmitting = useFormProcessing()
const { errorMessage, setValue, showError, value } = useField<File>({
Expand Down
18 changes: 15 additions & 3 deletions packages/ui/src/providers/UploadEdits/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,26 @@ import type { UploadEdits } from 'payload'

import React from 'react'

export type UploadEditsProviderProps = {
children: React.ReactNode
initialUploadEdits?: UploadEdits
}
export type UploadEditsContext = {
getUploadEdits: () => UploadEdits
resetUploadEdits: () => void
updateUploadEdits: (edits: UploadEdits) => void
uploadEdits: UploadEdits
}

const Context = React.createContext<UploadEditsContext>({
getUploadEdits: () => undefined,
resetUploadEdits: undefined,
updateUploadEdits: undefined,
uploadEdits: undefined,
})

export const UploadEditsProvider = ({ children }) => {
const [uploadEdits, setUploadEdits] = React.useState<UploadEdits>(undefined)
export const UploadEditsProvider = ({ children, initialUploadEdits }: UploadEditsProviderProps) => {
const [uploadEdits, setUploadEdits] = React.useState<UploadEdits>(initialUploadEdits || {})

const resetUploadEdits = () => {
setUploadEdits({})
Expand All @@ -29,7 +35,13 @@ export const UploadEditsProvider = ({ children }) => {
}))
}

return <Context value={{ resetUploadEdits, updateUploadEdits, uploadEdits }}>{children}</Context>
const getUploadEdits = () => uploadEdits

return (
<Context value={{ getUploadEdits, resetUploadEdits, updateUploadEdits, uploadEdits }}>
{children}
</Context>
)
}

export const useUploadEdits = (): UploadEditsContext => React.use(Context)
1 change: 1 addition & 0 deletions packages/ui/src/views/Edit/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ export function DefaultEditView({
isLockingEnabled,
setDocumentIsLocked,
startRouteTransition,
redirectAfterCreate,
],
)

Expand Down