Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: store focal point on uploads #6436

Merged
merged 1 commit into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/next/src/fetchAPI-multipart/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ type FetchAPIFileUpload = (args: {
request: Request
}) => Promise<FetchAPIFileUploadResponse>
export const fetchAPIFileUpload: FetchAPIFileUpload = async ({ options, request }) => {
const uploadOptions = { ...DEFAULT_OPTIONS, ...options }
const uploadOptions: FetchAPIFileUploadOptions = { ...DEFAULT_OPTIONS, ...options }
if (!isEligibleRequest(request)) {
debugLog(uploadOptions, 'Request is not eligible for file upload!')
return {
Expand Down
1 change: 1 addition & 0 deletions packages/payload/src/collections/operations/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export const createOperation = async <TSlug extends keyof GeneratedTypes['collec
collection,
config,
data,
operation: 'create',
overwriteExistingFiles,
req,
throwOnMissingFile:
Expand Down
1 change: 1 addition & 0 deletions packages/payload/src/collections/operations/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export const updateOperation = async <TSlug extends keyof GeneratedTypes['collec
collection,
config,
data: bulkUpdateData,
operation: 'update',
overwriteExistingFiles,
req,
throwOnMissingFile: false,
Expand Down
1 change: 1 addition & 0 deletions packages/payload/src/collections/operations/updateByID.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export const updateByIDOperation = async <TSlug extends keyof GeneratedTypes['co
collection,
config,
data,
operation: 'update',
overwriteExistingFiles,
req,
throwOnMissingFile: false,
Expand Down
2 changes: 1 addition & 1 deletion packages/payload/src/exports/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export { traverseFields as beforeValidateTraverseFields } from '../fields/hooks/

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 {
Expand Down
13 changes: 0 additions & 13 deletions packages/payload/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,6 @@ import type { GeneratedTypes } from '../index.js'
import type { validOperators } from './constants.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 = {
context: RequestContext
/** The locale that should be used for a field when it is not translated to the requested locale */
Expand Down
2 changes: 1 addition & 1 deletion packages/payload/src/uploads/canResizeImage.ts
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion packages/payload/src/uploads/cropImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const percentToPixel = (value, 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 {
const { height, width, x, y } = cropData

Expand Down
92 changes: 79 additions & 13 deletions packages/payload/src/uploads/generateFileData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,24 @@ import sanitize from 'sanitize-filename'
import type { Collection } from '../collections/config/types.js'
import type { SanitizedConfig } from '../config/types.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 canResizeImage from './canResizeImage.js'
import cropImage from './cropImage.js'
import { canResizeImage } from './canResizeImage.js'
import { cropImage } from './cropImage.js'
import { getExternalFile } from './getExternalFile.js'
import { getFileByPath } from './getFileByPath.js'
import { getImageSize } from './getImageSize.js'
import getSafeFileName from './getSafeFilename.js'
import resizeAndTransformImageSizes from './imageResizer.js'
import isImage from './isImage.js'
import { getSafeFileName } from './getSafeFilename.js'
import { resizeAndTransformImageSizes } from './imageResizer.js'
import { isImage } from './isImage.js'

type Args<T> = {
collection: Collection
config: SanitizedConfig
data: T
operation: 'create' | 'update'
originalDoc?: T
overwriteExistingFiles?: boolean
req: PayloadRequestWithData
throwOnMissingFile?: boolean
Expand All @@ -38,6 +40,8 @@ type Result<T> = Promise<{
export const generateFileData = async <T>({
collection: { config: collectionConfig },
data,
operation,
originalDoc,
overwriteExistingFiles,
req,
throwOnMissingFile,
Expand All @@ -53,10 +57,22 @@ export const generateFileData = async <T>({

let file = req.file

const uploadEdits = req.query['uploadEdits'] || {}

const { disableLocalStorage, formatOptions, imageSizes, resizeOptions, staticDir, trimOptions } =
collectionConfig.upload
const uploadEdits = parseUploadEditsFromReqOrIncomingData({
data,
operation,
originalDoc,
req,
})

const {
disableLocalStorage,
focalPoint: focalPointEnabled = true,
formatOptions,
imageSizes,
resizeOptions,
staticDir,
trimOptions,
} = collectionConfig.upload

const staticPath = staticDir

Expand Down Expand Up @@ -228,9 +244,9 @@ export const generateFileData = async <T>({
}
}

if (Array.isArray(imageSizes) && fileSupportsResize && sharp) {
if (fileSupportsResize && (Array.isArray(imageSizes) || focalPointEnabled !== false)) {
req.payloadUploadSizes = {}
const { sizeData, sizesToSave } = await resizeAndTransformImageSizes({
const { focalPoint, sizeData, sizesToSave } = await resizeAndTransformImageSizes({
config: collectionConfig,
dimensions: !cropData
? dimensions
Expand All @@ -245,13 +261,16 @@ export const generateFileData = async <T>({
savedFilename: fsSafeName || file.name,
sharp,
staticPath,
uploadEdits,
})

fileData.sizes = sizeData
fileData.focalX = focalPoint?.x
fileData.focalY = focalPoint?.y
filesToSave.push(...sizesToSave)
}
} catch (err) {
console.error(err)
req.payload.logger.error(err)
throw new FileUploadError(req.t)
}

Expand All @@ -265,3 +284,50 @@ export const generateFileData = async <T>({
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
}
19 changes: 19 additions & 0 deletions packages/payload/src/uploads/getBaseFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,25 @@ export const getBaseUploadFields = ({ collection, config }: Options): Field[] =>
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) {
mimeType.validate = mimeTypeValidator(uploadOptions.mimeTypes)
}
Expand Down
4 changes: 1 addition & 3 deletions packages/payload/src/uploads/getSafeFilename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type Args = {
staticPath: string
}

async function getSafeFileName({
export async function getSafeFileName({
collectionSlug,
desiredFilename,
req,
Expand All @@ -51,5 +51,3 @@ async function getSafeFileName({
}
return modifiedFilename
}

export default getSafeFileName
Loading