@sentroy-co/client-sdk 2.13.0 → 2.13.2

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,3 +1,4 @@
1
+ import "react-advanced-cropper/dist/style.css";
1
2
  export interface CropDialogProps {
2
3
  open: boolean;
3
4
  /** Crop edilecek dosya. Image MIME değilse modal hiç açılmaz (caller'da
@@ -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":"AAGA,OAAO,uCAAuC,CAAA;AAwC9C,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,2CAgYjB"}
@@ -1,13 +1,32 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.CropDialog = CropDialog;
7
4
  const jsx_runtime_1 = require("react/jsx-runtime");
8
5
  const react_1 = require("react");
9
- const react_easy_crop_1 = __importDefault(require("react-easy-crop"));
6
+ const react_advanced_cropper_1 = require("react-advanced-cropper");
10
7
  const react_2 = require("motion/react");
8
+ require("react-advanced-cropper/dist/style.css");
9
+ /**
10
+ * Image crop dialog — iOS Photos benzeri full-screen crop UI. Önceki
11
+ * `react-easy-crop` implementation'ı drag UX ve preview render
12
+ * tarafında zayıftı; `react-advanced-cropper` daha modern stencil
13
+ * sistemi + native-feel pinch/zoom verir.
14
+ *
15
+ * Akış (storage upload pipeline'ından preprocess hook):
16
+ * - Aspect preset toolbar (1:1, 4:3, 16:9, 3:2, 9:16, Free)
17
+ * - Rotate 90° CW/CCW butonları (R / Shift+R)
18
+ * - Cropper'ın `getCanvas()` çıktısından **live preview**
19
+ * thumbnail — kullanıcı Apply'a basmadan sonucu görür.
20
+ * - Output pixel boyutu (örn. 1200×800) read-out.
21
+ * - Apply: ref üzerinden `getCanvas().toBlob` → File.
22
+ * - "Use original": orijinal File döner; Cancel: null.
23
+ *
24
+ * Tam ekran: `inset-0`, scrim'siz; consent flow gibi destination-only UX —
25
+ * dialog her şeyi kaplar, dikkat dağıtmaz.
26
+ *
27
+ * Lazy subpath (`@sentroy-co/client-sdk/react/crop`) — ana SDK import'u
28
+ * cropper bundle'ı yutmasın.
29
+ */
11
30
  const ASPECT_PRESETS = [
12
31
  { id: "free", label: "Free", aspect: null },
13
32
  { id: "1:1", label: "1:1", aspect: 1 },
@@ -17,24 +36,26 @@ const ASPECT_PRESETS = [
17
36
  { id: "9:16", label: "9:16", aspect: 9 / 16 },
18
37
  ];
19
38
  const MAX_PIXEL_GUARD = 50_000_000; // ~24 MP — üstü tarayıcı memory peak'i riskli
39
+ const PREVIEW_MAX_DIM = 240;
20
40
  function CropDialog({ open, file, onClose, defaultAspect = "free", outputQuality = 0.92, }) {
21
41
  const [imageUrl, setImageUrl] = (0, react_1.useState)(null);
22
42
  const [aspectId, setAspectId] = (0, react_1.useState)(defaultAspect);
23
- const [crop, setCrop] = (0, react_1.useState)({ x: 0, y: 0 });
24
- const [zoom, setZoom] = (0, react_1.useState)(1);
25
- const [croppedAreaPixels, setCroppedAreaPixels] = (0, react_1.useState)(null);
26
43
  const [busy, setBusy] = (0, react_1.useState)(false);
27
44
  const [tooLarge, setTooLarge] = (0, react_1.useState)(false);
45
+ const [outputSize, setOutputSize] = (0, react_1.useState)(null);
46
+ const [previewDataUrl, setPreviewDataUrl] = (0, react_1.useState)(null);
47
+ const cropperRef = (0, react_1.useRef)(null);
48
+ const previewRafRef = (0, react_1.useRef)(null);
28
49
  // Object URL lifecycle
29
50
  (0, react_1.useEffect)(() => {
30
51
  if (!open)
31
52
  return;
32
53
  const url = URL.createObjectURL(file);
33
54
  setImageUrl(url);
34
- setCrop({ x: 0, y: 0 });
35
- setZoom(1);
36
55
  setAspectId(defaultAspect);
37
56
  setTooLarge(false);
57
+ setOutputSize(null);
58
+ setPreviewDataUrl(null);
38
59
  // Pixel guard — large image decode tarayıcıyı çökertir
39
60
  const img = new Image();
40
61
  img.onload = () => {
@@ -43,19 +64,95 @@ function CropDialog({ open, file, onClose, defaultAspect = "free", outputQuality
43
64
  }
44
65
  };
45
66
  img.src = url;
46
- return () => URL.revokeObjectURL(url);
67
+ return () => {
68
+ URL.revokeObjectURL(url);
69
+ if (previewRafRef.current !== null) {
70
+ cancelAnimationFrame(previewRafRef.current);
71
+ previewRafRef.current = null;
72
+ }
73
+ };
47
74
  }, [open, file, defaultAspect]);
48
- const onCropComplete = (0, react_1.useCallback)((_area, areaPixels) => {
49
- setCroppedAreaPixels(areaPixels);
75
+ const aspectRatio = ASPECT_PRESETS.find((p) => p.id === aspectId)?.aspect ?? undefined;
76
+ // Aspect preset değişirse cropper coordinates'ini yeni aspect'e göre
77
+ // güncelle. `setCoordinates` ile aspect'i zorla.
78
+ (0, react_1.useEffect)(() => {
79
+ if (!cropperRef.current)
80
+ return;
81
+ if (aspectRatio === undefined)
82
+ return;
83
+ const state = cropperRef.current.getState();
84
+ if (!state)
85
+ return;
86
+ const { coordinates } = state;
87
+ if (!coordinates)
88
+ return;
89
+ const current = coordinates.width / coordinates.height;
90
+ if (Math.abs(current - aspectRatio) < 0.001)
91
+ return;
92
+ // Aspect ratio'ya snap — width'i koru, height'i hesapla
93
+ const newWidth = coordinates.width;
94
+ const newHeight = coordinates.width / aspectRatio;
95
+ cropperRef.current.setCoordinates({
96
+ width: newWidth,
97
+ height: newHeight,
98
+ });
99
+ }, [aspectRatio, aspectId]);
100
+ // Live preview render — cropper change event'inde getCanvas() ile
101
+ // küçük thumbnail üret. RAF ile throttle.
102
+ const renderPreview = (0, react_1.useCallback)(() => {
103
+ if (previewRafRef.current !== null) {
104
+ cancelAnimationFrame(previewRafRef.current);
105
+ }
106
+ previewRafRef.current = requestAnimationFrame(() => {
107
+ previewRafRef.current = null;
108
+ const cropper = cropperRef.current;
109
+ if (!cropper)
110
+ return;
111
+ const canvas = cropper.getCanvas({
112
+ // Preview canvas — düşük boyut, smooth (output quality değil)
113
+ maxWidth: PREVIEW_MAX_DIM * 2,
114
+ maxHeight: PREVIEW_MAX_DIM * 2,
115
+ imageSmoothingQuality: "medium",
116
+ });
117
+ if (!canvas)
118
+ return;
119
+ setOutputSize({ w: canvas.width, h: canvas.height });
120
+ // Data URL — küçük canvas; performant
121
+ try {
122
+ setPreviewDataUrl(canvas.toDataURL("image/jpeg", 0.7));
123
+ }
124
+ catch {
125
+ // toDataURL nadir tainted-canvas durumunda fail edebilir; sessiz geç
126
+ }
127
+ });
50
128
  }, []);
51
- const aspect = ASPECT_PRESETS.find((p) => p.id === aspectId)?.aspect ?? undefined;
129
+ const handleCropperChange = (0, react_1.useCallback)(() => {
130
+ renderPreview();
131
+ }, [renderPreview]);
132
+ const handleCropperReady = (0, react_1.useCallback)(() => {
133
+ renderPreview();
134
+ }, [renderPreview]);
52
135
  const handleApply = (0, react_1.useCallback)(async () => {
53
- if (!imageUrl || !croppedAreaPixels)
136
+ const cropper = cropperRef.current;
137
+ if (!cropper)
54
138
  return;
55
139
  setBusy(true);
56
140
  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
141
+ const canvas = cropper.getCanvas({
142
+ imageSmoothingQuality: "high",
143
+ });
144
+ if (!canvas) {
145
+ setBusy(false);
146
+ return;
147
+ }
148
+ const outputMime = file.type === "image/png" ? "image/png" : "image/jpeg";
149
+ const blob = await new Promise((resolve) => {
150
+ canvas.toBlob((b) => resolve(b), outputMime, outputMime === "image/jpeg" ? outputQuality : undefined);
151
+ });
152
+ if (!blob) {
153
+ setBusy(false);
154
+ return;
155
+ }
59
156
  const ext = blob.type === "image/png" ? "png" : "jpg";
60
157
  const baseName = file.name.replace(/\.[^.]+$/, "");
61
158
  const cropped = new File([blob], `${baseName}.${ext}`, {
@@ -66,61 +163,106 @@ function CropDialog({ open, file, onClose, defaultAspect = "free", outputQuality
66
163
  finally {
67
164
  setBusy(false);
68
165
  }
69
- }, [imageUrl, croppedAreaPixels, file, onClose, outputQuality]);
166
+ }, [file, onClose, outputQuality]);
70
167
  const handleUseOriginal = (0, react_1.useCallback)(() => onClose(file), [file, onClose]);
71
168
  const handleCancel = (0, react_1.useCallback)(() => onClose(null), [onClose]);
72
- // ESC kapatır
169
+ const handleRotate = (0, react_1.useCallback)((delta) => {
170
+ cropperRef.current?.rotateImage(delta);
171
+ }, []);
172
+ const handleFlip = (0, react_1.useCallback)((axis) => {
173
+ cropperRef.current?.flipImage(axis === "h", axis === "v");
174
+ }, []);
175
+ // Keyboard shortcuts — ESC kapat, R rotate, F flip
73
176
  (0, react_1.useEffect)(() => {
74
177
  if (!open)
75
178
  return;
76
179
  const onKey = (e) => {
180
+ if (e.target instanceof HTMLElement &&
181
+ (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA")) {
182
+ return;
183
+ }
77
184
  if (e.key === "Escape") {
78
185
  e.stopPropagation();
79
186
  handleCancel();
187
+ return;
188
+ }
189
+ if (e.key === "r" || e.key === "R") {
190
+ e.preventDefault();
191
+ handleRotate(e.shiftKey ? -90 : 90);
80
192
  }
81
193
  };
82
194
  window.addEventListener("keydown", onKey);
83
195
  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) => {
88
- if (e.target === e.currentTarget)
89
- 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: () => {
93
- setZoom(1);
94
- setCrop({ x: 0, y: 0 });
95
- }, 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")) }));
196
+ }, [open, handleCancel, handleRotate]);
197
+ return ((0, jsx_runtime_1.jsx)(react_2.AnimatePresence, { children: open && imageUrl && ((0, jsx_runtime_1.jsxs)(react_2.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.2 },
198
+ // Tam ekran scrim değil, kendisi background; iOS Photos UX
199
+ className: "fixed inset-0 z-[60] flex flex-col bg-black text-white", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between gap-3 border-b border-white/10 bg-black/40 px-4 py-3 backdrop-blur-sm", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleCancel, disabled: busy, className: "rounded-md px-3 py-1.5 text-sm text-white/70 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-50", children: "Cancel" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-col items-center text-center", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm font-semibold", children: "Crop image" }), (0, jsx_runtime_1.jsx)("span", { className: "truncate max-w-xs text-[11px] text-white/50", children: file.name })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleApply, disabled: busy || tooLarge, className: "rounded-md bg-white px-3 py-1.5 text-sm font-medium text-black transition-opacity hover:opacity-90 disabled:opacity-50", children: busy ? "Cropping…" : "Apply" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center gap-2 border-b border-white/10 bg-black/30 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-full px-3 py-1 text-xs transition-colors", aspectId === p.id
200
+ ? "bg-white text-black"
201
+ : "text-white/60 hover:bg-white/10 hover:text-white"), children: p.label }, p.id))) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)(ToolbarIconButton, { onClick: () => handleRotate(-90), title: "Rotate left (Shift+R)", ariaLabel: "Rotate left", children: (0, jsx_runtime_1.jsx)(RotateLeftIcon, {}) }), (0, jsx_runtime_1.jsx)(ToolbarIconButton, { onClick: () => handleRotate(90), title: "Rotate right (R)", ariaLabel: "Rotate right", children: (0, jsx_runtime_1.jsx)(RotateRightIcon, {}) }), (0, jsx_runtime_1.jsx)("span", { className: "mx-1 h-5 w-px bg-white/15" }), (0, jsx_runtime_1.jsx)(ToolbarIconButton, { onClick: () => handleFlip("h"), title: "Flip horizontal", ariaLabel: "Flip horizontal", children: (0, jsx_runtime_1.jsx)(FlipHorizontalIcon, {}) }), (0, jsx_runtime_1.jsx)(ToolbarIconButton, { onClick: () => handleFlip("v"), title: "Flip vertical", ariaLabel: "Flip vertical", children: (0, jsx_runtime_1.jsx)(FlipVerticalIcon, {}) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-1 min-h-0 flex-col 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_advanced_cropper_1.Cropper, { ref: cropperRef, src: imageUrl, className: "sentroy-cropper",
202
+ // Stencil props aspect lock + iOS-like rect stencil grid
203
+ stencilProps: {
204
+ aspectRatio: aspectRatio,
205
+ grid: true,
206
+ movable: true,
207
+ resizable: true,
208
+ },
209
+ // Background overlay'i koyu yap (image dışı kalan kısım)
210
+ backgroundClassName: "sentroy-cropper-background", onChange: handleCropperChange, onReady: handleCropperReady })) }), (0, jsx_runtime_1.jsxs)("aside", { className: "flex w-full shrink-0 flex-col gap-4 border-t border-white/10 bg-black/30 p-4 md:w-72 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-[10px] font-medium uppercase tracking-wider text-white/50", children: "Preview" }), outputSize && ((0, jsx_runtime_1.jsxs)("span", { className: "font-mono text-[10px] text-white/40", children: [outputSize.w, "\u00D7", outputSize.h] }))] }), (0, jsx_runtime_1.jsx)("div", { className: "flex min-h-[160px] items-center justify-center rounded-lg border border-white/10 bg-black/50 p-3", children: tooLarge ? ((0, jsx_runtime_1.jsx)("span", { className: "text-[11px] text-white/50", children: "Preview unavailable" })) : previewDataUrl ? (
211
+ /* eslint-disable-next-line @next/next/no-img-element */
212
+ (0, jsx_runtime_1.jsx)("img", { src: previewDataUrl, alt: "Crop preview", className: "max-h-[240px] max-w-full rounded-md object-contain shadow-md" })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-[11px] text-white/40", children: "Adjust crop\u2026" })) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5 text-[11px] text-white/60", children: [(0, jsx_runtime_1.jsx)(Stat, { label: "Aspect", value: ASPECT_PRESETS.find((p) => p.id === aspectId)?.label ??
213
+ "Free" }), outputSize && ((0, jsx_runtime_1.jsx)(Stat, { label: "Output", value: `${outputSize.w} × ${outputSize.h} px` })), (0, jsx_runtime_1.jsx)(Stat, { label: "Format", value: file.type === "image/png" ? "PNG" : "JPEG" })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleUseOriginal, disabled: busy, className: "mt-auto w-full rounded-md border border-white/20 px-3 py-1.5 text-xs text-white/70 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-50", children: "Use original (skip crop)" })] })] }), (0, jsx_runtime_1.jsx)("style", { children: `
214
+ .sentroy-cropper {
215
+ height: 100%;
216
+ width: 100%;
217
+ background: #000;
218
+ }
219
+ .sentroy-cropper-background {
220
+ background-color: rgba(0, 0, 0, 0.7);
221
+ }
222
+ .sentroy-cropper .advanced-cropper-stencil-overlay {
223
+ background: rgba(0, 0, 0, 0.55);
224
+ }
225
+ .sentroy-cropper .advanced-cropper-rectangle-stencil__draggable-area {
226
+ border: 1px solid rgba(255, 255, 255, 0.95);
227
+ }
228
+ .sentroy-cropper .advanced-cropper-line-wrapper {
229
+ background: rgba(255, 255, 255, 0.95);
230
+ }
231
+ .sentroy-cropper .advanced-cropper-handler-wrapper--west-north,
232
+ .sentroy-cropper .advanced-cropper-handler-wrapper--north-east,
233
+ .sentroy-cropper .advanced-cropper-handler-wrapper--east-south,
234
+ .sentroy-cropper .advanced-cropper-handler-wrapper--south-west {
235
+ width: 22px;
236
+ height: 22px;
237
+ }
238
+ .sentroy-cropper .advanced-cropper-handler-wrapper__draggable {
239
+ background: #fff;
240
+ border: 2px solid #000;
241
+ width: 12px;
242
+ height: 12px;
243
+ border-radius: 2px;
244
+ }
245
+ ` })] }, "backdrop")) }));
96
246
  }
