Skip to content

Commit a7ac5ef

Browse files
feat: improves crop rendering in thumbnail (#6260)
1 parent 15c7a9d commit a7ac5ef

File tree

3 files changed

+98
-31
lines changed

3 files changed

+98
-31
lines changed

packages/ui/src/elements/EditUpload/index.tsx

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import 'react-image-crop/dist/ReactCrop.css'
99

1010
import { editDrawerSlug } from '../../elements/Upload/index.js'
1111
import { Plus } from '../../icons/Plus/index.js'
12-
import { useFormQueryParams } from '../../providers/FormQueryParams/index.js'
1312
import { useTranslation } from '../../providers/Translation/index.js'
1413
import { Button } from '../Button/index.js'
1514
import './index.scss'
@@ -27,40 +26,60 @@ const Input: React.FC<{ name: string; onChange: (value: string) => void; value:
2726
</div>
2827
)
2928

29+
type FocalPosition = {
30+
x: number
31+
y: number
32+
}
33+
3034
export type EditUploadProps = {
3135
doc?: Data
3236
fileName: string
3337
fileSrc: string
3438
imageCacheTag?: string
39+
initialCrop?: CropType
40+
initialFocalPoint?: FocalPosition
41+
onSave?: ({ crop, pointPosition }: { crop: CropType; pointPosition: FocalPosition }) => void
3542
showCrop?: boolean
3643
showFocalPoint?: boolean
3744
}
3845

46+
const defaultCrop: CropType = {
47+
height: 100,
48+
unit: '%',
49+
width: 100,
50+
x: 0,
51+
y: 0,
52+
}
53+
54+
const defaultPointPosition: FocalPosition = {
55+
x: 50,
56+
y: 50,
57+
}
58+
3959
export const EditUpload: React.FC<EditUploadProps> = ({
4060
fileName,
4161
fileSrc,
4262
imageCacheTag,
63+
initialCrop,
64+
initialFocalPoint,
65+
onSave,
4366
showCrop,
4467
showFocalPoint,
4568
}) => {
46-
const { Modal, useModal } = facelessUIImport
69+
const { useModal } = facelessUIImport
4770

4871
const { closeModal } = useModal()
4972
const { t } = useTranslation()
50-
const { dispatchFormQueryParams, formQueryParams } = useFormQueryParams()
51-
const { uploadEdits } = formQueryParams || {}
52-
const [crop, setCrop] = useState<CropType>({
53-
height: uploadEdits?.crop?.height || 100,
54-
unit: '%',
55-
width: uploadEdits?.crop?.width || 100,
56-
x: uploadEdits?.crop?.x || 0,
57-
y: uploadEdits?.crop?.y || 0,
58-
})
59-
60-
const [pointPosition, setPointPosition] = useState<{ x: number; y: number }>({
61-
x: uploadEdits?.focalPoint?.x || 50,
62-
y: uploadEdits?.focalPoint?.y || 50,
63-
})
73+
74+
const [crop, setCrop] = useState<CropType>(() => ({
75+
...defaultCrop,
76+
...initialCrop,
77+
}))
78+
79+
const [pointPosition, setPointPosition] = useState<FocalPosition>(() => ({
80+
...defaultPointPosition,
81+
...initialFocalPoint,
82+
}))
6483
const [checkBounds, setCheckBounds] = useState<boolean>(false)
6584
const [originalHeight, setOriginalHeight] = useState<number>(0)
6685
const [originalWidth, setOriginalWidth] = useState<number>(0)
@@ -92,18 +111,11 @@ export const EditUpload: React.FC<EditUploadProps> = ({
92111
}
93112

94113
const saveEdits = () => {
95-
dispatchFormQueryParams({
96-
type: 'SET',
97-
params: {
98-
uploadEdits:
99-
crop || pointPosition
100-
? {
101-
crop: crop || null,
102-
focalPoint: pointPosition ? pointPosition : null,
103-
}
104-
: null,
105-
},
106-
})
114+
if (typeof onSave === 'function')
115+
onSave({
116+
crop,
117+
pointPosition,
118+
})
107119
closeModal(editDrawerSlug)
108120
}
109121

@@ -145,7 +157,7 @@ export const EditUpload: React.FC<EditUploadProps> = ({
145157
aria-label={t('general:applyChanges')}
146158
buttonStyle="primary"
147159
className={`${baseClass}__save`}
148-
onClick={() => saveEdits()}
160+
onClick={saveEdits}
149161
>
150162
{t('general:applyChanges')}
151163
</Button>

packages/ui/src/elements/Upload/index.scss

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,24 @@
2323
width: 150px;
2424

2525
.thumbnail {
26-
position: absolute;
26+
position: relative;
2727
width: 100%;
2828
height: 100%;
2929
object-fit: contain;
3030
}
3131
}
3232

33+
.file-details {
34+
img {
35+
position: relative;
36+
min-width: 100%;
37+
height: 100%;
38+
transform: scale(var(--file-details-thumbnail--zoom));
39+
top: var(--file-details-thumbnail--top-offset);
40+
left: var(--file-details-thumbnail--left-offset);
41+
}
42+
}
43+
3344
&__remove {
3445
margin: calc($baseline * 1.5) $baseline $baseline 0;
3546
place-self: flex-start;

packages/ui/src/elements/Upload/index.tsx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
import type { FormState, SanitizedCollectionConfig } from 'payload/types'
33

44
import { FieldError } from '@payloadcms/ui/forms/FieldError'
5+
import { useFormQueryParams } from '@payloadcms/ui/providers/FormQueryParams'
56
import { isImage } from 'payload/utilities'
67
import React, { useCallback, useEffect, useState } from 'react'
78

89
import { fieldBaseClass } from '../../fields/shared/index.js'
9-
import { useFormSubmitted } from '../../forms/Form/context.js'
10+
import { useForm, useFormSubmitted } from '../../forms/Form/context.js'
1011
import { useField } from '../../forms/useField/index.js'
1112
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
1213
import { useTranslation } from '../../providers/Translation/index.js'
@@ -65,12 +66,15 @@ export const Upload: React.FC<UploadProps> = (props) => {
6566
const [replacingFile, setReplacingFile] = useState(false)
6667
const [fileSrc, setFileSrc] = useState<null | string>(null)
6768
const { t } = useTranslation()
69+
const { setModified } = useForm()
70+
const { dispatchFormQueryParams, formQueryParams } = useFormQueryParams()
6871
const [doc, setDoc] = useState(reduceFieldsToValues(initialState || {}, true))
6972
const { docPermissions } = useDocumentInfo()
7073
const { errorMessage, setValue, showError, value } = useField<File>({
7174
path: 'file',
7275
validate,
7376
})
77+
const [crop, setCrop] = useState({ x: 0, y: 0 })
7478

7579
const handleFileNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
7680
const updatedFileName = e.target.value
@@ -96,6 +100,40 @@ export const Upload: React.FC<UploadProps> = (props) => {
96100
setFileSrc('')
97101
}, [setValue])
98102

103+
const onEditsSave = React.useCallback(
104+
({ crop, pointPosition }) => {
105+
setCrop({
106+
x: crop.x || 0,
107+
y: crop.y || 0,
108+
})
109+
const zoomScale = 100 / Math.min(crop.width, crop.height)
110+
111+
document.documentElement.style.setProperty('--file-details-thumbnail--zoom', `${zoomScale}`)
112+
document.documentElement.style.setProperty(
113+
'--file-details-thumbnail--top-offset',
114+
`${zoomScale * (50 - crop.height / 2 - crop.y)}%`,
115+
)
116+
document.documentElement.style.setProperty(
117+
'--file-details-thumbnail--left-offset',
118+
`${zoomScale * (50 - crop.width / 2 - crop.x)}%`,
119+
)
120+
setModified(true)
121+
dispatchFormQueryParams({
122+
type: 'SET',
123+
params: {
124+
uploadEdits:
125+
crop || pointPosition
126+
? {
127+
crop: crop || null,
128+
focalPoint: pointPosition ? pointPosition : null,
129+
}
130+
: null,
131+
},
132+
})
133+
},
134+
[dispatchFormQueryParams, setModified],
135+
)
136+
99137
useEffect(() => {
100138
setDoc(reduceFieldsToValues(initialState || {}, true))
101139
setReplacingFile(false)
@@ -200,6 +238,12 @@ export const Upload: React.FC<UploadProps> = (props) => {
200238
fileName={value?.name || doc?.filename}
201239
fileSrc={fileSrc || doc?.url}
202240
imageCacheTag={lastSubmittedTime}
241+
initialCrop={formQueryParams?.uploadEdits?.crop ?? {}}
242+
initialFocalPoint={{
243+
x: formQueryParams?.uploadEdits?.focalPoint.x || 0,
244+
y: formQueryParams?.uploadEdits?.focalPoint.y || 0,
245+
}}
246+
onSave={onEditsSave}
203247
showCrop={showCrop}
204248
showFocalPoint={showFocalPoint}
205249
/>

0 commit comments

Comments
 (0)