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:
Elliot DeNolf
2024-05-20 15:57:52 -04:00
committed by GitHub
parent fa7cc376d1
commit 36fda30c61
20 changed files with 390 additions and 127 deletions

View File

@@ -12,6 +12,7 @@ import { Uploads2 } from './collections/Upload2/index.js'
import {
audioSlug,
enlargeSlug,
focalNoSizesSlug,
mediaSlug,
reduceSlug,
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,
fields: [],

View File

@@ -14,6 +14,8 @@ import configPromise from './config.js'
import { createStreamableFile } from './createStreamableFile.js'
import {
enlargeSlug,
focalNoSizesSlug,
focalOnlySlug,
mediaSlug,
reduceSlug,
relationSlug,
@@ -73,6 +75,8 @@ describe('Collections - Uploads', () => {
// Check api response
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('.png')
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)
})
})
describe('delete', () => {
it('should remove related files when deleting by ID', async () => {
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', () => {
it('should enlarge images if resize options `withoutEnlargement` is set to false', async () => {
const small = await getFileByPath(path.resolve(dirname, './small.png'))

View File

@@ -15,6 +15,7 @@ export interface Config {
'object-fit': ObjectFit;
'crop-only': CropOnly;
'focal-only': FocalOnly;
'focal-no-sizes': FocalNoSize;
media: Media;
enlarge: Enlarge;
reduce: Reduce;
@@ -64,6 +65,8 @@ export interface Media {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
sizes?: {
maintainedAspectRatio?: {
url?: string | null;
@@ -204,6 +207,8 @@ export interface Version {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -230,6 +235,8 @@ export interface GifResize {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
sizes?: {
small?: {
url?: string | null;
@@ -264,6 +271,8 @@ export interface NoImageSize {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -280,6 +289,8 @@ export interface ObjectFit {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
sizes?: {
fitContain?: {
url?: string | null;
@@ -330,6 +341,8 @@ export interface CropOnly {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
sizes?: {
focalTest?: {
url?: string | null;
@@ -372,6 +385,8 @@ export interface FocalOnly {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
sizes?: {
focalTest?: {
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
* via the `definition` "enlarge".
@@ -414,6 +447,8 @@ export interface Enlarge {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
sizes?: {
accidentalSameSize?: {
url?: string | null;
@@ -472,6 +507,8 @@ export interface Reduce {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
sizes?: {
accidentalSameSize?: {
url?: string | null;
@@ -522,6 +559,8 @@ export interface MediaTrim {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
sizes?: {
trimNumber?: {
url?: string | null;
@@ -564,6 +603,8 @@ export interface UnstoredMedia {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -580,6 +621,8 @@ export interface ExternallyServedMedia {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -612,6 +655,8 @@ export interface Uploads1 {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -629,6 +674,8 @@ export interface Uploads2 {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -645,6 +692,8 @@ export interface AdminThumbnailFunction {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -661,6 +710,8 @@ export interface AdminThumbnailSize {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
sizes?: {
small?: {
url?: string | null;
@@ -695,6 +746,8 @@ export interface OptionalFile {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -711,6 +764,8 @@ export interface RequiredFile {
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema

View File

@@ -1,19 +1,12 @@
export const usersSlug = 'users'
export const mediaSlug = 'media'
export const relationSlug = 'relation'
export const audioSlug = 'audio'
export const enlargeSlug = 'enlarge'
export const focalNoSizesSlug = 'focal-no-sizes'
export const focalOnlySlug = 'focal-only'
export const reduceSlug = 'reduce'
export const adminThumbnailFunctionSlug = 'admin-thumbnail-function'
export const adminThumbnailSizeSlug = 'admin-thumbnail-size'
export const unstoredMediaSlug = 'unstored-media'
export const versionSlug = 'versions'