@sentroy-co/client-sdk 2.13.0 → 2.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"CropDialog.d.ts","sourceRoot":"","sources":["../../../src/react/crop/CropDialog.tsx"],"names":[],"mappings":"AAsCA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,CAAA;IACb;mBACe;IACf,IAAI,EAAE,IAAI,CAAA;IACV,4EAA4E;IAC5E,OAAO,EAAE,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAA;IACtC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;yDACqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,aAAsB,EACtB,aAAoB,GACrB,EAAE,eAAe,2CA2OjB"}
1
+ {"version":3,"file":"CropDialog.d.ts","sourceRoot":"","sources":["../../../src/react/crop/CropDialog.tsx"],"names":[],"mappings":"AA8CA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,CAAA;IACb;mBACe;IACf,IAAI,EAAE,IAAI,CAAA;IACV,4EAA4E;IAC5E,OAAO,EAAE,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAA;IACtC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;yDACqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,aAAsB,EACtB,aAAoB,GACrB,EAAE,eAAe,2CAiYjB"}
@@ -17,14 +17,19 @@ const ASPECT_PRESETS = [
17
17
  { id: "9:16", label: "9:16", aspect: 9 / 16 },
18
18
  ];
19
19
  const MAX_PIXEL_GUARD = 50_000_000; // ~24 MP — üstü tarayıcı memory peak'i riskli