97
- /**
98
- * Canvas ile crop area'yı çıkar + Blob döndür.
99
- * Output MIME: PNG ise PNG, diğerleri JPEG (transparency yoksa).
100
- */
101
- async function getCroppedBlob(imageUrl, area, sourceMime, quality) {
102
- const image = await loadImage(imageUrl);
103
- const canvas = document.createElement("canvas");
104
- canvas.width = area.width;
105
- canvas.height = area.height;
106
- const ctx = canvas.getContext("2d");
107
- if (!ctx)
108
- 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);
110
- const outputMime = sourceMime === "image/png" ? "image/png" : "image/jpeg";
111
- return new Promise((resolve, reject) => {
112
- canvas.toBlob((blob) => (blob ? resolve(blob) : reject(new Error("toBlob returned null"))), outputMime, outputMime === "image/jpeg" ? quality : undefined);
113
- });
247
+ function Stat({ label, value }) {
248
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { children: label }), (0, jsx_runtime_1.jsx)("span", { className: "font-mono text-white/80", children: value })] }));
114
249
  }
115
- function loadImage(url) {
116
- return new Promise((resolve, reject) => {
117
- const img = new Image();
118
- img.onload = () => resolve(img);
119
- img.onerror = reject;
120
- img.src = url;
121
- });
250
+ function ToolbarIconButton({ onClick, title, ariaLabel, children, }) {
251
+ return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onClick, title: title, "aria-label": ariaLabel, className: "inline-flex size-8 items-center justify-center rounded-md text-white/70 transition-colors hover:bg-white/10 hover:text-white", children: children }));
122
252
  }
