Skip to content

Commit b372a34

Browse files
authored
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.
1 parent 769ca03 commit b372a34

File tree

7 files changed

+80
-3
lines changed

7 files changed

+80
-3
lines changed

docs/upload/overview.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ _An asterisk denotes that an option is required._
9595
| **`adminThumbnail`** | Set the way that the [Admin Panel](../admin/overview) will display thumbnails for this Collection. [More](#admin-thumbnails) |
9696
| **`bulkUpload`** | Allow users to upload in bulk from the list view, default is true |
9797
| **`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. |
98+
| **`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/) |
9899
| **`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) |
99100
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
100101
| **`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). |

packages/payload/src/uploads/generateFileData.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export const generateFileData = async <T>({
6767
})
6868

6969
const {
70+
constructorOptions = {},
7071
disableLocalStorage,
7172
focalPoint: focalPointEnabled = true,
7273
formatOptions,
@@ -143,9 +144,11 @@ export const generateFileData = async <T>({
143144
let mime: string
144145
const fileHasAdjustments =
145146
fileSupportsResize &&
146-
Boolean(resizeOptions || formatOptions || trimOptions || file.tempFilePath)
147+
Boolean(
148+
resizeOptions || formatOptions || trimOptions || constructorOptions || file.tempFilePath,
149+
)
147150

148-
const sharpOptions: SharpOptions = {}
151+
const sharpOptions: SharpOptions = { ...constructorOptions }
149152

150153
if (fileIsAnimatedType) {
151154
sharpOptions.animated = true

packages/payload/src/uploads/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ResizeOptions, Sharp } from 'sharp'
1+
import type { ResizeOptions, Sharp, SharpOptions } from 'sharp'
22

33
import type { TypeWithID } from '../collections/config/types.js'
44
import type { PayloadComponent } from '../config/types.js'
@@ -138,6 +138,11 @@ export type UploadConfig = {
138138
* @default true
139139
*/
140140
cacheTags?: boolean
141+
/**
142+
* Sharp constructor options to be passed to the uploaded file.
143+
* @link https://sharp.pixelplumbing.com/api-constructor/#sharp
144+
*/
145+
constructorOptions?: SharpOptions
141146
/**
142147
* Enables cropping of images.
143148
* @default true
@@ -186,6 +191,7 @@ export type UploadConfig = {
186191
* - If a handler returns null, the next handler will be run.
187192
* - If no handlers return a response the file will be returned by default.
188193
*
194+
* @link https://sharp.pixelplumbing.com/api-output/#toformat
189195
* @default undefined
190196
*/
191197
handlers?: ((

test/uploads/config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
allowListMediaSlug,
2121
animatedTypeMedia,
2222
audioSlug,
23+
constructorOptionsSlug,
2324
customFileNameMediaSlug,
2425
enlargeSlug,
2526
focalNoSizesSlug,
@@ -832,6 +833,16 @@ export default buildConfigWithDefaults({
832833
focalPoint: false,
833834
},
834835
},
836+
{
837+
slug: constructorOptionsSlug,
838+
fields: [],
839+
upload: {
840+
constructorOptions: {
841+
limitInputPixels: 100, // set lower than the collection upload fileSize limit default to test
842+
},
843+
staticDir: path.resolve(dirname, './media'),
844+
},
845+
},
835846
],
836847
onInit: async (payload) => {
837848
const uploadsDir = path.resolve(dirname, './media')

test/uploads/e2e.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
adminUploadControlSlug,
2929
animatedTypeMedia,
3030
audioSlug,
31+
constructorOptionsSlug,
3132
customFileNameMediaSlug,
3233
customUploadFieldSlug,
3334
focalOnlySlug,
@@ -75,6 +76,7 @@ let hideFileInputOnCreateURL: AdminUrlUtil
7576
let bestFitURL: AdminUrlUtil
7677
let withoutEnlargementResizeOptionsURL: AdminUrlUtil
7778
let threeDimensionalURL: AdminUrlUtil
79+
let constructorOptionsURL: AdminUrlUtil
7880
let consoleErrorsFromPage: string[] = []
7981
let collectErrorsFromPage: () => boolean
8082
let stopCollectingErrorsFromPage: () => boolean
@@ -113,6 +115,7 @@ describe('Uploads', () => {
113115
bestFitURL = new AdminUrlUtil(serverURL, 'best-fit')
114116
withoutEnlargementResizeOptionsURL = new AdminUrlUtil(serverURL, withoutEnlargeSlug)
115117
threeDimensionalURL = new AdminUrlUtil(serverURL, threeDimensionalSlug)
118+
constructorOptionsURL = new AdminUrlUtil(serverURL, constructorOptionsSlug)
116119

117120
const context = await browser.newContext()
118121
page = await context.newPage()
@@ -1531,4 +1534,15 @@ describe('Uploads', () => {
15311534
await expect(imageUploadCell).toHaveText('<No Image Upload>')
15321535
await expect(imageRelationshipCell).toHaveText('<No Image Relationship>')
15331536
})
1537+
1538+
test('should respect Sharp constructorOptions', async () => {
1539+
await page.goto(constructorOptionsURL.create)
1540+
1541+
await page.setInputFiles('input[type="file"]', path.resolve(dirname, './animated.webp'))
1542+
1543+
const filename = page.locator('.file-field__filename')
1544+
1545+
await expect(filename).toHaveValue('animated.webp')
1546+
await saveDocAndAssert(page, '#action-save', 'error')
1547+
})
15341548
})

test/uploads/payload-types.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export interface Config {
107107
'best-fit': BestFit;
108108
'list-view-preview': ListViewPreview;
109109
'three-dimensional': ThreeDimensional;
110+
'constructor-options': ConstructorOption;
110111
users: User;
111112
'payload-locked-documents': PayloadLockedDocument;
112113
'payload-preferences': PayloadPreference;
@@ -154,6 +155,7 @@ export interface Config {
154155
'best-fit': BestFitSelect<false> | BestFitSelect<true>;
155156
'list-view-preview': ListViewPreviewSelect<false> | ListViewPreviewSelect<true>;
156157
'three-dimensional': ThreeDimensionalSelect<false> | ThreeDimensionalSelect<true>;
158+
'constructor-options': ConstructorOptionsSelect<false> | ConstructorOptionsSelect<true>;
157159
users: UsersSelect<false> | UsersSelect<true>;
158160
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
159161
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
@@ -1367,6 +1369,24 @@ export interface ThreeDimensional {
13671369
width?: number | null;
13681370
height?: number | null;
13691371
}
1372+
/**
1373+
* This interface was referenced by `Config`'s JSON-Schema
1374+
* via the `definition` "constructor-options".
1375+
*/
1376+
export interface ConstructorOption {
1377+
id: string;
1378+
updatedAt: string;
1379+
createdAt: string;
1380+
url?: string | null;
1381+
thumbnailURL?: string | null;
1382+
filename?: string | null;
1383+
mimeType?: string | null;
1384+
filesize?: number | null;
1385+
width?: number | null;
1386+
height?: number | null;
1387+
focalX?: number | null;
1388+
focalY?: number | null;
1389+
}
13701390
/**
13711391
* This interface was referenced by `Config`'s JSON-Schema
13721392
* via the `definition` "users".
@@ -1551,6 +1571,10 @@ export interface PayloadLockedDocument {
15511571
relationTo: 'three-dimensional';
15521572
value: string | ThreeDimensional;
15531573
} | null)
1574+
| ({
1575+
relationTo: 'constructor-options';
1576+
value: string | ConstructorOption;
1577+
} | null)
15541578
| ({
15551579
relationTo: 'users';
15561580
value: string | User;
@@ -2852,6 +2876,23 @@ export interface ThreeDimensionalSelect<T extends boolean = true> {
28522876
width?: T;
28532877
height?: T;
28542878
}
2879+
/**
2880+
* This interface was referenced by `Config`'s JSON-Schema
2881+
* via the `definition` "constructor-options_select".
2882+
*/
2883+
export interface ConstructorOptionsSelect<T extends boolean = true> {
2884+
updatedAt?: T;
2885+
createdAt?: T;
2886+
url?: T;
2887+
thumbnailURL?: T;
2888+
filename?: T;
2889+
mimeType?: T;
2890+
filesize?: T;
2891+
width?: T;
2892+
height?: T;
2893+
focalX?: T;
2894+
focalY?: T;
2895+
}
28552896
/**
28562897
* This interface was referenced by `Config`'s JSON-Schema
28572898
* via the `definition` "users_select".

test/uploads/shared.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ export const allowListMediaSlug = 'allow-list-media'
2828

2929
export const listViewPreviewSlug = 'list-view-preview'
3030
export const threeDimensionalSlug = 'three-dimensional'
31+
export const constructorOptionsSlug = 'constructor-options'

0 commit comments

Comments
 (0)