20
+ const PREVIEW_MAX_DIM = 220; // sidebar thumbnail için max edge (px)
20
21
  function CropDialog({ open, file, onClose, defaultAspect = "free", outputQuality = 0.92, }) {
21
22
  const [imageUrl, setImageUrl] = (0, react_1.useState)(null);
22
23
  const [aspectId, setAspectId] = (0, react_1.useState)(defaultAspect);
23
24
  const [crop, setCrop] = (0, react_1.useState)({ x: 0, y: 0 });
24
25
  const [zoom, setZoom] = (0, react_1.useState)(1);
26
+ const [rotation, setRotation] = (0, react_1.useState)(0);
25
27
  const [croppedAreaPixels, setCroppedAreaPixels] = (0, react_1.useState)(null);
26
28
  const [busy, setBusy] = (0, react_1.useState)(false);
27
29
  const [tooLarge, setTooLarge] = (0, react_1.useState)(false);
30
+ const previewCanvasRef = (0, react_1.useRef)(null);
31
+ const previewRafRef = (0, react_1.useRef)(null);
32
+ const imageElRef = (0, react_1.useRef)(null);
28
33
  // Object URL lifecycle
29
34
  (0, react_1.useEffect)(() => {
30
35
  if (!open)
@@ -33,29 +38,71 @@ function CropDialog({ open, file, onClose, defaultAspect = "free", outputQuality
33
38
  setImageUrl(url);
34
39
  setCrop({ x: 0, y: 0 });
35
40
  setZoom(1);
41
+ setRotation(0);
36
42
  setAspectId(defaultAspect);
37
43
  setTooLarge(false);
38
- // Pixel guard large image decode tarayıcıyı çökertir
44
+ // Pixel guard + cache HTMLImageElement (preview drawImage source).
39
45
  const img = new Image();
40
46
  img.onload = () => {
41
47
  if (img.naturalWidth * img.naturalHeight > MAX_PIXEL_GUARD) {
42
48
  setTooLarge(true);
43
49
  }
50
+ imageElRef.current = img;
44
51
  };
45
52
  img.src = url;
46
- return () => URL.revokeObjectURL(url);
53
+ return () => {
54
+ URL.revokeObjectURL(url);
55
+ imageElRef.current = null;
56
+ };
47
57
  }, [open, file, defaultAspect]);
48
58
  const onCropComplete = (0, react_1.useCallback)((_area, areaPixels) => {
49
59
  setCroppedAreaPixels(areaPixels);
50
60
  }, []);
61
+ // Live preview render — crop / rotation değiştikçe sağ panel thumbnail'ı
62
+ // güncelle. requestAnimationFrame ile throttle (drag sırasında her event'te
63
+ // canvas çizmek pahalı).
64
+ (0, react_1.useEffect)(() => {
65
+ if (!croppedAreaPixels || !imageElRef.current || tooLarge)
66
+ return;
67
+ if (previewRafRef.current !== null) {
68
+ cancelAnimationFrame(previewRafRef.current);
69
+ }
70
+ previewRafRef.current = requestAnimationFrame(() => {
71
+ const canvas = previewCanvasRef.current;
72
+ const img = imageElRef.current;
73
+ if (!canvas || !img)
74
+ return;
75
+ const area = croppedAreaPixels;
76
+ // Preview boyutu — aspect korunarak max edge PREVIEW_MAX_DIM
77
+ const scale = area.width >= area.height
78
+ ? PREVIEW_MAX_DIM / area.width
79
+ : PREVIEW_MAX_DIM / area.height;
80
+ const pw = Math.max(1, Math.round(area.width * scale));
81
+ const ph = Math.max(1, Math.round(area.height * scale));
82
+ canvas.width = pw;
83
+ canvas.height = ph;
84
+ const ctx = canvas.getContext("2d");
85
+ if (!ctx)
86
+ return;
87
+ ctx.imageSmoothingQuality = "high";
88
+ ctx.clearRect(0, 0, pw, ph);
89
+ drawRotatedCrop(ctx, img, area, rotation, pw, ph);
90
+ previewRafRef.current = null;
91
+ });
92
+ return () => {
93
+ if (previewRafRef.current !== null) {
94
+ cancelAnimationFrame(previewRafRef.current);
95
+ previewRafRef.current = null;
96
+ }
97
+ };
98
+ }, [croppedAreaPixels, rotation, tooLarge]);
51
99
  const aspect = ASPECT_PRESETS.find((p) => p.id === aspectId)?.aspect ?? undefined;
52
100
  const handleApply = (0, react_1.useCallback)(async () => {
53
101
  if (!imageUrl || !croppedAreaPixels)
54
102
  return;
55
103
  setBusy(true);
56
104
  try {
57
- const blob = await getCroppedBlob(imageUrl, croppedAreaPixels, file.type, outputQuality);
58
- // Cropped File — orijinal name'i koru ama uzantı output type'ına göre
105
+ const blob = await getCroppedBlob(imageUrl, croppedAreaPixels, rotation, file.type, outputQuality);
59
106
  const ext = blob.type === "image/png" ? "png" : "jpg";
60
107
  const baseName = file.name.replace(/\.[^.]+$/, "");
61
108
  const cropped = new File([blob], `${baseName}.${ext}`, {
@@ -66,10 +113,16 @@ function CropDialog({ open, file, onClose, defaultAspect = "free", outputQuality
66
113
  finally {
67
114
  setBusy(false);
68
115
  }
69
- }, [imageUrl, croppedAreaPixels, file, onClose, outputQuality]);
116
+ }, [imageUrl, croppedAreaPixels, rotation, file, onClose, outputQuality]);
70
117
  const handleUseOriginal = (0, react_1.useCallback)(() => onClose(file), [file, onClose]);
71
118
  const handleCancel = (0, react_1.useCallback)(() => onClose(null), [onClose]);
72
- // ESC kapatır
119
+ const handleRotate = (0, react_1.useCallback)((delta) => {
120
+ setRotation((r) => {
121
+ const next = (r + delta + 360) % 360;
122
+ return next;
123
+ });
124
+ }, []);
125
+ // ESC kapatır, R döndürür
73
126
  (0, react_1.useEffect)(() => {
74
127
  if (!open)
75
128
  return;
@@ -78,27 +131,76 @@ function CropDialog({ open, file, onClose, defaultAspect = "free", outputQuality
78
131
  e.stopPropagation();
79
132
  handleCancel();
80
133
  }
134
+ else if (e.key === "r" || e.key === "R") {
135
+ if (e.target instanceof HTMLElement &&
136
+ (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA")) {
137
+ return;
138
+ }
139
+ e.preventDefault();
140
+ handleRotate(e.shiftKey ? -90 : 90);
141
+ }
81
142
  };
82
143
  window.addEventListener("keydown", onKey);
83
144
  return () => window.removeEventListener("keydown", onKey);
84
- }, [open, handleCancel]);
85
- return ((0, jsx_runtime_1.jsx)(react_2.AnimatePresence, { children: open && imageUrl && ((0, jsx_runtime_1.jsx)(react_2.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.2 },
86
- // z-index ana MediaManager modal'ından yüksek (nested)
87
- className: "fixed inset-0 z-[60] flex items-center justify-center bg-black/80 backdrop-blur-sm p-4", onClick: (e) => {
145
+ }, [open, handleCancel, handleRotate]);
146
+ const outputWidth = croppedAreaPixels ? Math.round(croppedAreaPixels.width) : 0;
147
+ const outputHeight = croppedAreaPixels ? Math.round(croppedAreaPixels.height) : 0;
148
+ // Rotation 90° / 270° iken output dimensions swap edilir (canvas rotate
149
+ // sonrası kullanıcı görsel olarak swap görür).
150
+ const displayW = rotation % 180 === 0 ? outputWidth : outputHeight;
151
+ const displayH = rotation % 180 === 0 ? outputHeight : outputWidth;
152
+ return ((0, jsx_runtime_1.jsx)(react_2.AnimatePresence, { children: open && imageUrl && ((0, jsx_runtime_1.jsx)(react_2.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.2 }, className: "fixed inset-0 z-[60] flex items-center justify-center bg-black/80 backdrop-blur-sm p-4", onClick: (e) => {
88
153
  if (e.target === e.currentTarget)
89
154
  handleCancel();
90
- }, children: (0, jsx_runtime_1.jsxs)(react_2.motion.div, { initial: { opacity: 0, scale: 0.96, y: 8 }, animate: { opacity: 1, scale: 1, y: 0 }, exit: { opacity: 0, scale: 0.98 }, transition: { duration: 0.25, ease: [0.22, 1, 0.36, 1] }, className: "flex h-[min(90vh,720px)] w-full max-w-3xl flex-col overflow-hidden rounded-xl border bg-background shadow-2xl", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between gap-3 border-b px-4 py-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm font-semibold", children: "Crop image" }), (0, jsx_runtime_1.jsx)("span", { className: "truncate text-xs text-muted-foreground", children: file.name })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleCancel, className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground", "aria-label": "Cancel", children: (0, jsx_runtime_1.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "size-4", children: (0, jsx_runtime_1.jsx)("path", { d: "M18 6 6 18M6 6l12 12" }) }) })] }), (0, jsx_runtime_1.jsx)("div", { className: "flex flex-wrap items-center gap-1 border-b bg-muted/20 px-3 py-2", children: ASPECT_PRESETS.map((p) => ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setAspectId(p.id), className: cls("rounded-md px-2.5 py-1 text-xs transition-colors", aspectId === p.id
91
- ? "bg-foreground text-background"
92
- : "text-muted-foreground hover:bg-muted hover:text-foreground"), children: p.label }, p.id))) }), (0, jsx_runtime_1.jsx)("div", { className: "relative flex-1 bg-black", children: tooLarge ? ((0, jsx_runtime_1.jsx)("div", { className: "flex h-full w-full items-center justify-center p-6 text-center text-sm text-white/70", children: "Image too large to crop in browser. Upload as-is or resize beforehand." })) : ((0, jsx_runtime_1.jsx)(react_easy_crop_1.default, { image: imageUrl, crop: crop, zoom: zoom, aspect: aspect, onCropChange: setCrop, onCropComplete: onCropComplete, onZoomChange: setZoom, showGrid: true, objectFit: "contain" })) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-3 border-t bg-muted/20 px-4 py-3", children: [!tooLarge && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs text-muted-foreground", children: "Zoom" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setZoom((z) => Math.max(1, z - 0.1)), className: "rounded-md border px-2 py-0.5 text-xs hover:bg-muted/50", children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "range", min: 1, max: 3, step: 0.05, value: zoom, onChange: (e) => setZoom(Number(e.target.value)), className: "flex-1 accent-foreground" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setZoom((z) => Math.min(3, z + 0.1)), className: "rounded-md border px-2 py-0.5 text-xs hover:bg-muted/50", children: "+" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => {
155
+ }, children: (0, jsx_runtime_1.jsxs)(react_2.motion.div, { initial: { opacity: 0, scale: 0.96, y: 8 }, animate: { opacity: 1, scale: 1, y: 0 }, exit: { opacity: 0, scale: 0.98 }, transition: { duration: 0.25, ease: [0.22, 1, 0.36, 1] }, className: "flex h-[min(92vh,760px)] w-full max-w-5xl flex-col overflow-hidden rounded-xl border bg-background shadow-2xl", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between gap-3 border-b px-4 py-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col min-w-0", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm font-semibold", children: "Crop image" }), (0, jsx_runtime_1.jsx)("span", { className: "truncate text-xs text-muted-foreground", children: file.name })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleCancel, className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground", "aria-label": "Cancel", children: (0, jsx_runtime_1.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "size-4", children: (0, jsx_runtime_1.jsx)("path", { d: "M18 6 6 18M6 6l12 12" }) }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center gap-1 border-b bg-muted/20 px-3 py-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex flex-1 flex-wrap items-center gap-1", children: ASPECT_PRESETS.map((p) => ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setAspectId(p.id), className: cls("rounded-md px-2.5 py-1 text-xs transition-colors", aspectId === p.id
156
+ ? "bg-foreground text-background"
157
+ : "text-muted-foreground hover:bg-muted hover:text-foreground"), children: p.label }, p.id))) }), (0, jsx_runtime_1.jsxs)("div", { className: "ms-auto flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => handleRotate(-90), title: "Rotate left (Shift+R)", "aria-label": "Rotate left", className: "rounded-md border px-2 py-1 text-xs text-muted-foreground hover:bg-muted/50 hover:text-foreground", children: (0, jsx_runtime_1.jsx)(RotateLeftIcon, {}) }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => handleRotate(90), title: "Rotate right (R)", "aria-label": "Rotate right", className: "rounded-md border px-2 py-1 text-xs text-muted-foreground hover:bg-muted/50 hover:text-foreground", children: (0, jsx_runtime_1.jsx)(RotateRightIcon, {}) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-1 min-h-0 flex-col overflow-hidden md:flex-row", children: [(0, jsx_runtime_1.jsx)("div", { className: "relative flex-1 bg-black", children: tooLarge ? ((0, jsx_runtime_1.jsx)("div", { className: "flex h-full w-full items-center justify-center p-6 text-center text-sm text-white/70", children: "Image too large to crop in browser. Upload as-is or resize beforehand." })) : ((0, jsx_runtime_1.jsx)(react_easy_crop_1.default, { image: imageUrl, crop: crop, zoom: zoom, rotation: rotation, aspect: aspect, onCropChange: setCrop, onCropComplete: onCropComplete, onZoomChange: setZoom, onRotationChange: setRotation, showGrid: true, objectFit: "contain" })) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex w-full shrink-0 flex-col gap-3 border-t bg-muted/10 p-4 md:w-64 md:border-l md:border-t-0", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground", children: "Preview" }), displayW > 0 && displayH > 0 && ((0, jsx_runtime_1.jsxs)("span", { className: "font-mono text-[10px] text-muted-foreground", children: [displayW, "\u00D7", displayH] }))] }), (0, jsx_runtime_1.jsx)("div", { className: "flex min-h-[140px] items-center justify-center rounded-lg border border-dashed bg-background/40 p-2", children: tooLarge ? ((0, jsx_runtime_1.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Preview unavailable" })) : croppedAreaPixels ? ((0, jsx_runtime_1.jsx)("canvas", { ref: previewCanvasRef, className: "max-h-[220px] max-w-full rounded-sm shadow-sm" })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Adjust crop\u2026" })) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1 text-[11px] text-muted-foreground", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { children: "Aspect" }), (0, jsx_runtime_1.jsx)("span", { className: "font-mono", children: ASPECT_PRESETS.find((p) => p.id === aspectId)?.label ??
158
+ "Free" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { children: "Rotation" }), (0, jsx_runtime_1.jsxs)("span", { className: "font-mono", children: [rotation, "\u00B0"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { children: "Zoom" }), (0, jsx_runtime_1.jsxs)("span", { className: "font-mono", children: [zoom.toFixed(2), "\u00D7"] })] })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-3 border-t bg-muted/20 px-4 py-3", children: [!tooLarge && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs text-muted-foreground", children: "Zoom" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setZoom((z) => Math.max(1, z - 0.1)), className: "rounded-md border px-2 py-0.5 text-xs hover:bg-muted/50", children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "range", min: 1, max: 3, step: 0.05, value: zoom, onChange: (e) => setZoom(Number(e.target.value)), className: "flex-1 accent-foreground" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setZoom((z) => Math.min(3, z + 0.1)), className: "rounded-md border px-2 py-0.5 text-xs hover:bg-muted/50", children: "+" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => {
93
159
  setZoom(1);
160
+ setRotation(0);
94
161
  setCrop({ x: 0, y: 0 });
95
162
  }, className: "rounded-md border px-2 py-0.5 text-xs hover:bg-muted/50", children: "Reset" })] })), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-end gap-2", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleCancel, disabled: busy, className: "rounded-md border px-3 py-1.5 text-xs hover:bg-muted/50", children: "Cancel" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleUseOriginal, disabled: busy, className: "rounded-md border px-3 py-1.5 text-xs hover:bg-muted/50", children: "Use original" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleApply, disabled: busy || tooLarge || !croppedAreaPixels, className: "rounded-md bg-foreground px-3 py-1.5 text-xs font-medium text-background hover:opacity-90 disabled:opacity-50", children: busy ? "Cropping…" : "Apply crop" })] })] })] }) }, "backdrop")) }));
