fix(payload, ui): unable to save animated file types with undefined image sizes (#6757)
## Description V2 PR [here](https://github.com/payloadcms/payload/pull/6733) Additionally fixes issue with image thumbnails not updating properly until page refresh. Image thumbnails properly update on document save now. - [x] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## Checklist: - [x] I have added tests that prove my fix is effective or that my feature works - [x] Existing test suite passes locally with my changes
This commit is contained in:
@@ -8,11 +8,11 @@ export async function cropImage({ cropData, dimensions, file, sharp }) {
|
|||||||
try {
|
try {
|
||||||
const { height, width, x, y } = cropData
|
const { height, width, x, y } = cropData
|
||||||
|
|
||||||
const fileIsAnimated = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
|
const fileIsAnimatedType = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
|
||||||
|
|
||||||
const sharpOptions: SharpOptions = {}
|
const sharpOptions: SharpOptions = {}
|
||||||
|
|
||||||
if (fileIsAnimated) sharpOptions.animated = true
|
if (fileIsAnimatedType) sharpOptions.animated = true
|
||||||
|
|
||||||
const formattedCropData = {
|
const formattedCropData = {
|
||||||
height: percentToPixel(height, dimensions.height),
|
height: percentToPixel(height, dimensions.height),
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export const generateFileData = async <T>({
|
|||||||
let newData = data
|
let newData = data
|
||||||
const filesToSave: FileToSave[] = []
|
const filesToSave: FileToSave[] = []
|
||||||
const fileData: Partial<FileData> = {}
|
const fileData: Partial<FileData> = {}
|
||||||
const fileIsAnimated = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
|
const fileIsAnimatedType = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
|
||||||
const cropData =
|
const cropData =
|
||||||
typeof uploadEdits === 'object' && 'crop' in uploadEdits ? uploadEdits.crop : undefined
|
typeof uploadEdits === 'object' && 'crop' in uploadEdits ? uploadEdits.crop : undefined
|
||||||
|
|
||||||
@@ -131,9 +131,9 @@ export const generateFileData = async <T>({
|
|||||||
|
|
||||||
const sharpOptions: SharpOptions = {}
|
const sharpOptions: SharpOptions = {}
|
||||||
|
|
||||||
if (fileIsAnimated) sharpOptions.animated = true
|
if (fileIsAnimatedType) sharpOptions.animated = true
|
||||||
|
|
||||||
if (sharp && (fileIsAnimated || fileHasAdjustments)) {
|
if (sharp && (fileIsAnimatedType || fileHasAdjustments)) {
|
||||||
if (file.tempFilePath) {
|
if (file.tempFilePath) {
|
||||||
sharpFile = sharp(file.tempFilePath, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
|
sharpFile = sharp(file.tempFilePath, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
|
||||||
} else {
|
} else {
|
||||||
@@ -217,7 +217,7 @@ export const generateFileData = async <T>({
|
|||||||
}
|
}
|
||||||
fileData.width = info.width
|
fileData.width = info.width
|
||||||
fileData.height = info.height
|
fileData.height = info.height
|
||||||
if (fileIsAnimated) {
|
if (fileIsAnimatedType) {
|
||||||
const metadata = await sharpFile.metadata()
|
const metadata = await sharpFile.metadata()
|
||||||
fileData.height = metadata.pages ? info.height / metadata.pages : info.height
|
fileData.height = metadata.pages ? info.height / metadata.pages : info.height
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ export const getBaseUploadFields = ({ collection, config }: Options): Field[] =>
|
|||||||
hooks: {
|
hooks: {
|
||||||
afterRead: [
|
afterRead: [
|
||||||
({ data, value }) => {
|
({ data, value }) => {
|
||||||
if (value) return value
|
if (value && size.height && size.width) return value
|
||||||
|
|
||||||
const sizeFilename = data?.sizes?.[size.name]?.filename
|
const sizeFilename = data?.sizes?.[size.name]?.filename
|
||||||
|
|
||||||
|
|||||||
@@ -255,10 +255,10 @@ export async function resizeAndTransformImageSizes({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine if the file is animated
|
// Determine if the file is animated
|
||||||
const fileIsAnimated = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
|
const fileIsAnimatedType = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
|
||||||
const sharpOptions: SharpOptions = {}
|
const sharpOptions: SharpOptions = {}
|
||||||
|
|
||||||
if (fileIsAnimated) sharpOptions.animated = true
|
if (fileIsAnimatedType) sharpOptions.animated = true
|
||||||
|
|
||||||
const sharpBase: Sharp | undefined = sharp(file.tempFilePath || file.data, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
|
const sharpBase: Sharp | undefined = sharp(file.tempFilePath || file.data, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
|
||||||
|
|
||||||
@@ -279,16 +279,26 @@ export async function resizeAndTransformImageSizes({
|
|||||||
const metadata = await sharpBase.metadata()
|
const metadata = await sharpBase.metadata()
|
||||||
|
|
||||||
if (incomingFocalPoint && applyPayloadAdjustments(imageResizeConfig, dimensions)) {
|
if (incomingFocalPoint && applyPayloadAdjustments(imageResizeConfig, dimensions)) {
|
||||||
const { height: resizeHeight, width: resizeWidth } = imageResizeConfig
|
let { height: resizeHeight, width: resizeWidth } = imageResizeConfig
|
||||||
const resizeAspectRatio = resizeWidth / resizeHeight
|
|
||||||
const originalAspectRatio = dimensions.width / dimensions.height
|
const originalAspectRatio = dimensions.width / dimensions.height
|
||||||
const prioritizeHeight = resizeAspectRatio < originalAspectRatio
|
|
||||||
|
// Calculate resizeWidth based on original aspect ratio if it's undefined
|
||||||
|
if (resizeHeight && !resizeWidth) {
|
||||||
|
resizeWidth = Math.round(resizeHeight * originalAspectRatio)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate resizeHeight based on original aspect ratio if it's undefined
|
||||||
|
if (resizeWidth && !resizeHeight) {
|
||||||
|
resizeHeight = Math.round(resizeWidth / originalAspectRatio)
|
||||||
|
}
|
||||||
|
|
||||||
// Scale the image up or down to fit the resize dimensions
|
// Scale the image up or down to fit the resize dimensions
|
||||||
const scaledImage = imageToResize.resize({
|
const scaledImage = imageToResize.resize({
|
||||||
height: prioritizeHeight ? resizeHeight : null,
|
height: resizeHeight,
|
||||||
width: prioritizeHeight ? null : resizeWidth,
|
width: resizeWidth,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { info: scaledImageInfo } = await scaledImage.toBuffer({ resolveWithObject: true })
|
const { info: scaledImageInfo } = await scaledImage.toBuffer({ resolveWithObject: true })
|
||||||
|
|
||||||
const safeResizeWidth = resizeWidth ?? scaledImageInfo.width
|
const safeResizeWidth = resizeWidth ?? scaledImageInfo.width
|
||||||
@@ -298,10 +308,16 @@ export async function resizeAndTransformImageSizes({
|
|||||||
)
|
)
|
||||||
const safeOffsetX = Math.min(Math.max(0, leftFocalEdge), maxOffsetX)
|
const safeOffsetX = Math.min(Math.max(0, leftFocalEdge), maxOffsetX)
|
||||||
|
|
||||||
const safeResizeHeight = resizeHeight ?? scaledImageInfo.height
|
const isAnimated = fileIsAnimatedType && metadata.pages
|
||||||
|
|
||||||
const maxOffsetY = fileIsAnimated
|
let safeResizeHeight = resizeHeight ?? scaledImageInfo.height
|
||||||
? resizeHeight - safeResizeHeight
|
|
||||||
|
if (isAnimated && resizeHeight === undefined) {
|
||||||
|
safeResizeHeight = scaledImageInfo.height / metadata.pages
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxOffsetY = isAnimated
|
||||||
|
? safeResizeHeight - (resizeHeight ?? safeResizeHeight)
|
||||||
: scaledImageInfo.height - safeResizeHeight
|
: scaledImageInfo.height - safeResizeHeight
|
||||||
|
|
||||||
const topFocalEdge = Math.round(
|
const topFocalEdge = Math.round(
|
||||||
@@ -310,7 +326,7 @@ export async function resizeAndTransformImageSizes({
|
|||||||
const safeOffsetY = Math.min(Math.max(0, topFocalEdge), maxOffsetY)
|
const safeOffsetY = Math.min(Math.max(0, topFocalEdge), maxOffsetY)
|
||||||
|
|
||||||
// extract the focal area from the scaled image
|
// extract the focal area from the scaled image
|
||||||
resized = (fileIsAnimated ? imageToResize : scaledImage).extract({
|
resized = (fileIsAnimatedType ? imageToResize : scaledImage).extract({
|
||||||
height: safeResizeHeight,
|
height: safeResizeHeight,
|
||||||
left: safeOffsetX,
|
left: safeOffsetX,
|
||||||
top: safeOffsetY,
|
top: safeOffsetY,
|
||||||
@@ -364,7 +380,7 @@ export async function resizeAndTransformImageSizes({
|
|||||||
name: imageResizeConfig.name,
|
name: imageResizeConfig.name,
|
||||||
filename: imageNameWithDimensions,
|
filename: imageNameWithDimensions,
|
||||||
filesize: size,
|
filesize: size,
|
||||||
height: fileIsAnimated && metadata.pages ? height / metadata.pages : height,
|
height: fileIsAnimatedType && metadata.pages ? height / metadata.pages : height,
|
||||||
mimeType: mimeInfo?.mime || mimeType,
|
mimeType: mimeInfo?.mime || mimeType,
|
||||||
sizesToSave: [{ buffer: bufferData, path: imagePath }],
|
sizesToSave: [{ buffer: bufferData, path: imagePath }],
|
||||||
width,
|
width,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const ThumbnailContext = React.createContext({
|
|||||||
export const useThumbnailContext = () => React.useContext(ThumbnailContext)
|
export const useThumbnailContext = () => React.useContext(ThumbnailContext)
|
||||||
|
|
||||||
export const Thumbnail: React.FC<ThumbnailProps> = (props) => {
|
export const Thumbnail: React.FC<ThumbnailProps> = (props) => {
|
||||||
const { className = '', doc: { filename } = {}, fileSrc, size } = props
|
const { className = '', doc: { filename } = {}, fileSrc, imageCacheTag, size } = props
|
||||||
const [fileExists, setFileExists] = React.useState(undefined)
|
const [fileExists, setFileExists] = React.useState(undefined)
|
||||||
|
|
||||||
const classNames = [baseClass, `${baseClass}--size-${size || 'medium'}`, className].join(' ')
|
const classNames = [baseClass, `${baseClass}--size-${size || 'medium'}`, className].join(' ')
|
||||||
@@ -54,7 +54,12 @@ export const Thumbnail: React.FC<ThumbnailProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<div className={classNames}>
|
<div className={classNames}>
|
||||||
{fileExists === undefined && <ShimmerEffect height="100%" />}
|
{fileExists === undefined && <ShimmerEffect height="100%" />}
|
||||||
{fileExists && <img alt={filename as string} src={fileSrc} />}
|
{fileExists && (
|
||||||
|
<img
|
||||||
|
alt={filename as string}
|
||||||
|
src={`${fileSrc}${imageCacheTag ? `?${imageCacheTag}` : ''}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{fileExists === false && <File />}
|
{fileExists === false && <File />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -30,17 +30,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-details {
|
|
||||||
img {
|
|
||||||
position: relative;
|
|
||||||
min-width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
transform: scale(var(--file-details-thumbnail--zoom));
|
|
||||||
top: var(--file-details-thumbnail--top-offset);
|
|
||||||
left: var(--file-details-thumbnail--left-offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__remove {
|
&__remove {
|
||||||
margin: calc($baseline * 1.5) $baseline $baseline 0;
|
margin: calc($baseline * 1.5) $baseline $baseline 0;
|
||||||
place-self: flex-start;
|
place-self: flex-start;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import React, { useCallback, useEffect, useState } from 'react'
|
|||||||
|
|
||||||
import { fieldBaseClass } from '../../fields/shared/index.js'
|
import { fieldBaseClass } from '../../fields/shared/index.js'
|
||||||
import { FieldError } from '../../forms/FieldError/index.js'
|
import { FieldError } from '../../forms/FieldError/index.js'
|
||||||
import { useForm, useFormSubmitted } from '../../forms/Form/context.js'
|
import { useForm } from '../../forms/Form/context.js'
|
||||||
import { useField } from '../../forms/useField/index.js'
|
import { useField } from '../../forms/useField/index.js'
|
||||||
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
|
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
|
||||||
import { useFormQueryParams } from '../../providers/FormQueryParams/index.js'
|
import { useFormQueryParams } from '../../providers/FormQueryParams/index.js'
|
||||||
@@ -62,7 +62,6 @@ export type UploadProps = {
|
|||||||
export const Upload: React.FC<UploadProps> = (props) => {
|
export const Upload: React.FC<UploadProps> = (props) => {
|
||||||
const { collectionSlug, initialState, onChange, updatedAt, uploadConfig } = props
|
const { collectionSlug, initialState, onChange, updatedAt, uploadConfig } = props
|
||||||
|
|
||||||
const submitted = useFormSubmitted()
|
|
||||||
const [replacingFile, setReplacingFile] = useState(false)
|
const [replacingFile, setReplacingFile] = useState(false)
|
||||||
const [fileSrc, setFileSrc] = useState<null | string>(null)
|
const [fileSrc, setFileSrc] = useState<null | string>(null)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -106,17 +105,7 @@ export const Upload: React.FC<UploadProps> = (props) => {
|
|||||||
x: crop.x || 0,
|
x: crop.x || 0,
|
||||||
y: crop.y || 0,
|
y: crop.y || 0,
|
||||||
})
|
})
|
||||||
const zoomScale = 100 / Math.min(crop.width, crop.height)
|
|
||||||
|
|
||||||
document.documentElement.style.setProperty('--file-details-thumbnail--zoom', `${zoomScale}`)
|
|
||||||
document.documentElement.style.setProperty(
|
|
||||||
'--file-details-thumbnail--top-offset',
|
|
||||||
`${zoomScale * (50 - crop.height / 2 - crop.y)}%`,
|
|
||||||
)
|
|
||||||
document.documentElement.style.setProperty(
|
|
||||||
'--file-details-thumbnail--left-offset',
|
|
||||||
`${zoomScale * (50 - crop.width / 2 - crop.x)}%`,
|
|
||||||
)
|
|
||||||
setModified(true)
|
setModified(true)
|
||||||
dispatchFormQueryParams({
|
dispatchFormQueryParams({
|
||||||
type: 'SET',
|
type: 'SET',
|
||||||
@@ -171,8 +160,6 @@ export const Upload: React.FC<UploadProps> = (props) => {
|
|||||||
|
|
||||||
const showFocalPoint = focalPoint && (hasImageSizes || hasResizeOptions || focalPointEnabled)
|
const showFocalPoint = focalPoint && (hasImageSizes || hasResizeOptions || focalPointEnabled)
|
||||||
|
|
||||||
const lastSubmittedTime = submitted ? new Date().toISOString() : null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[fieldBaseClass, baseClass].filter(Boolean).join(' ')}>
|
<div className={[fieldBaseClass, baseClass].filter(Boolean).join(' ')}>
|
||||||
<FieldError message={errorMessage} showError={showError} />
|
<FieldError message={errorMessage} showError={showError} />
|
||||||
@@ -183,7 +170,7 @@ export const Upload: React.FC<UploadProps> = (props) => {
|
|||||||
doc={doc}
|
doc={doc}
|
||||||
handleRemove={canRemoveUpload ? handleFileRemoval : undefined}
|
handleRemove={canRemoveUpload ? handleFileRemoval : undefined}
|
||||||
hasImageSizes={hasImageSizes}
|
hasImageSizes={hasImageSizes}
|
||||||
imageCacheTag={lastSubmittedTime}
|
imageCacheTag={doc.updatedAt}
|
||||||
uploadConfig={uploadConfig}
|
uploadConfig={uploadConfig}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -238,7 +225,7 @@ export const Upload: React.FC<UploadProps> = (props) => {
|
|||||||
<EditUpload
|
<EditUpload
|
||||||
fileName={value?.name || doc?.filename}
|
fileName={value?.name || doc?.filename}
|
||||||
fileSrc={fileSrc || doc?.url}
|
fileSrc={fileSrc || doc?.url}
|
||||||
imageCacheTag={lastSubmittedTime}
|
imageCacheTag={doc.updatedAt}
|
||||||
initialCrop={formQueryParams?.uploadEdits?.crop ?? {}}
|
initialCrop={formQueryParams?.uploadEdits?.crop ?? {}}
|
||||||
initialFocalPoint={{
|
initialFocalPoint={{
|
||||||
x: formQueryParams?.uploadEdits?.focalPoint.x || doc.focalX || 50,
|
x: formQueryParams?.uploadEdits?.focalPoint.x || doc.focalX || 50,
|
||||||
@@ -257,7 +244,7 @@ export const Upload: React.FC<UploadProps> = (props) => {
|
|||||||
slug={sizePreviewSlug}
|
slug={sizePreviewSlug}
|
||||||
title={t('upload:sizesFor', { label: doc?.filename })}
|
title={t('upload:sizesFor', { label: doc?.filename })}
|
||||||
>
|
>
|
||||||
<PreviewSizes doc={doc} uploadConfig={uploadConfig} />
|
<PreviewSizes doc={doc} imageCacheTag={doc.updatedAt} uploadConfig={uploadConfig} />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ describe('Upload', () => {
|
|||||||
await uploadImage()
|
await uploadImage()
|
||||||
await expect(page.locator('.file-field .file-details img')).toHaveAttribute(
|
await expect(page.locator('.file-field .file-details img')).toHaveAttribute(
|
||||||
'src',
|
'src',
|
||||||
'/api/uploads/file/payload-1.jpg',
|
/\/api\/uploads\/file\/payload-1\.jpg(\?.*)?$/,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
BIN
test/uploads/animated.webp
Normal file
BIN
test/uploads/animated.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
@@ -10,6 +10,7 @@ import { AdminThumbnailSize } from './collections/AdminThumbnailSize/index.js'
|
|||||||
import { Uploads1 } from './collections/Upload1/index.js'
|
import { Uploads1 } from './collections/Upload1/index.js'
|
||||||
import { Uploads2 } from './collections/Upload2/index.js'
|
import { Uploads2 } from './collections/Upload2/index.js'
|
||||||
import {
|
import {
|
||||||
|
animatedTypeMedia,
|
||||||
audioSlug,
|
audioSlug,
|
||||||
enlargeSlug,
|
enlargeSlug,
|
||||||
focalNoSizesSlug,
|
focalNoSizesSlug,
|
||||||
@@ -290,6 +291,42 @@ export default buildConfigWithDefaults({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
slug: animatedTypeMedia,
|
||||||
|
fields: [],
|
||||||
|
upload: {
|
||||||
|
staticDir: path.resolve(dirname, './media'),
|
||||||
|
resizeOptions: {
|
||||||
|
position: 'center',
|
||||||
|
width: 200,
|
||||||
|
height: 200,
|
||||||
|
},
|
||||||
|
imageSizes: [
|
||||||
|
{
|
||||||
|
name: 'squareSmall',
|
||||||
|
width: 480,
|
||||||
|
height: 480,
|
||||||
|
position: 'centre',
|
||||||
|
withoutEnlargement: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'undefinedHeight',
|
||||||
|
width: 300,
|
||||||
|
height: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'undefinedWidth',
|
||||||
|
width: undefined,
|
||||||
|
height: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'undefinedAll',
|
||||||
|
width: undefined,
|
||||||
|
height: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
slug: enlargeSlug,
|
slug: enlargeSlug,
|
||||||
fields: [],
|
fields: [],
|
||||||
@@ -501,6 +538,43 @@ export default buildConfigWithDefaults({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Create animated type images
|
||||||
|
const animatedImageFilePath = path.resolve(dirname, './animated.webp')
|
||||||
|
const animatedImageFile = await getFileByPath(animatedImageFilePath)
|
||||||
|
|
||||||
|
await payload.create({
|
||||||
|
collection: animatedTypeMedia,
|
||||||
|
data: {},
|
||||||
|
file: animatedImageFile,
|
||||||
|
})
|
||||||
|
|
||||||
|
await payload.create({
|
||||||
|
collection: versionSlug,
|
||||||
|
data: {
|
||||||
|
_status: 'published',
|
||||||
|
title: 'upload',
|
||||||
|
},
|
||||||
|
file: animatedImageFile,
|
||||||
|
})
|
||||||
|
|
||||||
|
const nonAnimatedImageFilePath = path.resolve(dirname, './non-animated.webp')
|
||||||
|
const nonAnimatedImageFile = await getFileByPath(nonAnimatedImageFilePath)
|
||||||
|
|
||||||
|
await payload.create({
|
||||||
|
collection: animatedTypeMedia,
|
||||||
|
data: {},
|
||||||
|
file: nonAnimatedImageFile,
|
||||||
|
})
|
||||||
|
|
||||||
|
await payload.create({
|
||||||
|
collection: versionSlug,
|
||||||
|
data: {
|
||||||
|
_status: 'published',
|
||||||
|
title: 'upload',
|
||||||
|
},
|
||||||
|
file: nonAnimatedImageFile,
|
||||||
|
})
|
||||||
|
|
||||||
// Create audio
|
// Create audio
|
||||||
const audioFilePath = path.resolve(dirname, './audio.mp3')
|
const audioFilePath = path.resolve(dirname, './audio.mp3')
|
||||||
const audioFile = await getFileByPath(audioFilePath)
|
const audioFile = await getFileByPath(audioFilePath)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
|||||||
import {
|
import {
|
||||||
adminThumbnailFunctionSlug,
|
adminThumbnailFunctionSlug,
|
||||||
adminThumbnailSizeSlug,
|
adminThumbnailSizeSlug,
|
||||||
|
animatedTypeMedia,
|
||||||
audioSlug,
|
audioSlug,
|
||||||
mediaSlug,
|
mediaSlug,
|
||||||
relationSlug,
|
relationSlug,
|
||||||
@@ -35,6 +36,7 @@ let payload: PayloadTestSDK<Config>
|
|||||||
let client: RESTClient
|
let client: RESTClient
|
||||||
let serverURL: string
|
let serverURL: string
|
||||||
let mediaURL: AdminUrlUtil
|
let mediaURL: AdminUrlUtil
|
||||||
|
let animatedTypeMediaURL: AdminUrlUtil
|
||||||
let audioURL: AdminUrlUtil
|
let audioURL: AdminUrlUtil
|
||||||
let relationURL: AdminUrlUtil
|
let relationURL: AdminUrlUtil
|
||||||
let adminThumbnailSizeURL: AdminUrlUtil
|
let adminThumbnailSizeURL: AdminUrlUtil
|
||||||
@@ -52,6 +54,7 @@ describe('uploads', () => {
|
|||||||
await client.login()
|
await client.login()
|
||||||
|
|
||||||
mediaURL = new AdminUrlUtil(serverURL, mediaSlug)
|
mediaURL = new AdminUrlUtil(serverURL, mediaSlug)
|
||||||
|
animatedTypeMediaURL = new AdminUrlUtil(serverURL, animatedTypeMedia)
|
||||||
audioURL = new AdminUrlUtil(serverURL, audioSlug)
|
audioURL = new AdminUrlUtil(serverURL, audioSlug)
|
||||||
relationURL = new AdminUrlUtil(serverURL, relationSlug)
|
relationURL = new AdminUrlUtil(serverURL, relationSlug)
|
||||||
adminThumbnailSizeURL = new AdminUrlUtil(serverURL, adminThumbnailSizeSlug)
|
adminThumbnailSizeURL = new AdminUrlUtil(serverURL, adminThumbnailSizeSlug)
|
||||||
@@ -120,6 +123,26 @@ describe('uploads', () => {
|
|||||||
await saveDocAndAssert(page)
|
await saveDocAndAssert(page)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should create animated file upload', async () => {
|
||||||
|
await page.goto(animatedTypeMediaURL.create)
|
||||||
|
|
||||||
|
await page.setInputFiles('input[type="file"]', path.resolve(dirname, './animated.webp'))
|
||||||
|
const animatedFilename = page.locator('.file-field__filename')
|
||||||
|
|
||||||
|
await expect(animatedFilename).toHaveValue('animated.webp')
|
||||||
|
|
||||||
|
await saveDocAndAssert(page)
|
||||||
|
|
||||||
|
await page.goto(animatedTypeMediaURL.create)
|
||||||
|
|
||||||
|
await page.setInputFiles('input[type="file"]', path.resolve(dirname, './non-animated.webp'))
|
||||||
|
const nonAnimatedFileName = page.locator('.file-field__filename')
|
||||||
|
|
||||||
|
await expect(nonAnimatedFileName).toHaveValue('non-animated.webp')
|
||||||
|
|
||||||
|
await saveDocAndAssert(page)
|
||||||
|
})
|
||||||
|
|
||||||
test('should show resized images', async () => {
|
test('should show resized images', async () => {
|
||||||
await page.goto(mediaURL.edit(pngDoc.id))
|
await page.goto(mediaURL.edit(pngDoc.id))
|
||||||
|
|
||||||
@@ -209,7 +232,7 @@ describe('uploads', () => {
|
|||||||
// choose from existing
|
// choose from existing
|
||||||
await openDocDrawer(page, '.list-drawer__toggler')
|
await openDocDrawer(page, '.list-drawer__toggler')
|
||||||
|
|
||||||
await expect(page.locator('.cell-title')).toContainText('draft')
|
await expect(page.locator('.row-3 .cell-title')).toContainText('draft')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should restrict mimetype based on filterOptions', async () => {
|
test('should restrict mimetype based on filterOptions', async () => {
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ export const getMimeType = (
|
|||||||
case 'svg':
|
case 'svg':
|
||||||
type = 'image/svg+xml'
|
type = 'image/svg+xml'
|
||||||
break
|
break
|
||||||
|
case 'webp':
|
||||||
|
type = 'image/webp'
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
type = 'image/png'
|
type = 'image/png'
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
test/uploads/non-animated.webp
Normal file
BIN
test/uploads/non-animated.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
@@ -190,6 +190,53 @@ export interface Media {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
export interface AnimatedTypeMedia {
|
||||||
|
id: string
|
||||||
|
updatedAt: string
|
||||||
|
createdAt: string
|
||||||
|
url?: string
|
||||||
|
filename?: string
|
||||||
|
mimeType?: string
|
||||||
|
filesize?: number
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
focalX?: number
|
||||||
|
focalY?: number
|
||||||
|
sizes?: {
|
||||||
|
squareSmall?: {
|
||||||
|
url?: string
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
mimeType?: string
|
||||||
|
filesize?: number
|
||||||
|
filename?: string
|
||||||
|
}
|
||||||
|
undefinedHeight?: {
|
||||||
|
url?: string
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
mimeType?: string
|
||||||
|
filesize?: number
|
||||||
|
filename?: string
|
||||||
|
}
|
||||||
|
undefinedWidth?: {
|
||||||
|
url?: string
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
mimeType?: string
|
||||||
|
filesize?: number
|
||||||
|
filename?: string
|
||||||
|
}
|
||||||
|
undefinedAll?: {
|
||||||
|
url?: string
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
mimeType?: string
|
||||||
|
filesize?: number
|
||||||
|
filename?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "versions".
|
* via the `definition` "versions".
|
||||||
@@ -821,6 +868,6 @@ export interface PayloadMigration {
|
|||||||
|
|
||||||
|
|
||||||
declare module 'payload' {
|
declare module 'payload' {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export interface GeneratedTypes extends Config {}
|
export interface GeneratedTypes extends Config {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ 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'
|
||||||
|
export const animatedTypeMedia = 'animated-type-media'
|
||||||
|
|||||||
Reference in New Issue
Block a user