@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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CropDialog.d.ts","sourceRoot":"","sources":["../../../src/react/crop/CropDialog.tsx"],"names":[],"mappings":"
|
|
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
|
|
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 () =>
|
|
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
|
|
49
|
-
|
|
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
|
|
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
|
-
|
|
136
|
+
const cropper = cropperRef.current;
|
|
137
|
+
if (!cropper)
|
|
54
138
|
return;
|
|
55
139
|
setBusy(true);
|
|
56
140
|
try {
|
|
57
|
-
const
|
|
58
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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.
|
|
86
|
-
//
|
|
87
|
-
className: "fixed inset-0 z-[60] flex items-center justify-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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
|
|
116
|
-
return
|
|
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.
|
|
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-
|
|
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-
|
|
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 —
|
|
7
|
-
*
|
|
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
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
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
|
-
*
|
|
14
|
-
*
|
|
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
|
|
17
|
-
*
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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 () =>
|
|
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
|
|
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
|
-
|
|
162
|
+
const cropper = cropperRef.current
|
|
163
|
+
if (!cropper) return
|
|
100
164
|
setBusy(true)
|
|
101
165
|
try {
|
|
102
|
-
const
|
|
103
|
-
|
|
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
|
-
}, [
|
|
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
|
|
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
|
-
//
|
|
141
|
-
className="fixed inset-0 z-[60] flex
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
</
|
|
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
|
-
|
|
182
|
-
|
|
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-
|
|
276
|
+
"rounded-full px-3 py-1 text-xs transition-colors",
|
|
190
277
|
aspectId === p.id
|
|
191
|
-
? "bg-
|
|
192
|
-
: "text-
|
|
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
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
aspect
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
{/*
|
|
223
|
-
<
|
|
224
|
-
|
|
225
|
-
<
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
289
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
+
}
|