96
163
  }
164
+ /**
165
+ * Rotated crop'u canvas'a yaz. Rotation 0° fast-path; aksi halde önce
166
+ * "rotated bounding box" canvas'ı oluştur (orijinal image'in döndürülmüş
167
+ * versiyonu), sonra croppedAreaPixels koordinatlarında çıkar.
168
+ *
169
+ * react-easy-crop's `croppedAreaPixels` rotation-aware: koordinatlar
170
+ * orijinal image'i rotasyon merkezinden döndürdükten sonraki "visual"
171
+ * bounding box üzerinden hesaplanır — biz aynı transform'u canvas'a
172
+ * uygulayıp aynı koordinatlardan drawImage yapıyoruz.
173
+ */
174
+ function drawRotatedCrop(ctx, image, area, rotation, dstW, dstH) {
175
+ if (rotation === 0) {
176
+ ctx.drawImage(image, area.x, area.y, area.width, area.height, 0, 0, dstW, dstH);
177
+ return;
178
+ }
179
+ // Build a rotated source canvas at the size of the rotated bounding box,
180
+ // then crop from it.
181
+ const rad = (rotation * Math.PI) / 180;
182
+ const sin = Math.abs(Math.sin(rad));
183
+ const cos = Math.abs(Math.cos(rad));
184
+ const iw = image.naturalWidth;
185
+ const ih = image.naturalHeight;
186
+ const bbW = iw * cos + ih * sin;
187
+ const bbH = iw * sin + ih * cos;
188
+ const tmp = document.createElement("canvas");
189
+ tmp.width = bbW;
190
+ tmp.height = bbH;
191
+ const tctx = tmp.getContext("2d");
192
+ if (!tctx)
193
+ return;
194
+ tctx.translate(bbW / 2, bbH / 2);
195
+ tctx.rotate(rad);
196
+ tctx.drawImage(image, -iw / 2, -ih / 2);
197
+ ctx.drawImage(tmp, area.x, area.y, area.width, area.height, 0, 0, dstW, dstH);
198
+ }
97
199
  /**
98
200
  * Canvas ile crop area'yı çıkar + Blob döndür.
99
201
  * Output MIME: PNG ise PNG, diğerleri JPEG (transparency yoksa).
100
202
  */