123
253
  function cls(...arr) {
124
254
  return arr.filter(Boolean).join(" ");
125
255
  }
256
+ function RotateLeftIcon() {
257
+ return ((0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "size-4", "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" })] }));
258
+ }
259
+ function RotateRightIcon() {
260
+ return ((0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "size-4", "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" })] }));
261
+ }
262
+ function FlipHorizontalIcon() {
263
+ return ((0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "size-4", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("path", { d: "M12 3v18" }), (0, jsx_runtime_1.jsx)("path", { d: "M16 7l4 5-4 5" }), (0, jsx_runtime_1.jsx)("path", { d: "M8 7l-4 5 4 5" })] }));
264
+ }
265
+ function FlipVerticalIcon() {
266
+ return ((0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "size-4", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("path", { d: "M3 12h18" }), (0, jsx_runtime_1.jsx)("path", { d: "M7 8l5-4 5 4" }), (0, jsx_runtime_1.jsx)("path", { d: "M7 16l5 4 5-4" })] }));
267
+ }
126
268
  //# 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":"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"}
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.2",
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",
@@ -100,6 +100,6 @@
100
100
  },
101
101
  "dependencies": {
102
102
  "motion": "^12.38.0",
103
- "react-easy-crop": "^5.5.7"
103
+ "react-advanced-cropper": "^0.20.1"
104
104
  }
