feat(ui): add hideFileInputOnCreate and hideRemoveFile to collection upload config (#11217)
### What? Two new configuration properties added for upload enabled collections. - *hideFileInputOnCreate* - Set to `true` to prevent the admin UI from showing file inputs during document creation, useful for programmatic file generation. - *hideRemoveFile* - Set to `true` to prevent the admin UI having a way to remove an existing file while editing. ### Why? When using file uploads that get created programmatically in `beforeOperation` hooks or files created using `jobs`, or when `filesRequiredOnCreate` is false, you may want to use these new flags to prevent users from interacting with these controls. ### How? The new properties only impact the admin UI components to dial in the UX for various use cases. Screenshot showing that the upload controls are not available on create:  Screenshot showing hideRemoveFile has removed the ability to remove the existing file:  Prerequisite for https://github.com/payloadcms/payload/pull/10795
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
customFileNameMediaSlug,
|
||||
enlargeSlug,
|
||||
focalNoSizesSlug,
|
||||
hideFileInputOnCreateSlug,
|
||||
mediaSlug,
|
||||
mediaWithoutCacheTagsSlug,
|
||||
mediaWithoutRelationPreviewSlug,
|
||||
@@ -50,6 +51,11 @@ export default buildConfigWithDefaults({
|
||||
type: 'upload',
|
||||
relationTo: versionSlug,
|
||||
},
|
||||
{
|
||||
name: 'hideFileInputOnCreate',
|
||||
type: 'upload',
|
||||
relationTo: hideFileInputOnCreateSlug,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -693,6 +699,36 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: hideFileInputOnCreateSlug,
|
||||
upload: {
|
||||
hideFileInputOnCreate: true,
|
||||
hideRemoveFile: true,
|
||||
staticDir: path.resolve(dirname, 'uploads'),
|
||||
},
|
||||
hooks: {
|
||||
beforeOperation: [
|
||||
({ req, operation }) => {
|
||||
if (operation !== 'create') {
|
||||
return
|
||||
}
|
||||
const buffer = Buffer.from('This file was generated by a hook', 'utf-8')
|
||||
req.file = {
|
||||
name: `${new Date().toISOString()}.txt`,
|
||||
data: buffer,
|
||||
mimetype: 'text/plain',
|
||||
size: buffer.length,
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
onInit: async (payload) => {
|
||||
const uploadsDir = path.resolve(dirname, './media')
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
customFileNameMediaSlug,
|
||||
customUploadFieldSlug,
|
||||
focalOnlySlug,
|
||||
hideFileInputOnCreateSlug,
|
||||
mediaSlug,
|
||||
mediaWithoutCacheTagsSlug,
|
||||
relationPreviewSlug,
|
||||
@@ -63,6 +64,7 @@ let customFileNameURL: AdminUrlUtil
|
||||
let uploadsOne: AdminUrlUtil
|
||||
let uploadsTwo: AdminUrlUtil
|
||||
let customUploadFieldURL: AdminUrlUtil
|
||||
let hideFileInputOnCreateURL: AdminUrlUtil
|
||||
|
||||
describe('Uploads', () => {
|
||||
let page: Page
|
||||
@@ -92,6 +94,7 @@ describe('Uploads', () => {
|
||||
uploadsOne = new AdminUrlUtil(serverURL, 'uploads-1')
|
||||
uploadsTwo = new AdminUrlUtil(serverURL, 'uploads-2')
|
||||
customUploadFieldURL = new AdminUrlUtil(serverURL, customUploadFieldSlug)
|
||||
hideFileInputOnCreateURL = new AdminUrlUtil(serverURL, hideFileInputOnCreateSlug)
|
||||
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
@@ -366,10 +369,10 @@ describe('Uploads', () => {
|
||||
await page.locator('.doc-drawer__header-close').click()
|
||||
|
||||
// remove the selected versioned image
|
||||
await page.locator('.field-type:nth-of-type(2) .icon--x').click()
|
||||
await page.locator('#field-versionedImage .icon--x').click()
|
||||
|
||||
// choose from existing
|
||||
await openDocDrawer(page, '.upload__listToggler')
|
||||
await openDocDrawer(page, '#field-versionedImage .upload__listToggler')
|
||||
|
||||
await expect(page.locator('.row-3 .cell-title')).toContainText('draft')
|
||||
})
|
||||
@@ -456,7 +459,6 @@ describe('Uploads', () => {
|
||||
|
||||
test('should render adminThumbnail when using a function', async () => {
|
||||
await page.goto(adminThumbnailFunctionURL.list)
|
||||
await page.waitForURL(adminThumbnailFunctionURL.list)
|
||||
|
||||
// Ensure sure false or null shows generic file svg
|
||||
const genericUploadImage = page.locator('tr.row-1 .thumbnail img')
|
||||
@@ -468,7 +470,6 @@ describe('Uploads', () => {
|
||||
|
||||
test('should render adminThumbnail when using a custom thumbnail URL with additional queries', async () => {
|
||||
await page.goto(adminThumbnailWithSearchQueriesURL.list)
|
||||
await page.waitForURL(adminThumbnailWithSearchQueriesURL.list)
|
||||
|
||||
const genericUploadImage = page.locator('tr.row-1 .thumbnail img')
|
||||
// Match the URL with the regex pattern
|
||||
@@ -539,7 +540,6 @@ describe('Uploads', () => {
|
||||
|
||||
test('should render adminThumbnail when using a specific size', async () => {
|
||||
await page.goto(adminThumbnailSizeURL.list)
|
||||
await page.waitForURL(adminThumbnailSizeURL.list)
|
||||
|
||||
// Ensure sure false or null shows generic file svg
|
||||
const genericUploadImage = page.locator('tr.row-1 .thumbnail img')
|
||||
@@ -747,7 +747,6 @@ describe('Uploads', () => {
|
||||
test('should apply field value to all bulk upload files after edit many', async () => {
|
||||
// Navigate to the upload creation page
|
||||
await page.goto(uploadsOne.create)
|
||||
await page.waitForURL(uploadsOne.create)
|
||||
|
||||
// Upload single file
|
||||
await page.setInputFiles(
|
||||
@@ -1120,4 +1119,29 @@ describe('Uploads', () => {
|
||||
const relationPreview6 = page.locator('.cell-imageWithoutPreview3 img')
|
||||
await expect(relationPreview6).toBeHidden()
|
||||
})
|
||||
|
||||
test('should hide file input when disableCreateFileInput is true on collection create', async () => {
|
||||
await page.goto(hideFileInputOnCreateURL.create)
|
||||
await page.waitForURL(hideFileInputOnCreateURL.create)
|
||||
|
||||
await expect(page.locator('.file-field__upload')).toBeHidden()
|
||||
})
|
||||
|
||||
test('should hide bulk upload from list view when disableCreateFileInput is true', async () => {
|
||||
await page.goto(hideFileInputOnCreateURL.list)
|
||||
|
||||
await expect(page.locator('.list-header')).not.toContainText('Bulk Upload')
|
||||
})
|
||||
|
||||
test('should hide remove button in file input when hideRemove is true', async () => {
|
||||
const doc = await payload.create({
|
||||
collection: hideFileInputOnCreateSlug,
|
||||
data: {
|
||||
title: 'test',
|
||||
},
|
||||
})
|
||||
await page.goto(hideFileInputOnCreateURL.edit(doc.id))
|
||||
|
||||
await expect(page.locator('.file-field .file-details__remove')).toBeHidden()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,10 +6,65 @@
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Supported timezones in IANA format.
|
||||
*
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "supportedTimezones".
|
||||
*/
|
||||
export type SupportedTimezones =
|
||||
| 'Pacific/Midway'
|
||||
| 'Pacific/Niue'
|
||||
| 'Pacific/Honolulu'
|
||||
| 'Pacific/Rarotonga'
|
||||
| 'America/Anchorage'
|
||||
| 'Pacific/Gambier'
|
||||
| 'America/Los_Angeles'
|
||||
| 'America/Tijuana'
|
||||
| 'America/Denver'
|
||||
| 'America/Phoenix'
|
||||
| 'America/Chicago'
|
||||
| 'America/Guatemala'
|
||||
| 'America/New_York'
|
||||
| 'America/Bogota'
|
||||
| 'America/Caracas'
|
||||
| 'America/Santiago'
|
||||
| 'America/Buenos_Aires'
|
||||
| 'America/Sao_Paulo'
|
||||
| 'Atlantic/South_Georgia'
|
||||
| 'Atlantic/Azores'
|
||||
| 'Atlantic/Cape_Verde'
|
||||
| 'Europe/London'
|
||||
| 'Europe/Berlin'
|
||||
| 'Africa/Lagos'
|
||||
| 'Europe/Athens'
|
||||
| 'Africa/Cairo'
|
||||
| 'Europe/Moscow'
|
||||
| 'Asia/Riyadh'
|
||||
| 'Asia/Dubai'
|
||||
| 'Asia/Baku'
|
||||
| 'Asia/Karachi'
|
||||
| 'Asia/Tashkent'
|
||||
| 'Asia/Calcutta'
|
||||
| 'Asia/Dhaka'
|
||||
| 'Asia/Almaty'
|
||||
| 'Asia/Jakarta'
|
||||
| 'Asia/Bangkok'
|
||||
| 'Asia/Shanghai'
|
||||
| 'Asia/Singapore'
|
||||
| 'Asia/Tokyo'
|
||||
| 'Asia/Seoul'
|
||||
| 'Australia/Sydney'
|
||||
| 'Pacific/Guam'
|
||||
| 'Pacific/Noumea'
|
||||
| 'Pacific/Auckland'
|
||||
| 'Pacific/Fiji';
|
||||
|
||||
export interface Config {
|
||||
auth: {
|
||||
users: UserAuthOperations;
|
||||
};
|
||||
blocks: {};
|
||||
collections: {
|
||||
relation: Relation;
|
||||
audio: Audio;
|
||||
@@ -44,6 +99,7 @@ export interface Config {
|
||||
'media-without-cache-tags': MediaWithoutCacheTag;
|
||||
'media-without-relation-preview': MediaWithoutRelationPreview;
|
||||
'relation-preview': RelationPreview;
|
||||
'hide-file-input-on-create': HideFileInputOnCreate;
|
||||
users: User;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
@@ -84,6 +140,7 @@ export interface Config {
|
||||
'media-without-cache-tags': MediaWithoutCacheTagsSelect<false> | MediaWithoutCacheTagsSelect<true>;
|
||||
'media-without-relation-preview': MediaWithoutRelationPreviewSelect<false> | MediaWithoutRelationPreviewSelect<true>;
|
||||
'relation-preview': RelationPreviewSelect<false> | RelationPreviewSelect<true>;
|
||||
'hide-file-input-on-create': HideFileInputOnCreateSelect<false> | HideFileInputOnCreateSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
@@ -129,6 +186,7 @@ export interface Relation {
|
||||
id: string;
|
||||
image?: (string | null) | Media;
|
||||
versionedImage?: (string | null) | Version;
|
||||
hideFileInputOnCreate?: (string | null) | HideFileInputOnCreate;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -292,6 +350,25 @@ export interface Version {
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "hide-file-input-on-create".
|
||||
*/
|
||||
export interface HideFileInputOnCreate {
|
||||
id: string;
|
||||
title?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
thumbnailURL?: string | null;
|
||||
filename?: string | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "audio".
|
||||
@@ -1328,6 +1405,10 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'relation-preview';
|
||||
value: string | RelationPreview;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'hide-file-input-on-create';
|
||||
value: string | HideFileInputOnCreate;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
@@ -1381,6 +1462,7 @@ export interface PayloadMigration {
|
||||
export interface RelationSelect<T extends boolean = true> {
|
||||
image?: T;
|
||||
versionedImage?: T;
|
||||
hideFileInputOnCreate?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
@@ -2509,6 +2591,24 @@ export interface RelationPreviewSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "hide-file-input-on-create_select".
|
||||
*/
|
||||
export interface HideFileInputOnCreateSelect<T extends boolean = true> {
|
||||
title?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
url?: T;
|
||||
thumbnailURL?: T;
|
||||
filename?: T;
|
||||
mimeType?: T;
|
||||
filesize?: T;
|
||||
width?: T;
|
||||
height?: T;
|
||||
focalX?: T;
|
||||
focalY?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users_select".
|
||||
|
||||
@@ -17,6 +17,7 @@ export const unstoredMediaSlug = 'unstored-media'
|
||||
export const versionSlug = 'versions'
|
||||
export const animatedTypeMedia = 'animated-type-media'
|
||||
export const customUploadFieldSlug = 'custom-upload-field'
|
||||
export const hideFileInputOnCreateSlug = 'hide-file-input-on-create'
|
||||
export const withMetadataSlug = 'with-meta-data'
|
||||
export const withoutMetadataSlug = 'without-meta-data'
|
||||
export const withOnlyJPEGMetadataSlug = 'with-only-jpeg-meta-data'
|
||||
|
||||
Reference in New Issue
Block a user