feat(ui): adds constructorOptions to upload config (#12766)
### What? Adds `constructorOptions` property to the upload config to allow any of [these options](https://sharp.pixelplumbing.com/api-constructor/) to be passed to the Sharp library. ### Why? Users should be able to extend the Sharp library config as needed, to define useful properties like `limitInputPixels` etc. ### How? Creates new config option `constructorOptions` which passes any compatible options directly to the Sharp library. #### Reported by client.
This commit is contained in:
@@ -95,6 +95,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) |
|
||||
| **`bulkUpload`** | Allow users to upload in bulk from the list view, default is true |
|
||||
| **`cacheTags`** | Set to `false` to disable the cache tag set in the UI for the admin thumbnail component. Useful for when CDNs don't allow certain cache queries. |
|
||||
| **`constructorOptions`** | An object passed to the the Sharp image library that accepts any Constructor options and applies them to the upload file. [More](https://sharp.pixelplumbing.com/api-constructor/) |
|
||||
| **`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). |
|
||||
|
||||
@@ -67,6 +67,7 @@ export const generateFileData = async <T>({
|
||||
})
|
||||
|
||||
const {
|
||||
constructorOptions = {},
|
||||
disableLocalStorage,
|
||||
focalPoint: focalPointEnabled = true,
|
||||
formatOptions,
|
||||
@@ -143,9 +144,11 @@ export const generateFileData = async <T>({
|
||||
let mime: string
|
||||
const fileHasAdjustments =
|
||||
fileSupportsResize &&
|
||||
Boolean(resizeOptions || formatOptions || trimOptions || file.tempFilePath)
|
||||
Boolean(
|
||||
resizeOptions || formatOptions || trimOptions || constructorOptions || file.tempFilePath,
|
||||
)
|
||||
|
||||
const sharpOptions: SharpOptions = {}
|
||||
const sharpOptions: SharpOptions = { ...constructorOptions }
|
||||
|
||||
if (fileIsAnimatedType) {
|
||||
sharpOptions.animated = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ResizeOptions, Sharp } from 'sharp'
|
||||
import type { ResizeOptions, Sharp, SharpOptions } from 'sharp'
|
||||
|
||||
import type { TypeWithID } from '../collections/config/types.js'
|
||||
import type { PayloadComponent } from '../config/types.js'
|
||||
@@ -138,6 +138,11 @@ export type UploadConfig = {
|
||||
* @default true
|
||||
*/
|
||||
cacheTags?: boolean
|
||||
/**
|
||||
* Sharp constructor options to be passed to the uploaded file.
|
||||
* @link https://sharp.pixelplumbing.com/api-constructor/#sharp
|
||||
*/
|
||||
constructorOptions?: SharpOptions
|
||||
/**
|
||||
* Enables cropping of images.
|
||||
* @default true
|
||||
@@ -186,6 +191,7 @@ export type UploadConfig = {
|
||||
* - If a handler returns null, the next handler will be run.
|
||||
* - If no handlers return a response the file will be returned by default.
|
||||
*
|
||||
* @link https://sharp.pixelplumbing.com/api-output/#toformat
|
||||
* @default undefined
|
||||
*/
|
||||
handlers?: ((
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
allowListMediaSlug,
|
||||
animatedTypeMedia,
|
||||
audioSlug,
|
||||
constructorOptionsSlug,
|
||||
customFileNameMediaSlug,
|
||||
enlargeSlug,
|
||||
focalNoSizesSlug,
|
||||
@@ -832,6 +833,16 @@ export default buildConfigWithDefaults({
|
||||
focalPoint: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: constructorOptionsSlug,
|
||||
fields: [],
|
||||
upload: {
|
||||
constructorOptions: {
|
||||
limitInputPixels: 100, // set lower than the collection upload fileSize limit default to test
|
||||
},
|
||||
staticDir: path.resolve(dirname, './media'),
|
||||
},
|
||||
},
|
||||
],
|
||||
onInit: async (payload) => {
|
||||
const uploadsDir = path.resolve(dirname, './media')
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
adminUploadControlSlug,
|
||||
animatedTypeMedia,
|
||||
audioSlug,
|
||||
constructorOptionsSlug,
|
||||
customFileNameMediaSlug,
|
||||
customUploadFieldSlug,
|
||||
focalOnlySlug,
|
||||
@@ -75,6 +76,7 @@ let hideFileInputOnCreateURL: AdminUrlUtil
|
||||
let bestFitURL: AdminUrlUtil
|
||||
let withoutEnlargementResizeOptionsURL: AdminUrlUtil
|
||||
let threeDimensionalURL: AdminUrlUtil
|
||||
let constructorOptionsURL: AdminUrlUtil
|
||||
let consoleErrorsFromPage: string[] = []
|
||||
let collectErrorsFromPage: () => boolean
|
||||
let stopCollectingErrorsFromPage: () => boolean
|
||||
@@ -113,6 +115,7 @@ describe('Uploads', () => {
|
||||
bestFitURL = new AdminUrlUtil(serverURL, 'best-fit')
|
||||
withoutEnlargementResizeOptionsURL = new AdminUrlUtil(serverURL, withoutEnlargeSlug)
|
||||
threeDimensionalURL = new AdminUrlUtil(serverURL, threeDimensionalSlug)
|
||||
constructorOptionsURL = new AdminUrlUtil(serverURL, constructorOptionsSlug)
|
||||
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
@@ -1531,4 +1534,15 @@ describe('Uploads', () => {
|
||||
await expect(imageUploadCell).toHaveText('<No Image Upload>')
|
||||
await expect(imageRelationshipCell).toHaveText('<No Image Relationship>')
|
||||
})
|
||||
|
||||
test('should respect Sharp constructorOptions', async () => {
|
||||
await page.goto(constructorOptionsURL.create)
|
||||
|
||||
await page.setInputFiles('input[type="file"]', path.resolve(dirname, './animated.webp'))
|
||||
|
||||
const filename = page.locator('.file-field__filename')
|
||||
|
||||
await expect(filename).toHaveValue('animated.webp')
|
||||
await saveDocAndAssert(page, '#action-save', 'error')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -107,6 +107,7 @@ export interface Config {
|
||||
'best-fit': BestFit;
|
||||
'list-view-preview': ListViewPreview;
|
||||
'three-dimensional': ThreeDimensional;
|
||||
'constructor-options': ConstructorOption;
|
||||
users: User;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
@@ -154,6 +155,7 @@ export interface Config {
|
||||
'best-fit': BestFitSelect<false> | BestFitSelect<true>;
|
||||
'list-view-preview': ListViewPreviewSelect<false> | ListViewPreviewSelect<true>;
|
||||
'three-dimensional': ThreeDimensionalSelect<false> | ThreeDimensionalSelect<true>;
|
||||
'constructor-options': ConstructorOptionsSelect<false> | ConstructorOptionsSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
@@ -1367,6 +1369,24 @@ export interface ThreeDimensional {
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "constructor-options".
|
||||
*/
|
||||
export interface ConstructorOption {
|
||||
id: string;
|
||||
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` "users".
|
||||
@@ -1551,6 +1571,10 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'three-dimensional';
|
||||
value: string | ThreeDimensional;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'constructor-options';
|
||||
value: string | ConstructorOption;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
@@ -2852,6 +2876,23 @@ export interface ThreeDimensionalSelect<T extends boolean = true> {
|
||||
width?: T;
|
||||
height?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "constructor-options_select".
|
||||
*/
|
||||
export interface ConstructorOptionsSelect<T extends boolean = true> {
|
||||
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".
|
||||
|
||||
@@ -28,3 +28,4 @@ export const allowListMediaSlug = 'allow-list-media'
|
||||
|
||||
export const listViewPreviewSlug = 'list-view-preview'
|
||||
export const threeDimensionalSlug = 'three-dimensional'
|
||||
export const constructorOptionsSlug = 'constructor-options'
|
||||
|
||||
Reference in New Issue
Block a user