105
105
  }
@@ -1,31 +1,35 @@
1
- import { useCallback, useEffect, useState } from "react"
2
- import Cropper from "react-easy-crop"
1
+ import { useCallback, useEffect, useRef, useState } from "react"
2
+ import { Cropper, type CropperRef } from "react-advanced-cropper"
3
3
  import { motion, AnimatePresence } from "motion/react"
4
+ import "react-advanced-cropper/dist/style.css"
4
5
 
5
6
  /**
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:
7
+ * Image crop dialog — iOS Photos benzeri full-screen crop UI. Önceki
8
+ * `react-easy-crop` implementation'ı drag UX ve preview render
9
+ * tarafında zayıftı; `react-advanced-cropper` daha modern stencil
10
+ * sistemi + native-feel pinch/zoom verir.
11
+ *
12
+ * Akış (storage upload pipeline'ından preprocess hook):
8
13
  * - Aspect preset toolbar (1:1, 4:3, 16:9, 3:2, 9:16, Free)
9
- * - Zoom slider (+/− ile de)
10
- * - Pan + touch built-in (`react-easy-crop`)
11
- * - Apply cropped Blob, Cancel → null, "Use original" → original File
14
+ * - Rotate 90° CW/CCW butonları (R / Shift+R)
15
+ * - Cropper'ın `getCanvas()` çıktısından **live preview**
16
+ * thumbnail kullanıcı Apply'a basmadan sonucu görür.
17
+ * - Output pixel boyutu (örn. 1200×800) read-out.
18
+ * - Apply: ref üzerinden `getCanvas().toBlob` → File.
19
+ * - "Use original": orijinal File döner; Cancel: null.
12
20
  *
13
- * Ayrı bir entry point (`@sentroy-co/client-sdk/react/crop`) ana SDK
14
- * import'u `react-easy-crop`'u bundle'a çekmesin (lazy subpath).
21
+ * Tam ekran: `inset-0`, scrim'siz; consent flow gibi destination-only UX
22
+ * dialog her şeyi kaplar, dikkat dağıtmaz.
15
23
  *
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.
24
+ * Lazy subpath (`@sentroy-co/client-sdk/react/crop`) ana SDK import'u
25
+ * cropper bundle'ı yutmasın.
19
26
  */
20
27
 
