web(avatar-crop): fix narrow-image centering and add circular tg-like mask
All checks were successful
CI / test (push) Successful in 39s
All checks were successful
CI / test (push) Successful in 39s
This commit is contained in:
@@ -12,6 +12,7 @@ const VIEWPORT_SIZE = 320;
|
||||
const OUTPUT_SIZE = 500;
|
||||
const MIN_ZOOM = 1;
|
||||
const MAX_ZOOM = 4;
|
||||
const MASK_RATIO = 0.88;
|
||||
|
||||
export function AvatarCropModal({ open, file, onCancel, onApply }: AvatarCropModalProps) {
|
||||
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
||||
@@ -162,13 +163,13 @@ export function AvatarCropModal({ open, file, onCancel, onApply }: AvatarCropMod
|
||||
<div className="absolute inset-0 flex items-center justify-center p-4" onClick={(event) => event.stopPropagation()}>
|
||||
<div className="w-full max-w-lg rounded-2xl border border-slate-700/80 bg-slate-900/95 p-4 shadow-2xl">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-slate-100">Crop avatar</p>
|
||||
<p className="text-sm font-semibold text-slate-100">Drag to reposition</p>
|
||||
<button className="rounded bg-slate-700 px-2 py-1 text-xs text-slate-100 disabled:opacity-60" disabled={processing} onClick={onCancel} type="button">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto mb-3 h-[320px] w-[320px] overflow-hidden rounded-xl border border-slate-700 bg-slate-950">
|
||||
<div className="mx-auto mb-3 h-[320px] w-[320px] overflow-hidden rounded-2xl border border-slate-700 bg-slate-950">
|
||||
<div
|
||||
className="relative h-full w-full cursor-grab select-none active:cursor-grabbing"
|
||||
onPointerDown={handlePointerDown}
|
||||
@@ -193,6 +194,12 @@ export function AvatarCropModal({ open, file, onCancel, onApply }: AvatarCropMod
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<div
|
||||
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full border border-slate-200/70 shadow-[0_0_0_9999px_rgba(15,23,42,0.58)]"
|
||||
style={{ width: `${Math.floor(VIEWPORT_SIZE * MASK_RATIO)}px`, height: `${Math.floor(VIEWPORT_SIZE * MASK_RATIO)}px` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -229,10 +236,16 @@ export function AvatarCropModal({ open, file, onCancel, onApply }: AvatarCropMod
|
||||
}
|
||||
|
||||
function clampPosition(pos: { x: number; y: number }, width: number, height: number) {
|
||||
const minX = Math.min(0, VIEWPORT_SIZE - width);
|
||||
const maxX = 0;
|
||||
const minY = Math.min(0, VIEWPORT_SIZE - height);
|
||||
const maxY = 0;
|
||||
if (width <= VIEWPORT_SIZE && height <= VIEWPORT_SIZE) {
|
||||
return {
|
||||
x: (VIEWPORT_SIZE - width) / 2,
|
||||
y: (VIEWPORT_SIZE - height) / 2,
|
||||
};
|
||||
}
|
||||
const minX = width <= VIEWPORT_SIZE ? (VIEWPORT_SIZE - width) / 2 : VIEWPORT_SIZE - width;
|
||||
const maxX = width <= VIEWPORT_SIZE ? (VIEWPORT_SIZE - width) / 2 : 0;
|
||||
const minY = height <= VIEWPORT_SIZE ? (VIEWPORT_SIZE - height) / 2 : VIEWPORT_SIZE - height;
|
||||
const maxY = height <= VIEWPORT_SIZE ? (VIEWPORT_SIZE - height) / 2 : 0;
|
||||
return {
|
||||
x: Math.max(minX, Math.min(maxX, pos.x)),
|
||||
y: Math.max(minY, Math.min(maxY, pos.y)),
|
||||
|
||||
Reference in New Issue
Block a user