Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/admin/react-hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ The `useDocumentInfo` hook provides information about the current document being
| **`lastUpdateTime`** | Timestamp of the last update to the document. |
| **`mostRecentVersionIsAutosaved`** | Whether the most recent version is an autosaved version. |
| **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences. [More details](./preferences). |
| **`savedDocumentData`** | The saved data of the document. |
| **`data`** | The saved data of the document. |
| **`setDocFieldPreferences`** | Method to set preferences for a specific field. [More details](./preferences). |
| **`setDocumentTitle`** | Method to set the document title. |
| **`setHasPublishedDoc`** | Method to update whether the document has been published. |
Expand Down
152 changes: 32 additions & 120 deletions packages/ui/src/elements/Autosave/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type { ClientCollectionConfig, ClientGlobalConfig } from 'payload'
import { dequal } from 'dequal/lite'
import { reduceFieldsToValues, versionDefaults } from 'payload/shared'
import React, { useDeferredValue, useEffect, useRef, useState } from 'react'
import { toast } from 'sonner'

import {
useAllFormFields,
Expand All @@ -17,13 +16,11 @@ import { useDebounce } from '../../hooks/useDebounce.js'
import { useEffectEvent } from '../../hooks/useEffectEvent.js'
import { useQueues } from '../../hooks/useQueues.js'
import { useConfig } from '../../providers/Config/index.js'
import { useDocumentEvents } from '../../providers/DocumentEvents/index.js'
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
import { useLocale } from '../../providers/Locale/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { formatTimeToNow } from '../../utilities/formatDocTitle/formatDateTitle.js'
import { reduceFieldsToValuesWithValidation } from '../../utilities/reduceFieldsToValuesWithValidation.js'
import { useDocumentDrawerContext } from '../DocumentDrawer/Provider.js'
import { LeaveWithoutSaving } from '../LeaveWithoutSaving/index.js'
import './index.scss'

Expand Down Expand Up @@ -51,16 +48,11 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
incrementVersionCount,
lastUpdateTime,
mostRecentVersionIsAutosaved,
setLastUpdateTime,
setMostRecentVersionIsAutosaved,
setUnpublishedVersionCount,
updateSavedDocumentData,
} = useDocumentInfo()

const { onSave: onSaveFromDocumentDrawer } = useDocumentDrawerContext()

const { reportUpdate } = useDocumentEvents()
const { dispatchFields, isValid, setBackgroundProcessing, setIsValid } = useForm()
const { isValid, setBackgroundProcessing, submit } = useForm()

const [formState] = useAllFormFields()
const modified = useFormModified()
Expand Down Expand Up @@ -151,118 +143,38 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
method = 'POST'
}

