From 51bc8b44169bb80fa1eb4b5fd8294c5a8cb45f86 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Wed, 11 Sep 2024 14:34:03 -0400 Subject: [PATCH] feat: document drawer controls (#7679) ## Description Currently, you cannot create, delete, or duplicate documents within the document drawer directly. To create a document within a relationship field, for example, you must first navigate to the parent field and open the "create new" drawer. Similarly (but worse), to duplicate or delete a document, you must _navigate to the parent document to perform these actions_ which is incredibly disruptive to the content editing workflow. This becomes especially apparent within the relationship field where you can edit documents inline, but cannot duplicate or delete them. This PR supports all document-level actions within the document drawer so that these actions can be performed on-the-fly without navigating away. Inline duplication flow on a polymorphic "hasOne" relationship: https://github.com/user-attachments/assets/bb80404a-079d-44a1-b9bc-14eb2ab49a46 Inline deletion flow on a polymorphic "hasOne" relationship: https://github.com/user-attachments/assets/10f3587f-f70a-4cca-83ee-5dbcad32f063 - [x] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository. ## Type of change - [x] New feature (non-breaking change which adds functionality) ## Checklist: - [x] I have added tests that prove my fix is effective or that my feature works - [x] Existing test suite passes locally with my changes --- .../next/src/views/Edit/Default/index.tsx | 13 ++ .../ui/src/elements/DeleteDocument/index.tsx | 87 ++++++-- .../src/elements/DocumentControls/index.tsx | 51 ++++- .../elements/DocumentDrawer/DrawerContent.tsx | 42 +++- .../ui/src/elements/DocumentDrawer/types.ts | 5 + packages/ui/src/elements/Drawer/index.tsx | 5 +- .../src/elements/DuplicateDocument/index.tsx | 58 ++++-- packages/ui/src/elements/ReactSelect/types.ts | 6 +- packages/ui/src/fields/Relationship/index.tsx | 91 ++++++++- .../src/fields/Relationship/optionsReducer.ts | 21 ++ packages/ui/src/fields/Relationship/types.ts | 16 +- .../ui/src/providers/DocumentInfo/index.tsx | 2 +- .../ui/src/providers/DocumentInfo/types.ts | 20 +- test/access-control/e2e.spec.ts | 2 +- test/admin/e2e/1/e2e.spec.ts | 2 +- test/admin/seed.ts | 5 +- test/fields-relationship/e2e.spec.ts | 2 +- .../collections/Relationship/e2e.spec.ts | 185 +++++++++++++++++- test/fields/collections/Text/index.ts | 3 + test/fields/payload-types.ts | 7 + test/fields/seed.ts | 32 +++ test/helpers.ts | 5 - test/helpers/e2e/openDocControls.ts | 6 + test/localization/e2e.spec.ts | 2 +- 24 files changed, 591 insertions(+), 77 deletions(-) create mode 100644 test/helpers/e2e/openDocControls.ts diff --git a/packages/next/src/views/Edit/Default/index.tsx b/packages/next/src/views/Edit/Default/index.tsx index 41319b89c..1feb2bfc8 100644 --- a/packages/next/src/views/Edit/Default/index.tsx +++ b/packages/next/src/views/Edit/Default/index.tsx @@ -43,6 +43,7 @@ export const DefaultEditView: React.FC = () => { BeforeFields, collectionSlug, disableActions, + disableCreate, disableLeaveWithoutSaving, docPermissions, getDocPreferences, @@ -54,7 +55,12 @@ export const DefaultEditView: React.FC = () => { initialState, isEditing, isInitializing, + onDelete, + onDrawerCreate, + onDuplicate, onSave: onSaveFromContext, + redirectAfterDelete, + redirectAfterDuplicate, } = useDocumentInfo() const { refreshCookieAsync, user } = useAuth() @@ -223,11 +229,18 @@ export const DefaultEditView: React.FC = () => { apiURL={apiURL} data={data} disableActions={disableActions} + disableCreate={disableCreate} hasPublishPermission={hasPublishPermission} hasSavePermission={hasSavePermission} id={id} isEditing={isEditing} + onDelete={onDelete} + onDrawerCreate={onDrawerCreate} + onDuplicate={onDuplicate} + onSave={onSave} permissions={docPermissions} + redirectAfterDelete={redirectAfterDelete} + redirectAfterDuplicate={redirectAfterDuplicate} slug={collectionConfig?.slug || globalConfig?.slug} /> = (props) => { - const { id, buttonId, collectionSlug, singularLabel, title: titleFromProps } = props + const { + id, + buttonId, + collectionSlug, + onDelete, + redirectAfterDelete = true, + singularLabel, + title: titleFromProps, + } = props const { config: { routes: { admin: adminRoute, api }, serverURL, }, + getEntityConfig, } = useConfig() + const collectionConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig + const { setModified } = useForm() const [deleting, setDeleting] = useState(false) - const { toggleModal } = useModal() + const { closeModal, toggleModal } = useModal() const router = useRouter() const { i18n, t } = useTranslation() const { title } = useDocumentInfo() + const editDepth = useEditDepth() const titleToRender = titleFromProps || title || id @@ -55,9 +73,16 @@ export const DeleteDocument: React.FC = (props) => { toast.error(t('error:deletingTitle', { title })) }, [t, title]) + useEffect(() => { + return () => { + closeModal(modalSlug) + } + }, [closeModal, modalSlug]) + const handleDelete = useCallback(async () => { setDeleting(true) setModified(false) + try { await requests .delete(`${serverURL}${api}/${collectionSlug}/${id}`, { @@ -73,19 +98,32 @@ export const DeleteDocument: React.FC = (props) => { if (res.status < 400) { setDeleting(false) toggleModal(modalSlug) + toast.success( t('general:titleDeleted', { label: getTranslation(singularLabel, i18n), title }) || json.message, ) - return router.push( - formatAdminURL({ - adminRoute, - path: `/collections/${collectionSlug}`, - }), - ) + if (redirectAfterDelete) { + return router.push( + formatAdminURL({ + adminRoute, + path: `/collections/${collectionSlug}`, + }), + ) + } + + if (typeof onDelete === 'function') { + await onDelete({ id, collectionConfig }) + } + + toggleModal(modalSlug) + + return } + toggleModal(modalSlug) + if (json.errors) { json.errors.forEach((error) => toast.error(error.message)) } else { @@ -114,6 +152,9 @@ export const DeleteDocument: React.FC = (props) => { router, adminRoute, addDefaultError, + redirectAfterDelete, + onDelete, + collectionConfig, ]) if (id) { @@ -128,7 +169,13 @@ export const DeleteDocument: React.FC = (props) => { > {t('general:delete')} - +

{t('general:confirmDeletion')}

@@ -158,7 +205,11 @@ export const DeleteDocument: React.FC = (props) => {