feat: store focal point on uploads (#6436)
Store focal point data on uploads as `focalX` and `focalY` Addresses https://github.com/payloadcms/payload/discussions/4082 Mirrors #6364 for beta branch.
This commit is contained in:
@@ -152,7 +152,7 @@ type FetchAPIFileUpload = (args: {
|
|||||||
request: Request
|
request: Request
|
||||||
}) => Promise<FetchAPIFileUploadResponse>
|
}) => Promise<FetchAPIFileUploadResponse>
|
||||||
export const fetchAPIFileUpload: FetchAPIFileUpload = async ({ options, request }) => {
|
export const fetchAPIFileUpload: FetchAPIFileUpload = async ({ options, request }) => {
|
||||||
const uploadOptions = { ...DEFAULT_OPTIONS, ...options }
|
const uploadOptions: FetchAPIFileUploadOptions = { ...DEFAULT_OPTIONS, ...options }
|
||||||
if (!isEligibleRequest(request)) {
|
if (!isEligibleRequest(request)) {
|
||||||
debugLog(uploadOptions, 'Request is not eligible for file upload!')
|
debugLog(uploadOptions, 'Request is not eligible for file upload!')
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ export const createOperation = async <TSlug extends keyof GeneratedTypes['collec
|
|||||||
collection,
|
collection,
|
||||||
config,
|
config,
|
||||||
data,
|
data,
|
||||||
|
operation: 'create',
|
||||||
overwriteExistingFiles,
|
overwriteExistingFiles,
|
||||||
req,
|
req,
|
||||||
throwOnMissingFile:
|
throwOnMissingFile:
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ export const updateOperation = async <TSlug extends keyof GeneratedTypes['collec
|
|||||||
collection,
|
collection,
|
||||||
config,
|
config,
|
||||||
data: bulkUpdateData,
|
data: bulkUpdateData,
|
||||||
|
operation: 'update',
|
||||||
overwriteExistingFiles,
|
overwriteExistingFiles,
|
||||||
req,
|
req,
|
||||||
throwOnMissingFile: false,
|
throwOnMissingFile: false,
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ export const updateByIDOperation = async <TSlug extends keyof GeneratedTypes['co
|
|||||||
collection,
|
collection,
|
||||||
config,
|
config,
|
||||||
data,
|
data,
|
||||||
|
operation: 'update',
|
||||||
overwriteExistingFiles,
|
overwriteExistingFiles,
|
||||||
req,
|
req,
|
||||||
throwOnMissingFile: false,
|
throwOnMissingFile: false,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export { traverseFields as beforeValidateTraverseFields } from '../fields/hooks/
|
|||||||
|
|
||||||
export { formatFilesize } from '../uploads/formatFilesize.js'
|
export { formatFilesize } from '../uploads/formatFilesize.js'
|
||||||
|
|
||||||
export { default as isImage } from '../uploads/isImage.js'
|
export { isImage } from '../uploads/isImage.js'
|
||||||
|
|
||||||
export { combineMerge } from '../utilities/combineMerge.js'
|
export { combineMerge } from '../utilities/combineMerge.js'
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -7,19 +7,6 @@ import type { GeneratedTypes } from '../index.js'
|
|||||||
import type { validOperators } from './constants.js'
|
import type { validOperators } from './constants.js'
|
||||||
export type { Payload as Payload } from '../index.js'
|
export type { Payload as Payload } from '../index.js'
|
||||||
|
|
||||||
export type UploadEdits = {
|
|
||||||
crop?: {
|
|
||||||
height?: number
|
|
||||||
width?: number
|
|
||||||
x?: number
|
|
||||||
y?: number
|
|
||||||
}
|
|
||||||
focalPoint?: {
|
|
||||||
x?: number
|
|
||||||
y?: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CustomPayloadRequestProperties = {
|
export type CustomPayloadRequestProperties = {
|
||||||
context: RequestContext
|
context: RequestContext
|
||||||
/** The locale that should be used for a field when it is not translated to the requested locale */
|
/** The locale that should be used for a field when it is not translated to the requested locale */
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export default function canResizeImage(mimeType: string): boolean {
|
export function canResizeImage(mimeType: string): boolean {
|
||||||
return ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/tiff'].indexOf(mimeType) > -1
|
return ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/tiff'].indexOf(mimeType) > -1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export const percentToPixel = (value, dimension) => {
|
|||||||
return Math.floor((parseFloat(value) / 100) * dimension)
|
return Math.floor((parseFloat(value) / 100) * dimension)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function cropImage({ cropData, dimensions, file, sharp }) {
|
export async function cropImage({ cropData, dimensions, file, sharp }) {
|
||||||
try {
|
try {
|
||||||
const { height, width, x, y } = cropData
|
const { height, width, x, y } = cropData
|
||||||
|
|
||||||
|
|||||||
@@ -9,22 +9,24 @@ import sanitize from 'sanitize-filename'
|
|||||||
import type { Collection } from '../collections/config/types.js'
|
import type { Collection } from '../collections/config/types.js'
|
||||||
import type { SanitizedConfig } from '../config/types.js'
|
import type { SanitizedConfig } from '../config/types.js'
|
||||||
import type { PayloadRequestWithData } from '../types/index.js'
|
import type { PayloadRequestWithData } from '../types/index.js'
|
||||||
import type { FileData, FileToSave, ProbedImageSize } from './types.js'
|
import type { FileData, FileToSave, ProbedImageSize, UploadEdits } from './types.js'
|
||||||
|
|
||||||
import { FileRetrievalError, FileUploadError, MissingFile } from '../errors/index.js'
|
import { FileRetrievalError, FileUploadError, MissingFile } from '../errors/index.js'
|
||||||
import canResizeImage from './canResizeImage.js'
|
import { canResizeImage } from './canResizeImage.js'
|
||||||
import cropImage from './cropImage.js'
|
import { cropImage } from './cropImage.js'
|
||||||
import { getExternalFile } from './getExternalFile.js'
|
import { getExternalFile } from './getExternalFile.js'
|
||||||
import { getFileByPath } from './getFileByPath.js'
|
import { getFileByPath } from './getFileByPath.js'
|
||||||
import { getImageSize } from './getImageSize.js'
|
import { getImageSize } from './getImageSize.js'
|
||||||
import getSafeFileName from './getSafeFilename.js'
|
import { getSafeFileName } from './getSafeFilename.js'
|
||||||
import resizeAndTransformImageSizes from './imageResizer.js'
|
import { resizeAndTransformImageSizes } from './imageResizer.js'
|
||||||
import isImage from './isImage.js'
|
import { isImage } from './isImage.js'
|
||||||
|
|
||||||
type Args<T> = {
|
type Args<T> = {
|
||||||
collection: Collection
|
collection: Collection
|
||||||
config: SanitizedConfig
|
config: SanitizedConfig
|
||||||
data: T
|
data: T
|
||||||
|
operation: 'create' | 'update'
|
||||||
|
originalDoc?: T
|
||||||
overwriteExistingFiles?: boolean
|
overwriteExistingFiles?: boolean
|
||||||
req: PayloadRequestWithData
|
req: PayloadRequestWithData
|
||||||
throwOnMissingFile?: boolean
|
throwOnMissingFile?: boolean
|
||||||
@@ -38,6 +40,8 @@ type Result<T> = Promise<{
|
|||||||
export const generateFileData = async <T>({
|
export const generateFileData = async <T>({
|
||||||
collection: { config: collectionConfig },
|
collection: { config: collectionConfig },
|
||||||
data,
|
data,
|
||||||
|
operation,
|
||||||
|
originalDoc,
|
||||||
overwriteExistingFiles,
|
overwriteExistingFiles,
|
||||||
req,
|
req,
|
||||||
throwOnMissingFile,
|
throwOnMissingFile,
|
||||||
@@ -53,10 +57,22 @@ export const generateFileData = async <T>({
|
|||||||
|
|
||||||
let file = req.file
|
let file = req.file
|
||||||
|
|
||||||
const uploadEdits = req.query['uploadEdits'] || {}
|
const uploadEdits = parseUploadEditsFromReqOrIncomingData({
|
||||||
|
data,
|
||||||
|
operation,
|
||||||
|
originalDoc,
|
||||||
|
req,
|
||||||
|
})
|
||||||
|
|
||||||
const { disableLocalStorage, formatOptions, imageSizes, resizeOptions, staticDir, trimOptions } =
|
const {
|
||||||
collectionConfig.upload
|
disableLocalStorage,
|
||||||
|
focalPoint: focalPointEnabled = true,
|
||||||
|
formatOptions,
|
||||||
|
imageSizes,
|
||||||
|
resizeOptions,
|
||||||
|
staticDir,
|
||||||
|
trimOptions,
|
||||||
|
} = collectionConfig.upload
|
||||||
|
|
||||||
const staticPath = staticDir
|
const staticPath = staticDir
|
||||||
|
|
||||||
@@ -228,9 +244,9 @@ export const generateFileData = async <T>({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(imageSizes) && fileSupportsResize && sharp) {
|
if (fileSupportsResize && (Array.isArray(imageSizes) || focalPointEnabled !== false)) {
|
||||||
req.payloadUploadSizes = {}
|
req.payloadUploadSizes = {}
|
||||||
const { sizeData, sizesToSave } = await resizeAndTransformImageSizes({
|
const { focalPoint, sizeData, sizesToSave } = await resizeAndTransformImageSizes({
|
||||||
config: collectionConfig,
|
config: collectionConfig,
|
||||||
dimensions: !cropData
|
dimensions: !cropData
|
||||||
? dimensions
|
? dimensions
|
||||||
@@ -245,13 +261,16 @@ export const generateFileData = async <T>({
|
|||||||
savedFilename: fsSafeName || file.name,
|
savedFilename: fsSafeName || file.name,
|
||||||
sharp,
|
sharp,
|
||||||
staticPath,
|
staticPath,
|
||||||
|
uploadEdits,
|
||||||
})
|
})
|
||||||
|
|
||||||
fileData.sizes = sizeData
|
fileData.sizes = sizeData
|
||||||
|
fileData.focalX = focalPoint?.x
|
||||||
|
fileData.focalY = focalPoint?.y
|
||||||
filesToSave.push(...sizesToSave)
|
filesToSave.push(...sizesToSave)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
req.payload.logger.error(err)
|
||||||
throw new FileUploadError(req.t)
|
throw new FileUploadError(req.t)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,3 +284,50 @@ export const generateFileData = async <T>({
|
|||||||
files: filesToSave,
|
files: filesToSave,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse upload edits from req or incoming data
|
||||||
|
*/
|
||||||
|
function parseUploadEditsFromReqOrIncomingData(args: {
|
||||||
|
data: unknown
|
||||||
|
operation: 'create' | 'update'
|
||||||
|
originalDoc: unknown
|
||||||
|
req: PayloadRequestWithData
|
||||||
|
}): UploadEdits {
|
||||||
|
const { data, operation, originalDoc, req } = args
|
||||||
|
|
||||||
|
// Get intended focal point change from query string or incoming data
|
||||||
|
const {
|
||||||
|
uploadEdits = {},
|
||||||
|
}: {
|
||||||
|
uploadEdits?: UploadEdits
|
||||||
|
} = req.query || {}
|
||||||
|
|
||||||
|
if (uploadEdits.focalPoint) return uploadEdits
|
||||||
|
|
||||||
|
const incomingData = data as FileData
|
||||||
|
const origDoc = originalDoc as FileData
|
||||||
|
|
||||||
|
// If no change in focal point, return undefined.
|
||||||
|
// This prevents a refocal operation triggered from admin, because it always sends the focal point.
|
||||||
|
if (origDoc && incomingData.focalX === origDoc.focalX && incomingData.focalY === origDoc.focalY) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (incomingData?.focalX && incomingData?.focalY) {
|
||||||
|
uploadEdits.focalPoint = {
|
||||||
|
x: incomingData.focalX,
|
||||||
|
y: incomingData.focalY,
|
||||||
|
}
|
||||||
|
return uploadEdits
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no focal point is set, default to center
|
||||||
|
if (operation === 'create') {
|
||||||
|
uploadEdits.focalPoint = {
|
||||||
|
x: 50,
|
||||||
|
y: 50,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uploadEdits
|
||||||
|
}
|
||||||
|
|||||||
@@ -149,6 +149,25 @@ export const getBaseUploadFields = ({ collection, config }: Options): Field[] =>
|
|||||||
height,
|
height,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// Add focal point fields if not disabled
|
||||||
|
if (
|
||||||
|
uploadOptions.focalPoint !== false ||
|
||||||
|
uploadOptions.imageSizes ||
|
||||||
|
uploadOptions.resizeOptions
|
||||||
|
) {
|
||||||
|
uploadFields = uploadFields.concat(
|
||||||
|
['focalX', 'focalY'].map((name) => {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
type: 'number',
|
||||||
|
admin: {
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (uploadOptions.mimeTypes) {
|
if (uploadOptions.mimeTypes) {
|
||||||
mimeType.validate = mimeTypeValidator(uploadOptions.mimeTypes)
|
mimeType.validate = mimeTypeValidator(uploadOptions.mimeTypes)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ type Args = {
|
|||||||
staticPath: string
|
staticPath: string
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSafeFileName({
|
export async function getSafeFileName({
|
||||||
collectionSlug,
|
collectionSlug,
|
||||||
desiredFilename,
|
desiredFilename,
|
||||||
req,
|
req,
|
||||||
@@ -51,5 +51,3 @@ async function getSafeFileName({
|
|||||||
}
|
}
|
||||||
return modifiedFilename
|
return modifiedFilename
|
||||||
}
|
}
|
||||||
|
|
||||||
export default getSafeFileName
|
|
||||||
|
|||||||
@@ -8,8 +8,15 @@ import sanitize from 'sanitize-filename'
|
|||||||
|
|
||||||
import type { SanitizedCollectionConfig } from '../collections/config/types.js'
|
import type { SanitizedCollectionConfig } from '../collections/config/types.js'
|
||||||
import type { SharpDependency } from '../config/types.js'
|
import type { SharpDependency } from '../config/types.js'
|
||||||
import type { PayloadRequestWithData, UploadEdits } from '../types/index.js'
|
import type { PayloadRequestWithData } from '../types/index.js'
|
||||||
import type { FileSize, FileSizes, FileToSave, ImageSize, ProbedImageSize } from './types.js'
|
import type {
|
||||||
|
FileSize,
|
||||||
|
FileSizes,
|
||||||
|
FileToSave,
|
||||||
|
ImageSize,
|
||||||
|
ProbedImageSize,
|
||||||
|
UploadEdits,
|
||||||
|
} from './types.js'
|
||||||
|
|
||||||
import { isNumber } from '../utilities/isNumber.js'
|
import { isNumber } from '../utilities/isNumber.js'
|
||||||
import fileExists from './fileExists.js'
|
import fileExists from './fileExists.js'
|
||||||
@@ -19,18 +26,16 @@ type ResizeArgs = {
|
|||||||
dimensions: ProbedImageSize
|
dimensions: ProbedImageSize
|
||||||
file: PayloadRequestWithData['file']
|
file: PayloadRequestWithData['file']
|
||||||
mimeType: string
|
mimeType: string
|
||||||
req: PayloadRequestWithData & {
|
req: PayloadRequestWithData
|
||||||
query?: {
|
|
||||||
uploadEdits?: UploadEdits
|
|
||||||
}
|
|
||||||
}
|
|
||||||
savedFilename: string
|
savedFilename: string
|
||||||
sharp: SharpDependency
|
sharp?: SharpDependency
|
||||||
staticPath: string
|
staticPath: string
|
||||||
|
uploadEdits?: UploadEdits
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Result from resizing and transforming the requested image sizes */
|
/** Result from resizing and transforming the requested image sizes */
|
||||||
type ImageSizesResult = {
|
type ImageSizesResult = {
|
||||||
|
focalPoint?: UploadEdits['focalPoint']
|
||||||
sizeData: FileSizes
|
sizeData: FileSizes
|
||||||
sizesToSave: FileToSave[]
|
sizesToSave: FileToSave[]
|
||||||
}
|
}
|
||||||
@@ -71,6 +76,16 @@ const createImageName = (
|
|||||||
extension: string,
|
extension: string,
|
||||||
) => `${outputImageName}-${width}x${height}.${extension}`
|
) => `${outputImageName}-${width}x${height}.${extension}`
|
||||||
|
|
||||||
|
type CreateResultArgs = {
|
||||||
|
filename?: FileSize['filename']
|
||||||
|
filesize?: FileSize['filesize']
|
||||||
|
height?: FileSize['height']
|
||||||
|
mimeType?: FileSize['mimeType']
|
||||||
|
name: string
|
||||||
|
sizesToSave?: FileToSave[]
|
||||||
|
width?: FileSize['width']
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the result object for the image resize operation based on the
|
* Create the result object for the image resize operation based on the
|
||||||
* provided parameters. If the name is not provided, an empty result object
|
* provided parameters. If the name is not provided, an empty result object
|
||||||
@@ -85,26 +100,28 @@ const createImageName = (
|
|||||||
* @param sizesToSave - the sizes to save
|
* @param sizesToSave - the sizes to save
|
||||||
* @returns the result object
|
* @returns the result object
|
||||||
*/
|
*/
|
||||||
const createResult = (
|
const createResult = ({
|
||||||
name: string,
|
name,
|
||||||
filename: FileSize['filename'] = null,
|
filename = null,
|
||||||
width: FileSize['width'] = null,
|
filesize = null,
|
||||||
height: FileSize['height'] = null,
|
height = null,
|
||||||
filesize: FileSize['filesize'] = null,
|
mimeType = null,
|
||||||
mimeType: FileSize['mimeType'] = null,
|
sizesToSave = [],
|
||||||
sizesToSave: FileToSave[] = [],
|
width = null,
|
||||||
): ImageSizesResult => ({
|
}: CreateResultArgs): ImageSizesResult => {
|
||||||
sizeData: {
|
return {
|
||||||
[name]: {
|
sizeData: {
|
||||||
filename,
|
[name]: {
|
||||||
filesize,
|
filename,
|
||||||
height,
|
filesize,
|
||||||
mimeType,
|
height,
|
||||||
width,
|
mimeType,
|
||||||
|
width,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
sizesToSave,
|
||||||
sizesToSave,
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the image needs to be resized according to the requested dimensions
|
* Check if the image needs to be resized according to the requested dimensions
|
||||||
@@ -208,7 +225,7 @@ const sanitizeResizeConfig = (resizeConfig: ImageSize): ImageSize => {
|
|||||||
* @param resizeConfig - the resize config
|
* @param resizeConfig - the resize config
|
||||||
* @returns the result of the resize operation(s)
|
* @returns the result of the resize operation(s)
|
||||||
*/
|
*/
|
||||||
export default async function resizeAndTransformImageSizes({
|
export async function resizeAndTransformImageSizes({
|
||||||
config,
|
config,
|
||||||
dimensions,
|
dimensions,
|
||||||
file,
|
file,
|
||||||
@@ -217,10 +234,27 @@ export default async function resizeAndTransformImageSizes({
|
|||||||
savedFilename,
|
savedFilename,
|
||||||
sharp,
|
sharp,
|
||||||
staticPath,
|
staticPath,
|
||||||
|
uploadEdits,
|
||||||
}: ResizeArgs): Promise<ImageSizesResult> {
|
}: ResizeArgs): Promise<ImageSizesResult> {
|
||||||
const { imageSizes } = config.upload
|
const { focalPoint: focalPointEnabled = true, imageSizes } = config.upload
|
||||||
// Noting to resize here so return as early as possible
|
|
||||||
if (!imageSizes) return { sizeData: {}, sizesToSave: [] }
|
// Focal point adjustments
|
||||||
|
const incomingFocalPoint = uploadEdits.focalPoint
|
||||||
|
? {
|
||||||
|
x: isNumber(uploadEdits.focalPoint.x) ? Math.round(uploadEdits.focalPoint.x) : 50,
|
||||||
|
y: isNumber(uploadEdits.focalPoint.y) ? Math.round(uploadEdits.focalPoint.y) : 50,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
const defaultResult: ImageSizesResult = {
|
||||||
|
...(focalPointEnabled && incomingFocalPoint && { focalPoint: incomingFocalPoint }),
|
||||||
|
sizeData: {},
|
||||||
|
sizesToSave: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!imageSizes || !sharp) {
|
||||||
|
return defaultResult
|
||||||
|
}
|
||||||
|
|
||||||
const sharpBase = sharp(file.tempFilePath || file.data).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
|
const sharpBase = sharp(file.tempFilePath || file.data).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
|
||||||
|
|
||||||
@@ -232,16 +266,13 @@ export default async function resizeAndTransformImageSizes({
|
|||||||
// skipped COMPLETELY and thus will not be included in the resulting images.
|
// skipped COMPLETELY and thus will not be included in the resulting images.
|
||||||
// All further format/trim options will thus be skipped as well.
|
// All further format/trim options will thus be skipped as well.
|
||||||
if (preventResize(imageResizeConfig, dimensions)) {
|
if (preventResize(imageResizeConfig, dimensions)) {
|
||||||
return createResult(imageResizeConfig.name)
|
return createResult({ name: imageResizeConfig.name })
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageToResize = sharpBase.clone()
|
const imageToResize = sharpBase.clone()
|
||||||
let resized = imageToResize
|
let resized = imageToResize
|
||||||
|
|
||||||
if (
|
if (incomingFocalPoint && applyPayloadAdjustments(imageResizeConfig, dimensions)) {
|
||||||
req.query?.uploadEdits?.focalPoint &&
|
|
||||||
applyPayloadAdjustments(imageResizeConfig, dimensions)
|
|
||||||
) {
|
|
||||||
const { height: resizeHeight, width: resizeWidth } = imageResizeConfig
|
const { height: resizeHeight, width: resizeWidth } = imageResizeConfig
|
||||||
const resizeAspectRatio = resizeWidth / resizeHeight
|
const resizeAspectRatio = resizeWidth / resizeHeight
|
||||||
const originalAspectRatio = dimensions.width / dimensions.height
|
const originalAspectRatio = dimensions.width / dimensions.height
|
||||||
@@ -254,27 +285,17 @@ export default async function resizeAndTransformImageSizes({
|
|||||||
})
|
})
|
||||||
const { info: scaledImageInfo } = await scaledImage.toBuffer({ resolveWithObject: true })
|
const { info: scaledImageInfo } = await scaledImage.toBuffer({ resolveWithObject: true })
|
||||||
|
|
||||||
// Focal point adjustments
|
|
||||||
const focalPoint = {
|
|
||||||
x: isNumber(req.query.uploadEdits.focalPoint?.x)
|
|
||||||
? req.query.uploadEdits.focalPoint.x
|
|
||||||
: 50,
|
|
||||||
y: isNumber(req.query.uploadEdits.focalPoint?.y)
|
|
||||||
? req.query.uploadEdits.focalPoint.y
|
|
||||||
: 50,
|
|
||||||
}
|
|
||||||
|
|
||||||
const safeResizeWidth = resizeWidth ?? scaledImageInfo.width
|
const safeResizeWidth = resizeWidth ?? scaledImageInfo.width
|
||||||
const maxOffsetX = scaledImageInfo.width - safeResizeWidth
|
const maxOffsetX = scaledImageInfo.width - safeResizeWidth
|
||||||
const leftFocalEdge = Math.round(
|
const leftFocalEdge = Math.round(
|
||||||
scaledImageInfo.width * (focalPoint.x / 100) - safeResizeWidth / 2,
|
scaledImageInfo.width * (incomingFocalPoint.x / 100) - safeResizeWidth / 2,
|
||||||
)
|
)
|
||||||
const safeOffsetX = Math.min(Math.max(0, leftFocalEdge), maxOffsetX)
|
const safeOffsetX = Math.min(Math.max(0, leftFocalEdge), maxOffsetX)
|
||||||
|
|
||||||
const safeResizeHeight = resizeHeight ?? scaledImageInfo.height
|
const safeResizeHeight = resizeHeight ?? scaledImageInfo.height
|
||||||
const maxOffsetY = scaledImageInfo.height - safeResizeHeight
|
const maxOffsetY = scaledImageInfo.height - safeResizeHeight
|
||||||
const topFocalEdge = Math.round(
|
const topFocalEdge = Math.round(
|
||||||
scaledImageInfo.height * (focalPoint.y / 100) - safeResizeHeight / 2,
|
scaledImageInfo.height * (incomingFocalPoint.y / 100) - safeResizeHeight / 2,
|
||||||
)
|
)
|
||||||
const safeOffsetY = Math.min(Math.max(0, topFocalEdge), maxOffsetY)
|
const safeOffsetY = Math.min(Math.max(0, topFocalEdge), maxOffsetY)
|
||||||
|
|
||||||
@@ -306,7 +327,9 @@ export default async function resizeAndTransformImageSizes({
|
|||||||
|
|
||||||
const sanitizedImage = getSanitizedImageData(savedFilename)
|
const sanitizedImage = getSanitizedImageData(savedFilename)
|
||||||
|
|
||||||
req.payloadUploadSizes[imageResizeConfig.name] = bufferData
|
if (req.payloadUploadSizes) {
|
||||||
|
req.payloadUploadSizes[imageResizeConfig.name] = bufferData
|
||||||
|
}
|
||||||
|
|
||||||
const mimeInfo = await fromBuffer(bufferData)
|
const mimeInfo = await fromBuffer(bufferData)
|
||||||
|
|
||||||
@@ -327,15 +350,15 @@ export default async function resizeAndTransformImageSizes({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { height, size, width } = bufferInfo
|
const { height, size, width } = bufferInfo
|
||||||
return createResult(
|
return createResult({
|
||||||
imageResizeConfig.name,
|
name: imageResizeConfig.name,
|
||||||
imageNameWithDimensions,
|
filename: imageNameWithDimensions,
|
||||||
width,
|
filesize: size,
|
||||||
height,
|
height,
|
||||||
size,
|
mimeType: mimeInfo?.mime || mimeType,
|
||||||
mimeInfo?.mime || mimeType,
|
sizesToSave: [{ buffer: bufferData, path: imagePath }],
|
||||||
[{ buffer: bufferData, path: imagePath }],
|
width,
|
||||||
)
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -345,6 +368,6 @@ export default async function resizeAndTransformImageSizes({
|
|||||||
acc.sizesToSave.push(...result.sizesToSave)
|
acc.sizesToSave.push(...result.sizesToSave)
|
||||||
return acc
|
return acc
|
||||||
},
|
},
|
||||||
{ sizeData: {}, sizesToSave: [] },
|
{ ...defaultResult },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export default function isImage(mimeType: string): boolean {
|
export function isImage(mimeType: string): boolean {
|
||||||
return (
|
return (
|
||||||
['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/webp', 'image/avif'].indexOf(
|
['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/webp', 'image/avif'].indexOf(
|
||||||
mimeType,
|
mimeType,
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export type FileSizes = {
|
|||||||
export type FileData = {
|
export type FileData = {
|
||||||
filename: string
|
filename: string
|
||||||
filesize: number
|
filesize: number
|
||||||
|
focalX?: number
|
||||||
|
focalY?: number
|
||||||
height: number
|
height: number
|
||||||
mimeType: string
|
mimeType: string
|
||||||
sizes: FileSizes
|
sizes: FileSizes
|
||||||
@@ -117,3 +119,16 @@ export type FileToSave = {
|
|||||||
buffer: Buffer
|
buffer: Buffer
|
||||||
path: string
|
path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UploadEdits = {
|
||||||
|
crop?: {
|
||||||
|
height?: number
|
||||||
|
width?: number
|
||||||
|
x?: number
|
||||||
|
y?: number
|
||||||
|
}
|
||||||
|
focalPoint?: {
|
||||||
|
x?: number
|
||||||
|
y?: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,13 +32,12 @@ type FocalPosition = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type EditUploadProps = {
|
export type EditUploadProps = {
|
||||||
doc?: Data
|
|
||||||
fileName: string
|
fileName: string
|
||||||
fileSrc: string
|
fileSrc: string
|
||||||
imageCacheTag?: string
|
imageCacheTag?: string
|
||||||
initialCrop?: CropType
|
initialCrop?: CropType
|
||||||
initialFocalPoint?: FocalPosition
|
initialFocalPoint?: FocalPosition
|
||||||
onSave?: ({ crop, pointPosition }: { crop: CropType; pointPosition: FocalPosition }) => void
|
onSave?: ({ crop, focalPosition }: { crop: CropType; focalPosition: FocalPosition }) => void
|
||||||
showCrop?: boolean
|
showCrop?: boolean
|
||||||
showFocalPoint?: boolean
|
showFocalPoint?: boolean
|
||||||
}
|
}
|
||||||
@@ -51,11 +50,6 @@ const defaultCrop: CropType = {
|
|||||||
y: 0,
|
y: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultPointPosition: FocalPosition = {
|
|
||||||
x: 50,
|
|
||||||
y: 50,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const EditUpload: React.FC<EditUploadProps> = ({
|
export const EditUpload: React.FC<EditUploadProps> = ({
|
||||||
fileName,
|
fileName,
|
||||||
fileSrc,
|
fileSrc,
|
||||||
@@ -76,8 +70,15 @@ export const EditUpload: React.FC<EditUploadProps> = ({
|
|||||||
...initialCrop,
|
...initialCrop,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const [pointPosition, setPointPosition] = useState<FocalPosition>(() => ({
|
const defaultFocalPosition: FocalPosition = {
|
||||||
...defaultPointPosition,
|
x: 50,
|
||||||
|
y: 50,
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log({ initialFocalPoint })
|
||||||
|
|
||||||
|
const [focalPosition, setFocalPosition] = useState<FocalPosition>(() => ({
|
||||||
|
...defaultFocalPosition,
|
||||||
...initialFocalPoint,
|
...initialFocalPoint,
|
||||||
}))
|
}))
|
||||||
const [checkBounds, setCheckBounds] = useState<boolean>(false)
|
const [checkBounds, setCheckBounds] = useState<boolean>(false)
|
||||||
@@ -103,10 +104,16 @@ export const EditUpload: React.FC<EditUploadProps> = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const fineTuneFocalPoint = ({ coordinate, value }: { coordinate: 'x' | 'y'; value: string }) => {
|
const fineTuneFocalPosition = ({
|
||||||
|
coordinate,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
coordinate: 'x' | 'y'
|
||||||
|
value: string
|
||||||
|
}) => {
|
||||||
const intValue = parseInt(value)
|
const intValue = parseInt(value)
|
||||||
if (intValue >= 0 && intValue <= 100) {
|
if (intValue >= 0 && intValue <= 100) {
|
||||||
setPointPosition((prevPosition) => ({ ...prevPosition, [coordinate]: intValue }))
|
setFocalPosition((prevPosition) => ({ ...prevPosition, [coordinate]: intValue }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,13 +121,13 @@ export const EditUpload: React.FC<EditUploadProps> = ({
|
|||||||
if (typeof onSave === 'function')
|
if (typeof onSave === 'function')
|
||||||
onSave({
|
onSave({
|
||||||
crop,
|
crop,
|
||||||
pointPosition,
|
focalPosition,
|
||||||
})
|
})
|
||||||
closeModal(editDrawerSlug)
|
closeModal(editDrawerSlug)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDragEnd = React.useCallback(({ x, y }) => {
|
const onDragEnd = React.useCallback(({ x, y }) => {
|
||||||
setPointPosition({ x, y })
|
setFocalPosition({ x, y })
|
||||||
setCheckBounds(false)
|
setCheckBounds(false)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -133,7 +140,7 @@ export const EditUpload: React.FC<EditUploadProps> = ({
|
|||||||
((boundsRect.left - containerRect.left + boundsRect.width / 2) / containerRect.width) * 100
|
((boundsRect.left - containerRect.left + boundsRect.width / 2) / containerRect.width) * 100
|
||||||
const yCenter =
|
const yCenter =
|
||||||
((boundsRect.top - containerRect.top + boundsRect.height / 2) / containerRect.height) * 100
|
((boundsRect.top - containerRect.top + boundsRect.height / 2) / containerRect.height) * 100
|
||||||
setPointPosition({ x: xCenter, y: yCenter })
|
setFocalPosition({ x: xCenter, y: yCenter })
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileSrcToUse = imageCacheTag ? `${fileSrc}?${imageCacheTag}` : fileSrc
|
const fileSrcToUse = imageCacheTag ? `${fileSrc}?${imageCacheTag}` : fileSrc
|
||||||
@@ -209,7 +216,7 @@ export const EditUpload: React.FC<EditUploadProps> = ({
|
|||||||
checkBounds={showCrop ? checkBounds : false}
|
checkBounds={showCrop ? checkBounds : false}
|
||||||
className={`${baseClass}__focalPoint`}
|
className={`${baseClass}__focalPoint`}
|
||||||
containerRef={focalWrapRef}
|
containerRef={focalWrapRef}
|
||||||
initialPosition={pointPosition}
|
initialPosition={focalPosition}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
setCheckBounds={showCrop ? setCheckBounds : false}
|
setCheckBounds={showCrop ? setCheckBounds : false}
|
||||||
>
|
>
|
||||||
@@ -280,13 +287,13 @@ export const EditUpload: React.FC<EditUploadProps> = ({
|
|||||||
<div className={`${baseClass}__inputsWrap`}>
|
<div className={`${baseClass}__inputsWrap`}>
|
||||||
<Input
|
<Input
|
||||||
name="X %"
|
name="X %"
|
||||||
onChange={(value) => fineTuneFocalPoint({ coordinate: 'x', value })}
|
onChange={(value) => fineTuneFocalPosition({ coordinate: 'x', value })}
|
||||||
value={pointPosition.x.toFixed(0)}
|
value={focalPosition.x.toFixed(0)}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
name="Y %"
|
name="Y %"
|
||||||
onChange={(value) => fineTuneFocalPoint({ coordinate: 'y', value })}
|
onChange={(value) => fineTuneFocalPosition({ coordinate: 'y', value })}
|
||||||
value={pointPosition.y.toFixed(0)}
|
value={focalPosition.y.toFixed(0)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export const Upload: React.FC<UploadProps> = (props) => {
|
|||||||
}, [setValue])
|
}, [setValue])
|
||||||
|
|
||||||
const onEditsSave = React.useCallback(
|
const onEditsSave = React.useCallback(
|
||||||
({ crop, pointPosition }) => {
|
({ crop, focalPosition }) => {
|
||||||
setCrop({
|
setCrop({
|
||||||
x: crop.x || 0,
|
x: crop.x || 0,
|
||||||
y: crop.y || 0,
|
y: crop.y || 0,
|
||||||
@@ -122,10 +122,10 @@ export const Upload: React.FC<UploadProps> = (props) => {
|
|||||||
type: 'SET',
|
type: 'SET',
|
||||||
params: {
|
params: {
|
||||||
uploadEdits:
|
uploadEdits:
|
||||||
crop || pointPosition
|
crop || focalPosition
|
||||||
? {
|
? {
|
||||||
crop: crop || null,
|
crop: crop || null,
|
||||||
focalPoint: pointPosition ? pointPosition : null,
|
focalPoint: focalPosition ? focalPosition : null,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
},
|
},
|
||||||
@@ -164,10 +164,12 @@ export const Upload: React.FC<UploadProps> = (props) => {
|
|||||||
|
|
||||||
const hasImageSizes = uploadConfig?.imageSizes?.length > 0
|
const hasImageSizes = uploadConfig?.imageSizes?.length > 0
|
||||||
const hasResizeOptions = Boolean(uploadConfig?.resizeOptions)
|
const hasResizeOptions = Boolean(uploadConfig?.resizeOptions)
|
||||||
|
// Explicity check if set to true, default is undefined
|
||||||
|
const focalPointEnabled = uploadConfig?.focalPoint === true
|
||||||
|
|
||||||
const { crop: showCrop = true, focalPoint = true } = uploadConfig
|
const { crop: showCrop = true, focalPoint = true } = uploadConfig
|
||||||
|
|
||||||
const showFocalPoint = focalPoint && (hasImageSizes || hasResizeOptions)
|
const showFocalPoint = focalPoint && (hasImageSizes || hasResizeOptions || focalPointEnabled)
|
||||||
|
|
||||||
const lastSubmittedTime = submitted ? new Date().toISOString() : null
|
const lastSubmittedTime = submitted ? new Date().toISOString() : null
|
||||||
|
|
||||||
@@ -234,14 +236,13 @@ export const Upload: React.FC<UploadProps> = (props) => {
|
|||||||
{(value || doc.filename) && (
|
{(value || doc.filename) && (
|
||||||
<Drawer Header={null} slug={editDrawerSlug}>
|
<Drawer Header={null} slug={editDrawerSlug}>
|
||||||
<EditUpload
|
<EditUpload
|
||||||
doc={doc || undefined}
|
|
||||||
fileName={value?.name || doc?.filename}
|
fileName={value?.name || doc?.filename}
|
||||||
fileSrc={fileSrc || doc?.url}
|
fileSrc={fileSrc || doc?.url}
|
||||||
imageCacheTag={lastSubmittedTime}
|
imageCacheTag={lastSubmittedTime}
|
||||||
initialCrop={formQueryParams?.uploadEdits?.crop ?? {}}
|
initialCrop={formQueryParams?.uploadEdits?.crop ?? {}}
|
||||||
initialFocalPoint={{
|
initialFocalPoint={{
|
||||||
x: formQueryParams?.uploadEdits?.focalPoint.x || 0,
|
x: formQueryParams?.uploadEdits?.focalPoint.x || doc.focalX || 50,
|
||||||
y: formQueryParams?.uploadEdits?.focalPoint.y || 0,
|
y: formQueryParams?.uploadEdits?.focalPoint.y || doc.focalY || 50,
|
||||||
}}
|
}}
|
||||||
onSave={onEditsSave}
|
onSave={onEditsSave}
|
||||||
showCrop={showCrop}
|
showCrop={showCrop}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { Uploads2 } from './collections/Upload2/index.js'
|
|||||||
import {
|
import {
|
||||||
audioSlug,
|
audioSlug,
|
||||||
enlargeSlug,
|
enlargeSlug,
|
||||||
|
focalNoSizesSlug,
|
||||||
mediaSlug,
|
mediaSlug,
|
||||||
reduceSlug,
|
reduceSlug,
|
||||||
relationSlug,
|
relationSlug,
|
||||||
@@ -183,6 +184,16 @@ export default buildConfigWithDefaults({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
slug: focalNoSizesSlug,
|
||||||
|
fields: [],
|
||||||
|
upload: {
|
||||||
|
crop: false,
|
||||||
|
focalPoint: true,
|
||||||
|
mimeTypes: ['image/png', 'image/jpg', 'image/jpeg'],
|
||||||
|
staticDir: './focal-no-sizes',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
slug: mediaSlug,
|
slug: mediaSlug,
|
||||||
fields: [],
|
fields: [],
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import configPromise from './config.js'
|
|||||||
import { createStreamableFile } from './createStreamableFile.js'
|
import { createStreamableFile } from './createStreamableFile.js'
|
||||||
import {
|
import {
|
||||||
enlargeSlug,
|
enlargeSlug,
|
||||||
|
focalNoSizesSlug,
|
||||||
|
focalOnlySlug,
|
||||||
mediaSlug,
|
mediaSlug,
|
||||||
reduceSlug,
|
reduceSlug,
|
||||||
relationSlug,
|
relationSlug,
|
||||||
@@ -73,6 +75,8 @@ describe('Collections - Uploads', () => {
|
|||||||
|
|
||||||
// Check api response
|
// Check api response
|
||||||
expect(doc.mimeType).toEqual('image/png')
|
expect(doc.mimeType).toEqual('image/png')
|
||||||
|
expect(doc.focalX).toEqual(50)
|
||||||
|
expect(doc.focalY).toEqual(50)
|
||||||
expect(sizes.maintainedAspectRatio.url).toContain('/api/media/file/image')
|
expect(sizes.maintainedAspectRatio.url).toContain('/api/media/file/image')
|
||||||
expect(sizes.maintainedAspectRatio.url).toContain('.png')
|
expect(sizes.maintainedAspectRatio.url).toContain('.png')
|
||||||
expect(sizes.maintainedAspectRatio.width).toEqual(1024)
|
expect(sizes.maintainedAspectRatio.width).toEqual(1024)
|
||||||
@@ -286,7 +290,6 @@ describe('Collections - Uploads', () => {
|
|||||||
expect(await fileExists(path.join(expectedPath, mediaDoc.sizes.icon.filename))).toBe(false)
|
expect(await fileExists(path.join(expectedPath, mediaDoc.sizes.icon.filename))).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('delete', () => {
|
describe('delete', () => {
|
||||||
it('should remove related files when deleting by ID', async () => {
|
it('should remove related files when deleting by ID', async () => {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
@@ -527,6 +530,88 @@ describe('Collections - Uploads', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('focal point', () => {
|
||||||
|
let file
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
// Create image
|
||||||
|
const filePath = path.resolve(dirname, './image.png')
|
||||||
|
file = await getFileByPath(filePath)
|
||||||
|
file.name = 'focal.png'
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should be able to set focal point through local API', async () => {
|
||||||
|
const doc = await payload.create({
|
||||||
|
collection: focalOnlySlug,
|
||||||
|
data: {
|
||||||
|
focalX: 5,
|
||||||
|
focalY: 5,
|
||||||
|
},
|
||||||
|
file,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(doc.focalX).toEqual(5)
|
||||||
|
expect(doc.focalY).toEqual(5)
|
||||||
|
|
||||||
|
const updatedFocal = await payload.update({
|
||||||
|
collection: focalOnlySlug,
|
||||||
|
id: doc.id,
|
||||||
|
data: {
|
||||||
|
focalX: 10,
|
||||||
|
focalY: 10,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(updatedFocal.focalX).toEqual(10)
|
||||||
|
expect(updatedFocal.focalY).toEqual(10)
|
||||||
|
|
||||||
|
const updateWithoutFocal = await payload.update({
|
||||||
|
collection: focalOnlySlug,
|
||||||
|
id: doc.id,
|
||||||
|
data: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Expect focal point to be the same
|
||||||
|
expect(updateWithoutFocal.focalX).toEqual(10)
|
||||||
|
expect(updateWithoutFocal.focalY).toEqual(10)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should default focal point to 50, 50', async () => {
|
||||||
|
const doc = await payload.create({
|
||||||
|
collection: focalOnlySlug,
|
||||||
|
data: {
|
||||||
|
// No focal point
|
||||||
|
},
|
||||||
|
file,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(doc.focalX).toEqual(50)
|
||||||
|
expect(doc.focalY).toEqual(50)
|
||||||
|
|
||||||
|
const updateWithoutFocal = await payload.update({
|
||||||
|
collection: focalOnlySlug,
|
||||||
|
id: doc.id,
|
||||||
|
data: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(updateWithoutFocal.focalX).toEqual(50)
|
||||||
|
expect(updateWithoutFocal.focalY).toEqual(50)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should set focal point even if no sizes defined', async () => {
|
||||||
|
const doc = await payload.create({
|
||||||
|
collection: focalNoSizesSlug, // config without sizes
|
||||||
|
data: {
|
||||||
|
// No focal point
|
||||||
|
},
|
||||||
|
file,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(doc.focalX).toEqual(50)
|
||||||
|
expect(doc.focalY).toEqual(50)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('Image Manipulation', () => {
|
describe('Image Manipulation', () => {
|
||||||
it('should enlarge images if resize options `withoutEnlargement` is set to false', async () => {
|
it('should enlarge images if resize options `withoutEnlargement` is set to false', async () => {
|
||||||
const small = await getFileByPath(path.resolve(dirname, './small.png'))
|
const small = await getFileByPath(path.resolve(dirname, './small.png'))
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export interface Config {
|
|||||||
'object-fit': ObjectFit;
|
'object-fit': ObjectFit;
|
||||||
'crop-only': CropOnly;
|
'crop-only': CropOnly;
|
||||||
'focal-only': FocalOnly;
|
'focal-only': FocalOnly;
|
||||||
|
'focal-no-sizes': FocalNoSize;
|
||||||
media: Media;
|
media: Media;
|
||||||
enlarge: Enlarge;
|
enlarge: Enlarge;
|
||||||
reduce: Reduce;
|
reduce: Reduce;
|
||||||
@@ -64,6 +65,8 @@ export interface Media {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
sizes?: {
|
sizes?: {
|
||||||
maintainedAspectRatio?: {
|
maintainedAspectRatio?: {
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
@@ -204,6 +207,8 @@ export interface Version {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
@@ -230,6 +235,8 @@ export interface GifResize {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
sizes?: {
|
sizes?: {
|
||||||
small?: {
|
small?: {
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
@@ -264,6 +271,8 @@ export interface NoImageSize {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
@@ -280,6 +289,8 @@ export interface ObjectFit {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
sizes?: {
|
sizes?: {
|
||||||
fitContain?: {
|
fitContain?: {
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
@@ -330,6 +341,8 @@ export interface CropOnly {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
sizes?: {
|
sizes?: {
|
||||||
focalTest?: {
|
focalTest?: {
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
@@ -372,6 +385,8 @@ export interface FocalOnly {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
sizes?: {
|
sizes?: {
|
||||||
focalTest?: {
|
focalTest?: {
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
@@ -399,6 +414,24 @@ export interface FocalOnly {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "focal-no-sizes".
|
||||||
|
*/
|
||||||
|
export interface FocalNoSize {
|
||||||
|
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
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "enlarge".
|
* via the `definition` "enlarge".
|
||||||
@@ -414,6 +447,8 @@ export interface Enlarge {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
sizes?: {
|
sizes?: {
|
||||||
accidentalSameSize?: {
|
accidentalSameSize?: {
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
@@ -472,6 +507,8 @@ export interface Reduce {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
sizes?: {
|
sizes?: {
|
||||||
accidentalSameSize?: {
|
accidentalSameSize?: {
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
@@ -522,6 +559,8 @@ export interface MediaTrim {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
sizes?: {
|
sizes?: {
|
||||||
trimNumber?: {
|
trimNumber?: {
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
@@ -564,6 +603,8 @@ export interface UnstoredMedia {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
@@ -580,6 +621,8 @@ export interface ExternallyServedMedia {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
@@ -612,6 +655,8 @@ export interface Uploads1 {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
@@ -629,6 +674,8 @@ export interface Uploads2 {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
@@ -645,6 +692,8 @@ export interface AdminThumbnailFunction {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
@@ -661,6 +710,8 @@ export interface AdminThumbnailSize {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
sizes?: {
|
sizes?: {
|
||||||
small?: {
|
small?: {
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
@@ -695,6 +746,8 @@ export interface OptionalFile {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
@@ -711,6 +764,8 @@ export interface RequiredFile {
|
|||||||
filesize?: number | null;
|
filesize?: number | null;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
export const usersSlug = 'users'
|
export const usersSlug = 'users'
|
||||||
|
|
||||||
export const mediaSlug = 'media'
|
export const mediaSlug = 'media'
|
||||||
|
|
||||||
export const relationSlug = 'relation'
|
export const relationSlug = 'relation'
|
||||||
|
|
||||||
export const audioSlug = 'audio'
|
export const audioSlug = 'audio'
|
||||||
|
|
||||||
export const enlargeSlug = 'enlarge'
|
export const enlargeSlug = 'enlarge'
|
||||||
|
export const focalNoSizesSlug = 'focal-no-sizes'
|
||||||
|
export const focalOnlySlug = 'focal-only'
|
||||||
export const reduceSlug = 'reduce'
|
export const reduceSlug = 'reduce'
|
||||||
|
|
||||||
export const adminThumbnailFunctionSlug = 'admin-thumbnail-function'
|
export const adminThumbnailFunctionSlug = 'admin-thumbnail-function'
|
||||||
|
|
||||||
export const adminThumbnailSizeSlug = 'admin-thumbnail-size'
|
export const adminThumbnailSizeSlug = 'admin-thumbnail-size'
|
||||||
|
|
||||||
export const unstoredMediaSlug = 'unstored-media'
|
export const unstoredMediaSlug = 'unstored-media'
|
||||||
|
|
||||||
export const versionSlug = 'versions'
|
export const versionSlug = 'versions'
|
||||||
|
|||||||
Reference in New Issue
Block a user