if (url) {
if (modifiedRef.current) {
const { data, valid } = reduceFieldsToValuesWithValidation(formStateRef.current, true)

data._status = 'draft'

const skipSubmission =
submitted && !valid && versionsConfig?.drafts && versionsConfig?.drafts?.validate

if (!skipSubmission) {
let res

try {
res = await fetch(url, {
body: JSON.stringify(data),
credentials: 'include',
headers: {
'Accept-Language': i18n.language,
'Content-Type': 'application/json',
},
method,
})
} catch (_err) {
// Swallow Error
}

const newDate = new Date()
// We need to log the time in order to figure out if we need to trigger the state off later
endTimestamp = newDate.getTime()

const json = await res.json()

if (res.status === 200) {
setLastUpdateTime(newDate.getTime())

reportUpdate({
id,
entitySlug,
updatedAt: newDate.toISOString(),
})

// if onSaveFromDocumentDrawer is defined, call it
if (typeof onSaveFromDocumentDrawer === 'function') {
void onSaveFromDocumentDrawer({
...json,
operation: 'update',
})
}

if (!mostRecentVersionIsAutosaved) {
incrementVersionCount()
setMostRecentVersionIsAutosaved(true)
setUnpublishedVersionCount((prev) => prev + 1)
}
}

if (versionsConfig?.drafts && versionsConfig?.drafts?.validate && json?.errors) {
if (Array.isArray(json.errors)) {
const [fieldErrors, nonFieldErrors] = json.errors.reduce(
([fieldErrs, nonFieldErrs], err) => {
const newFieldErrs = []
const newNonFieldErrs = []

if (err?.message) {
newNonFieldErrs.push(err)
}

if (Array.isArray(err?.data)) {
err.data.forEach((dataError) => {
if (dataError?.field) {
newFieldErrs.push(dataError)
} else {
newNonFieldErrs.push(dataError)
}
})
}

return [
[...fieldErrs, ...newFieldErrs],
[...nonFieldErrs, ...newNonFieldErrs],
]
},
[[], []],
)

dispatchFields({
type: 'ADD_SERVER_ERRORS',
errors: fieldErrors,
})

nonFieldErrors.forEach((err) => {
toast.error(err.message || i18n.t('error:unknown'))
})

setIsValid(false)
hideIndicator()
return
}
} else {
// If it's not an error then we can update the document data inside the context
const document = json?.doc || json?.result

// Manually update the data since this function doesn't fire the `submit` function from useForm
if (document) {
setIsValid(true)
updateSavedDocumentData(document)
}
}

hideIndicator()
}
const { valid } = reduceFieldsToValuesWithValidation(formStateRef.current, true)

const skipSubmission =
submitted && !valid && versionsConfig?.drafts && versionsConfig?.drafts?.validate

if (!skipSubmission && modifiedRef.current && url) {
const result = await submit({
action: url,
context: {
incrementVersionCount: false,
},
disableFormWhileProcessing: false,
disableSuccessStatus: true,
method,
overrides: {
_status: 'draft',
},
skipValidation: versionsConfig?.drafts && !versionsConfig?.drafts?.validate,
})

if (result && result?.res?.ok && !mostRecentVersionIsAutosaved) {
incrementVersionCount()
setMostRecentVersionIsAutosaved(true)
setUnpublishedVersionCount((prev) => prev + 1)
}

const newDate = new Date()

// We need to log the time in order to figure out if we need to trigger the state off later
endTimestamp = newDate.getTime()

hideIndicator()
}
}
},
Expand Down
5 changes: 5 additions & 0 deletions packages/ui/src/elements/DocumentDrawer/Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export type DocumentDrawerContextProps = {
}) => Promise<void> | void
readonly onSave?: (args: {
collectionConfig?: ClientCollectionConfig
/**
* @experimental - Note: this property is experimental and may change in the future. Use as your own discretion.
* If you want to pass additional data to the onSuccess callback, you can use this context object.
*/
context?: Record<string, unknown>
doc: TypeWithID
operation: 'create' | 'update'
result: Data
Expand Down
32 changes: 14 additions & 18 deletions packages/ui/src/elements/Upload/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export const Upload_v4: React.FC<UploadProps_v4> = (props) => {

const { t } = useTranslation()
const { setModified } = useForm()
const { id, docPermissions, savedDocumentData, setUploadStatus } = useDocumentInfo()
const { id, data, docPermissions, setUploadStatus } = useDocumentInfo()
const isFormSubmitting = useFormProcessing()
const { errorMessage, setValue, showError, value } = useField<File>({
path: 'file',
Expand Down Expand Up @@ -349,7 +349,7 @@ export const Upload_v4: React.FC<UploadProps_v4> = (props) => {

const acceptMimeTypes = uploadConfig.mimeTypes?.join(', ')

const imageCacheTag = uploadConfig?.cacheTags && savedDocumentData?.updatedAt
const imageCacheTag = uploadConfig?.cacheTags && data?.updatedAt

useEffect(() => {
const handleControlFileUrl = async () => {
Expand All @@ -375,11 +375,11 @@ export const Upload_v4: React.FC<UploadProps_v4> = (props) => {
return (
<div className={[fieldBaseClass, baseClass].filter(Boolean).join(' ')}>
<FieldError message={errorMessage} showError={showError} />
{savedDocumentData && savedDocumentData.filename && !removedFile && (
{data && data.filename && !removedFile && (
<FileDetails
collectionSlug={collectionSlug}
customUploadActions={customActions}
doc={savedDocumentData}
doc={data}
enableAdjustments={showCrop || showFocalPoint}
handleRemove={canRemoveUpload ? handleFileRemoval : undefined}
hasImageSizes={hasImageSizes}
Expand All @@ -388,7 +388,7 @@ export const Upload_v4: React.FC<UploadProps_v4> = (props) => {
uploadConfig={uploadConfig}
/>
)}
{((!uploadConfig.hideFileInputOnCreate && !savedDocumentData?.filename) || removedFile) && (
{((!uploadConfig.hideFileInputOnCreate && !data?.filename) || removedFile) && (
<div className={`${baseClass}__upload`}>
{!value && !showUrlInput && (
<Dropzone onChange={handleFileSelection}>
Expand Down Expand Up @@ -506,7 +506,7 @@ export const Upload_v4: React.FC<UploadProps_v4> = (props) => {
<UploadActions
customActions={customActions}
enableAdjustments={showCrop || showFocalPoint}
enablePreviewSizes={hasImageSizes && savedDocumentData?.filename && !removedFile}
enablePreviewSizes={hasImageSizes && data?.filename && !removedFile}
mimeType={value.type}
/>
</div>
Expand All @@ -523,17 +523,17 @@ export const Upload_v4: React.FC<UploadProps_v4> = (props) => {
)}
</div>
)}
{(value || savedDocumentData?.filename) && (
{(value || data?.filename) && (
<EditDepthProvider>
<Drawer Header={null} slug={editDrawerSlug}>
<EditUpload
fileName={value?.name || savedDocumentData?.filename}
fileSrc={savedDocumentData?.url || fileSrc}
fileName={value?.name || data?.filename}
fileSrc={data?.url || fileSrc}
imageCacheTag={imageCacheTag}
initialCrop={uploadEdits?.crop ?? undefined}
initialFocalPoint={{
x: uploadEdits?.focalPoint?.x || savedDocumentData?.focalX || 50,
y: uploadEdits?.focalPoint?.y || savedDocumentData?.focalY || 50,
x: uploadEdits?.focalPoint?.x || data?.focalX || 50,
y: uploadEdits?.focalPoint?.y || data?.focalY || 50,
}}
onSave={onEditsSave}
showCrop={showCrop}
Expand All @@ -542,18 +542,14 @@ export const Upload_v4: React.FC<UploadProps_v4> = (props) => {
</Drawer>
</EditDepthProvider>
)}
{savedDocumentData && hasImageSizes && (
{data && hasImageSizes && (
<Drawer
className={`${baseClass}__previewDrawer`}
hoverTitle
slug={sizePreviewSlug}
title={t('upload:sizesFor', { label: savedDocumentData.filename })}
title={t('upload:sizesFor', { label: data.filename })}
>
<PreviewSizes
doc={savedDocumentData}
imageCacheTag={imageCacheTag}
uploadConfig={uploadConfig}
/>
<PreviewSizes doc={data} imageCacheTag={imageCacheTag} uploadConfig={uploadConfig} />
</Drawer>
)}
</div>
Expand Down
Loading
Loading