21
- interface CropArea {
22
- x: number
23
- y: number
24
- width: number
25
- height: number
26
- }
27
-
28
- const ASPECT_PRESETS: Array<{ id: string; label: string; aspect: number | null }> = [
28
+ const ASPECT_PRESETS: Array<{
29
+ id: string
30
+ label: string
31
+ aspect: number | null
32
+ }> = [
29
33
  { id: "free", label: "Free", aspect: null },
30
34
  { id: "1:1", label: "1:1", aspect: 1 },
31
35
  { id: "16:9", label: "16:9", aspect: 16 / 9 },
@@ -35,6 +39,7 @@ const ASPECT_PRESETS: Array<{ id: string; label: string; aspect: number | null }
35
39
  ]
36
40
 
37
41
  const MAX_PIXEL_GUARD = 50_000_000 // ~24 MP — üstü tarayıcı memory peak'i riskli
42
+ const PREVIEW_MAX_DIM = 240
38
43
 
39
44
  export interface CropDialogProps {
40
45
  open: boolean
@@ -59,21 +64,25 @@ export function CropDialog({
59
64
  }: CropDialogProps) {
60
65
  const [imageUrl, setImageUrl] = useState<string | null>(null)
61
66
  const [aspectId, setAspectId] = useState(defaultAspect)
62
- const [crop, setCrop] = useState({ x: 0, y: 0 })
63
- const [zoom, setZoom] = useState(1)
64
- const [croppedAreaPixels, setCroppedAreaPixels] = useState<CropArea | null>(null)
65
67
  const [busy, setBusy] = useState(false)
66
68
  const [tooLarge, setTooLarge] = useState(false)
69
+ const [outputSize, setOutputSize] = useState<{ w: number; h: number } | null>(
70
+ null,
71
+ )
72
+ const [previewDataUrl, setPreviewDataUrl] = useState<string | null>(null)
73
+
74
+ const cropperRef = useRef<CropperRef | null>(null)
75
+ const previewRafRef = useRef<number | null>(null)
67
76
 
68
77
  // Object URL lifecycle
69
78
  useEffect(() => {
70
79
  if (!open) return
71
80
  const url = URL.createObjectURL(file)
72
81
  setImageUrl(url)
73
- setCrop({ x: 0, y: 0 })
74
- setZoom(1)
75
82
  setAspectId(defaultAspect)
76
83
  setTooLarge(false)
84
+ setOutputSize(null)
85
+ setPreviewDataUrl(null)
77
86
  // Pixel guard — large image decode tarayıcıyı çökertir
78
87
  const img = new Image()
79
88
  img.onload = () => {
@@ -82,25 +91,97 @@ export function CropDialog({
82
91
  }
83
92
  }
84
93
  img.src = url
85
- return () => URL.revokeObjectURL(url)
94
+ return () => {
95
+ URL.revokeObjectURL(url)
96
+ if (previewRafRef.current !== null) {
97
+ cancelAnimationFrame(previewRafRef.current)
98
+ previewRafRef.current = null
99
+ }
100
+ }
86
101
  }, [open, file, defaultAspect])
87
102
 
88
- const onCropComplete = useCallback(
89
- (_area: CropArea, areaPixels: CropArea) => {
90
- setCroppedAreaPixels(areaPixels)
91
- },
92
- [],
93
- )
94
-
95
- const aspect =
103
+ const aspectRatio =
96
104
  ASPECT_PRESETS.find((p) => p.id === aspectId)?.aspect ?? undefined
97
105
 
106
+ // Aspect preset değişirse cropper coordinates'ini yeni aspect'e göre
107
+ // güncelle. `setCoordinates` ile aspect'i zorla.
108
+ useEffect(() => {
109
+ if (!cropperRef.current) return
110
+ if (aspectRatio === undefined) return
111
+ const state = cropperRef.current.getState()
112
+ if (!state) return
113
+ const { coordinates } = state
114
+ if (!coordinates) return
115
+ const current = coordinates.width / coordinates.height
116
+ if (Math.abs(current - aspectRatio) < 0.001) return
117
+ // Aspect ratio'ya snap — width'i koru, height'i hesapla
118
+ const newWidth = coordinates.width
119
+ const newHeight = coordinates.width / aspectRatio
120
+ cropperRef.current.setCoordinates({
121
+ width: newWidth,
122
+ height: newHeight,
123
+ })
124
+ }, [aspectRatio, aspectId])
125
+
126
+ // Live preview render — cropper change event'inde getCanvas() ile
127
+ // küçük thumbnail üret. RAF ile throttle.
128
+ const renderPreview = useCallback(() => {
129
+ if (previewRafRef.current !== null) {
130
+ cancelAnimationFrame(previewRafRef.current)
131
+ }
132
+ previewRafRef.current = requestAnimationFrame(() => {
133
+ previewRafRef.current = null
134
+ const cropper = cropperRef.current
135
+ if (!cropper) return
136
+ const canvas = cropper.getCanvas({
137
+ // Preview canvas — düşük boyut, smooth (output quality değil)
138
+ maxWidth: PREVIEW_MAX_DIM * 2,
139
+ maxHeight: PREVIEW_MAX_DIM * 2,
140
+ imageSmoothingQuality: "medium",
141
+ })
142
+ if (!canvas) return
143
+ setOutputSize({ w: canvas.width, h: canvas.height })
144
+ // Data URL — küçük canvas; performant
145
+ try {
146
+ setPreviewDataUrl(canvas.toDataURL("image/jpeg", 0.7))
147
+ } catch {
148
+ // toDataURL nadir tainted-canvas durumunda fail edebilir; sessiz geç
149
+ }
150
+ })
151
+ }, [])
152
+
153
+ const handleCropperChange = useCallback(() => {
154
+ renderPreview()
155
+ }, [renderPreview])
156
+
157
+ const handleCropperReady = useCallback(() => {
158
+ renderPreview()
159
+ }, [renderPreview])
160
+
98
161
  const handleApply = useCallback(async () => {
99
- if (!imageUrl || !croppedAreaPixels) return
162
+ const cropper = cropperRef.current
163
+ if (!cropper) return
100
164
  setBusy(true)
101
165
  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
166
+ const canvas = cropper.getCanvas({
167
+ imageSmoothingQuality: "high",
168
+ })
169
+ if (!canvas) {
170
+ setBusy(false)
171
+ return
172
+ }
173
+ const outputMime = file.type === "image/png" ? "image/png" : "image/jpeg"
174
+ const blob = await new Promise<Blob | null>((resolve) => {
175
+ canvas.toBlob(
176
+ (b) => resolve(b),
177
+ outputMime,
178
+ outputMime === "image/jpeg" ? outputQuality : undefined,
179
+ )
180
+ })
181
+ if (!blob) {
182
+ setBusy(false)
183
+ return
184
+ }
104
185
  const ext = blob.type === "image/png" ? "png" : "jpg"
105
186
  const baseName = file.name.replace(/\.[^.]+$/, "")
106
187
  const cropped = new File([blob], `${baseName}.${ext}`, {
@@ -110,23 +191,40 @@ export function CropDialog({
110
191
  } finally {
111
192
  setBusy(false)
112
193
  }
113
- }, [imageUrl, croppedAreaPixels, file, onClose, outputQuality])
194
+ }, [file, onClose, outputQuality])
114
195
 
115
196
  const handleUseOriginal = useCallback(() => onClose(file), [file, onClose])
116
197
  const handleCancel = useCallback(() => onClose(null), [onClose])
198
+ const handleRotate = useCallback((delta: 90 | -90) => {
199
+ cropperRef.current?.rotateImage(delta)
200
+ }, [])
201
+ const handleFlip = useCallback((axis: "h" | "v") => {
202
+ cropperRef.current?.flipImage(axis === "h", axis === "v")
203
+ }, [])
117
204
 
118
- // ESC kapatır
205
+ // Keyboard shortcuts — ESC kapat, R rotate, F flip
119
206
  useEffect(() => {
120
207
  if (!open) return
121
208
  const onKey = (e: KeyboardEvent) => {
209
+ if (
210
+ e.target instanceof HTMLElement &&
211
+ (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA")
212
+ ) {
213
+ return
214
+ }
122
215
  if (e.key === "Escape") {
123
216
  e.stopPropagation()
124
217
  handleCancel()
218
+ return
219
+ }
220
+ if (e.key === "r" || e.key === "R") {
221
+ e.preventDefault()
222
+ handleRotate(e.shiftKey ? -90 : 90)
125
223
  }
126
224
  }
127
225
  window.addEventListener("keydown", onKey)
128
226
  return () => window.removeEventListener("keydown", onKey)
129
- }, [open, handleCancel])
227
+ }, [open, handleCancel, handleRotate])
130
228
 
131
229
  return (
132
230
  <AnimatePresence>
@@ -137,67 +235,90 @@ export function CropDialog({
137
235
  animate={{ opacity: 1 }}
138
236
  exit={{ opacity: 0 }}
139
237
  transition={{ duration: 0.2 }}
140
- // z-index ana MediaManager modal'ından yüksek (nested)
141
- className="fixed inset-0 z-[60] flex items-center justify-center bg-black/80 backdrop-blur-sm p-4"
142
- onClick={(e) => {
143
- if (e.target === e.currentTarget) handleCancel()
144
- }}
238
+ // Tam ekran scrim değil, kendisi background; iOS Photos UX
239
+ className="fixed inset-0 z-[60] flex flex-col bg-black text-white"
145
240
  >
146
- <motion.div
147
- initial={{ opacity: 0, scale: 0.96, y: 8 }}
148
- animate={{ opacity: 1, scale: 1, y: 0 }}
149
- exit={{ opacity: 0, scale: 0.98 }}
150
- 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"
152
- >
153
- {/* Header */}
154
- <div className="flex items-center justify-between gap-3 border-b px-4 py-3">
155
- <div className="flex flex-col">
156
- <span className="text-sm font-semibold">Crop image</span>
157
- <span className="truncate text-xs text-muted-foreground">
158
- {file.name}
159
- </span>
160
- </div>
161
- <button
162
- type="button"
163
- onClick={handleCancel}
164
- className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground"
165
- aria-label="Cancel"
166
- >
167
- <svg
168
- viewBox="0 0 24 24"
169
- fill="none"
170
- stroke="currentColor"
171
- strokeWidth="2"
172
- strokeLinecap="round"
173
- strokeLinejoin="round"
174
- className="size-4"
175
- >
176
- <path d="M18 6 6 18M6 6l12 12" />
177
- </svg>
178
- </button>
241
+ {/* Header */}
242
+ <div className="flex items-center justify-between gap-3 border-b border-white/10 bg-black/40 px-4 py-3 backdrop-blur-sm">
243
+ <button
244
+ type="button"
245
+ onClick={handleCancel}
246
+ disabled={busy}
247
+ className="rounded-md px-3 py-1.5 text-sm text-white/70 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-50"
248
+ >
249
+ Cancel
250
+ </button>
251
+ <div className="flex min-w-0 flex-col items-center text-center">
252
+ <span className="text-sm font-semibold">Crop image</span>
253
+ <span className="truncate max-w-xs text-[11px] text-white/50">
254
+ {file.name}
255
+ </span>
179
256
  </div>
257
+ <button
258
+ type="button"
259
+ onClick={handleApply}
260
+ disabled={busy || tooLarge}
261
+ className="rounded-md bg-white px-3 py-1.5 text-sm font-medium text-black transition-opacity hover:opacity-90 disabled:opacity-50"
262
+ >
263
+ {busy ? "Cropping…" : "Apply"}
264
+ </button>
265
+ </div>
180
266
 
181
- {/* Aspect toolbar */}
182
- <div className="flex flex-wrap items-center gap-1 border-b bg-muted/20 px-3 py-2">
267
+ {/* Aspect + rotate toolbar */}
268
+ <div className="flex flex-wrap items-center gap-2 border-b border-white/10 bg-black/30 px-3 py-2">
269
+ <div className="flex flex-1 flex-wrap items-center gap-1">
183
270
  {ASPECT_PRESETS.map((p) => (
184
271
  <button
185
272
  key={p.id}
186
273
  type="button"
187
274
  onClick={() => setAspectId(p.id)}
188
275
  className={cls(
189
- "rounded-md px-2.5 py-1 text-xs transition-colors",
276
+ "rounded-full px-3 py-1 text-xs transition-colors",
190
277
  aspectId === p.id
191
- ? "bg-foreground text-background"
192
- : "text-muted-foreground hover:bg-muted hover:text-foreground",
278
+ ? "bg-white text-black"
279
+ : "text-white/60 hover:bg-white/10 hover:text-white",
193
280
  )}
194
281
  >
195
282
  {p.label}
196
283
  </button>
197
284
  ))}
198
285
  </div>
286
+ <div className="flex items-center gap-1">
287
+ <ToolbarIconButton
288
+ onClick={() => handleRotate(-90)}
289
+ title="Rotate left (Shift+R)"
290
+ ariaLabel="Rotate left"
291
+ >
292
+ <RotateLeftIcon />
293
+ </ToolbarIconButton>
294
+ <ToolbarIconButton
295
+ onClick={() => handleRotate(90)}
296
+ title="Rotate right (R)"
297
+ ariaLabel="Rotate right"
298
+ >
299
+ <RotateRightIcon />
300
+ </ToolbarIconButton>
301
+ <span className="mx-1 h-5 w-px bg-white/15" />
302
+ <ToolbarIconButton
303
+ onClick={() => handleFlip("h")}
304
+ title="Flip horizontal"
305
+ ariaLabel="Flip horizontal"
306
+ >
307
+ <FlipHorizontalIcon />
308
+ </ToolbarIconButton>
309
+ <ToolbarIconButton
310
+ onClick={() => handleFlip("v")}
311
+ title="Flip vertical"
312
+ ariaLabel="Flip vertical"
313
+ >
314
+ <FlipVerticalIcon />
315
+ </ToolbarIconButton>
316
+ </div>
317
+ </div>
199
318
 
200
- {/* Cropper canvas */}
319
+ {/* Main: cropper + side panel */}
320
+ <div className="flex flex-1 min-h-0 flex-col md:flex-row">
321
+ {/* Cropper stage */}
201
322
  <div className="relative flex-1 bg-black">
202
323
  {tooLarge ? (
203
324
  <div className="flex h-full w-full items-center justify-center p-6 text-center text-sm text-white/70">
@@ -206,139 +327,233 @@ export function CropDialog({
206
327
  </div>
207
328
  ) : (
208
329
  <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"
330
+ ref={cropperRef}
331
+ src={imageUrl}
332
+ className="sentroy-cropper"
333
+ // Stencil props — aspect lock + iOS-like rect stencil grid
334
+ stencilProps={{
335
+ aspectRatio: aspectRatio,
336
+ grid: true,
337
+ movable: true,
338
+ resizable: true,
339
+ }}
340
+ // Background overlay'i koyu yap (image dışı kalan kısım)
341
+ backgroundClassName="sentroy-cropper-background"
342
+ onChange={handleCropperChange}
343
+ onReady={handleCropperReady}
218
344
  />
219
345
  )}
220
346
  </div>
221
347
 
222
- {/* Zoom + actions */}
223
- <div className="flex flex-col gap-3 border-t bg-muted/20 px-4 py-3">
224
- {!tooLarge && (
225
- <div className="flex items-center gap-3">
226
- <span className="text-xs text-muted-foreground">Zoom</span>
227
- <button
228
- type="button"
229
- onClick={() => setZoom((z) => Math.max(1, z - 0.1))}
230
- className="rounded-md border px-2 py-0.5 text-xs hover:bg-muted/50"
231
- >
232
-
233
- </button>
234
- <input
235
- type="range"
236
- min={1}
237
- max={3}
238
- step={0.05}
239
- value={zoom}
240
- onChange={(e) => setZoom(Number(e.target.value))}
241
- className="flex-1 accent-foreground"
348
+ {/* Side panel: live preview + readout */}
349
+ <aside className="flex w-full shrink-0 flex-col gap-4 border-t border-white/10 bg-black/30 p-4 md:w-72 md:border-l md:border-t-0">
350
+ <div className="flex items-center justify-between">
351
+ <span className="text-[10px] font-medium uppercase tracking-wider text-white/50">
352
+ Preview
353
+ </span>
354
+ {outputSize && (
355
+ <span className="font-mono text-[10px] text-white/40">
356
+ {outputSize.w}×{outputSize.h}
357
+ </span>
358
+ )}
359
+ </div>
360
+ <div className="flex min-h-[160px] items-center justify-center rounded-lg border border-white/10 bg-black/50 p-3">
361
+ {tooLarge ? (
362
+ <span className="text-[11px] text-white/50">
363
+ Preview unavailable
364
+ </span>
365
+ ) : previewDataUrl ? (
366
+ /* eslint-disable-next-line @next/next/no-img-element */
367
+ <img
368
+ src={previewDataUrl}
369
+ alt="Crop preview"
370
+ className="max-h-[240px] max-w-full rounded-md object-contain shadow-md"
242
371
  />
243
- <button
244
- type="button"
245
- onClick={() => setZoom((z) => Math.min(3, z + 0.1))}
246
- className="rounded-md border px-2 py-0.5 text-xs hover:bg-muted/50"
247
- >
248
- +
249
- </button>
250
- <button
251
- type="button"
252
- onClick={() => {
253
- setZoom(1)
254
- setCrop({ x: 0, y: 0 })
255
- }}
256
- className="rounded-md border px-2 py-0.5 text-xs hover:bg-muted/50"
257
- >
258
- Reset
259
- </button>
260
- </div>
261
- )}
262
- <div className="flex items-center justify-end gap-2">
263
- <button
264
- type="button"
265
- onClick={handleCancel}
266
- disabled={busy}
267
- className="rounded-md border px-3 py-1.5 text-xs hover:bg-muted/50"
268
- >
269
- Cancel
270
- </button>
271
- <button
272
- type="button"
273
- onClick={handleUseOriginal}
274
- disabled={busy}
275
- className="rounded-md border px-3 py-1.5 text-xs hover:bg-muted/50"
276
- >
277
- Use original
278
- </button>
279
- <button
280
- type="button"
281
- onClick={handleApply}
282
- disabled={busy || tooLarge || !croppedAreaPixels}
283
- className="rounded-md bg-foreground px-3 py-1.5 text-xs font-medium text-background hover:opacity-90 disabled:opacity-50"
284
- >
285
- {busy ? "Cropping…" : "Apply crop"}
286
- </button>
372
+ ) : (
373
+ <span className="text-[11px] text-white/40">
374
+ Adjust crop…
375
+ </span>
376
+ )}
287
377
  </div>
288
- </div>
289
- </motion.div>
378
+ <div className="flex flex-col gap-1.5 text-[11px] text-white/60">
379
+ <Stat
380
+ label="Aspect"
381
+ value={
382
+ ASPECT_PRESETS.find((p) => p.id === aspectId)?.label ??
383
+ "Free"
384
+ }
385
+ />
386
+ {outputSize && (
387
+ <Stat
388
+ label="Output"
389
+ value={`${outputSize.w} × ${outputSize.h} px`}
390
+ />
391
+ )}
392
+ <Stat
393
+ label="Format"
394
+ value={file.type === "image/png" ? "PNG" : "JPEG"}
395
+ />
396
+ </div>
397
+ <button
398
+ type="button"
399
+ onClick={handleUseOriginal}
400
+ disabled={busy}
401
+ className="mt-auto w-full rounded-md border border-white/20 px-3 py-1.5 text-xs text-white/70 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-50"
402
+ >
403
+ Use original (skip crop)
404
+ </button>
405
+ </aside>
406
+ </div>
407
+
408
+ {/* Local cropper styles — paket default'unu Sentroy paletine
409
+ align ediyoruz. Stencil border ve grid çizgisini ince-keskin
410
+ tutuyoruz (iOS Photos benzeri). */}
411
+ <style>{`
412
+ .sentroy-cropper {
413
+ height: 100%;
414
+ width: 100%;
415
+ background: #000;
416
+ }
417
+ .sentroy-cropper-background {
418
+ background-color: rgba(0, 0, 0, 0.7);
419
+ }
420
+ .sentroy-cropper .advanced-cropper-stencil-overlay {
421
+ background: rgba(0, 0, 0, 0.55);
422
+ }
423
+ .sentroy-cropper .advanced-cropper-rectangle-stencil__draggable-area {
424
+ border: 1px solid rgba(255, 255, 255, 0.95);
425
+ }
426
+ .sentroy-cropper .advanced-cropper-line-wrapper {
427
+ background: rgba(255, 255, 255, 0.95);
428
+ }
429
+ .sentroy-cropper .advanced-cropper-handler-wrapper--west-north,
430
+ .sentroy-cropper .advanced-cropper-handler-wrapper--north-east,
431
+ .sentroy-cropper .advanced-cropper-handler-wrapper--east-south,
432
+ .sentroy-cropper .advanced-cropper-handler-wrapper--south-west {
433
+ width: 22px;
434
+ height: 22px;
435
+ }
436
+ .sentroy-cropper .advanced-cropper-handler-wrapper__draggable {
437
+ background: #fff;
438
+ border: 2px solid #000;
439
+ width: 12px;
440
+ height: 12px;
441
+ border-radius: 2px;
442
+ }
443
+ `}</style>
290
444
  </motion.div>
291
445
  )}
292
446
  </AnimatePresence>
293
447
  )
294
448
  }
295
449
 
296
- /**
297
- * Canvas ile crop area'yı çıkar + Blob döndür.
298
- * Output MIME: PNG ise PNG, diğerleri JPEG (transparency yoksa).
299
- */
300
- async function getCroppedBlob(
301
- imageUrl: string,
302
- area: CropArea,
303
- sourceMime: string,
304
- quality: number,
305
- ): Promise<Blob> {
306
- const image = await loadImage(imageUrl)
307
- const canvas = document.createElement("canvas")
308
- canvas.width = area.width
309
- canvas.height = area.height
310
- const ctx = canvas.getContext("2d")
311
- 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,
450
+ function Stat({ label, value }: { label: string; value: string }) {
451
+ return (
452
+ <div className="flex items-center justify-between">
453
+ <span>{label}</span>
454
+ <span className="font-mono text-white/80">{value}</span>
455
+ </div>
322
456
  )
323
- const outputMime = sourceMime === "image/png" ? "image/png" : "image/jpeg"
324
- return new Promise<Blob>((resolve, reject) => {
325
- canvas.toBlob(
326
- (blob) => (blob ? resolve(blob) : reject(new Error("toBlob returned null"))),
327
- outputMime,
328
- outputMime === "image/jpeg" ? quality : undefined,
329
- )
330
- })
331
457
  }
332
458
 
333
- function loadImage(url: string): Promise<HTMLImageElement> {
334
- return new Promise((resolve, reject) => {
335
- const img = new Image()
336
- img.onload = () => resolve(img)
337
- img.onerror = reject
338
- img.src = url
339
- })
459
+ function ToolbarIconButton({
460
+ onClick,
461
+ title,
462
+ ariaLabel,
463
+ children,
464
+ }: {
465
+ onClick: () => void
466
+ title: string
467
+ ariaLabel: string
468
+ children: React.ReactNode
469
+ }) {
470
+ return (
471
+ <button
472
+ type="button"
473
+ onClick={onClick}
474
+ title={title}
475
+ aria-label={ariaLabel}
476
+ className="inline-flex size-8 items-center justify-center rounded-md text-white/70 transition-colors hover:bg-white/10 hover:text-white"
477
+ >
478
+ {children}
479
+ </button>
480
+ )
340
481
  }
341
482
 
342
483
  function cls(...arr: Array<string | false | null | undefined>): string {
343
484
  return arr.filter(Boolean).join(" ")
344
485
  }
486
+
487
+ function RotateLeftIcon() {
488
+ return (
489
+ <svg
490
+ viewBox="0 0 24 24"
491
+ fill="none"
492
+ stroke="currentColor"
493
+ strokeWidth="2"
494
+ strokeLinecap="round"
495
+ strokeLinejoin="round"
496
+ className="size-4"
497
+ aria-hidden="true"
498
+ >
499
+ <path d="M3 12a9 9 0 1 0 3-6.7" />
500
+ <path d="M3 4v5h5" />
501
+ </svg>
502
+ )
503
+ }
504
+
505
+ function RotateRightIcon() {
506
+ return (
507
+ <svg
508
+ viewBox="0 0 24 24"
509
+ fill="none"
510
+ stroke="currentColor"
511
+ strokeWidth="2"
512
+ strokeLinecap="round"
513
+ strokeLinejoin="round"
514
+ className="size-4"
515
+ aria-hidden="true"
516
+ >
517
+ <path d="M21 12a9 9 0 1 1-3-6.7" />
518
+ <path d="M21 4v5h-5" />
519
+ </svg>
520
+ )
521
+ }
522
+
523
+ function FlipHorizontalIcon() {
524
+ return (
525
+ <svg
526
+ viewBox="0 0 24 24"
527
+ fill="none"
528
+ stroke="currentColor"
529
+ strokeWidth="2"
530
+ strokeLinecap="round"
531
+ strokeLinejoin="round"
532
+ className="size-4"
533
+ aria-hidden="true"
534
+ >
535
+ <path d="M12 3v18" />
536
+ <path d="M16 7l4 5-4 5" />
537
+ <path d="M8 7l-4 5 4 5" />
538
+ </svg>
539
+ )
540
+ }
541
+
542
+ function FlipVerticalIcon() {
543
+ return (
544
+ <svg
545
+ viewBox="0 0 24 24"
546
+ fill="none"
547
+ stroke="currentColor"
548
+ strokeWidth="2"
549
+ strokeLinecap="round"
550
+ strokeLinejoin="round"
551
+ className="size-4"
552
+ aria-hidden="true"
553
+ >
554
+ <path d="M3 12h18" />
555
+ <path d="M7 8l5-4 5 4" />
556
+ <path d="M7 16l5 4 5-4" />
557
+ </svg>
558
+ )
559
+ }