101
- async function getCroppedBlob(imageUrl, area, sourceMime, quality) {
203
+ async function getCroppedBlob(imageUrl, area, rotation, sourceMime, quality) {
102
204
  const image = await loadImage(imageUrl);
103
205
  const canvas = document.createElement("canvas");
104
206
  canvas.width = area.width;
@@ -106,7 +208,8 @@ async function getCroppedBlob(imageUrl, area, sourceMime, quality) {
106
208
  const ctx = canvas.getContext("2d");
107
209
  if (!ctx)
108
210
  throw new Error("Canvas 2D context unavailable");
109
- ctx.drawImage(image, area.x, area.y, area.width, area.height, 0, 0, area.width, area.height);
211
+ ctx.imageSmoothingQuality = "high";
212
+ drawRotatedCrop(ctx, image, area, rotation, area.width, area.height);
110
213
  const outputMime = sourceMime === "image/png" ? "image/png" : "image/jpeg";
111
214
  return new Promise((resolve, reject) => {
112
215
  canvas.toBlob((blob) => (blob ? resolve(blob) : reject(new Error("toBlob returned null"))), outputMime, outputMime === "image/jpeg" ? quality : undefined);
@@ -123,4 +226,10 @@ function loadImage(url) {
123
226
  function cls(...arr) {
124
227
  return arr.filter(Boolean).join(" ");
125
228
  }
229
+ function RotateLeftIcon() {
230
+ return ((0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "size-3.5", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("path", { d: "M3 12a9 9 0 1 0 3-6.7" }), (0, jsx_runtime_1.jsx)("path", { d: "M3 4v5h5" })] }));
231
+ }
232
+ function RotateRightIcon() {
233
+ return ((0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "size-3.5", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("path", { d: "M21 12a9 9 0 1 1-3-6.7" }), (0, jsx_runtime_1.jsx)("path", { d: "M21 4v5h-5" })] }));
234
+ }
126
235
  //# sourceMappingURL=CropDialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CropDialog.js","sourceRoot":"","sources":["../../../src/react/crop/CropDialog.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CropDialog.js","sourceRoot":"","sources":["../../../src/react/crop/CropDialog.tsx"],"names":[],"mappings":";;;;;AA4DA,gCAuYC;;AAncD,iCAAgE;AAChE,sEAAqC;AACrC,wCAAsD;AAgCtD,MAAM,cAAc,GAAgE;IAClF,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE;IAC3C,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE;IACtC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE;IAC7C,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE;IAC1C,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE;IAC1C,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;CAC9C,CAAA;AAED,MAAM,eAAe,GAAG,UAAU,CAAA,CAAC,8CAA8C;AACjF,MAAM,eAAe,GAAG,GAAG,CAAA,CAAC,uCAAuC;AAgBnE,SAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,aAAa,GAAG,MAAM,EACtB,aAAa,GAAG,IAAI,GACJ;IAChB,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,IAAA,gBAAQ,EAAgB,IAAI,CAAC,CAAA;IAC7D,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,IAAA,gBAAQ,EAAC,aAAa,CAAC,CAAA;IACvD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,IAAA,gBAAQ,EAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IAChD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,IAAA,gBAAQ,EAAC,CAAC,CAAC,CAAA;IACnC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,IAAA,gBAAQ,EAAC,CAAC,CAAC,CAAA;IAC3C,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,IAAA,gBAAQ,EAAkB,IAAI,CAAC,CAAA;IACjF,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,IAAA,gBAAQ,EAAC,KAAK,CAAC,CAAA;IACvC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,IAAA,gBAAQ,EAAC,KAAK,CAAC,CAAA;IAC/C,MAAM,gBAAgB,GAAG,IAAA,cAAM,EAA2B,IAAI,CAAC,CAAA;IAC/D,MAAM,aAAa,GAAG,IAAA,cAAM,EAAgB,IAAI,CAAC,CAAA;IACjD,MAAM,UAAU,GAAG,IAAA,cAAM,EAA0B,IAAI,CAAC,CAAA;IAExD,uBAAuB;IACvB,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;QACrC,WAAW,CAAC,GAAG,CAAC,CAAA;QAChB,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;QACvB,OAAO,CAAC,CAAC,CAAC,CAAA;QACV,WAAW,CAAC,CAAC,CAAC,CAAA;QACd,WAAW,CAAC,aAAa,CAAC,CAAA;QAC1B,WAAW,CAAC,KAAK,CAAC,CAAA;QAClB,mEAAmE;QACnE,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE,CAAA;QACvB,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE;YAChB,IAAI,GAAG,CAAC,YAAY,GAAG,GAAG,CAAC,aAAa,GAAG,eAAe,EAAE,CAAC;gBAC3D,WAAW,CAAC,IAAI,CAAC,CAAA;YACnB,CAAC;YACD,UAAU,CAAC,OAAO,GAAG,GAAG,CAAA;QAC1B,CAAC,CAAA;QACD,GAAG,CAAC,GAAG,GAAG,GAAG,CAAA;QACb,OAAO,GAAG,EAAE;YACV,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAA;YACxB,UAAU,CAAC,OAAO,GAAG,IAAI,CAAA;QAC3B,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC,CAAA;IAE/B,MAAM,cAAc,GAAG,IAAA,mBAAW,EAChC,CAAC,KAAe,EAAE,UAAoB,EAAE,EAAE;QACxC,oBAAoB,CAAC,UAAU,CAAC,CAAA;IAClC,CAAC,EACD,EAAE,CACH,CAAA;IAED,yEAAyE;IACzE,4EAA4E;IAC5E,yBAAyB;IACzB,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,CAAC,iBAAiB,IAAI,CAAC,UAAU,CAAC,OAAO,IAAI,QAAQ;YAAE,OAAM;QACjE,IAAI,aAAa,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YACnC,oBAAoB,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;QAC7C,CAAC;QACD,aAAa,CAAC,OAAO,GAAG,qBAAqB,CAAC,GAAG,EAAE;YACjD,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,CAAA;YACvC,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAA;YAC9B,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG;gBAAE,OAAM;YAC3B,MAAM,IAAI,GAAG,iBAAiB,CAAA;YAC9B,6DAA6D;YAC7D,MAAM,KAAK,GACT,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM;gBACvB,CAAC,CAAC,eAAe,GAAG,IAAI,CAAC,KAAK;gBAC9B,CAAC,CAAC,eAAe,GAAG,IAAI,CAAC,MAAM,CAAA;YACnC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAA;YACtD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAA;YACvD,MAAM,CAAC,KAAK,GAAG,EAAE,CAAA;YACjB,MAAM,CAAC,MAAM,GAAG,EAAE,CAAA;YAClB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YACnC,IAAI,CAAC,GAAG;gBAAE,OAAM;YAChB,GAAG,CAAC,qBAAqB,GAAG,MAAM,CAAA;YAClC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;YAC3B,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;YACjD,aAAa,CAAC,OAAO,GAAG,IAAI,CAAA;QAC9B,CAAC,CAAC,CAAA;QACF,OAAO,GAAG,EAAE;YACV,IAAI,aAAa,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;gBACnC,oBAAoB,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;gBAC3C,aAAa,CAAC,OAAO,GAAG,IAAI,CAAA;YAC9B,CAAC;QACH,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,iBAAiB,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAA;IAE3C,MAAM,MAAM,GACV,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,EAAE,MAAM,IAAI,SAAS,CAAA;IAEpE,MAAM,WAAW,GAAG,IAAA,mBAAW,EAAC,KAAK,IAAI,EAAE;QACzC,IAAI,CAAC,QAAQ,IAAI,CAAC,iBAAiB;YAAE,OAAM;QAC3C,OAAO,CAAC,IAAI,CAAC,CAAA;QACb,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,QAAQ,EACR,iBAAiB,EACjB,QAAQ,EACR,IAAI,CAAC,IAAI,EACT,aAAa,CACd,CAAA;YACD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAA;YACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;YAClD,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,QAAQ,IAAI,GAAG,EAAE,EAAE;gBACrD,IAAI,EAAE,IAAI,CAAC,IAAI;aAChB,CAAC,CAAA;YACF,OAAO,CAAC,OAAO,CAAC,CAAA;QAClB,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,CAAA;QAChB,CAAC;IACH,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAA;IAEzE,MAAM,iBAAiB,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;IAC3E,MAAM,YAAY,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IAChE,MAAM,YAAY,GAAG,IAAA,mBAAW,EAC9B,CAAC,KAAe,EAAE,EAAE;QAClB,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE;YAChB,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,CAAA;YACpC,OAAO,IAAI,CAAA;QACb,CAAC,CAAC,CAAA;IACJ,CAAC,EACD,EAAE,CACH,CAAA;IAED,0BAA0B;IAC1B,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,KAAK,GAAG,CAAC,CAAgB,EAAE,EAAE;YACjC,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;gBACvB,CAAC,CAAC,eAAe,EAAE,CAAA;gBACnB,YAAY,EAAE,CAAA;YAChB,CAAC;iBAAM,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;gBAC1C,IACE,CAAC,CAAC,MAAM,YAAY,WAAW;oBAC/B,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,KAAK,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,KAAK,UAAU,CAAC,EACjE,CAAC;oBACD,OAAM;gBACR,CAAC;gBACD,CAAC,CAAC,cAAc,EAAE,CAAA;gBAClB,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;YACrC,CAAC;QACH,CAAC,CAAA;QACD,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;QACzC,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;IAC3D,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC,CAAA;IAEtC,MAAM,WAAW,GAAG,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/E,MAAM,YAAY,GAAG,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACjF,wEAAwE;IACxE,+CAA+C;IAC/C,MAAM,QAAQ,GAAG,QAAQ,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAA;IAClE,MAAM,QAAQ,GAAG,QAAQ,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAA;IAElE,OAAO,CACL,uBAAC,uBAAe,cACb,IAAI,IAAI,QAAQ,IAAI,CACnB,uBAAC,cAAM,CAAC,GAAG,IAET,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EACvB,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EACvB,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EACpB,UAAU,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,EAC7B,SAAS,EAAC,wFAAwF,EAClG,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;gBACb,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,aAAa;oBAAE,YAAY,EAAE,CAAA;YAClD,CAAC,YAED,wBAAC,cAAM,CAAC,GAAG,IACT,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,EAC1C,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EACvC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EACjC,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EACxD,SAAS,EAAC,+GAA+G,aAGzH,iCAAK,SAAS,EAAC,4DAA4D,aACzE,iCAAK,SAAS,EAAC,uBAAuB,aACpC,iCAAM,SAAS,EAAC,uBAAuB,2BAAkB,EACzD,iCAAM,SAAS,EAAC,wCAAwC,YACrD,IAAI,CAAC,IAAI,GACL,IACH,EACN,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,YAAY,EACrB,SAAS,EAAC,kGAAkG,gBACjG,QAAQ,YAEnB,gCACE,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,SAAS,EAAC,QAAQ,YAElB,iCAAM,CAAC,EAAC,sBAAsB,GAAG,GAC7B,GACC,IACL,EAGN,iCAAK,SAAS,EAAC,kEAAkE,aAC/E,gCAAK,SAAS,EAAC,0CAA0C,YACtD,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CACzB,mCAEE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,EAChC,SAAS,EAAE,GAAG,CACZ,kDAAkD,EAClD,QAAQ,KAAK,CAAC,CAAC,EAAE;wCACf,CAAC,CAAC,+BAA+B;wCACjC,CAAC,CAAC,4DAA4D,CACjE,YAEA,CAAC,CAAC,KAAK,IAVH,CAAC,CAAC,EAAE,CAWF,CACV,CAAC,GACE,EACN,iCAAK,SAAS,EAAC,iCAAiC,aAC9C,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,EAChC,KAAK,EAAC,uBAAuB,gBAClB,aAAa,EACxB,SAAS,EAAC,mGAAmG,YAE7G,uBAAC,cAAc,KAAG,GACX,EACT,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,EAC/B,KAAK,EAAC,kBAAkB,gBACb,cAAc,EACzB,SAAS,EAAC,mGAAmG,YAE7G,uBAAC,eAAe,KAAG,GACZ,IACL,IACF,EAGN,iCAAK,SAAS,EAAC,0DAA0D,aAEvE,gCAAK,SAAS,EAAC,0BAA0B,YACtC,QAAQ,CAAC,CAAC,CAAC,CACV,gCAAK,SAAS,EAAC,sFAAsF,uFAG/F,CACP,CAAC,CAAC,CAAC,CACF,uBAAC,yBAAO,IACN,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,OAAO,EACrB,cAAc,EAAE,cAAc,EAC9B,YAAY,EAAE,OAAO,EACrB,gBAAgB,EAAE,WAAW,EAC7B,QAAQ,QACR,SAAS,EAAC,SAAS,GACnB,CACH,GACG,EAGN,iCAAK,SAAS,EAAC,gGAAgG,aAC7G,iCAAK,SAAS,EAAC,mCAAmC,aAChD,iCAAM,SAAS,EAAC,oEAAoE,wBAE7E,EACN,QAAQ,GAAG,CAAC,IAAI,QAAQ,GAAG,CAAC,IAAI,CAC/B,kCAAM,SAAS,EAAC,6CAA6C,aAC1D,QAAQ,YAAG,QAAQ,IACf,CACR,IACG,EACN,gCAAK,SAAS,EAAC,qGAAqG,YACjH,QAAQ,CAAC,CAAC,CAAC,CACV,iCAAM,SAAS,EAAC,mCAAmC,oCAE5C,CACR,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,CACtB,mCACE,GAAG,EAAE,gBAAgB,EACrB,SAAS,EAAC,+CAA+C,GACzD,CACH,CAAC,CAAC,CAAC,CACF,iCAAM,SAAS,EAAC,mCAAmC,kCAE5C,CACR,GACG,EACN,iCAAK,SAAS,EAAC,uDAAuD,aACpE,iCAAK,SAAS,EAAC,mCAAmC,aAChD,sDAAmB,EACnB,iCAAM,SAAS,EAAC,WAAW,YACxB,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,EAAE,KAAK;4DACnD,MAAM,GACH,IACH,EACN,iCAAK,SAAS,EAAC,mCAAmC,aAChD,wDAAqB,EACrB,kCAAM,SAAS,EAAC,WAAW,aAAE,QAAQ,cAAS,IAC1C,EACN,iCAAK,SAAS,EAAC,mCAAmC,aAChD,oDAAiB,EACjB,kCAAM,SAAS,EAAC,WAAW,aAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,cAAS,IACjD,IACF,IACF,IACF,EAGN,iCAAK,SAAS,EAAC,oDAAoD,aAChE,CAAC,QAAQ,IAAI,CACZ,iCAAK,SAAS,EAAC,yBAAyB,aACtC,iCAAM,SAAS,EAAC,+BAA+B,qBAAY,EAC3D,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,EACnD,SAAS,EAAC,yDAAyD,uBAG5D,EACT,kCACE,IAAI,EAAC,OAAO,EACZ,GAAG,EAAE,CAAC,EACN,GAAG,EAAE,CAAC,EACN,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,IAAI,EACX,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAChD,SAAS,EAAC,0BAA0B,GACpC,EACF,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,EACnD,SAAS,EAAC,yDAAyD,kBAG5D,EACT,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE;4CACZ,OAAO,CAAC,CAAC,CAAC,CAAA;4CACV,WAAW,CAAC,CAAC,CAAC,CAAA;4CACd,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;wCACzB,CAAC,EACD,SAAS,EAAC,yDAAyD,sBAG5D,IACL,CACP,EACD,iCAAK,SAAS,EAAC,qCAAqC,aAClD,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,IAAI,EACd,SAAS,EAAC,yDAAyD,uBAG5D,EACT,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,iBAAiB,EAC1B,QAAQ,EAAE,IAAI,EACd,SAAS,EAAC,yDAAyD,6BAG5D,EACT,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,IAAI,IAAI,QAAQ,IAAI,CAAC,iBAAiB,EAChD,SAAS,EAAC,+GAA+G,YAExH,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,GAC3B,IACL,IACF,IACK,IApOT,UAAU,CAqOH,CACd,GACe,CACnB,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,eAAe,CACtB,GAA6B,EAC7B,KAAuB,EACvB,IAAc,EACd,QAAgB,EAChB,IAAY,EACZ,IAAY;IAEZ,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;QACnB,GAAG,CAAC,SAAS,CACX,KAAK,EACL,IAAI,CAAC,CAAC,EACN,IAAI,CAAC,CAAC,EACN,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,MAAM,EACX,CAAC,EACD,CAAC,EACD,IAAI,EACJ,IAAI,CACL,CAAA;QACD,OAAM;IACR,CAAC;IACD,yEAAyE;IACzE,qBAAqB;IACrB,MAAM,GAAG,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,CAAA;IACtC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;IACnC,MAAM,EAAE,GAAG,KAAK,CAAC,YAAY,CAAA;IAC7B,MAAM,EAAE,GAAG,KAAK,CAAC,aAAa,CAAA;IAC9B,MAAM,GAAG,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,GAAG,CAAA;IAC/B,MAAM,GAAG,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,GAAG,CAAA;IAC/B,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAA;IAC5C,GAAG,CAAC,KAAK,GAAG,GAAG,CAAA;IACf,GAAG,CAAC,MAAM,GAAG,GAAG,CAAA;IAChB,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,CAAC,IAAI;QAAE,OAAM;IACjB,IAAI,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAA;IAChC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAChB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;IACvC,GAAG,CAAC,SAAS,CACX,GAAG,EACH,IAAI,CAAC,CAAC,EACN,IAAI,CAAC,CAAC,EACN,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,MAAM,EACX,CAAC,EACD,CAAC,EACD,IAAI,EACJ,IAAI,CACL,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,cAAc,CAC3B,QAAgB,EAChB,IAAc,EACd,QAAgB,EAChB,UAAkB,EAClB,OAAe;IAEf,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAA;IACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAA;IAC/C,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;IACzB,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;IAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;IACnC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAA;IAC1D,GAAG,CAAC,qBAAqB,GAAG,MAAM,CAAA;IAClC,eAAe,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IACpE,MAAM,UAAU,GAAG,UAAU,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAA;IAC1E,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,MAAM,CAAC,MAAM,CACX,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAC5E,UAAU,EACV,UAAU,KAAK,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAClD,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE,CAAA;QACvB,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAC/B,GAAG,CAAC,OAAO,GAAG,MAAM,CAAA;QACpB,GAAG,CAAC,GAAG,GAAG,GAAG,CAAA;IACf,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,GAAG,CAAC,GAAG,GAA6C;IAC3D,OAAO,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACtC,CAAC;AAED,SAAS,cAAc;IACrB,OAAO,CACL,iCACE,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,SAAS,EAAC,UAAU,iBACR,MAAM,aAElB,iCAAM,CAAC,EAAC,uBAAuB,GAAG,EAClC,iCAAM,CAAC,EAAC,UAAU,GAAG,IACjB,CACP,CAAA;AACH,CAAC;AAED,SAAS,eAAe;IACtB,OAAO,CACL,iCACE,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,SAAS,EAAC,UAAU,iBACR,MAAM,aAElB,iCAAM,CAAC,EAAC,wBAAwB,GAAG,EACnC,iCAAM,CAAC,EAAC,YAAY,GAAG,IACnB,CACP,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentroy-co/client-sdk",
3
- "version": "2.13.0",
3
+ "version": "2.13.1",
4
4
  "description": "TypeScript SDK + CLI for the Sentroy platform — mail, storage, env vault + React components.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,21 +1,28 @@
1
- import { useCallback, useEffect, useState } from "react"
1
+ import { useCallback, useEffect, useRef, useState } from "react"
2
2
  import Cropper from "react-easy-crop"
3
3
  import { motion, AnimatePresence } from "motion/react"
4
4
 
5
5
  /**
6
- * Image crop dialog — `react-easy-crop` üzerine ince bir wrapper. Storage
7
- * upload akışında `preprocessFile` hook'unun içinde çağrılır:
6
+ * Image crop dialog — `react-easy-crop` üzerine professional crop UI:
8
7
  * - Aspect preset toolbar (1:1, 4:3, 16:9, 3:2, 9:16, Free)
9
8
  * - Zoom slider (+/− ile de)
10
- * - Pan + touch built-in (`react-easy-crop`)
11
- * - Apply cropped Blob, Cancel null, "Use original" → original File
9
+ * - Rotate 90° CW/CCW (`R` shortcut)
10
+ * - Sağ panelde **live preview thumbnail** crop alanı her değiştiğinde
11
+ * küçük canvas'a aynı transformasyonu uygulayıp output sonucunu gösterir;
12
+ * kullanıcı Apply'a basmadan "ne çıkacak" göründüğü için
13
+ * Photoshop / Figma seviyesinde feedback.
14
+ * - Output pixel boyutu (örn. 1200×800) — RP'lerin export presetlerine
15
+ * hizalanmak için.
16
+ * - Apply → cropped Blob, Cancel → null, "Use original" → original File.
12
17
  *
13
18
  * Ayrı bir entry point (`@sentroy-co/client-sdk/react/crop`) — ana SDK
14
19
  * import'u `react-easy-crop`'u bundle'a çekmesin (lazy subpath).
15
20
  *
16
- * Lazy çağrı pattern: Caller tarafında `await openCropDialog(file)` yardımcı
17
- * fonksiyonu (bkz `./openCropDialog`) modal mount/unmount'u yönetir,
18
- * Promise<File | null> döner.
21
+ * `getCroppedBlob` rotation-aware: önce kaynak image rotate edilir,
22
+ * sonra `croppedAreaPixels`'in döndürülmüş koordinat sisteminden çıkarılır.
23
+ * react-easy-crop'un `croppedAreaPixels` çıktısı zaten rotation'a göre
24
+ * transform edilmiş — biz canvas'a aynı rotation'ı uygulayıp aynı koordinat
25
+ * sisteminde drawImage yapıyoruz.
19
26
  */
20
27
 
21
28
  interface CropArea {
@@ -35,6 +42,7 @@ const ASPECT_PRESETS: Array<{ id: string; label: string; aspect: number | null }
35
42
  ]
36
43
 
37
44
  const MAX_PIXEL_GUARD = 50_000_000 // ~24 MP — üstü tarayıcı memory peak'i riskli
45
+ const PREVIEW_MAX_DIM = 220 // sidebar thumbnail için max edge (px)
38
46
 
39
47
  export interface CropDialogProps {
40
48
  open: boolean
@@ -61,9 +69,13 @@ export function CropDialog({
61
69
  const [aspectId, setAspectId] = useState(defaultAspect)
62
70
  const [crop, setCrop] = useState({ x: 0, y: 0 })
63
71
  const [zoom, setZoom] = useState(1)
72
+ const [rotation, setRotation] = useState(0)
64
73
  const [croppedAreaPixels, setCroppedAreaPixels] = useState<CropArea | null>(null)
65
74
  const [busy, setBusy] = useState(false)
66
75
  const [tooLarge, setTooLarge] = useState(false)
76
+ const previewCanvasRef = useRef<HTMLCanvasElement | null>(null)
77
+ const previewRafRef = useRef<number | null>(null)
78
+ const imageElRef = useRef<HTMLImageElement | null>(null)
67
79
 
68
80
  // Object URL lifecycle
69
81
  useEffect(() => {
@@ -72,17 +84,22 @@ export function CropDialog({
72
84
  setImageUrl(url)
73
85
  setCrop({ x: 0, y: 0 })
74
86
  setZoom(1)
87
+ setRotation(0)
75
88
  setAspectId(defaultAspect)
76
89
  setTooLarge(false)
77
- // Pixel guard large image decode tarayıcıyı çökertir
90
+ // Pixel guard + cache HTMLImageElement (preview drawImage source).
78
91
  const img = new Image()
79
92
  img.onload = () => {
80
93
  if (img.naturalWidth * img.naturalHeight > MAX_PIXEL_GUARD) {
81
94
  setTooLarge(true)
82
95
  }
96
+ imageElRef.current = img
83
97
  }
84
98
  img.src = url
85
- return () => URL.revokeObjectURL(url)
99
+ return () => {
100
+ URL.revokeObjectURL(url)
101
+ imageElRef.current = null
102
+ }
86
103
  }, [open, file, defaultAspect])
87
104
 
88
105
  const onCropComplete = useCallback(
@@ -92,6 +109,43 @@ export function CropDialog({
92
109
  [],
93
110
  )
94
111
 
112
+ // Live preview render — crop / rotation değiştikçe sağ panel thumbnail'ı
113
+ // güncelle. requestAnimationFrame ile throttle (drag sırasında her event'te
114
+ // canvas çizmek pahalı).
115
+ useEffect(() => {
116
+ if (!croppedAreaPixels || !imageElRef.current || tooLarge) return
117
+ if (previewRafRef.current !== null) {
118
+ cancelAnimationFrame(previewRafRef.current)
119
+ }
120
+ previewRafRef.current = requestAnimationFrame(() => {
121
+ const canvas = previewCanvasRef.current
122
+ const img = imageElRef.current
123
+ if (!canvas || !img) return
124
+ const area = croppedAreaPixels
125
+ // Preview boyutu — aspect korunarak max edge PREVIEW_MAX_DIM
126
+ const scale =
127
+ area.width >= area.height
128
+ ? PREVIEW_MAX_DIM / area.width
129
+ : PREVIEW_MAX_DIM / area.height
130
+ const pw = Math.max(1, Math.round(area.width * scale))
131
+ const ph = Math.max(1, Math.round(area.height * scale))
132
+ canvas.width = pw
133
+ canvas.height = ph
134
+ const ctx = canvas.getContext("2d")
135
+ if (!ctx) return
136
+ ctx.imageSmoothingQuality = "high"
137
+ ctx.clearRect(0, 0, pw, ph)
138
+ drawRotatedCrop(ctx, img, area, rotation, pw, ph)
139
+ previewRafRef.current = null
140
+ })
141
+ return () => {
142
+ if (previewRafRef.current !== null) {
143
+ cancelAnimationFrame(previewRafRef.current)
144
+ previewRafRef.current = null
145
+ }
146
+ }
147
+ }, [croppedAreaPixels, rotation, tooLarge])
148
+
95
149
  const aspect =
96
150
  ASPECT_PRESETS.find((p) => p.id === aspectId)?.aspect ?? undefined
97
151
 
@@ -99,8 +153,13 @@ export function CropDialog({
99
153
  if (!imageUrl || !croppedAreaPixels) return
100
154
  setBusy(true)
101
155
  try {
102
- const blob = await getCroppedBlob(imageUrl, croppedAreaPixels, file.type, outputQuality)
103
- // Cropped File — orijinal name'i koru ama uzantı output type'ına göre
156
+ const blob = await getCroppedBlob(
157
+ imageUrl,
158
+ croppedAreaPixels,
159
+ rotation,
160
+ file.type,
161
+ outputQuality,
162
+ )
104
163
  const ext = blob.type === "image/png" ? "png" : "jpg"
105
164
  const baseName = file.name.replace(/\.[^.]+$/, "")
106
165
  const cropped = new File([blob], `${baseName}.${ext}`, {
@@ -110,23 +169,48 @@ export function CropDialog({
110
169
  } finally {
111
170
  setBusy(false)
112
171
  }
113
- }, [imageUrl, croppedAreaPixels, file, onClose, outputQuality])
172
+ }, [imageUrl, croppedAreaPixels, rotation, file, onClose, outputQuality])
114
173
 
115
174
  const handleUseOriginal = useCallback(() => onClose(file), [file, onClose])
116
175
  const handleCancel = useCallback(() => onClose(null), [onClose])
176
+ const handleRotate = useCallback(
177
+ (delta: 90 | -90) => {
178
+ setRotation((r) => {
179
+ const next = (r + delta + 360) % 360
180
+ return next
181
+ })
182
+ },
183
+ [],
184
+ )
117
185
 
118
- // ESC kapatır
186
+ // ESC kapatır, R döndürür
119
187
  useEffect(() => {
120
188
  if (!open) return
121
189
  const onKey = (e: KeyboardEvent) => {
122
190
  if (e.key === "Escape") {
123
191
  e.stopPropagation()
124
192
  handleCancel()
193
+ } else if (e.key === "r" || e.key === "R") {
194
+ if (
195
+ e.target instanceof HTMLElement &&
196
+ (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA")
197
+ ) {
198
+ return
199
+ }
200
+ e.preventDefault()
201
+ handleRotate(e.shiftKey ? -90 : 90)
125
202
  }
126
203
  }
127
204
  window.addEventListener("keydown", onKey)
128
205
  return () => window.removeEventListener("keydown", onKey)
129
- }, [open, handleCancel])
206
+ }, [open, handleCancel, handleRotate])
207
+
208
+ const outputWidth = croppedAreaPixels ? Math.round(croppedAreaPixels.width) : 0
209
+ const outputHeight = croppedAreaPixels ? Math.round(croppedAreaPixels.height) : 0
210
+ // Rotation 90° / 270° iken output dimensions swap edilir (canvas rotate
211
+ // sonrası kullanıcı görsel olarak swap görür).
212
+ const displayW = rotation % 180 === 0 ? outputWidth : outputHeight
213
+ const displayH = rotation % 180 === 0 ? outputHeight : outputWidth
130
214
 
131
215
  return (
132
216
  <AnimatePresence>
@@ -137,7 +221,6 @@ export function CropDialog({
137
221
  animate={{ opacity: 1 }}
138
222
  exit={{ opacity: 0 }}
139
223
  transition={{ duration: 0.2 }}
140
- // z-index ana MediaManager modal'ından yüksek (nested)
141
224
  className="fixed inset-0 z-[60] flex items-center justify-center bg-black/80 backdrop-blur-sm p-4"
142
225
  onClick={(e) => {
143
226
  if (e.target === e.currentTarget) handleCancel()
@@ -148,11 +231,11 @@ export function CropDialog({
148
231
  animate={{ opacity: 1, scale: 1, y: 0 }}
149
232
  exit={{ opacity: 0, scale: 0.98 }}
150
233
  transition={{ duration: 0.25, ease: [0.22, 1, 0.36, 1] }}
151
- className="flex h-[min(90vh,720px)] w-full max-w-3xl flex-col overflow-hidden rounded-xl border bg-background shadow-2xl"
234
+ className="flex h-[min(92vh,760px)] w-full max-w-5xl flex-col overflow-hidden rounded-xl border bg-background shadow-2xl"
152
235
  >
153
236
  {/* Header */}
154
237
  <div className="flex items-center justify-between gap-3 border-b px-4 py-3">
155
- <div className="flex flex-col">
238
+ <div className="flex flex-col min-w-0">
156
239
  <span className="text-sm font-semibold">Crop image</span>
157
240
  <span className="truncate text-xs text-muted-foreground">
158
241
  {file.name}
@@ -178,45 +261,119 @@ export function CropDialog({
178
261
  </button>
179
262
  </div>
180
263
 
181
- {/* Aspect toolbar */}
264
+ {/* Aspect + rotate toolbar */}
182
265
  <div className="flex flex-wrap items-center gap-1 border-b bg-muted/20 px-3 py-2">
183
- {ASPECT_PRESETS.map((p) => (
266
+ <div className="flex flex-1 flex-wrap items-center gap-1">
267
+ {ASPECT_PRESETS.map((p) => (
268
+ <button
269
+ key={p.id}
270
+ type="button"
271
+ onClick={() => setAspectId(p.id)}
272
+ className={cls(
273
+ "rounded-md px-2.5 py-1 text-xs transition-colors",
274
+ aspectId === p.id
275
+ ? "bg-foreground text-background"
276
+ : "text-muted-foreground hover:bg-muted hover:text-foreground",
277
+ )}
278
+ >
279
+ {p.label}
280
+ </button>
281
+ ))}
282
+ </div>
283
+ <div className="ms-auto flex items-center gap-1">
184
284
  <button
185
- key={p.id}
186
285
  type="button"
187
- onClick={() => setAspectId(p.id)}
188
- className={cls(
189
- "rounded-md px-2.5 py-1 text-xs transition-colors",
190
- aspectId === p.id
191
- ? "bg-foreground text-background"
192
- : "text-muted-foreground hover:bg-muted hover:text-foreground",
193
- )}
286
+ onClick={() => handleRotate(-90)}
287
+ title="Rotate left (Shift+R)"
288
+ aria-label="Rotate left"
289
+ className="rounded-md border px-2 py-1 text-xs text-muted-foreground hover:bg-muted/50 hover:text-foreground"
290
+ >
291
+ <RotateLeftIcon />
292
+ </button>
293
+ <button
294
+ type="button"
295
+ onClick={() => handleRotate(90)}
296
+ title="Rotate right (R)"
297
+ aria-label="Rotate right"
298
+ className="rounded-md border px-2 py-1 text-xs text-muted-foreground hover:bg-muted/50 hover:text-foreground"
194
299
  >
195
- {p.label}
300
+ <RotateRightIcon />
196
301
  </button>
197
- ))}
302
+ </div>
198
303
  </div>
199
304
 
200
- {/* Cropper canvas */}
201
- <div className="relative flex-1 bg-black">
202
- {tooLarge ? (
203
- <div className="flex h-full w-full items-center justify-center p-6 text-center text-sm text-white/70">
204
- Image too large to crop in browser. Upload as-is or resize
205
- beforehand.
305
+ {/* Body: cropper + preview sidebar */}
306
+ <div className="flex flex-1 min-h-0 flex-col overflow-hidden md:flex-row">
307
+ {/* Cropper canvas */}
308
+ <div className="relative flex-1 bg-black">
309
+ {tooLarge ? (
310
+ <div className="flex h-full w-full items-center justify-center p-6 text-center text-sm text-white/70">
311
+ Image too large to crop in browser. Upload as-is or resize
312
+ beforehand.
313
+ </div>
314
+ ) : (
315
+ <Cropper
316
+ image={imageUrl}
317
+ crop={crop}
318
+ zoom={zoom}
319
+ rotation={rotation}
320
+ aspect={aspect}
321
+ onCropChange={setCrop}
322
+ onCropComplete={onCropComplete}
323
+ onZoomChange={setZoom}
324
+ onRotationChange={setRotation}
325
+ showGrid
326
+ objectFit="contain"
327
+ />
328
+ )}
329
+ </div>
330
+
331
+ {/* Preview sidebar */}
332
+ <div className="flex w-full shrink-0 flex-col gap-3 border-t bg-muted/10 p-4 md:w-64 md:border-l md:border-t-0">
333
+ <div className="flex items-center justify-between">
334
+ <span className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
335
+ Preview
336
+ </span>
337
+ {displayW > 0 && displayH > 0 && (
338
+ <span className="font-mono text-[10px] text-muted-foreground">
339
+ {displayW}×{displayH}
340
+ </span>
341
+ )}
206
342
  </div>
207
- ) : (
208
- <Cropper
209
- image={imageUrl}
210
- crop={crop}
211
- zoom={zoom}
212
- aspect={aspect}
213
- onCropChange={setCrop}
214
- onCropComplete={onCropComplete}
215
- onZoomChange={setZoom}
216
- showGrid
217
- objectFit="contain"
218
- />
219
- )}
343
+ <div className="flex min-h-[140px] items-center justify-center rounded-lg border border-dashed bg-background/40 p-2">
344
+ {tooLarge ? (
345
+ <span className="text-[11px] text-muted-foreground">
346
+ Preview unavailable
347
+ </span>
348
+ ) : croppedAreaPixels ? (
349
+ <canvas
350
+ ref={previewCanvasRef}
351
+ className="max-h-[220px] max-w-full rounded-sm shadow-sm"
352
+ />
353
+ ) : (
354
+ <span className="text-[11px] text-muted-foreground">
355
+ Adjust crop…
356
+ </span>
357
+ )}
358
+ </div>
359
+ <div className="flex flex-col gap-1 text-[11px] text-muted-foreground">
360
+ <div className="flex items-center justify-between">
361
+ <span>Aspect</span>
362
+ <span className="font-mono">
363
+ {ASPECT_PRESETS.find((p) => p.id === aspectId)?.label ??
364
+ "Free"}
365
+ </span>
366
+ </div>
367
+ <div className="flex items-center justify-between">
368
+ <span>Rotation</span>
369
+ <span className="font-mono">{rotation}°</span>
370
+ </div>
371
+ <div className="flex items-center justify-between">
372
+ <span>Zoom</span>
373
+ <span className="font-mono">{zoom.toFixed(2)}×</span>
374
+ </div>
375
+ </div>
376
+ </div>
220
377
  </div>
221
378
 
222
379
  {/* Zoom + actions */}
@@ -251,6 +408,7 @@ export function CropDialog({
251
408
  type="button"
252
409
  onClick={() => {
253
410
  setZoom(1)
411
+ setRotation(0)
254
412
  setCrop({ x: 0, y: 0 })
255
413
  }}
256
414
  className="rounded-md border px-2 py-0.5 text-xs hover:bg-muted/50"
@@ -293,6 +451,68 @@ export function CropDialog({
293
451
  )
294
452
  }
295
453
 
454
+ /**
455
+ * Rotated crop'u canvas'a yaz. Rotation 0° fast-path; aksi halde önce
456
+ * "rotated bounding box" canvas'ı oluştur (orijinal image'in döndürülmüş
457
+ * versiyonu), sonra croppedAreaPixels koordinatlarında çıkar.
458
+ *
459
+ * react-easy-crop's `croppedAreaPixels` rotation-aware: koordinatlar
460
+ * orijinal image'i rotasyon merkezinden döndürdükten sonraki "visual"
461
+ * bounding box üzerinden hesaplanır — biz aynı transform'u canvas'a
462
+ * uygulayıp aynı koordinatlardan drawImage yapıyoruz.
463
+ */
464
+ function drawRotatedCrop(
465
+ ctx: CanvasRenderingContext2D,
466
+ image: HTMLImageElement,
467
+ area: CropArea,
468
+ rotation: number,
469
+ dstW: number,
470
+ dstH: number,
471
+ ): void {
472
+ if (rotation === 0) {
473
+ ctx.drawImage(
474
+ image,
475
+ area.x,
476
+ area.y,
477
+ area.width,
478
+ area.height,
479
+ 0,
480
+ 0,
481
+ dstW,
482
+ dstH,
483
+ )
484
+ return
485
+ }
486
+ // Build a rotated source canvas at the size of the rotated bounding box,
487
+ // then crop from it.
488
+ const rad = (rotation * Math.PI) / 180
489
+ const sin = Math.abs(Math.sin(rad))
490
+ const cos = Math.abs(Math.cos(rad))
491
+ const iw = image.naturalWidth
492
+ const ih = image.naturalHeight
493
+ const bbW = iw * cos + ih * sin
494
+ const bbH = iw * sin + ih * cos
495
+ const tmp = document.createElement("canvas")
496
+ tmp.width = bbW
497
+ tmp.height = bbH
498
+ const tctx = tmp.getContext("2d")
499
+ if (!tctx) return
500
+ tctx.translate(bbW / 2, bbH / 2)
501
+ tctx.rotate(rad)
502
+ tctx.drawImage(image, -iw / 2, -ih / 2)
503
+ ctx.drawImage(
504
+ tmp,
505
+ area.x,
506
+ area.y,
507
+ area.width,
508
+ area.height,
509
+ 0,
510
+ 0,
511
+ dstW,
512
+ dstH,
513
+ )
514
+ }
515
+
296
516
  /**
297
517
  * Canvas ile crop area'yı çıkar + Blob döndür.
298
518
  * Output MIME: PNG ise PNG, diğerleri JPEG (transparency yoksa).
@@ -300,6 +520,7 @@ export function CropDialog({
300
520
  async function getCroppedBlob(
301
521
  imageUrl: string,
302
522
  area: CropArea,
523
+ rotation: number,
303
524
  sourceMime: string,
304
525
  quality: number,
305
526
  ): Promise<Blob> {
@@ -309,17 +530,8 @@ async function getCroppedBlob(
309
530
  canvas.height = area.height
310
531
  const ctx = canvas.getContext("2d")
311
532
  if (!ctx) throw new Error("Canvas 2D context unavailable")
312
- ctx.drawImage(
313
- image,
314
- area.x,
315
- area.y,
316
- area.width,
317
- area.height,
318
- 0,
319
- 0,
320
- area.width,
321
- area.height,
322
- )
533
+ ctx.imageSmoothingQuality = "high"
534
+ drawRotatedCrop(ctx, image, area, rotation, area.width, area.height)
323
535
  const outputMime = sourceMime === "image/png" ? "image/png" : "image/jpeg"
324
536
  return new Promise<Blob>((resolve, reject) => {
325
537
  canvas.toBlob(
@@ -342,3 +554,39 @@ function loadImage(url: string): Promise<HTMLImageElement> {
342
554
  function cls(...arr: Array<string | false | null | undefined>): string {
343
555
  return arr.filter(Boolean).join(" ")
344
556
  }
557
+
558
+ function RotateLeftIcon() {
559
+ return (
560
+ <svg
561
+ viewBox="0 0 24 24"
562
+ fill="none"
563
+ stroke="currentColor"
564
+ strokeWidth="2"
565
+ strokeLinecap="round"
566
+ strokeLinejoin="round"
567
+ className="size-3.5"
568
+ aria-hidden="true"
569
+ >
570
+ <path d="M3 12a9 9 0 1 0 3-6.7" />
571
+ <path d="M3 4v5h5" />
572
+ </svg>
573
+ )
574
+ }
575
+
576
+ function RotateRightIcon() {
577
+ return (
578
+ <svg
579
+ viewBox="0 0 24 24"
580
+ fill="none"
581
+ stroke="currentColor"
582
+ strokeWidth="2"
583
+ strokeLinecap="round"
584
+ strokeLinejoin="round"
585
+ className="size-3.5"
586
+ aria-hidden="true"
587
+ >
588
+ <path d="M21 12a9 9 0 1 1-3-6.7" />
589
+ <path d="M21 4v5h-5" />
590
+ </svg>
591
+ )
592
+ }