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:
@@ -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: [],
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user