From 0841d5a35ee00650c703231a08fc9a361861ba67 Mon Sep 17 00:00:00 2001 From: Patrik Date: Fri, 19 Jul 2024 15:01:06 -0400 Subject: [PATCH] fix: uploads from drawer and focal point positioning (#7244) ## Description V3 PR [here](https://github.com/payloadcms/payload/pull/7117) - [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 --- .../components/elements/EditUpload/index.tsx | 110 ++++---- .../views/collections/Edit/Upload/index.tsx | 22 +- .../views/collections/Edit/types.ts | 12 - packages/payload/src/translations/ar.json | 2 +- packages/payload/src/translations/az.json | 2 +- packages/payload/src/translations/bg.json | 2 +- packages/payload/src/translations/cs.json | 2 +- packages/payload/src/translations/de.json | 2 +- packages/payload/src/translations/en.json | 2 +- packages/payload/src/translations/es.json | 2 +- packages/payload/src/translations/fa.json | 2 +- packages/payload/src/translations/fr.json | 2 +- packages/payload/src/translations/hr.json | 2 +- packages/payload/src/translations/hu.json | 2 +- packages/payload/src/translations/it.json | 2 +- packages/payload/src/translations/ja.json | 2 +- packages/payload/src/translations/ko.json | 2 +- packages/payload/src/translations/my.json | 2 +- packages/payload/src/translations/nb.json | 2 +- packages/payload/src/translations/nl.json | 2 +- packages/payload/src/translations/pl.json | 2 +- packages/payload/src/translations/pt.json | 2 +- packages/payload/src/translations/ro.json | 2 +- .../payload/src/translations/rs-latin.json | 2 +- packages/payload/src/translations/rs.json | 2 +- packages/payload/src/translations/ru.json | 2 +- packages/payload/src/translations/sv.json | 2 +- packages/payload/src/translations/th.json | 2 +- packages/payload/src/translations/tr.json | 2 +- .../src/translations/translation-schema.json | 2 +- packages/payload/src/translations/ua.json | 2 +- packages/payload/src/translations/vi.json | 2 +- packages/payload/src/translations/zh-tw.json | 2 +- packages/payload/src/translations/zh.json | 2 +- packages/payload/src/uploads/cropImage.ts | 33 ++- .../payload/src/uploads/generateFileData.ts | 10 +- packages/payload/src/uploads/imageResizer.ts | 250 +++++++++++------- packages/payload/src/uploads/types.ts | 29 +- test/fields/e2e.spec.ts | 44 ++- test/uploads/e2e.spec.ts | 59 +++++ test/uploads/horizontal-squares.jpg | Bin 0 -> 2601 bytes 41 files changed, 424 insertions(+), 207 deletions(-) create mode 100644 test/uploads/horizontal-squares.jpg diff --git a/packages/payload/src/admin/components/elements/EditUpload/index.tsx b/packages/payload/src/admin/components/elements/EditUpload/index.tsx index 5dbd46bf19..4aa2bf47f4 100644 --- a/packages/payload/src/admin/components/elements/EditUpload/index.tsx +++ b/packages/payload/src/admin/components/elements/EditUpload/index.tsx @@ -1,13 +1,12 @@ import { useModal } from '@faceless-ui/modal' import React, { forwardRef, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import ReactCrop, { type Crop as CropType } from 'react-image-crop' +import ReactCrop from 'react-image-crop' import 'react-image-crop/dist/ReactCrop.css' -import type { Data } from '../../forms/Form/types' +import type { UploadEdits } from '../../../../uploads/types' import Plus from '../../icons/Plus' -import { useUploadEdits } from '../../utilities/UploadEdits' import { editDrawerSlug } from '../../views/collections/Edit/Upload' import Button from '../Button' import './index.scss' @@ -42,58 +41,80 @@ type FocalPosition = { y: number } -export const EditUpload: React.FC<{ - doc?: Data +export type EditUploadProps = { fileName: string fileSrc: string imageCacheTag?: string + initialCrop?: UploadEdits['crop'] + initialFocalPoint?: FocalPosition + onSave?: (uploadEdits: UploadEdits) => void showCrop?: boolean showFocalPoint?: boolean -}> = ({ doc, fileName, fileSrc, imageCacheTag, showCrop, showFocalPoint }) => { +} + +const defaultCrop: UploadEdits['crop'] = { + height: 100, + unit: '%', + width: 100, + x: 0, + y: 0, +} + +export const EditUpload: React.FC = ({ + fileName, + fileSrc, + imageCacheTag, + initialCrop, + initialFocalPoint, + onSave, + showCrop, + showFocalPoint, +}) => { const { closeModal } = useModal() const { t } = useTranslation(['general', 'upload']) - const { updateUploadEdits, uploadEdits } = useUploadEdits() - const [focalPosition, setFocalPosition] = useState({ - x: uploadEdits?.focalPoint?.x || doc.focalX || 50, - y: uploadEdits?.focalPoint?.y || doc.focalY || 50, - }) + const [crop, setCrop] = useState(() => ({ + ...defaultCrop, + ...(initialCrop || {}), + })) + + const defaultFocalPosition: FocalPosition = { + x: 50, + y: 50, + } + + const [focalPosition, setFocalPosition] = useState(() => ({ + ...defaultFocalPosition, + ...initialFocalPoint, + })) const [checkBounds, setCheckBounds] = useState(false) - const [originalHeight, setOriginalHeight] = useState(0) - const [originalWidth, setOriginalWidth] = useState(0) + const [uncroppedPixelHeight, setUncroppedPixelHeight] = useState(0) + const [uncroppedPixelWidth, setUncroppedPixelWidth] = useState(0) const focalWrapRef = useRef() const imageRef = useRef() const cropRef = useRef() - const heightRef = useRef(null) - const widthRef = useRef(null) - - const [crop, setCrop] = useState({ - height: 100, - heightPixels: 0, - unit: '%', - width: 100, - widthPixels: 0, - x: 0, - y: 0, - }) + const heightInputRef = useRef(null) + const widthInputRef = useRef(null) const [imageLoaded, setImageLoaded] = useState(false) const onImageLoad = (e) => { - setOriginalHeight(e.currentTarget.naturalHeight) - setOriginalWidth(e.currentTarget.naturalWidth) + // set the default image height/width on load + setUncroppedPixelHeight(e.currentTarget.naturalHeight) + setUncroppedPixelWidth(e.currentTarget.naturalWidth) setImageLoaded(true) } const fineTuneCrop = ({ dimension, value }: { dimension: 'height' | 'width'; value: string }) => { const intValue = parseInt(value) - if (dimension === 'width' && intValue >= originalWidth) return null - if (dimension === 'height' && intValue >= originalHeight) return null + if (dimension === 'width' && intValue >= uncroppedPixelWidth) return null + if (dimension === 'height' && intValue >= uncroppedPixelHeight) return null - const percentage = 100 * (intValue / (dimension === 'width' ? originalWidth : originalHeight)) + const percentage = + 100 * (intValue / (dimension === 'width' ? uncroppedPixelWidth : uncroppedPixelHeight)) if (percentage === 100 || percentage === 0) return null @@ -117,16 +138,13 @@ export const EditUpload: React.FC<{ } const saveEdits = () => { - updateUploadEdits({ - crop: crop - ? { - ...crop, - heightPixels: Number(heightRef.current?.value ?? crop.heightPixels), - widthPixels: Number(widthRef.current?.value ?? crop.widthPixels), - } - : undefined, - focalPoint: focalPosition ? focalPosition : undefined, - }) + if (typeof onSave === 'function') + onSave({ + crop: crop ? crop : undefined, + focalPoint: focalPosition, + heightInPixels: Number(heightInputRef?.current?.value ?? uncroppedPixelHeight), + widthInPixels: Number(widthInputRef?.current?.value ?? uncroppedPixelWidth), + }) closeModal(editDrawerSlug) } @@ -181,7 +199,7 @@ export const EditUpload: React.FC<{ className={`${baseClass}__focal-wrapper`} ref={focalWrapRef} style={{ - aspectRatio: `${originalWidth / originalHeight}`, + aspectRatio: `${uncroppedPixelWidth / uncroppedPixelHeight}`, }} > {showCrop ? ( @@ -237,10 +255,8 @@ export const EditUpload: React.FC<{ onClick={() => setCrop({ height: 100, - heightPixels: originalHeight, unit: '%', width: 100, - widthPixels: originalWidth, x: 0, y: 0, }) @@ -257,14 +273,14 @@ export const EditUpload: React.FC<{ fineTuneCrop({ dimension: 'width', value })} - ref={widthRef} - value={((crop.width / 100) * originalWidth).toFixed(0)} + ref={widthInputRef} + value={((crop.width / 100) * uncroppedPixelWidth).toFixed(0)} /> fineTuneCrop({ dimension: 'height', value })} - ref={heightRef} - value={((crop.height / 100) * originalHeight).toFixed(0)} + ref={heightInputRef} + value={((crop.height / 100) * uncroppedPixelHeight).toFixed(0)} /> diff --git a/packages/payload/src/admin/components/views/collections/Edit/Upload/index.tsx b/packages/payload/src/admin/components/views/collections/Edit/Upload/index.tsx index f1dd61e1ed..70c1d995c0 100644 --- a/packages/payload/src/admin/components/views/collections/Edit/Upload/index.tsx +++ b/packages/payload/src/admin/components/views/collections/Edit/Upload/index.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'react-toastify' +import type { UploadEdits } from '../../../../../../uploads/types' import type { Props } from './types' import isImage from '../../../../../../uploads/isImage' @@ -13,6 +14,7 @@ import FileDetails from '../../../../elements/FileDetails' import PreviewSizes from '../../../../elements/PreviewSizes' import Thumbnail from '../../../../elements/Thumbnail' import Error from '../../../../forms/Error' +import { useForm } from '../../../../forms/Form/context' import reduceFieldsToValues from '../../../../forms/Form/reduceFieldsToValues' import { fieldBaseClass } from '../../../../forms/field-types/shared' import useField from '../../../../forms/useField' @@ -55,7 +57,8 @@ export const Upload: React.FC = (props) => { const [replacingFile, setReplacingFile] = useState(false) const [fileSrc, setFileSrc] = useState(null) const { t } = useTranslation(['upload', 'general']) - const { resetUploadEdits } = useUploadEdits() + const { setModified } = useForm() + const { resetUploadEdits, updateUploadEdits, uploadEdits } = useUploadEdits() const [doc, setDoc] = useState(reduceFieldsToValues(internalState || {}, true)) const { docPermissions } = useDocumentInfo() const { errorMessage, setValue, showError, value } = useField({ @@ -133,6 +136,14 @@ export const Upload: React.FC = (props) => { setShowUrlInput(false) }, [handleFileChange, resetUploadEdits]) + const onEditsSave = useCallback( + (args: UploadEdits) => { + setModified(true) + updateUploadEdits(args) + }, + [setModified, updateUploadEdits], + ) + const handlePasteUrlClick = () => { setShowUrlInput((prev) => !prev) } @@ -222,7 +233,7 @@ export const Upload: React.FC = (props) => { onClick={handleUrlSubmit} type="button" > - {t('upload:addImage')} + {t('upload:addFile')} @@ -274,10 +285,15 @@ export const Upload: React.FC = (props) => { {(value || doc.filename) && ( diff --git a/packages/payload/src/admin/components/views/collections/Edit/types.ts b/packages/payload/src/admin/components/views/collections/Edit/types.ts index a439151fff..2eaf20a31c 100644 --- a/packages/payload/src/admin/components/views/collections/Edit/types.ts +++ b/packages/payload/src/admin/components/views/collections/Edit/types.ts @@ -4,15 +4,3 @@ export type IndexProps = { collection: SanitizedCollectionConfig isEditing?: boolean } -export type UploadEdits = { - crop?: { - height?: number - width?: number - x?: number - y?: number - } - focalPoint?: { - x?: number - y?: number - } -} diff --git a/packages/payload/src/translations/ar.json b/packages/payload/src/translations/ar.json index aa49f87731..dfc3801f91 100644 --- a/packages/payload/src/translations/ar.json +++ b/packages/payload/src/translations/ar.json @@ -283,7 +283,7 @@ "near": "قريب من" }, "upload": { - "addImage": "إضافة صورة", + "addFile": "إضافة ملف", "crop": "محصول", "cropToolDescription": "اسحب الزوايا المحددة للمنطقة، رسم منطقة جديدة أو قم بضبط القيم أدناه.", "dragAndDrop": "قم بسحب وإسقاط ملفّ", diff --git a/packages/payload/src/translations/az.json b/packages/payload/src/translations/az.json index 93f54f5cf9..ad5cd946fd 100644 --- a/packages/payload/src/translations/az.json +++ b/packages/payload/src/translations/az.json @@ -283,7 +283,7 @@ "near": "yaxın" }, "upload": { - "addImage": "Şəkil əlavə et", + "addFile": "Fayl əlavə et", "crop": "Məhsul", "cropToolDescription": "Seçilmiş sahənin köşələrini sürükləyin, yeni bir sahə çəkin və ya aşağıdakı dəyərləri düzəltin.", "dragAndDrop": "Faylı buraya sürükləyin və buraxın", diff --git a/packages/payload/src/translations/bg.json b/packages/payload/src/translations/bg.json index 131cde130d..0d64970da3 100644 --- a/packages/payload/src/translations/bg.json +++ b/packages/payload/src/translations/bg.json @@ -283,7 +283,7 @@ "near": "близко" }, "upload": { - "addImage": "Добавяне на изображение", + "addFile": "Добавяне на файл", "crop": "Изрязване", "cropToolDescription": "Плъзни ъглите на избраната област, избери нова област или коригирай стойностите по-долу.", "dragAndDrop": "Дръпни и пусни файл", diff --git a/packages/payload/src/translations/cs.json b/packages/payload/src/translations/cs.json index b38650e294..8fba9129ed 100644 --- a/packages/payload/src/translations/cs.json +++ b/packages/payload/src/translations/cs.json @@ -283,7 +283,7 @@ "near": "blízko" }, "upload": { - "addImage": "Přidat obrázek", + "addFile": "Přidat soubor", "crop": "Ořez", "cropToolDescription": "Přetáhněte rohy vybrané oblasti, nakreslete novou oblast nebo upravte níže uvedené hodnoty.", "dragAndDrop": "Přetáhněte soubor", diff --git a/packages/payload/src/translations/de.json b/packages/payload/src/translations/de.json index c7feeb9748..ae11a79eaf 100644 --- a/packages/payload/src/translations/de.json +++ b/packages/payload/src/translations/de.json @@ -283,7 +283,7 @@ "near": "in der Nähe" }, "upload": { - "addImage": "Bild hinzufügen", + "addFile": "Datei hinzufügen", "crop": "Zuschneiden", "cropToolDescription": "Ziehen Sie die Ecken des ausgewählten Bereichs, zeichnen Sie einen neuen Bereich oder passen Sie die Werte unten an.", "dragAndDrop": "Ziehen Sie eine Datei per Drag-and-Drop", diff --git a/packages/payload/src/translations/en.json b/packages/payload/src/translations/en.json index 1180f8be10..03bc8be82e 100644 --- a/packages/payload/src/translations/en.json +++ b/packages/payload/src/translations/en.json @@ -283,7 +283,7 @@ "near": "near" }, "upload": { - "addImage": "Add Image", + "addFile": "Add File", "crop": "Crop", "cropToolDescription": "Drag the corners of the selected area, draw a new area or adjust the values below.", "dragAndDrop": "Drag and drop a file", diff --git a/packages/payload/src/translations/es.json b/packages/payload/src/translations/es.json index 2b8ef4afe5..c788023888 100644 --- a/packages/payload/src/translations/es.json +++ b/packages/payload/src/translations/es.json @@ -283,7 +283,7 @@ "near": "cerca" }, "upload": { - "addImage": "Añadir imagen", + "addFile": "Añadir archivo", "crop": "Cultivo", "cropToolDescription": "Arrastra las esquinas del área seleccionada, dibuja un nuevo área o ajusta los valores a continuación.", "dragAndDrop": "Arrastra y suelta un archivo", diff --git a/packages/payload/src/translations/fa.json b/packages/payload/src/translations/fa.json index a463ebf2bd..04621409e7 100644 --- a/packages/payload/src/translations/fa.json +++ b/packages/payload/src/translations/fa.json @@ -283,7 +283,7 @@ "near": "نزدیک" }, "upload": { - "addImage": "اضافه کردن تصویر", + "addFile": "اضافه کردن فایل", "crop": "محصول", "cropToolDescription": "گوشه‌های منطقه انتخاب شده را بکشید، یک منطقه جدید رسم کنید یا مقادیر زیر را تنظیم کنید.", "dragAndDrop": "یک سند را بکشید و رها کنید", diff --git a/packages/payload/src/translations/fr.json b/packages/payload/src/translations/fr.json index 037fcc2e12..6e60b1e23b 100644 --- a/packages/payload/src/translations/fr.json +++ b/packages/payload/src/translations/fr.json @@ -283,7 +283,7 @@ "near": "proche" }, "upload": { - "addImage": "Ajouter une image", + "addFile": "Ajouter un fichier", "crop": "Recadrer", "cropToolDescription": "Faites glisser les coins de la zone sélectionnée, dessinez une nouvelle zone ou ajustez les valeurs ci-dessous.", "dragAndDrop": "Glisser-déposer un fichier", diff --git a/packages/payload/src/translations/hr.json b/packages/payload/src/translations/hr.json index 2465d67a27..68569a6ede 100644 --- a/packages/payload/src/translations/hr.json +++ b/packages/payload/src/translations/hr.json @@ -283,7 +283,7 @@ "near": "blizu" }, "upload": { - "addImage": "Dodaj sliku", + "addFile": "Dodaj datoteku", "crop": "Usjev", "cropToolDescription": "Povucite kutove odabranog područja, nacrtajte novo područje ili prilagodite vrijednosti ispod.", "dragAndDrop": "Povucite i ispustite datoteku", diff --git a/packages/payload/src/translations/hu.json b/packages/payload/src/translations/hu.json index cd4ab18310..edb20f9a00 100644 --- a/packages/payload/src/translations/hu.json +++ b/packages/payload/src/translations/hu.json @@ -283,7 +283,7 @@ "near": "közel" }, "upload": { - "addImage": "Kép hozzáadása", + "addFile": "Fájl hozzáadása", "crop": "Termés", "cropToolDescription": "Húzza a kijelölt terület sarkait, rajzoljon új területet, vagy igazítsa a lentebb található értékeket.", "dragAndDrop": "Húzzon ide egy fájlt", diff --git a/packages/payload/src/translations/it.json b/packages/payload/src/translations/it.json index 594a0aeccd..7711fc3fbb 100644 --- a/packages/payload/src/translations/it.json +++ b/packages/payload/src/translations/it.json @@ -284,7 +284,7 @@ "near": "vicino" }, "upload": { - "addImage": "Aggiungi immagine", + "addFile": "Aggiungi file", "crop": "Raccolto", "cropToolDescription": "Trascina gli angoli dell'area selezionata, disegna una nuova area o regola i valori qui sotto.", "dragAndDrop": "Trascina e rilascia un file", diff --git a/packages/payload/src/translations/ja.json b/packages/payload/src/translations/ja.json index e2d3045510..9153438ecc 100644 --- a/packages/payload/src/translations/ja.json +++ b/packages/payload/src/translations/ja.json @@ -283,7 +283,7 @@ "near": "近く" }, "upload": { - "addImage": "画像を追加", + "addFile": "ファイルを追加", "crop": "クロップ", "cropToolDescription": "選択したエリアのコーナーをドラッグしたり、新たなエリアを描画したり、下記の値を調整してください。", "dragAndDrop": "ファイルをドラッグ アンド ドロップする", diff --git a/packages/payload/src/translations/ko.json b/packages/payload/src/translations/ko.json index bcd003f9ba..0ab2fcf6a2 100644 --- a/packages/payload/src/translations/ko.json +++ b/packages/payload/src/translations/ko.json @@ -283,7 +283,7 @@ "near": "근처" }, "upload": { - "addImage": "이미지 추가", + "addFile": "파일 추가", "crop": "자르기", "cropToolDescription": "선택한 영역의 모퉁이를 드래그하거나 새로운 영역을 그리거나 아래의 값을 조정하세요.", "dragAndDrop": "파일을 끌어다 놓으세요", diff --git a/packages/payload/src/translations/my.json b/packages/payload/src/translations/my.json index 44516c0f64..9834bf9c2e 100644 --- a/packages/payload/src/translations/my.json +++ b/packages/payload/src/translations/my.json @@ -283,7 +283,7 @@ "near": "နီး" }, "upload": { - "addImage": "ပုံ ထည့်ပါ", + "addFile": "ဖိုင်ထည့်ပါ", "crop": "သုန်း", "cropToolDescription": "ရွေးထားသည့်ဧရိယာတွင်မွေးလျှက်မှုများကိုဆွဲပြီး, အသစ်တည်ပြီးသို့မဟုတ်အောက်ပါတ", "dragAndDrop": "ဖိုင်တစ်ဖိုင်ကို ဆွဲချလိုက်ပါ။", diff --git a/packages/payload/src/translations/nb.json b/packages/payload/src/translations/nb.json index 90564d09c8..487b3ecd8b 100644 --- a/packages/payload/src/translations/nb.json +++ b/packages/payload/src/translations/nb.json @@ -283,7 +283,7 @@ "near": "nær" }, "upload": { - "addImage": "Legg til bilde", + "addFile": "Legg til fil", "crop": "Beskjær", "cropToolDescription": "Dra hjørnene av det valgte området, tegn et nytt område eller juster verdiene nedenfor.", "dragAndDrop": "Dra og slipp en fil", diff --git a/packages/payload/src/translations/nl.json b/packages/payload/src/translations/nl.json index 8339f57419..0df57d3a1e 100644 --- a/packages/payload/src/translations/nl.json +++ b/packages/payload/src/translations/nl.json @@ -283,7 +283,7 @@ "near": "nabij" }, "upload": { - "addImage": "Afbeelding toevoegen", + "addFile": "Bestand toevoegen", "crop": "Bijsnijden", "cropToolDescription": "Sleep de hoeken van het geselecteerde gebied, teken een nieuw gebied of pas de waarden hieronder aan.", "dragAndDrop": "Sleep een bestand", diff --git a/packages/payload/src/translations/pl.json b/packages/payload/src/translations/pl.json index 6965c7f227..ed1fa0f8f0 100644 --- a/packages/payload/src/translations/pl.json +++ b/packages/payload/src/translations/pl.json @@ -284,7 +284,7 @@ "near": "blisko" }, "upload": { - "addImage": "Dodaj obraz", + "addFile": "Dodaj plik", "crop": "Przytnij", "cropToolDescription": "Przeciągnij narożniki wybranego obszaru, narysuj nowy obszar lub dostosuj poniższe wartości.", "dragAndDrop": "Przeciągnij i upuść plik", diff --git a/packages/payload/src/translations/pt.json b/packages/payload/src/translations/pt.json index 660fbf44a4..6e3172b969 100644 --- a/packages/payload/src/translations/pt.json +++ b/packages/payload/src/translations/pt.json @@ -283,7 +283,7 @@ "near": "perto" }, "upload": { - "addImage": "Adicionar imagem", + "addFile": "Adicionar arquivo", "crop": "Cultura", "cropToolDescription": "Arraste as bordas da área selecionada, desenhe uma nova área ou ajuste os valores abaixo.", "dragAndDrop": "Arraste e solte um arquivo", diff --git a/packages/payload/src/translations/ro.json b/packages/payload/src/translations/ro.json index fad702c288..c9f7a4744e 100644 --- a/packages/payload/src/translations/ro.json +++ b/packages/payload/src/translations/ro.json @@ -283,7 +283,7 @@ "near": "în apropiere de" }, "upload": { - "addImage": "Adaugă imagine", + "addFile": "Adaugă fișier", "crop": "Cultură", "cropToolDescription": "Trageți colțurile zonei selectate, desenați o nouă zonă sau ajustați valorile de mai jos.", "dragAndDrop": "Trageți și plasați un fișier", diff --git a/packages/payload/src/translations/rs-latin.json b/packages/payload/src/translations/rs-latin.json index 502208ce58..9cc8c33f93 100644 --- a/packages/payload/src/translations/rs-latin.json +++ b/packages/payload/src/translations/rs-latin.json @@ -283,7 +283,7 @@ "near": "blizu" }, "upload": { - "addImage": "Dodaj sliku", + "addFile": "Dodaj datoteku", "crop": "Isecite sliku", "cropToolDescription": "Prevucite uglove izabranog područja, nacrtajte novo područje ili prilagodite vrednosti ispod.", "dragAndDrop": "Prevucite i ispustite datoteku", diff --git a/packages/payload/src/translations/rs.json b/packages/payload/src/translations/rs.json index 68367a48db..2be495d538 100644 --- a/packages/payload/src/translations/rs.json +++ b/packages/payload/src/translations/rs.json @@ -283,7 +283,7 @@ "near": "близу" }, "upload": { - "addImage": "Додај слику", + "addFile": "Додај датотеку", "crop": "Исеците слику", "cropToolDescription": "Превуците углове изабраног подручја, нацртајте ново подручје или прилагодите вредности испод.", "dragAndDrop": "Превуците и испустите датотеку", diff --git a/packages/payload/src/translations/ru.json b/packages/payload/src/translations/ru.json index cc297bd483..db5f8a6467 100644 --- a/packages/payload/src/translations/ru.json +++ b/packages/payload/src/translations/ru.json @@ -283,7 +283,7 @@ "near": "рядом" }, "upload": { - "addImage": "Добавить изображение", + "addFile": "Добавить файл", "crop": "Обрезать", "cropToolDescription": "Перетащите углы выбранной области, нарисуйте новую область или отрегулируйте значения ниже.", "dragAndDrop": "Перетащите файл", diff --git a/packages/payload/src/translations/sv.json b/packages/payload/src/translations/sv.json index a0e2b6d906..92c1025f0c 100644 --- a/packages/payload/src/translations/sv.json +++ b/packages/payload/src/translations/sv.json @@ -283,7 +283,7 @@ "near": "nära" }, "upload": { - "addImage": "Lägg till bild", + "addFile": "Lägg till fil", "crop": "Skörd", "cropToolDescription": "Dra i hörnen på det valda området, rita ett nytt område eller justera värdena nedan.", "dragAndDrop": "Dra och släpp en fil", diff --git a/packages/payload/src/translations/th.json b/packages/payload/src/translations/th.json index ba1cc9431e..6818ab35c3 100644 --- a/packages/payload/src/translations/th.json +++ b/packages/payload/src/translations/th.json @@ -283,7 +283,7 @@ "near": "ใกล้" }, "upload": { - "addImage": "เพิ่มรูปภาพ", + "addFile": "เพิ่มไฟล์", "crop": "พืชผล", "cropToolDescription": "ลากมุมของพื้นที่ที่เลือก, วาดพื้นที่ใหม่หรือปรับค่าด้านล่าง", "dragAndDrop": "ลากและวางไฟล์", diff --git a/packages/payload/src/translations/tr.json b/packages/payload/src/translations/tr.json index 040cf468e8..23b4cba2af 100644 --- a/packages/payload/src/translations/tr.json +++ b/packages/payload/src/translations/tr.json @@ -283,7 +283,7 @@ "near": "yakın" }, "upload": { - "addImage": "Resim ekle", + "addFile": "Dosya ekle", "crop": "Kırp", "cropToolDescription": "Seçili alanın köşelerini sürükleyin, yeni bir alan çizin veya aşağıdaki değerleri ayarlayın.", "dragAndDrop": "Bir dosyayı sürükleyip bırakın", diff --git a/packages/payload/src/translations/translation-schema.json b/packages/payload/src/translations/translation-schema.json index 8bedf2b625..2aca88becc 100644 --- a/packages/payload/src/translations/translation-schema.json +++ b/packages/payload/src/translations/translation-schema.json @@ -1108,7 +1108,7 @@ "upload": { "additionalProperties": false, "properties": { - "addImage": { + "addFile": { "type": "string" }, "crop": { diff --git a/packages/payload/src/translations/ua.json b/packages/payload/src/translations/ua.json index b74dd1579d..4392e355a8 100644 --- a/packages/payload/src/translations/ua.json +++ b/packages/payload/src/translations/ua.json @@ -283,7 +283,7 @@ "near": "поруч" }, "upload": { - "addImage": "Додати зображення", + "addFile": "Додати файл", "crop": "Обрізати", "cropToolDescription": "Перетягніть кути обраної області, намалюйте нову область або скоригуйте значення нижче.", "dragAndDrop": "Перемістіть файл", diff --git a/packages/payload/src/translations/vi.json b/packages/payload/src/translations/vi.json index 55c2ba93dd..9074da54cd 100644 --- a/packages/payload/src/translations/vi.json +++ b/packages/payload/src/translations/vi.json @@ -283,7 +283,7 @@ "near": "gần" }, "upload": { - "addImage": "Thêm hình ảnh", + "addFile": "Thêm tập tin", "crop": "Mùa vụ", "cropToolDescription": "Kéo các góc của khu vực đã chọn, vẽ một khu vực mới hoặc điều chỉnh các giá trị dưới đây.", "dragAndDrop": "Kéo và thả một tập tin", diff --git a/packages/payload/src/translations/zh-tw.json b/packages/payload/src/translations/zh-tw.json index eaad55e34e..7036982833 100644 --- a/packages/payload/src/translations/zh-tw.json +++ b/packages/payload/src/translations/zh-tw.json @@ -283,7 +283,7 @@ "near": "附近" }, "upload": { - "addImage": "添加圖片", + "addFile": "添加文件", "crop": "裁剪", "cropToolDescription": "拖動所選區域的角落,繪製一個新區域或調整以下的值。", "dragAndDrop": "拖放一個檔案", diff --git a/packages/payload/src/translations/zh.json b/packages/payload/src/translations/zh.json index e08997fa11..02c7b8b1a4 100644 --- a/packages/payload/src/translations/zh.json +++ b/packages/payload/src/translations/zh.json @@ -283,7 +283,7 @@ "near": "附近" }, "upload": { - "addImage": "添加图片", + "addFile": "添加文件", "crop": "作物", "cropToolDescription": "拖动所选区域的角落,绘制一个新区域或调整以下的值。", "dragAndDrop": "拖放一个文件", diff --git a/packages/payload/src/uploads/cropImage.ts b/packages/payload/src/uploads/cropImage.ts index fd12288dd7..47ffafe123 100644 --- a/packages/payload/src/uploads/cropImage.ts +++ b/packages/payload/src/uploads/cropImage.ts @@ -1,26 +1,43 @@ +import type { UploadedFile } from 'express-fileupload' import type { SharpOptions } from 'sharp' import sharp from 'sharp' -export const percentToPixel = (value: string, dimension: number): number => { +import type { UploadEdits } from './types' + +export const percentToPixel = (value, dimension): number => { if (!value) return 0 return Math.floor((parseFloat(value) / 100) * dimension) } -export default async function cropImage({ cropData, dimensions, file }) { - try { - const fileIsAnimatedType = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype) - const { heightPixels, widthPixels, x, y } = cropData +type CropImageArgs = { + cropData: UploadEdits['crop'] + dimensions: { height: number; width: number } + file: UploadedFile + heightInPixels: number + widthInPixels: number +} +export async function cropImage({ + cropData, + dimensions, + file, + heightInPixels, + widthInPixels, +}: CropImageArgs) { + try { + const { x, y } = cropData + + const fileIsAnimatedType = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype) const sharpOptions: SharpOptions = {} if (fileIsAnimatedType) sharpOptions.animated = true - const formattedCropData: sharp.Region = { - height: Number(heightPixels), + const formattedCropData = { + height: Number(heightInPixels), left: percentToPixel(x, dimensions.width), top: percentToPixel(y, dimensions.height), - width: Number(widthPixels), + width: Number(widthInPixels), } const cropped = sharp(file.tempFilePath || file.data, sharpOptions).extract(formattedCropData) diff --git a/packages/payload/src/uploads/generateFileData.ts b/packages/payload/src/uploads/generateFileData.ts index 942f836727..810c51ec0c 100644 --- a/packages/payload/src/uploads/generateFileData.ts +++ b/packages/payload/src/uploads/generateFileData.ts @@ -16,7 +16,7 @@ import type { FileData, FileToSave, ProbedImageSize, UploadEdits } from './types import { FileUploadError, MissingFile } from '../errors' import FileRetrievalError from '../errors/FileRetrievalError' import canResizeImage from './canResizeImage' -import cropImage from './cropImage' +import { cropImage } from './cropImage' import { getExternalFile } from './getExternalFile' import getFileByPath from './getFileByPath' import getImageSize from './getImageSize' @@ -211,7 +211,13 @@ export const generateFileData = async ({ let fileForResize = file if (cropData) { - const { data: croppedImage, info } = await cropImage({ cropData, dimensions, file }) + const { data: croppedImage, info } = await cropImage({ + cropData, + dimensions, + file, + heightInPixels: uploadEdits.heightInPixels, + widthInPixels: uploadEdits.widthInPixels, + }) filesToSave.push({ buffer: croppedImage, diff --git a/packages/payload/src/uploads/imageResizer.ts b/packages/payload/src/uploads/imageResizer.ts index 44c117ca1e..8494652a01 100644 --- a/packages/payload/src/uploads/imageResizer.ts +++ b/packages/payload/src/uploads/imageResizer.ts @@ -1,5 +1,5 @@ import type { UploadedFile } from 'express-fileupload' -import type { OutputInfo, Sharp, SharpOptions } from 'sharp' +import type { Sharp, Metadata as SharpMetadata, SharpOptions } from 'sharp' import { fromBuffer } from 'file-type' import fs from 'fs' @@ -68,11 +68,21 @@ const getSanitizedImageData = (sourceImage: string): SanitizedImageData => { * @param extension - the extension to use * @returns the new image name that is not taken */ -const createImageName = ( - outputImageName: string, - { height, width }: OutputInfo, - extension: string, -) => `${outputImageName}-${width}x${height}.${extension}` + +type CreateImageNameArgs = { + extension: string + height: number + outputImageName: string + width: number +} +const createImageName = ({ + extension, + height, + outputImageName, + width, +}: CreateImageNameArgs): string => { + return `${outputImageName}-${width}x${height}.${extension}` +} type CreateResultArgs = { filename?: FileSize['filename'] @@ -122,46 +132,61 @@ const createResult = ({ } /** - * Check if the image needs to be resized according to the requested dimensions - * and the original image size. If the resize options withoutEnlargement or withoutReduction are provided, - * the image will be resized regardless of the requested dimensions, given that the - * width or height to be resized is provided. + * Determine whether or not to resize the image. + * - resize using image config + * - resize using image config with focal adjustments + * - do not resize at all * - * @param resizeConfig - object containing the requested dimensions and resize options - * @param original - the original image size - * @returns true if resizing is not needed, false otherwise + * `imageResizeConfig.withoutEnlargement`: + * - undefined [default]: uploading images with smaller width AND height than the image size will return null + * - false: always enlarge images to the image size + * - true: if the image is smaller than the image size, return the original image + * + * `imageResizeConfig.withoutReduction`: + * - false [default]: always enlarge images to the image size + * - true: if the image is smaller than the image size, return the original image + * + * @return 'omit' | 'resize' | 'resizeWithFocalPoint' */ -const preventResize = ( - { height: desiredHeight, width: desiredWidth, withoutEnlargement, withoutReduction }: ImageSize, - original: ProbedImageSize, -): boolean => { - // default is to allow reduction - if (withoutReduction !== undefined) { - return false // needs resize +const getImageResizeAction = ({ + dimensions: originalImage, + hasFocalPoint, + imageResizeConfig, +}: { + dimensions: ProbedImageSize + hasFocalPoint?: boolean + imageResizeConfig: ImageSize +}): 'omit' | 'resize' | 'resizeWithFocalPoint' => { + const { + fit, + height: targetHeight, + width: targetWidth, + withoutEnlargement, + withoutReduction, + } = imageResizeConfig + + // prevent upscaling by default when x and y are both smaller than target image size + if (targetHeight && targetWidth) { + const originalImageIsSmallerXAndY = + originalImage.width < targetWidth && originalImage.height < targetHeight + if (withoutEnlargement === undefined && originalImageIsSmallerXAndY) { + return 'omit' // prevent image size from being enlarged + } } - // default is to prevent enlargement - if (withoutEnlargement !== undefined) { - return false // needs resize - } + const originalImageIsSmallerXOrY = + originalImage.width < targetWidth || originalImage.height < targetHeight + if (fit === 'contain' || fit === 'inside') return 'resize' + if (!isNumber(targetHeight) && !isNumber(targetWidth)) return 'resize' - const isWidthOrHeightNotDefined = !desiredHeight || !desiredWidth - if (isWidthOrHeightNotDefined) { - // If width and height are not defined, it means there is a format conversion - // and the image needs to be "resized" (transformed). - return false // needs resize - } + const targetAspectRatio = targetWidth / targetHeight + const originalAspectRatio = originalImage.width / originalImage.height + if (originalAspectRatio === targetAspectRatio) return 'resize' - const hasInsufficientWidth = desiredWidth > original.width - const hasInsufficientHeight = desiredHeight > original.height - if (hasInsufficientWidth && hasInsufficientHeight) { - // doesn't need resize - prevent enlargement. This should only happen if both width and height are insufficient. - // if only one dimension is insufficient and the other is sufficient, resizing needs to happen, as the image - // should be resized to the sufficient dimension. - return true // do not create a new size - } + if (withoutEnlargement && originalImageIsSmallerXOrY) return 'resize' + if (withoutReduction && !originalImageIsSmallerXOrY) return 'resize' - return false // needs resize + return hasFocalPoint ? 'resizeWithFocalPoint' : 'resize' } /** @@ -209,6 +234,19 @@ const sanitizeResizeConfig = (resizeConfig: ImageSize): ImageSize => { return resizeConfig } +/** + * Used to extract height from images, animated or not. + * + * @param sharpMetadata - the sharp metadata + * @returns the height of the image + */ +function extractHeightFromImage(sharpMetadata: SharpMetadata): number { + if (sharpMetadata?.pages) { + return sharpMetadata.height / sharpMetadata.pages + } + return sharpMetadata.height +} + /** * For the provided image sizes, handle the resizing and the transforms * (format, trim, etc.) of each requested image size and return the result object. @@ -259,24 +297,28 @@ export default async function resizeAndTransformImageSizes({ 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 originalImageMeta = await sharpBase.metadata() + + const resizeImageMeta = { + height: extractHeightFromImage(originalImageMeta), + width: originalImageMeta.width, + } const results: ImageSizesResult[] = await Promise.all( imageSizes.map(async (imageResizeConfig): Promise => { imageResizeConfig = sanitizeResizeConfig(imageResizeConfig) - // This checks if a resize should happen. If not, the resized image will be - // skipped COMPLETELY and thus will not be included in the resulting images. - // All further format/trim options will thus be skipped as well. - if (preventResize(imageResizeConfig, dimensions)) { - return createResult({ name: imageResizeConfig.name }) - } + const resizeAction = getImageResizeAction({ + dimensions, + hasFocalPoint: Boolean(incomingFocalPoint), + imageResizeConfig, + }) + if (resizeAction === 'omit') return createResult({ name: imageResizeConfig.name }) const imageToResize = sharpBase.clone() let resized = imageToResize - const metadata = await sharpBase.metadata() - - if (incomingFocalPoint && applyPayloadAdjustments(imageResizeConfig, dimensions)) { + if (resizeAction === 'resizeWithFocalPoint') { let { height: resizeHeight, width: resizeWidth } = imageResizeConfig const originalAspectRatio = dimensions.width / dimensions.height @@ -291,44 +333,62 @@ export default async function resizeAndTransformImageSizes({ resizeHeight = Math.round(resizeWidth / originalAspectRatio) } - // Scale the image up or down to fit the resize dimensions - const scaledImage = imageToResize.resize({ - height: resizeHeight, - width: resizeWidth, - }) + if (!resizeHeight) resizeHeight = resizeImageMeta.height + if (!resizeWidth) resizeWidth = resizeImageMeta.width - const { info: scaledImageInfo } = await scaledImage.toBuffer({ resolveWithObject: true }) + // if requested image is larger than the incoming size, then resize using sharp and then extract with focal point + if (resizeHeight > resizeImageMeta.height || resizeWidth > resizeImageMeta.width) { + const resizeAspectRatio = resizeWidth / resizeHeight + const prioritizeHeight = resizeAspectRatio < originalAspectRatio + resized = imageToResize.resize({ + height: prioritizeHeight ? resizeHeight : undefined, + width: prioritizeHeight ? undefined : resizeWidth, + }) - const safeResizeWidth = resizeWidth ?? scaledImageInfo.width - const maxOffsetX = scaledImageInfo.width - safeResizeWidth - const leftFocalEdge = Math.round( - scaledImageInfo.width * (incomingFocalPoint.x / 100) - safeResizeWidth / 2, - ) - const safeOffsetX = Math.min(Math.max(0, leftFocalEdge), maxOffsetX) - - const isAnimated = fileIsAnimatedType && metadata.pages - - let safeResizeHeight = resizeHeight ?? scaledImageInfo.height - - if (isAnimated && resizeHeight === undefined) { - safeResizeHeight = scaledImageInfo.height / metadata.pages + // must read from buffer, resize.metadata will return the original image metadata + const { info } = await resized.toBuffer({ resolveWithObject: true }) + resizeImageMeta.height = extractHeightFromImage({ + ...originalImageMeta, + height: info.height, + }) + resizeImageMeta.width = info.width } - const maxOffsetY = isAnimated - ? safeResizeHeight - (resizeHeight ?? safeResizeHeight) - : scaledImageInfo.height - safeResizeHeight + const halfResizeX = resizeWidth / 2 + const xFocalCenter = resizeImageMeta.width * (incomingFocalPoint.x / 100) + const calculatedRightPixelBound = xFocalCenter + halfResizeX + let leftBound = xFocalCenter - halfResizeX - const topFocalEdge = Math.round( - scaledImageInfo.height * (incomingFocalPoint.y / 100) - safeResizeHeight / 2, - ) - const safeOffsetY = Math.min(Math.max(0, topFocalEdge), maxOffsetY) + // if the right bound is greater than the image width, adjust the left bound + // keeping focus on the right + if (calculatedRightPixelBound > resizeImageMeta.width) { + leftBound = resizeImageMeta.width - resizeWidth + } - // extract the focal area from the scaled image - resized = (fileIsAnimatedType ? imageToResize : scaledImage).extract({ - height: safeResizeHeight, - left: safeOffsetX, - top: safeOffsetY, - width: safeResizeWidth, + // if the left bound is less than 0, adjust the left bound to 0 + // keeping the focus on the left + if (leftBound < 0) leftBound = 0 + + const halfResizeY = resizeHeight / 2 + const yFocalCenter = resizeImageMeta.height * (incomingFocalPoint.y / 100) + const calculatedBottomPixelBound = yFocalCenter + halfResizeY + let topBound = yFocalCenter - halfResizeY + + // if the bottom bound is greater than the image height, adjust the top bound + // keeping the image as far right as possible + if (calculatedBottomPixelBound > resizeImageMeta.height) { + topBound = resizeImageMeta.height - resizeHeight + } + + // if the top bound is less than 0, adjust the top bound to 0 + // keeping the image focus near the top + if (topBound < 0) topBound = 0 + + resized = resized.extract({ + height: resizeHeight, + left: Math.floor(leftBound), + top: Math.floor(topBound), + width: resizeWidth, }) } else { resized = imageToResize.resize(imageResizeConfig) @@ -357,11 +417,15 @@ export default async function resizeAndTransformImageSizes({ const mimeInfo = await fromBuffer(bufferData) - const imageNameWithDimensions = createImageName( - sanitizedImage.name, - bufferInfo, - mimeInfo?.ext || sanitizedImage.ext, - ) + const imageNameWithDimensions = createImageName({ + extension: mimeInfo?.ext || sanitizedImage.ext, + height: extractHeightFromImage({ + ...originalImageMeta, + height: bufferInfo.height, + }), + outputImageName: sanitizedImage.name, + width: bufferInfo.width, + }) const imagePath = `${staticPath}/${imageNameWithDimensions}` @@ -378,7 +442,8 @@ export default async function resizeAndTransformImageSizes({ name: imageResizeConfig.name, filename: imageNameWithDimensions, filesize: size, - height: fileIsAnimatedType && metadata.pages ? height / metadata.pages : height, + height: + fileIsAnimatedType && originalImageMeta.pages ? height / originalImageMeta.pages : height, mimeType: mimeInfo?.mime || mimeType, sizesToSave: [{ buffer: bufferData, path: imagePath }], width, @@ -386,9 +451,12 @@ export default async function resizeAndTransformImageSizes({ }), ) - return results.reduce((acc, result) => { - Object.assign(acc.sizeData, result.sizeData) - acc.sizesToSave.push(...result.sizesToSave) - return acc - }, defaultResult) + return results.reduce( + (acc, result) => { + Object.assign(acc.sizeData, result.sizeData) + acc.sizesToSave.push(...result.sizesToSave) + return acc + }, + { ...defaultResult }, + ) } diff --git a/packages/payload/src/uploads/types.ts b/packages/payload/src/uploads/types.ts index 50184f0e59..8df43b38a8 100644 --- a/packages/payload/src/uploads/types.ts +++ b/packages/payload/src/uploads/types.ts @@ -124,15 +124,22 @@ export type FileToSave = { path: string } -export type UploadEdits = { - crop?: { - height?: number - width?: number - x?: number - y?: number - } - focalPoint?: { - x?: number - y?: number - } +type Crop = { + height: number + unit: '%' | 'px' + width: number + x: number + y: number +} + +type FocalPoint = { + x: number + y: number +} + +export type UploadEdits = { + crop?: Crop + focalPoint?: FocalPoint + heightInPixels?: number + widthInPixels?: number } diff --git a/test/fields/e2e.spec.ts b/test/fields/e2e.spec.ts index 5d1b28c192..709c6c7603 100644 --- a/test/fields/e2e.spec.ts +++ b/test/fields/e2e.spec.ts @@ -2090,8 +2090,8 @@ describe('fields', () => { const inputField = page.locator('.file-field__upload .file-field__remote-file') await inputField.fill(remoteImage) - const addImageButton = page.locator('.file-field__add-file') - await addImageButton.click() + const addFileButton = page.locator('.file-field__add-file') + await addFileButton.click() await expect(page.locator('.file-field .file-field__filename')).toHaveValue('og-image.jpg') @@ -2139,6 +2139,46 @@ describe('fields', () => { await expect(page.locator('.Toastify')).toContainText('successfully') }) + test('should upload after editing image inside a document drawer', async () => { + await uploadImage() + await wait(1000) + // Open the media drawer and create a png upload + + await page.locator('.field-type.upload .upload__toggler.doc-drawer__toggler').click() + + await page + .locator('[id^=doc-drawer_uploads_1_] .file-field__upload input[type="file"]') + .setInputFiles(path.resolve(__dirname, './uploads/payload.png')) + await expect( + page.locator('[id^=doc-drawer_uploads_1_] .file-field__upload .file-field__filename'), + ).toHaveValue('payload.png') + await page.locator('[id^=doc-drawer_uploads_1_] .file-field__edit').click() + await page + .locator('[id^=edit-upload] .edit-upload__input input[name="Width (px)"]') + .nth(1) + .fill('200') + await page + .locator('[id^=edit-upload] .edit-upload__input input[name="Height (px)"]') + .nth(1) + .fill('200') + await page.locator('[id^=edit-upload] button:has-text("Apply Changes")').nth(1).click() + await page.locator('[id^=doc-drawer_uploads_1_] #action-save').click() + await expect(page.locator('.Toastify')).toContainText('successfully') + + // Assert that the media field has the png upload + await expect( + page.locator('.field-type.upload .file-details .file-meta__url a'), + ).toHaveAttribute('href', '/uploads/payload-1.png') + await expect( + page.locator('.field-type.upload .file-details .file-meta__url a'), + ).toContainText('payload-1.png') + await expect(page.locator('.field-type.upload .file-details img')).toHaveAttribute( + 'src', + '/uploads/payload-1.png', + ) + await saveDocAndAssert(page) + }) + test('should clear selected upload', async () => { await uploadImage() await page.locator('.field-type.upload .upload__toggler.doc-drawer__toggler').click() diff --git a/test/uploads/e2e.spec.ts b/test/uploads/e2e.spec.ts index a01debf204..b511daa8eb 100644 --- a/test/uploads/e2e.spec.ts +++ b/test/uploads/e2e.spec.ts @@ -16,6 +16,7 @@ import { adminThumbnailSlug, animatedTypeMedia, audioSlug, + focalOnlySlug, globalWithMedia, mediaSlug, relationSlug, @@ -30,6 +31,7 @@ let audioURL: AdminUrlUtil let relationURL: AdminUrlUtil let adminThumbnailURL: AdminUrlUtil let globalURL: string +let focalOnlyURL: AdminUrlUtil describe('uploads', () => { let page: Page @@ -47,6 +49,7 @@ describe('uploads', () => { relationURL = new AdminUrlUtil(serverURL, relationSlug) adminThumbnailURL = new AdminUrlUtil(serverURL, adminThumbnailSlug) globalURL = new AdminUrlUtil(serverURL, globalWithMedia).global(globalWithMedia) + focalOnlyURL = new AdminUrlUtil(serverURL, focalOnlySlug) const context = await browser.newContext() page = await context.newPage() @@ -125,6 +128,25 @@ describe('uploads', () => { await saveDocAndAssert(page) }) + test('should show proper file names for resized animated file', 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.locator('.file-field__previewSizes').click() + + const smallSquareFilename = page + .locator('.preview-sizes__list .preview-sizes__sizeOption') + .nth(1) + .locator('.file-meta__url a') + await expect(smallSquareFilename).toContainText(/480x480\.webp$/) + }) + test('should show resized images', async () => { await page.goto(mediaURL.edit(pngDoc.id)) @@ -353,6 +375,43 @@ describe('uploads', () => { expect(greenDoc.filesize).toEqual(1205) expect(redDoc.filesize).toEqual(1207) }) + + test('should update image alignment based on focal point', async () => { + const updateFocalPosition = async (page: Page) => { + await page.goto(focalOnlyURL.create) + await page.waitForURL(focalOnlyURL.create) + // select and upload file + const fileChooserPromise = page.waitForEvent('filechooser') + await page.getByText('Select a file').click() + const fileChooser = await fileChooserPromise + await wait(1000) + await fileChooser.setFiles(path.join(__dirname, 'horizontal-squares.jpg')) + + await page.locator('.file-field__edit').click() + + // set focal point + await page.locator('.edit-upload__input input[name="X %"]').fill('12') // left focal point + await page.locator('.edit-upload__input input[name="Y %"]').fill('50') // top focal point + + // apply focal point + await page.locator('button:has-text("Apply Changes")').click() + await page.waitForSelector('button#action-save') + await page.locator('button#action-save').click() + await wait(1000) // Wait for the save + } + + await updateFocalPosition(page) // red square + const redSquareMediaID = page.url().split('/').pop() // get the ID of the doc + + const { doc: redDoc } = await client.findByID({ + id: redSquareMediaID, + slug: focalOnlySlug, + auth: true, + }) + + // without focal point update this generated size was equal to 1736 + expect(redDoc.sizes.focalTest.filesize).toEqual(1598) + }) }) describe('globals', () => { diff --git a/test/uploads/horizontal-squares.jpg b/test/uploads/horizontal-squares.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e543b942a02701057a6a66fe3fa0e8fb59814c43 GIT binary patch literal 2601 zcmex=PKf)jmb{qpT;N;>4N{9$BA`61pKv4;ZGmvGtxH&jM>IE^?-(uilW(3;BEXZKb z(EVQX`Uhl<5cXx~_8a~U7~+<*S3Jzc5M|&p*vBr7sanffr)&nMYF-nI+Lj*};>