feat: adds upload's relationship thumbnail (#7473)
## Description https://github.com/payloadcms/payload/pull/5015 's version for beta branch. @JessChowdhury - [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 <!-- Please delete options that are not relevant. --> - [X] New feature (non-breaking change which adds functionality) - [X] This change requires a documentation update ## 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 - [X] I have made corresponding changes to the documentation
This commit is contained in:
@@ -57,6 +57,7 @@ export const MyUploadField: Field = {
|
||||
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
|
||||
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`displayPreview`** | Enable displaying preview of the uploaded file. Overrides related Collection's `displayPreview` option. [More](/docs/upload/overview#collection-upload-options). |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. [Admin Options](../admin/fields#admin-options). |
|
||||
|
||||
@@ -94,6 +94,7 @@ _An asterisk denotes that an option is required._
|
||||
| **`adminThumbnail`** | Set the way that the [Admin Panel](../admin/overview) will display thumbnails for this Collection. [More](#admin-thumbnails) |
|
||||
| **`crop`** | Set to `false` to disable the cropping tool in the [Admin Panel](../admin/overview). Crop is enabled by default. [More](#crop-and-focal-point-selector) |
|
||||
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
|
||||
| **`displayPreview`** | Enable displaying preview of the uploaded file in Upload fields related to this Collection. Can be locally overridden by `displayPreview` option in Upload field. [More](/docs/fields/upload#config-options). |
|
||||
| **`externalFileHeaderFilter`** | Accepts existing headers and returns the headers after filtering or modifying. |
|
||||
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
|
||||
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the [Admin Panel](../admin/overview). The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
|
||||
|
||||
@@ -19,6 +19,7 @@ export type CellComponentProps = {
|
||||
}[]
|
||||
className?: string
|
||||
dateDisplayFormat?: DateField['admin']['date']['displayFormat']
|
||||
displayPreview?: boolean
|
||||
fieldType?: Field['type']
|
||||
isFieldAffectingData?: boolean
|
||||
label?: FormFieldBase['label']
|
||||
|
||||
@@ -521,6 +521,7 @@ export type UploadField = {
|
||||
Label?: LabelComponent
|
||||
}
|
||||
}
|
||||
displayPreview?: boolean
|
||||
filterOptions?: FilterOptions
|
||||
/**
|
||||
* Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached.
|
||||
|
||||
@@ -93,6 +93,12 @@ export type UploadConfig = {
|
||||
* @default false
|
||||
*/
|
||||
disableLocalStorage?: boolean
|
||||
/**
|
||||
* Enable displaying preview of the uploaded file in Upload fields related to this Collection.
|
||||
* Can be locally overridden by `displayPreview` option in Upload field.
|
||||
* @default false
|
||||
*/
|
||||
displayPreview?: boolean
|
||||
/**
|
||||
* Ability to filter/modify Request Headers when fetching a file.
|
||||
*
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useTranslation } from '../../../../../providers/Translation/index.js'
|
||||
import { canUseDOM } from '../../../../../utilities/canUseDOM.js'
|
||||
import { formatDocTitle } from '../../../../../utilities/formatDocTitle.js'
|
||||
import { useListRelationships } from '../../../RelationshipProvider/index.js'
|
||||
import { FileCell } from '../File/index.js'
|
||||
import './index.scss'
|
||||
|
||||
type Value = { relationTo: string; value: number | string }
|
||||
@@ -23,8 +24,10 @@ export interface RelationshipCellProps extends DefaultCellComponentProps<any> {
|
||||
|
||||
export const RelationshipCell: React.FC<RelationshipCellProps> = ({
|
||||
cellData,
|
||||
fieldType,
|
||||
label,
|
||||
relationTo,
|
||||
...props
|
||||
}) => {
|
||||
const config = useConfig()
|
||||
const { collections, routes } = config
|
||||
@@ -90,11 +93,30 @@ export const RelationshipCell: React.FC<RelationshipCellProps> = ({
|
||||
i18n,
|
||||
})
|
||||
|
||||
let fileField = null
|
||||
if (fieldType === 'upload') {
|
||||
const { name, customCellContext, displayPreview, schemaPath } = props
|
||||
const relatedCollectionPreview = !!relatedCollection.upload.displayPreview
|
||||
const previewAllowed =
|
||||
displayPreview || (relatedCollectionPreview && displayPreview !== false)
|
||||
if (previewAllowed && document) {
|
||||
fileField = (
|
||||
<FileCell
|
||||
cellData={label}
|
||||
customCellContext={customCellContext}
|
||||
name={name}
|
||||
rowData={document}
|
||||
schemaPath={schemaPath}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment key={i}>
|
||||
{document === false && `${t('general:untitled')} - ID: ${value}`}
|
||||
{document === null && `${t('general:loading')}...`}
|
||||
{document ? label : null}
|
||||
{document ? fileField || label : null}
|
||||
{values.length > i + 1 && ', '}
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
@@ -647,6 +647,7 @@ export const mapFields = (args: {
|
||||
}
|
||||
|
||||
cellComponentProps.relationTo = field.relationTo
|
||||
cellComponentProps.displayPreview = field.displayPreview
|
||||
fieldComponentPropsBase = uploadField
|
||||
break
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@ import {
|
||||
enlargeSlug,
|
||||
focalNoSizesSlug,
|
||||
mediaSlug,
|
||||
mediaWithRelationPreviewSlug,
|
||||
mediaWithoutRelationPreviewSlug,
|
||||
reduceSlug,
|
||||
relationPreviewSlug,
|
||||
relationSlug,
|
||||
unstoredMediaSlug,
|
||||
versionSlug,
|
||||
@@ -554,6 +557,67 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
},
|
||||
CustomUploadFieldCollection,
|
||||
{
|
||||
slug: mediaWithRelationPreviewSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
upload: {
|
||||
displayPreview: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: mediaWithoutRelationPreviewSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
upload: true,
|
||||
},
|
||||
{
|
||||
slug: relationPreviewSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'imageWithPreview1',
|
||||
type: 'upload',
|
||||
relationTo: mediaWithRelationPreviewSlug,
|
||||
},
|
||||
{
|
||||
name: 'imageWithPreview2',
|
||||
type: 'upload',
|
||||
relationTo: mediaWithRelationPreviewSlug,
|
||||
displayPreview: true,
|
||||
},
|
||||
{
|
||||
name: 'imageWithoutPreview1',
|
||||
type: 'upload',
|
||||
relationTo: mediaWithRelationPreviewSlug,
|
||||
displayPreview: false,
|
||||
},
|
||||
{
|
||||
name: 'imageWithoutPreview2',
|
||||
type: 'upload',
|
||||
relationTo: mediaWithoutRelationPreviewSlug,
|
||||
},
|
||||
{
|
||||
name: 'imageWithPreview3',
|
||||
type: 'upload',
|
||||
relationTo: mediaWithoutRelationPreviewSlug,
|
||||
displayPreview: true,
|
||||
},
|
||||
{
|
||||
name: 'imageWithoutPreview3',
|
||||
type: 'upload',
|
||||
relationTo: mediaWithoutRelationPreviewSlug,
|
||||
displayPreview: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
onInit: async (payload) => {
|
||||
const uploadsDir = path.resolve(dirname, './media')
|
||||
@@ -675,6 +739,31 @@ export default buildConfigWithDefaults({
|
||||
name: `function-image-${imageFile.name}`,
|
||||
},
|
||||
})
|
||||
|
||||
// Create media with and without relation preview
|
||||
const { id: uploadedImageWithPreview } = await payload.create({
|
||||
collection: mediaWithRelationPreviewSlug,
|
||||
data: {},
|
||||
file: imageFile,
|
||||
})
|
||||
|
||||
const { id: uploadedImageWithoutPreview } = await payload.create({
|
||||
collection: mediaWithoutRelationPreviewSlug,
|
||||
data: {},
|
||||
file: imageFile,
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: relationPreviewSlug,
|
||||
data: {
|
||||
imageWithPreview1: uploadedImageWithPreview,
|
||||
imageWithPreview2: uploadedImageWithPreview,
|
||||
imageWithoutPreview1: uploadedImageWithPreview,
|
||||
imageWithoutPreview2: uploadedImageWithoutPreview,
|
||||
imageWithPreview3: uploadedImageWithoutPreview,
|
||||
imageWithoutPreview3: uploadedImageWithoutPreview,
|
||||
},
|
||||
})
|
||||
},
|
||||
serverURL: undefined,
|
||||
upload: {
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { Config, Media } from './payload-types.js'
|
||||
|
||||
import {
|
||||
ensureCompilationIsDone,
|
||||
exactText,
|
||||
initPageConsoleErrorCatch,
|
||||
openDocDrawer,
|
||||
saveDocAndAssert,
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
audioSlug,
|
||||
focalOnlySlug,
|
||||
mediaSlug,
|
||||
relationPreviewSlug,
|
||||
relationSlug,
|
||||
withMetadataSlug,
|
||||
withOnlyJPEGMetadataSlug,
|
||||
@@ -48,6 +50,7 @@ let focalOnlyURL: AdminUrlUtil
|
||||
let withMetadataURL: AdminUrlUtil
|
||||
let withoutMetadataURL: AdminUrlUtil
|
||||
let withOnlyJPEGMetadataURL: AdminUrlUtil
|
||||
let relationPreviewURL: AdminUrlUtil
|
||||
|
||||
describe('uploads', () => {
|
||||
let page: Page
|
||||
@@ -70,6 +73,7 @@ describe('uploads', () => {
|
||||
withMetadataURL = new AdminUrlUtil(serverURL, withMetadataSlug)
|
||||
withoutMetadataURL = new AdminUrlUtil(serverURL, withoutMetadataSlug)
|
||||
withOnlyJPEGMetadataURL = new AdminUrlUtil(serverURL, withOnlyJPEGMetadataSlug)
|
||||
relationPreviewURL = new AdminUrlUtil(serverURL, relationPreviewSlug)
|
||||
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
@@ -594,4 +598,48 @@ describe('uploads', () => {
|
||||
expect(redDoc.sizes.focalTest.filesize).toEqual(1598)
|
||||
})
|
||||
})
|
||||
|
||||
test('should see upload previews in relation list if allowed in config', async () => {
|
||||
await page.goto(relationPreviewURL.list)
|
||||
|
||||
await wait(110)
|
||||
|
||||
// Show all columns with relations
|
||||
await page.locator('.list-controls__toggle-columns').click()
|
||||
await expect(page.locator('.column-selector')).toBeVisible()
|
||||
const imageWithoutPreview2Button = page.locator(`.column-selector .column-selector__column`, {
|
||||
hasText: exactText('Image Without Preview2'),
|
||||
})
|
||||
const imageWithPreview3Button = page.locator(`.column-selector .column-selector__column`, {
|
||||
hasText: exactText('Image With Preview3'),
|
||||
})
|
||||
const imageWithoutPreview3Button = page.locator(`.column-selector .column-selector__column`, {
|
||||
hasText: exactText('Image Without Preview3'),
|
||||
})
|
||||
await imageWithoutPreview2Button.click()
|
||||
await imageWithPreview3Button.click()
|
||||
await imageWithoutPreview3Button.click()
|
||||
|
||||
// Wait for the columns to be displayed
|
||||
await expect(page.locator('.cell-imageWithoutPreview3')).toBeVisible()
|
||||
|
||||
// collection's displayPreview: true, field's displayPreview: unset
|
||||
const relationPreview1 = page.locator('.cell-imageWithPreview1 img')
|
||||
await expect(relationPreview1).toBeVisible()
|
||||
// collection's displayPreview: true, field's displayPreview: true
|
||||
const relationPreview2 = page.locator('.cell-imageWithPreview2 img')
|
||||
await expect(relationPreview2).toBeVisible()
|
||||
// collection's displayPreview: true, field's displayPreview: false
|
||||
const relationPreview3 = page.locator('.cell-imageWithoutPreview1 img')
|
||||
await expect(relationPreview3).toBeHidden()
|
||||
// collection's displayPreview: false, field's displayPreview: unset
|
||||
const relationPreview4 = page.locator('.cell-imageWithoutPreview2 img')
|
||||
await expect(relationPreview4).toBeHidden()
|
||||
// collection's displayPreview: false, field's displayPreview: true
|
||||
const relationPreview5 = page.locator('.cell-imageWithPreview3 img')
|
||||
await expect(relationPreview5).toBeVisible()
|
||||
// collection's displayPreview: false, field's displayPreview: false
|
||||
const relationPreview6 = page.locator('.cell-imageWithoutPreview3 img')
|
||||
await expect(relationPreview6).toBeHidden()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -37,6 +37,9 @@ export interface Config {
|
||||
'required-file': RequiredFile;
|
||||
versions: Version;
|
||||
'custom-upload-field': CustomUploadField;
|
||||
'media-with-relation-preview': MediaWithRelationPreview;
|
||||
'media-without-relation-preview': MediaWithoutRelationPreview;
|
||||
'relation-preview': RelationPreview;
|
||||
users: User;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
@@ -949,6 +952,59 @@ export interface CustomUploadField {
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media-with-relation-preview".
|
||||
*/
|
||||
export interface MediaWithRelationPreview {
|
||||
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` "media-without-relation-preview".
|
||||
*/
|
||||
export interface MediaWithoutRelationPreview {
|
||||
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` "relation-preview".
|
||||
*/
|
||||
export interface RelationPreview {
|
||||
id: string;
|
||||
imageWithPreview1?: string | MediaWithRelationPreview | null;
|
||||
imageWithPreview2?: string | MediaWithRelationPreview | null;
|
||||
imageWithoutPreview1?: string | MediaWithRelationPreview | null;
|
||||
imageWithoutPreview2?: string | MediaWithoutRelationPreview | null;
|
||||
imageWithPreview3?: string | MediaWithoutRelationPreview | null;
|
||||
imageWithoutPreview3?: string | MediaWithoutRelationPreview | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
|
||||
@@ -6,6 +6,9 @@ export const enlargeSlug = 'enlarge'
|
||||
export const focalNoSizesSlug = 'focal-no-sizes'
|
||||
export const focalOnlySlug = 'focal-only'
|
||||
export const reduceSlug = 'reduce'
|
||||
export const relationPreviewSlug = 'relation-preview'
|
||||
export const mediaWithRelationPreviewSlug = 'media-with-relation-preview'
|
||||
export const mediaWithoutRelationPreviewSlug = 'media-without-relation-preview'
|
||||
export const adminThumbnailFunctionSlug = 'admin-thumbnail-function'
|
||||
export const adminThumbnailSizeSlug = 'admin-thumbnail-size'
|
||||
export const unstoredMediaSlug = 'unstored-media'
|
||||
|
||||
Reference in New Issue
Block a user