@sentroy-co/client-sdk 2.12.0 → 2.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +14 -0
- package/dist/react/crop/CropDialog.d.ts.map +1 -1
- package/dist/react/crop/CropDialog.js +124 -15
- package/dist/react/crop/CropDialog.js.map +1 -1
- package/dist/vault/index.d.ts.map +1 -1
- package/dist/vault/index.js +34 -6
- package/dist/vault/index.js.map +1 -1
- package/package.json +1 -1
- package/src/react/crop/CropDialog.tsx +308 -60
- package/src/vault/index.ts +41 -5
package/AGENTS.md
CHANGED
|
@@ -767,6 +767,20 @@ function ConfigPanel() {
|
|
|
767
767
|
| `apiKey` | `string` | `process.env.NEXT_PUBLIC_SENTROY_ENV_API_KEY` | Bearer token for browser polling |
|
|
768
768
|
| `refreshIntervalMs` | `number` | `300000` (5 min) | `0` to disable polling |
|
|
769
769
|
|
|
770
|
+
### Debug logging (`SENTROY_ENV_DEBUG`)
|
|
771
|
+
|
|
772
|
+
Set `SENTROY_ENV_DEBUG=1` (or `true`) on the consuming app to surface every fetch, cache hit, and fallback through `console.log`. Sample output:
|
|
773
|
+
|
|
774
|
+
```
|
|
775
|
+
[env-vault] fetching https://sentroy.com/api/env-vault/fetch
|
|
776
|
+
[env-vault] fetched 7 var(s) from sentroy-core/prod in 234ms
|
|
777
|
+
[env-vault] BETTER_AUTH_TURNSTILE_SECRET: vault hit
|
|
778
|
+
[env-vault] IPINFO_TOKEN: vault miss → process.env fallback
|
|
779
|
+
[env-vault] AI_GATEWAY_API_KEY: vault error (...) → process.env fallback
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
Default is off — turn on temporarily after a deploy to verify migrations are reading from the right source, then turn off to keep prod logs clean.
|
|
783
|
+
|
|
770
784
|
### Migration helper: `getEnvWithFallback(key)`
|
|
771
785
|
|
|
772
786
|
For codebases moving from `process.env` to vault gradually, use `getEnvWithFallback` — it tries vault first, falls back to `process.env[key]` on cache miss / fetch failure / missing token. The point is *zero downtime*: deploy the code change before populating the vault, and nothing breaks; fill the vault later, and the same code starts reading from there.
|
|
@@ -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":"AA8CA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,CAAA;IACb;mBACe;IACf,IAAI,EAAE,IAAI,CAAA;IACV,4EAA4E;IAC5E,OAAO,EAAE,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAA;IACtC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;yDACqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,aAAsB,EACtB,aAAoB,GACrB,EAAE,eAAe,2CAiYjB"}
|
|
@@ -17,14 +17,19 @@ const ASPECT_PRESETS = [
|
|
|
17
17
|
{ id: "9:16", label: "9:16", aspect: 9 / 16 },
|
|
18
18
|
];
|
|
19
19
|
const MAX_PIXEL_GUARD = 50_000_000; // ~24 MP — üstü tarayıcı memory peak'i riskli
|
|
20
|
+
const PREVIEW_MAX_DIM = 220; // sidebar thumbnail için max edge (px)
|
|
20
21
|
function CropDialog({ open, file, onClose, defaultAspect = "free", outputQuality = 0.92, }) {
|
|
21
22
|
const [imageUrl, setImageUrl] = (0, react_1.useState)(null);
|
|
22
23
|
const [aspectId, setAspectId] = (0, react_1.useState)(defaultAspect);
|
|
23
24
|
const [crop, setCrop] = (0, react_1.useState)({ x: 0, y: 0 });
|
|
24
25
|
const [zoom, setZoom] = (0, react_1.useState)(1);
|
|
26
|
+
const [rotation, setRotation] = (0, react_1.useState)(0);
|
|
25
27
|
const [croppedAreaPixels, setCroppedAreaPixels] = (0, react_1.useState)(null);
|
|
26
28
|
const [busy, setBusy] = (0, react_1.useState)(false);
|
|
27
29
|
const [tooLarge, setTooLarge] = (0, react_1.useState)(false);
|
|
30
|
+
const previewCanvasRef = (0, react_1.useRef)(null);
|
|
31
|
+
const previewRafRef = (0, react_1.useRef)(null);
|
|
32
|
+
const imageElRef = (0, react_1.useRef)(null);
|
|
28
33
|
// Object URL lifecycle
|
|
29
34
|
(0, react_1.useEffect)(() => {
|
|
30
35
|
if (!open)
|
|
@@ -33,29 +38,71 @@ function CropDialog({ open, file, onClose, defaultAspect = "free", outputQuality
|
|
|
33
38
|
setImageUrl(url);
|
|
34
39
|
setCrop({ x: 0, y: 0 });
|
|
35
40
|
setZoom(1);
|
|
41
|
+
setRotation(0);
|
|
36
42
|
setAspectId(defaultAspect);
|
|
37
43
|
setTooLarge(false);
|
|
38
|
-
// Pixel guard
|
|
44
|
+
// Pixel guard + cache HTMLImageElement (preview drawImage source).
|
|
39
45
|
const img = new Image();
|
|
40
46
|
img.onload = () => {
|
|
41
47
|
if (img.naturalWidth * img.naturalHeight > MAX_PIXEL_GUARD) {
|
|
42
48
|
setTooLarge(true);
|
|
43
49
|
}
|
|
50
|
+
imageElRef.current = img;
|
|
44
51
|
};
|
|
45
52
|
img.src = url;
|
|
46
|
-
return () =>
|
|
53
|
+
return () => {
|
|
54
|
+
URL.revokeObjectURL(url);
|
|
55
|
+
imageElRef.current = null;
|
|
56
|
+
};
|
|
47
57
|
}, [open, file, defaultAspect]);
|
|
48
58
|
const onCropComplete = (0, react_1.useCallback)((_area, areaPixels) => {
|
|
49
59
|
setCroppedAreaPixels(areaPixels);
|
|
50
60
|
}, []);
|
|
61
|
+
// Live preview render — crop / rotation değiştikçe sağ panel thumbnail'ı
|
|
62
|
+
// güncelle. requestAnimationFrame ile throttle (drag sırasında her event'te
|
|
63
|
+
// canvas çizmek pahalı).
|
|
64
|
+
(0, react_1.useEffect)(() => {
|
|
65
|
+
if (!croppedAreaPixels || !imageElRef.current || tooLarge)
|
|
66
|
+
return;
|
|
67
|
+
if (previewRafRef.current !== null) {
|
|
68
|
+
cancelAnimationFrame(previewRafRef.current);
|
|
69
|
+
}
|
|
70
|
+
previewRafRef.current = requestAnimationFrame(() => {
|
|
71
|
+
const canvas = previewCanvasRef.current;
|
|
72
|
+
const img = imageElRef.current;
|
|
73
|
+
if (!canvas || !img)
|
|
74
|
+
return;
|
|
75
|
+
const area = croppedAreaPixels;
|
|
76
|
+
// Preview boyutu — aspect korunarak max edge PREVIEW_MAX_DIM
|
|
77
|
+
const scale = area.width >= area.height
|
|
78
|
+
? PREVIEW_MAX_DIM / area.width
|
|
79
|
+
: PREVIEW_MAX_DIM / area.height;
|
|
80
|
+
const pw = Math.max(1, Math.round(area.width * scale));
|
|
81
|
+
const ph = Math.max(1, Math.round(area.height * scale));
|
|
82
|
+
canvas.width = pw;
|
|
83
|
+
canvas.height = ph;
|
|
84
|
+
const ctx = canvas.getContext("2d");
|
|
85
|
+
if (!ctx)
|
|
86
|
+
return;
|
|
87
|
+
ctx.imageSmoothingQuality = "high";
|
|
88
|
+
ctx.clearRect(0, 0, pw, ph);
|
|
89
|
+
drawRotatedCrop(ctx, img, area, rotation, pw, ph);
|
|
90
|
+
previewRafRef.current = null;
|
|
91
|
+
});
|
|
92
|
+
return () => {
|
|
93
|
+
if (previewRafRef.current !== null) {
|
|
94
|
+
cancelAnimationFrame(previewRafRef.current);
|
|
95
|
+
previewRafRef.current = null;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}, [croppedAreaPixels, rotation, tooLarge]);
|
|
51
99
|
const aspect = ASPECT_PRESETS.find((p) => p.id === aspectId)?.aspect ?? undefined;
|
|
52
100
|
const handleApply = (0, react_1.useCallback)(async () => {
|
|
53
101
|
if (!imageUrl || !croppedAreaPixels)
|
|
54
102
|
return;
|
|
55
103
|
setBusy(true);
|
|
56
104
|
try {
|
|
57
|
-
const blob = await getCroppedBlob(imageUrl, croppedAreaPixels, file.type, outputQuality);
|
|
58
|
-
// Cropped File — orijinal name'i koru ama uzantı output type'ına göre
|
|
105
|
+
const blob = await getCroppedBlob(imageUrl, croppedAreaPixels, rotation, file.type, outputQuality);
|
|
59
106
|
const ext = blob.type === "image/png" ? "png" : "jpg";
|
|
60
107
|
const baseName = file.name.replace(/\.[^.]+$/, "");
|
|
61
108
|
const cropped = new File([blob], `${baseName}.${ext}`, {
|
|
@@ -66,10 +113,16 @@ function CropDialog({ open, file, onClose, defaultAspect = "free", outputQuality
|
|
|
66
113
|
finally {
|
|
67
114
|
setBusy(false);
|
|
68
115
|
}
|
|
69
|
-
}, [imageUrl, croppedAreaPixels, file, onClose, outputQuality]);
|
|
116
|
+
}, [imageUrl, croppedAreaPixels, rotation, file, onClose, outputQuality]);
|
|
70
117
|
const handleUseOriginal = (0, react_1.useCallback)(() => onClose(file), [file, onClose]);
|
|
71
118
|
const handleCancel = (0, react_1.useCallback)(() => onClose(null), [onClose]);
|
|
72
|
-
|
|
119
|
+
const handleRotate = (0, react_1.useCallback)((delta) => {
|
|
120
|
+
setRotation((r) => {
|
|
121
|
+
const next = (r + delta + 360) % 360;
|
|
122
|
+
return next;
|
|
123
|
+
});
|
|
124
|
+
}, []);
|
|
125
|
+
// ESC kapatır, R döndürür
|
|
73
126
|
(0, react_1.useEffect)(() => {
|
|
74
127
|
if (!open)
|
|
75
128
|
return;
|
|
@@ -78,27 +131,76 @@ function CropDialog({ open, file, onClose, defaultAspect = "free", outputQuality
|
|
|
78
131
|
e.stopPropagation();
|
|
79
132
|
handleCancel();
|
|
80
133
|
}
|
|
134
|
+
else if (e.key === "r" || e.key === "R") {
|
|
135
|
+
if (e.target instanceof HTMLElement &&
|
|
136
|
+
(e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA")) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
e.preventDefault();
|
|
140
|
+
handleRotate(e.shiftKey ? -90 : 90);
|
|
141
|
+
}
|
|
81
142
|
};
|
|
82
143
|
window.addEventListener("keydown", onKey);
|
|
83
144
|
return () => window.removeEventListener("keydown", onKey);
|
|
84
|
-
}, [open, handleCancel]);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
145
|
+
}, [open, handleCancel, handleRotate]);
|
|
146
|
+
const outputWidth = croppedAreaPixels ? Math.round(croppedAreaPixels.width) : 0;
|
|
147
|
+
const outputHeight = croppedAreaPixels ? Math.round(croppedAreaPixels.height) : 0;
|
|
148
|
+
// Rotation 90° / 270° iken output dimensions swap edilir (canvas rotate
|
|
149
|
+
// sonrası kullanıcı görsel olarak swap görür).
|
|
150
|
+
const displayW = rotation % 180 === 0 ? outputWidth : outputHeight;
|
|
151
|
+
const displayH = rotation % 180 === 0 ? outputHeight : outputWidth;
|
|
152
|
+
return ((0, jsx_runtime_1.jsx)(react_2.AnimatePresence, { children: open && imageUrl && ((0, jsx_runtime_1.jsx)(react_2.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.2 }, className: "fixed inset-0 z-[60] flex items-center justify-center bg-black/80 backdrop-blur-sm p-4", onClick: (e) => {
|
|
88
153
|
if (e.target === e.currentTarget)
|
|
89
154
|
handleCancel();
|
|
90
|
-
}, children: (0, jsx_runtime_1.jsxs)(react_2.motion.div, { initial: { opacity: 0, scale: 0.96, y: 8 }, animate: { opacity: 1, scale: 1, y: 0 }, exit: { opacity: 0, scale: 0.98 }, transition: { duration: 0.25, ease: [0.22, 1, 0.36, 1] }, className: "flex h-[min(
|
|
91
|
-
|
|
92
|
-
|
|
155
|
+
}, children: (0, jsx_runtime_1.jsxs)(react_2.motion.div, { initial: { opacity: 0, scale: 0.96, y: 8 }, animate: { opacity: 1, scale: 1, y: 0 }, exit: { opacity: 0, scale: 0.98 }, transition: { duration: 0.25, ease: [0.22, 1, 0.36, 1] }, className: "flex h-[min(92vh,760px)] w-full max-w-5xl flex-col overflow-hidden rounded-xl border bg-background shadow-2xl", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between gap-3 border-b px-4 py-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col min-w-0", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm font-semibold", children: "Crop image" }), (0, jsx_runtime_1.jsx)("span", { className: "truncate text-xs text-muted-foreground", children: file.name })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleCancel, className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground", "aria-label": "Cancel", children: (0, jsx_runtime_1.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "size-4", children: (0, jsx_runtime_1.jsx)("path", { d: "M18 6 6 18M6 6l12 12" }) }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center gap-1 border-b bg-muted/20 px-3 py-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex flex-1 flex-wrap items-center gap-1", children: ASPECT_PRESETS.map((p) => ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setAspectId(p.id), className: cls("rounded-md px-2.5 py-1 text-xs transition-colors", aspectId === p.id
|
|
156
|
+
? "bg-foreground text-background"
|
|
157
|
+
: "text-muted-foreground hover:bg-muted hover:text-foreground"), children: p.label }, p.id))) }), (0, jsx_runtime_1.jsxs)("div", { className: "ms-auto flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => handleRotate(-90), title: "Rotate left (Shift+R)", "aria-label": "Rotate left", className: "rounded-md border px-2 py-1 text-xs text-muted-foreground hover:bg-muted/50 hover:text-foreground", children: (0, jsx_runtime_1.jsx)(RotateLeftIcon, {}) }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => handleRotate(90), title: "Rotate right (R)", "aria-label": "Rotate right", className: "rounded-md border px-2 py-1 text-xs text-muted-foreground hover:bg-muted/50 hover:text-foreground", children: (0, jsx_runtime_1.jsx)(RotateRightIcon, {}) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-1 min-h-0 flex-col overflow-hidden md:flex-row", children: [(0, jsx_runtime_1.jsx)("div", { className: "relative flex-1 bg-black", children: tooLarge ? ((0, jsx_runtime_1.jsx)("div", { className: "flex h-full w-full items-center justify-center p-6 text-center text-sm text-white/70", children: "Image too large to crop in browser. Upload as-is or resize beforehand." })) : ((0, jsx_runtime_1.jsx)(react_easy_crop_1.default, { image: imageUrl, crop: crop, zoom: zoom, rotation: rotation, aspect: aspect, onCropChange: setCrop, onCropComplete: onCropComplete, onZoomChange: setZoom, onRotationChange: setRotation, showGrid: true, objectFit: "contain" })) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex w-full shrink-0 flex-col gap-3 border-t bg-muted/10 p-4 md:w-64 md:border-l md:border-t-0", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground", children: "Preview" }), displayW > 0 && displayH > 0 && ((0, jsx_runtime_1.jsxs)("span", { className: "font-mono text-[10px] text-muted-foreground", children: [displayW, "\u00D7", displayH] }))] }), (0, jsx_runtime_1.jsx)("div", { className: "flex min-h-[140px] items-center justify-center rounded-lg border border-dashed bg-background/40 p-2", children: tooLarge ? ((0, jsx_runtime_1.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Preview unavailable" })) : croppedAreaPixels ? ((0, jsx_runtime_1.jsx)("canvas", { ref: previewCanvasRef, className: "max-h-[220px] max-w-full rounded-sm shadow-sm" })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Adjust crop\u2026" })) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1 text-[11px] text-muted-foreground", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { children: "Aspect" }), (0, jsx_runtime_1.jsx)("span", { className: "font-mono", children: ASPECT_PRESETS.find((p) => p.id === aspectId)?.label ??
|
|
158
|
+
"Free" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { children: "Rotation" }), (0, jsx_runtime_1.jsxs)("span", { className: "font-mono", children: [rotation, "\u00B0"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { children: "Zoom" }), (0, jsx_runtime_1.jsxs)("span", { className: "font-mono", children: [zoom.toFixed(2), "\u00D7"] })] })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-3 border-t bg-muted/20 px-4 py-3", children: [!tooLarge && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs text-muted-foreground", children: "Zoom" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setZoom((z) => Math.max(1, z - 0.1)), className: "rounded-md border px-2 py-0.5 text-xs hover:bg-muted/50", children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "range", min: 1, max: 3, step: 0.05, value: zoom, onChange: (e) => setZoom(Number(e.target.value)), className: "flex-1 accent-foreground" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setZoom((z) => Math.min(3, z + 0.1)), className: "rounded-md border px-2 py-0.5 text-xs hover:bg-muted/50", children: "+" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => {
|
|
93
159
|
setZoom(1);
|
|
160
|
+
setRotation(0);
|
|
94
161
|
setCrop({ x: 0, y: 0 });
|
|
95
162
|
}, className: "rounded-md border px-2 py-0.5 text-xs hover:bg-muted/50", children: "Reset" })] })), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-end gap-2", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleCancel, disabled: busy, className: "rounded-md border px-3 py-1.5 text-xs hover:bg-muted/50", children: "Cancel" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleUseOriginal, disabled: busy, className: "rounded-md border px-3 py-1.5 text-xs hover:bg-muted/50", children: "Use original" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleApply, disabled: busy || tooLarge || !croppedAreaPixels, className: "rounded-md bg-foreground px-3 py-1.5 text-xs font-medium text-background hover:opacity-90 disabled:opacity-50", children: busy ? "Cropping…" : "Apply crop" })] })] })] }) }, "backdrop")) }));
|
|
96
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Rotated crop'u canvas'a yaz. Rotation 0° fast-path; aksi halde önce
|
|
166
|
+
* "rotated bounding box" canvas'ı oluştur (orijinal image'in döndürülmüş
|
|
167
|
+
* versiyonu), sonra croppedAreaPixels koordinatlarında çıkar.
|
|
168
|
+
*
|
|
169
|
+
* react-easy-crop's `croppedAreaPixels` rotation-aware: koordinatlar
|
|
170
|
+
* orijinal image'i rotasyon merkezinden döndürdükten sonraki "visual"
|
|
171
|
+
* bounding box üzerinden hesaplanır — biz aynı transform'u canvas'a
|
|
172
|
+
* uygulayıp aynı koordinatlardan drawImage yapıyoruz.
|
|
173
|
+
*/
|
|
174
|
+
function drawRotatedCrop(ctx, image, area, rotation, dstW, dstH) {
|
|
175
|
+
if (rotation === 0) {
|
|
176
|
+
ctx.drawImage(image, area.x, area.y, area.width, area.height, 0, 0, dstW, dstH);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// Build a rotated source canvas at the size of the rotated bounding box,
|
|
180
|
+
// then crop from it.
|
|
181
|
+
const rad = (rotation * Math.PI) / 180;
|
|
182
|
+
const sin = Math.abs(Math.sin(rad));
|
|
183
|
+
const cos = Math.abs(Math.cos(rad));
|
|
184
|
+
const iw = image.naturalWidth;
|
|
185
|
+
const ih = image.naturalHeight;
|
|
186
|
+
const bbW = iw * cos + ih * sin;
|
|
187
|
+
const bbH = iw * sin + ih * cos;
|
|
188
|
+
const tmp = document.createElement("canvas");
|
|
189
|
+
tmp.width = bbW;
|
|
190
|
+
tmp.height = bbH;
|
|
191
|
+
const tctx = tmp.getContext("2d");
|
|
192
|
+
if (!tctx)
|
|
193
|
+
return;
|
|
194
|
+
tctx.translate(bbW / 2, bbH / 2);
|
|
195
|
+
tctx.rotate(rad);
|
|
196
|
+
tctx.drawImage(image, -iw / 2, -ih / 2);
|
|
197
|
+
ctx.drawImage(tmp, area.x, area.y, area.width, area.height, 0, 0, dstW, dstH);
|
|
198
|
+
}
|
|
97
199
|
/**
|
|
98
200
|
* Canvas ile crop area'yı çıkar + Blob döndür.
|
|
99
201
|
* Output MIME: PNG ise PNG, diğerleri JPEG (transparency yoksa).
|
|
100
202
|
*/
|
|
101
|
-
async function getCroppedBlob(imageUrl, area, sourceMime, quality) {
|
|
203
|
+
async function getCroppedBlob(imageUrl, area, rotation, sourceMime, quality) {
|
|
102
204
|
const image = await loadImage(imageUrl);
|
|
103
205
|
const canvas = document.createElement("canvas");
|
|
104
206
|
canvas.width = area.width;
|
|
@@ -106,7 +208,8 @@ async function getCroppedBlob(imageUrl, area, sourceMime, quality) {
|
|
|
106
208
|
const ctx = canvas.getContext("2d");
|
|
107
209
|
if (!ctx)
|
|
108
210
|
throw new Error("Canvas 2D context unavailable");
|
|
109
|
-
ctx.
|
|
211
|
+
ctx.imageSmoothingQuality = "high";
|
|
212
|
+
drawRotatedCrop(ctx, image, area, rotation, area.width, area.height);
|
|
110
213
|
const outputMime = sourceMime === "image/png" ? "image/png" : "image/jpeg";
|
|
111
214
|
return new Promise((resolve, reject) => {
|
|
112
215
|
canvas.toBlob((blob) => (blob ? resolve(blob) : reject(new Error("toBlob returned null"))), outputMime, outputMime === "image/jpeg" ? quality : undefined);
|
|
@@ -123,4 +226,10 @@ function loadImage(url) {
|
|
|
123
226
|
function cls(...arr) {
|
|
124
227
|
return arr.filter(Boolean).join(" ");
|
|
125
228
|
}
|
|
229
|
+
function RotateLeftIcon() {
|
|
230
|
+
return ((0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "size-3.5", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("path", { d: "M3 12a9 9 0 1 0 3-6.7" }), (0, jsx_runtime_1.jsx)("path", { d: "M3 4v5h5" })] }));
|
|
231
|
+
}
|
|
232
|
+
function RotateRightIcon() {
|
|
233
|
+
return ((0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "size-3.5", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("path", { d: "M21 12a9 9 0 1 1-3-6.7" }), (0, jsx_runtime_1.jsx)("path", { d: "M21 4v5h-5" })] }));
|
|
234
|
+
}
|
|
126
235
|
//# sourceMappingURL=CropDialog.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CropDialog.js","sourceRoot":"","sources":["../../../src/react/crop/CropDialog.tsx"],"names":[],"mappings":";;;;;AAoDA,gCAiPC;;AArSD,iCAAwD;AACxD,sEAAqC;AACrC,wCAAsD;AAyBtD,MAAM,cAAc,GAAgE;IAClF,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE;IAC3C,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE;IACtC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE;IAC7C,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE;IAC1C,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE;IAC1C,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;CAC9C,CAAA;AAED,MAAM,eAAe,GAAG,UAAU,CAAA,CAAC,8CAA8C;AAgBjF,SAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,aAAa,GAAG,MAAM,EACtB,aAAa,GAAG,IAAI,GACJ;IAChB,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,IAAA,gBAAQ,EAAgB,IAAI,CAAC,CAAA;IAC7D,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,IAAA,gBAAQ,EAAC,aAAa,CAAC,CAAA;IACvD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,IAAA,gBAAQ,EAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IAChD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,IAAA,gBAAQ,EAAC,CAAC,CAAC,CAAA;IACnC,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,IAAA,gBAAQ,EAAkB,IAAI,CAAC,CAAA;IACjF,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,IAAA,gBAAQ,EAAC,KAAK,CAAC,CAAA;IACvC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,IAAA,gBAAQ,EAAC,KAAK,CAAC,CAAA;IAE/C,uBAAuB;IACvB,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;QACrC,WAAW,CAAC,GAAG,CAAC,CAAA;QAChB,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;QACvB,OAAO,CAAC,CAAC,CAAC,CAAA;QACV,WAAW,CAAC,aAAa,CAAC,CAAA;QAC1B,WAAW,CAAC,KAAK,CAAC,CAAA;QAClB,uDAAuD;QACvD,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE,CAAA;QACvB,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE;YAChB,IAAI,GAAG,CAAC,YAAY,GAAG,GAAG,CAAC,aAAa,GAAG,eAAe,EAAE,CAAC;gBAC3D,WAAW,CAAC,IAAI,CAAC,CAAA;YACnB,CAAC;QACH,CAAC,CAAA;QACD,GAAG,CAAC,GAAG,GAAG,GAAG,CAAA;QACb,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAA;IACvC,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC,CAAA;IAE/B,MAAM,cAAc,GAAG,IAAA,mBAAW,EAChC,CAAC,KAAe,EAAE,UAAoB,EAAE,EAAE;QACxC,oBAAoB,CAAC,UAAU,CAAC,CAAA;IAClC,CAAC,EACD,EAAE,CACH,CAAA;IAED,MAAM,MAAM,GACV,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,EAAE,MAAM,IAAI,SAAS,CAAA;IAEpE,MAAM,WAAW,GAAG,IAAA,mBAAW,EAAC,KAAK,IAAI,EAAE;QACzC,IAAI,CAAC,QAAQ,IAAI,CAAC,iBAAiB;YAAE,OAAM;QAC3C,OAAO,CAAC,IAAI,CAAC,CAAA;QACb,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,CAAA;YACxF,sEAAsE;YACtE,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAA;YACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;YAClD,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,QAAQ,IAAI,GAAG,EAAE,EAAE;gBACrD,IAAI,EAAE,IAAI,CAAC,IAAI;aAChB,CAAC,CAAA;YACF,OAAO,CAAC,OAAO,CAAC,CAAA;QAClB,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,CAAA;QAChB,CAAC;IACH,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAA;IAE/D,MAAM,iBAAiB,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;IAC3E,MAAM,YAAY,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IAEhE,cAAc;IACd,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,KAAK,GAAG,CAAC,CAAgB,EAAE,EAAE;YACjC,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;gBACvB,CAAC,CAAC,eAAe,EAAE,CAAA;gBACnB,YAAY,EAAE,CAAA;YAChB,CAAC;QACH,CAAC,CAAA;QACD,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;QACzC,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;IAC3D,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,CAAA;IAExB,OAAO,CACL,uBAAC,uBAAe,cACb,IAAI,IAAI,QAAQ,IAAI,CACnB,uBAAC,cAAM,CAAC,GAAG,IAET,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EACvB,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EACvB,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EACpB,UAAU,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE;YAC7B,uDAAuD;YACvD,SAAS,EAAC,wFAAwF,EAClG,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;gBACb,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,aAAa;oBAAE,YAAY,EAAE,CAAA;YAClD,CAAC,YAED,wBAAC,cAAM,CAAC,GAAG,IACT,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,EAC1C,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EACvC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EACjC,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EACxD,SAAS,EAAC,+GAA+G,aAGzH,iCAAK,SAAS,EAAC,4DAA4D,aACzE,iCAAK,SAAS,EAAC,eAAe,aAC5B,iCAAM,SAAS,EAAC,uBAAuB,2BAAkB,EACzD,iCAAM,SAAS,EAAC,wCAAwC,YACrD,IAAI,CAAC,IAAI,GACL,IACH,EACN,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,YAAY,EACrB,SAAS,EAAC,kGAAkG,gBACjG,QAAQ,YAEnB,gCACE,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,SAAS,EAAC,QAAQ,YAElB,iCAAM,CAAC,EAAC,sBAAsB,GAAG,GAC7B,GACC,IACL,EAGN,gCAAK,SAAS,EAAC,kEAAkE,YAC9E,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CACzB,mCAEE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,EAChC,SAAS,EAAE,GAAG,CACZ,kDAAkD,EAClD,QAAQ,KAAK,CAAC,CAAC,EAAE;gCACf,CAAC,CAAC,+BAA+B;gCACjC,CAAC,CAAC,4DAA4D,CACjE,YAEA,CAAC,CAAC,KAAK,IAVH,CAAC,CAAC,EAAE,CAWF,CACV,CAAC,GACE,EAGN,gCAAK,SAAS,EAAC,0BAA0B,YACtC,QAAQ,CAAC,CAAC,CAAC,CACV,gCAAK,SAAS,EAAC,sFAAsF,uFAG/F,CACP,CAAC,CAAC,CAAC,CACF,uBAAC,yBAAO,IACN,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,OAAO,EACrB,cAAc,EAAE,cAAc,EAC9B,YAAY,EAAE,OAAO,EACrB,QAAQ,QACR,SAAS,EAAC,SAAS,GACnB,CACH,GACG,EAGN,iCAAK,SAAS,EAAC,oDAAoD,aAChE,CAAC,QAAQ,IAAI,CACZ,iCAAK,SAAS,EAAC,yBAAyB,aACtC,iCAAM,SAAS,EAAC,+BAA+B,qBAAY,EAC3D,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,EACnD,SAAS,EAAC,yDAAyD,uBAG5D,EACT,kCACE,IAAI,EAAC,OAAO,EACZ,GAAG,EAAE,CAAC,EACN,GAAG,EAAE,CAAC,EACN,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,IAAI,EACX,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAChD,SAAS,EAAC,0BAA0B,GACpC,EACF,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,EACnD,SAAS,EAAC,yDAAyD,kBAG5D,EACT,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE;4CACZ,OAAO,CAAC,CAAC,CAAC,CAAA;4CACV,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;wCACzB,CAAC,EACD,SAAS,EAAC,yDAAyD,sBAG5D,IACL,CACP,EACD,iCAAK,SAAS,EAAC,qCAAqC,aAClD,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,IAAI,EACd,SAAS,EAAC,yDAAyD,uBAG5D,EACT,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,iBAAiB,EAC1B,QAAQ,EAAE,IAAI,EACd,SAAS,EAAC,yDAAyD,6BAG5D,EACT,mCACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,IAAI,IAAI,QAAQ,IAAI,CAAC,iBAAiB,EAChD,SAAS,EAAC,+GAA+G,YAExH,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,GAC3B,IACL,IACF,IACK,IA1JT,UAAU,CA2JH,CACd,GACe,CACnB,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,cAAc,CAC3B,QAAgB,EAChB,IAAc,EACd,UAAkB,EAClB,OAAe;IAEf,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAA;IACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAA;IAC/C,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;IACzB,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;IAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;IACnC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAA;IAC1D,GAAG,CAAC,SAAS,CACX,KAAK,EACL,IAAI,CAAC,CAAC,EACN,IAAI,CAAC,CAAC,EACN,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,MAAM,EACX,CAAC,EACD,CAAC,EACD,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,MAAM,CACZ,CAAA;IACD,MAAM,UAAU,GAAG,UAAU,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAA;IAC1E,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,MAAM,CAAC,MAAM,CACX,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAC5E,UAAU,EACV,UAAU,KAAK,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAClD,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE,CAAA;QACvB,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAC/B,GAAG,CAAC,OAAO,GAAG,MAAM,CAAA;QACpB,GAAG,CAAC,GAAG,GAAG,GAAG,CAAA;IACf,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,GAAG,CAAC,GAAG,GAA6C;IAC3D,OAAO,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACtC,CAAC"}
|
|
1
|
+
{"version":3,"file":"CropDialog.js","sourceRoot":"","sources":["../../../src/react/crop/CropDialog.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vault/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;CACpB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vault/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;CACpB;AAsBD,UAAU,aAAa;IACrB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAcD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,aAAkB,GAAG,IAAI,CAYpE;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED,8EAA8E;AAC9E,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAGrD;AAED,2EAA2E;AAC3E,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAEhD;AAmED;;;;GAIG;AACH,wBAAsB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAGrE;AAED,8EAA8E;AAC9E,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQhE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAwB7B;AAED,4DAA4D;AAC5D,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAKlE;AAED,gEAAgE;AAChE,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAOrE;AAID,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,wBAAwB,CAAA;IAC/B,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;IACtC,qDAAqD;IACrD,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,eAAe;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,gCAAgC;IAC/C;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACjE;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AA8BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gCAAgC,GACxC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAmDzC"}
|
package/dist/vault/index.js
CHANGED
|
@@ -38,6 +38,21 @@ exports.getPublicEnvs = getPublicEnvs;
|
|
|
38
38
|
exports.createVaultWebhookHandler = createVaultWebhookHandler;
|
|
39
39
|
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
40
40
|
const DEFAULT_BASE_URL = "https://sentroy.com";
|
|
41
|
+
/**
|
|
42
|
+
* Opt-in runtime logging — `SENTROY_ENV_DEBUG=1` set edilirse her fetch
|
|
43
|
+
* (success/fail), cache hit ve fallback satırı stdout'a yazılır. Default
|
|
44
|
+
* kapalı; migration verification + prod sorunu debug için.
|
|
45
|
+
*/
|
|
46
|
+
function isDebug() {
|
|
47
|
+
const v = readEnv("SENTROY_ENV_DEBUG");
|
|
48
|
+
return v === "1" || v === "true";
|
|
49
|
+
}
|
|
50
|
+
function debugLog(...parts) {
|
|
51
|
+
if (isDebug()) {
|
|
52
|
+
// eslint-disable-next-line no-console
|
|
53
|
+
console.log("[env-vault]", ...parts);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
41
56
|
let resolvedBaseUrl = DEFAULT_BASE_URL;
|
|
42
57
|
let resolvedApiKey;
|
|
43
58
|
let cacheTtlMs = DEFAULT_TTL_MS;
|
|
@@ -90,12 +105,15 @@ async function fetchVariables() {
|
|
|
90
105
|
"Set it on the platform (Coolify env) or call configureEnvClient({ apiKey: ... }) at boot.");
|
|
91
106
|
}
|
|
92
107
|
const url = `${resolvedBaseUrl}/api/env-vault/fetch`;
|
|
108
|
+
const started = Date.now();
|
|
109
|
+
debugLog(`fetching ${url}`);
|
|
93
110
|
const res = await fetch(url, {
|
|
94
111
|
headers: { Authorization: `Bearer ${resolvedApiKey}` },
|
|
95
112
|
signal: AbortSignal.timeout(fetchTimeoutMs),
|
|
96
113
|
cache: "no-store",
|
|
97
114
|
});
|
|
98
115
|
if (!res.ok) {
|
|
116
|
+
debugLog(`fetch failed: ${res.status} ${res.statusText} (${Date.now() - started}ms)`);
|
|
99
117
|
throw new Error(`env-vault fetch failed: ${res.status} ${res.statusText} (url=${url})`);
|
|
100
118
|
}
|
|
101
119
|
const json = (await res.json());
|
|
@@ -104,6 +122,7 @@ async function fetchVariables() {
|
|
|
104
122
|
const map = new Map();
|
|
105
123
|
for (const v of json.data.variables)
|
|
106
124
|
map.set(v.key, v);
|
|
125
|
+
debugLog(`fetched ${map.size} var(s) from ${json.data.project}/${json.data.environment} in ${Date.now() - started}ms`);
|
|
107
126
|
return {
|
|
108
127
|
fetchedAt: Date.now(),
|
|
109
128
|
variables: map,
|
|
@@ -170,17 +189,26 @@ async function getEnvOrThrow(key) {
|
|
|
170
189
|
async function getEnvWithFallback(key) {
|
|
171
190
|
// Token yoksa bypass — vault fetch denemeyelim, log spam etmeyelim.
|
|
172
191
|
const apiKey = resolvedApiKey ?? readEnv("SENTROY_ENV_API_KEY");
|
|
173
|
-
if (!apiKey)
|
|
174
|
-
|
|
192
|
+
if (!apiKey) {
|
|
193
|
+
const pe = readEnv(key);
|
|
194
|
+
debugLog(`${key}: no-token bypass → ${pe !== undefined ? "process.env hit" : "miss"}`);
|
|
195
|
+
return pe;
|
|
196
|
+
}
|
|
175
197
|
try {
|
|
176
198
|
const v = await getEnv(key);
|
|
177
|
-
if (v !== undefined)
|
|
199
|
+
if (v !== undefined) {
|
|
200
|
+
debugLog(`${key}: vault hit`);
|
|
178
201
|
return v;
|
|
202
|
+
}
|
|
203
|
+
const pe = readEnv(key);
|
|
204
|
+
debugLog(`${key}: vault miss → ${pe !== undefined ? "process.env fallback" : "undefined"}`);
|
|
205
|
+
return pe;
|
|
179
206
|
}
|
|
180
|
-
catch {
|
|
181
|
-
|
|
207
|
+
catch (err) {
|
|
208
|
+
const pe = readEnv(key);
|
|
209
|
+
debugLog(`${key}: vault error (${err instanceof Error ? err.message : String(err)}) → ${pe !== undefined ? "process.env fallback" : "undefined"}`);
|
|
210
|
+
return pe;
|
|
182
211
|
}
|
|
183
|
-
return readEnv(key);
|
|
184
212
|
}
|
|
185
213
|
/** Tüm env'leri map olarak döner (dump için kullanışlı). */
|
|
186
214
|
async function getAllEnvs() {
|
package/dist/vault/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vault/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vault/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;;AA+DH,gDAYC;AAGD,wCAEC;AAGD,0CAGC;AAGD,gCAEC;AAwED,wBAGC;AAGD,sCAQC;AAmBD,gDA0BC;AAGD,gCAKC;AAGD,sCAOC;AA2ED,8DAqDC;AAhWD,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA;AACpC,MAAM,gBAAgB,GAAG,qBAAqB,CAAA;AAE9C;;;;GAIG;AACH,SAAS,OAAO;IACd,MAAM,CAAC,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAA;IACtC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,MAAM,CAAA;AAClC,CAAC;AAED,SAAS,QAAQ,CAAC,GAAG,KAAgB;IACnC,IAAI,OAAO,EAAE,EAAE,CAAC;QACd,sCAAsC;QACtC,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,GAAG,KAAK,CAAC,CAAA;IACtC,CAAC;AACH,CAAC;AAaD,IAAI,eAAe,GAAG,gBAAgB,CAAA;AACtC,IAAI,cAAkC,CAAA;AACtC,IAAI,UAAU,GAAG,cAAc,CAAA;AAC/B,IAAI,cAAc,GAAG,IAAI,CAAA;AACzB,IAAI,KAAK,GAAyB,IAAI,CAAA;AACtC,IAAI,cAAc,GAAyB,IAAI,CAAA;AAE/C,SAAS,OAAO,CAAC,IAAY;IAC3B,IAAI,OAAO,OAAO,KAAK,WAAW;QAAE,OAAO,SAAS,CAAA;IACpD,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;AAC5B,CAAC;AAED;;;GAGG;AACH,SAAgB,kBAAkB,CAAC,UAAyB,EAAE;IAC5D,IAAI,OAAO,CAAC,OAAO;QAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;;QAExE,eAAe,GAAG,CAChB,OAAO,CAAC,iCAAiC,CAAC;YAC1C,OAAO,CAAC,qBAAqB,CAAC;YAC9B,OAAO,CAAC,0BAA0B,CAAC;YACnC,gBAAgB,CACjB,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACvB,cAAc,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACjE,IAAI,OAAO,CAAC,UAAU;QAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAA;IAC9D,IAAI,OAAO,CAAC,SAAS;QAAE,cAAc,GAAG,OAAO,CAAC,SAAS,CAAA;AAC3D,CAAC;AAED,8EAA8E;AAC9E,SAAgB,cAAc,CAAC,OAAe;IAC5C,UAAU,GAAG,OAAO,GAAG,IAAI,CAAA;AAC7B,CAAC;AAED,8EAA8E;AACvE,KAAK,UAAU,eAAe;IACnC,KAAK,GAAG,IAAI,CAAA;IACZ,MAAM,WAAW,EAAE,CAAA;AACrB,CAAC;AAED,2EAA2E;AACpE,KAAK,UAAU,UAAU;IAC9B,MAAM,WAAW,EAAE,CAAA;AACrB,CAAC;AAED,KAAK,UAAU,cAAc;IAC3B,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,iEAAiE;QACjE,kBAAkB,EAAE,CAAA;IACtB,CAAC;IACD,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,gEAAgE;YAC9D,2FAA2F,CAC9F,CAAA;IACH,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,eAAe,sBAAsB,CAAA;IACpD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAC1B,QAAQ,CAAC,YAAY,GAAG,EAAE,CAAC,CAAA;IAC3B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,cAAc,EAAE,EAAE;QACtD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,cAAc,CAAC;QAC3C,KAAK,EAAE,UAAU;KAClB,CAAC,CAAA;IACF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,QAAQ,CAAC,iBAAiB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,KAAK,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,KAAK,CAAC,CAAA;QACrF,MAAM,IAAI,KAAK,CACb,2BAA2B,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,SAAS,GAAG,GAAG,CACvE,CAAA;IACH,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAM7B,CAAA;IACD,IAAI,CAAC,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;IACtE,MAAM,GAAG,GAAG,IAAI,GAAG,EAAuB,CAAA;IAC1C,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS;QAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;IACtD,QAAQ,CACN,WAAW,GAAG,CAAC,IAAI,gBAAgB,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,IAAI,CAC7G,CAAA;IACD,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,SAAS,EAAE,GAAG;QACd,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO;QAC1B,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW;KACnC,CAAA;AACH,CAAC;AAED,KAAK,UAAU,WAAW;IACxB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,IAAI,KAAK,IAAI,GAAG,GAAG,KAAK,CAAC,SAAS,GAAG,UAAU;QAAE,OAAO,KAAK,CAAA;IAC7D,IAAI,cAAc,EAAE,CAAC;QACnB,MAAM,cAAc,CAAA;QACpB,IAAI,KAAK;YAAE,OAAO,KAAK,CAAA;IACzB,CAAC;IACD,cAAc,GAAG,CAAC,KAAK,IAAI,EAAE;QAC3B,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,cAAc,EAAE,CAAA;QAChC,CAAC;gBAAS,CAAC;YACT,cAAc,GAAG,IAAI,CAAA;QACvB,CAAC;IACH,CAAC,CAAC,EAAE,CAAA;IACJ,MAAM,cAAc,CAAA;IACpB,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;IAC9D,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,MAAM,CAAC,GAAW;IACtC,MAAM,CAAC,GAAG,MAAM,WAAW,EAAE,CAAA;IAC7B,OAAO,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,CAAA;AACpC,CAAC;AAED,8EAA8E;AACvE,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,CAAA;IAC3B,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,gCAAgC,GAAG,4BAA4B,KAAK,EAAE,OAAO,IAAI,GAAG,SAAS,KAAK,EAAE,WAAW,IAAI,GAAG,GAAG,CAC1H,CAAA;IACH,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACI,KAAK,UAAU,kBAAkB,CACtC,GAAW;IAEX,oEAAoE;IACpE,MAAM,MAAM,GAAG,cAAc,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAA;IAC/D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QACvB,QAAQ,CAAC,GAAG,GAAG,uBAAuB,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAA;QACtF,OAAO,EAAE,CAAA;IACX,CAAC;IACD,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACpB,QAAQ,CAAC,GAAG,GAAG,aAAa,CAAC,CAAA;YAC7B,OAAO,CAAC,CAAA;QACV,CAAC;QACD,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QACvB,QAAQ,CAAC,GAAG,GAAG,kBAAkB,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAA;QAC3F,OAAO,EAAE,CAAA;IACX,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QACvB,QAAQ,CACN,GAAG,GAAG,kBAAkB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,WAAW,EAAE,CACzI,CAAA;QACD,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC;AAED,4DAA4D;AACrD,KAAK,UAAU,UAAU;IAC9B,MAAM,CAAC,GAAG,MAAM,WAAW,EAAE,CAAA;IAC7B,MAAM,GAAG,GAA2B,EAAE,CAAA;IACtC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAA;IAClD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,gEAAgE;AACzD,KAAK,UAAU,aAAa;IACjC,MAAM,CAAC,GAAG,MAAM,WAAW,EAAE,CAAA;IAC7B,MAAM,GAAG,GAA2B,EAAE,CAAA;IACtC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;QACjC,IAAI,CAAC,CAAC,MAAM;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAA;IAChC,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAkCD,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA;AAExC,KAAK,UAAU,kBAAkB,CAAC,CAAS,EAAE,CAAS;IACpD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACvC,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;IAC3C,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAA;AACnB,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,MAAc,EAAE,IAAY;IACvD,sDAAsD;IACtD,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;IACjC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CACvC,KAAK,EACL,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EACtB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,CAAC,CACT,CAAA;IACD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;IACvE,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAA;IACjC,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,GAAG,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IAC7D,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,yBAAyB,CACvC,OAAyC;IAEzC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,kBAAkB,CAAA;IACvD,OAAO,KAAK,EAAE,OAAgB,EAAE,EAAE;QAChC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,IAAI,EAAE,CAAA;QAClE,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAA;QACtD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,IAAI,QAAQ,CAAC,iDAAiD,EAAE;gBACrE,MAAM,EAAE,GAAG;aACZ,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;QAC1C,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAA;QACjC,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;QAC1D,IAAI,CAAC,CAAC,MAAM,kBAAkB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;YACvD,OAAO,IAAI,QAAQ,CAAC,oBAAoB,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;QAC5D,CAAC;QAED,IAAI,OAA4B,CAAA;QAChC,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAwB,CAAA;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,QAAQ,CAAC,mBAAmB,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;QAC3D,CAAC;QAED,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,CAAC,CAAC,CAAA;YACjD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,GAAG,QAAQ,EAAE,CAAC;gBACvD,OAAO,IAAI,QAAQ,CAAC,6CAA6C,EAAE;oBACjE,MAAM,EAAE,GAAG;iBACZ,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;gBACrB,MAAM,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;YACjC,CAAC;iBAAM,CAAC;gBACN,MAAM,eAAe,EAAE,CAAA;YACzB,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,IAAI,QAAQ,CACjB,kBAAkB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EACpE,EAAE,MAAM,EAAE,GAAG,EAAE,CAChB,CAAA;QACH,CAAC;QAED,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;YAChD,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CAAA;IACJ,CAAC,CAAA;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react"
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react"
|
|
2
2
|
import Cropper from "react-easy-crop"
|
|
3
3
|
import { motion, AnimatePresence } from "motion/react"
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Image crop dialog — `react-easy-crop` üzerine
|
|
7
|
-
* upload akışında `preprocessFile` hook'unun içinde çağrılır:
|
|
6
|
+
* Image crop dialog — `react-easy-crop` üzerine professional crop UI:
|
|
8
7
|
* - Aspect preset toolbar (1:1, 4:3, 16:9, 3:2, 9:16, Free)
|
|
9
8
|
* - Zoom slider (+/− ile de)
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
9
|
+
* - Rotate 90° CW/CCW (`R` shortcut)
|
|
10
|
+
* - Sağ panelde **live preview thumbnail** — crop alanı her değiştiğinde
|
|
11
|
+
* küçük canvas'a aynı transformasyonu uygulayıp output sonucunu gösterir;
|
|
12
|
+
* kullanıcı Apply'a basmadan "ne çıkacak" göründüğü için
|
|
13
|
+
* Photoshop / Figma seviyesinde feedback.
|
|
14
|
+
* - Output pixel boyutu (örn. 1200×800) — RP'lerin export presetlerine
|
|
15
|
+
* hizalanmak için.
|
|
16
|
+
* - Apply → cropped Blob, Cancel → null, "Use original" → original File.
|
|
12
17
|
*
|
|
13
18
|
* Ayrı bir entry point (`@sentroy-co/client-sdk/react/crop`) — ana SDK
|
|
14
19
|
* import'u `react-easy-crop`'u bundle'a çekmesin (lazy subpath).
|
|
15
20
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
21
|
+
* `getCroppedBlob` rotation-aware: önce kaynak image rotate edilir,
|
|
22
|
+
* sonra `croppedAreaPixels`'in döndürülmüş koordinat sisteminden çıkarılır.
|
|
23
|
+
* react-easy-crop'un `croppedAreaPixels` çıktısı zaten rotation'a göre
|
|
24
|
+
* transform edilmiş — biz canvas'a aynı rotation'ı uygulayıp aynı koordinat
|
|
25
|
+
* sisteminde drawImage yapıyoruz.
|
|
19
26
|
*/
|
|
20
27
|
|
|
21
28
|
interface CropArea {
|
|
@@ -35,6 +42,7 @@ const ASPECT_PRESETS: Array<{ id: string; label: string; aspect: number | null }
|
|
|
35
42
|
]
|
|
36
43
|
|
|
37
44
|
const MAX_PIXEL_GUARD = 50_000_000 // ~24 MP — üstü tarayıcı memory peak'i riskli
|
|
45
|
+
const PREVIEW_MAX_DIM = 220 // sidebar thumbnail için max edge (px)
|
|
38
46
|
|
|
39
47
|
export interface CropDialogProps {
|
|
40
48
|
open: boolean
|
|
@@ -61,9 +69,13 @@ export function CropDialog({
|
|
|
61
69
|
const [aspectId, setAspectId] = useState(defaultAspect)
|
|
62
70
|
const [crop, setCrop] = useState({ x: 0, y: 0 })
|
|
63
71
|
const [zoom, setZoom] = useState(1)
|
|
72
|
+
const [rotation, setRotation] = useState(0)
|
|
64
73
|
const [croppedAreaPixels, setCroppedAreaPixels] = useState<CropArea | null>(null)
|
|
65
74
|
const [busy, setBusy] = useState(false)
|
|
66
75
|
const [tooLarge, setTooLarge] = useState(false)
|
|
76
|
+
const previewCanvasRef = useRef<HTMLCanvasElement | null>(null)
|
|
77
|
+
const previewRafRef = useRef<number | null>(null)
|
|
78
|
+
const imageElRef = useRef<HTMLImageElement | null>(null)
|
|
67
79
|
|
|
68
80
|
// Object URL lifecycle
|
|
69
81
|
useEffect(() => {
|
|
@@ -72,17 +84,22 @@ export function CropDialog({
|
|
|
72
84
|
setImageUrl(url)
|
|
73
85
|
setCrop({ x: 0, y: 0 })
|
|
74
86
|
setZoom(1)
|
|
87
|
+
setRotation(0)
|
|
75
88
|
setAspectId(defaultAspect)
|
|
76
89
|
setTooLarge(false)
|
|
77
|
-
// Pixel guard
|
|
90
|
+
// Pixel guard + cache HTMLImageElement (preview drawImage source).
|
|
78
91
|
const img = new Image()
|
|
79
92
|
img.onload = () => {
|
|
80
93
|
if (img.naturalWidth * img.naturalHeight > MAX_PIXEL_GUARD) {
|
|
81
94
|
setTooLarge(true)
|
|
82
95
|
}
|
|
96
|
+
imageElRef.current = img
|
|
83
97
|
}
|
|
84
98
|
img.src = url
|
|
85
|
-
return () =>
|
|
99
|
+
return () => {
|
|
100
|
+
URL.revokeObjectURL(url)
|
|
101
|
+
imageElRef.current = null
|
|
102
|
+
}
|
|
86
103
|
}, [open, file, defaultAspect])
|
|
87
104
|
|
|
88
105
|
const onCropComplete = useCallback(
|
|
@@ -92,6 +109,43 @@ export function CropDialog({
|
|
|
92
109
|
[],
|
|
93
110
|
)
|
|
94
111
|
|
|
112
|
+
// Live preview render — crop / rotation değiştikçe sağ panel thumbnail'ı
|
|
113
|
+
// güncelle. requestAnimationFrame ile throttle (drag sırasında her event'te
|
|
114
|
+
// canvas çizmek pahalı).
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (!croppedAreaPixels || !imageElRef.current || tooLarge) return
|
|
117
|
+
if (previewRafRef.current !== null) {
|
|
118
|
+
cancelAnimationFrame(previewRafRef.current)
|
|
119
|
+
}
|
|
120
|
+
previewRafRef.current = requestAnimationFrame(() => {
|
|
121
|
+
const canvas = previewCanvasRef.current
|
|
122
|
+
const img = imageElRef.current
|
|
123
|
+
if (!canvas || !img) return
|
|
124
|
+
const area = croppedAreaPixels
|
|
125
|
+
// Preview boyutu — aspect korunarak max edge PREVIEW_MAX_DIM
|
|
126
|
+
const scale =
|
|
127
|
+
area.width >= area.height
|
|
128
|
+
? PREVIEW_MAX_DIM / area.width
|
|
129
|
+
: PREVIEW_MAX_DIM / area.height
|
|
130
|
+
const pw = Math.max(1, Math.round(area.width * scale))
|
|
131
|
+
const ph = Math.max(1, Math.round(area.height * scale))
|
|
132
|
+
canvas.width = pw
|
|
133
|
+
canvas.height = ph
|
|
134
|
+
const ctx = canvas.getContext("2d")
|
|
135
|
+
if (!ctx) return
|
|
136
|
+
ctx.imageSmoothingQuality = "high"
|
|
137
|
+
ctx.clearRect(0, 0, pw, ph)
|
|
138
|
+
drawRotatedCrop(ctx, img, area, rotation, pw, ph)
|
|
139
|
+
previewRafRef.current = null
|
|
140
|
+
})
|
|
141
|
+
return () => {
|
|
142
|
+
if (previewRafRef.current !== null) {
|
|
143
|
+
cancelAnimationFrame(previewRafRef.current)
|
|
144
|
+
previewRafRef.current = null
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}, [croppedAreaPixels, rotation, tooLarge])
|
|
148
|
+
|
|
95
149
|
const aspect =
|
|
96
150
|
ASPECT_PRESETS.find((p) => p.id === aspectId)?.aspect ?? undefined
|
|
97
151
|
|
|
@@ -99,8 +153,13 @@ export function CropDialog({
|
|
|
99
153
|
if (!imageUrl || !croppedAreaPixels) return
|
|
100
154
|
setBusy(true)
|
|
101
155
|
try {
|
|
102
|
-
const blob = await getCroppedBlob(
|
|
103
|
-
|
|
156
|
+
const blob = await getCroppedBlob(
|
|
157
|
+
imageUrl,
|
|
158
|
+
croppedAreaPixels,
|
|
159
|
+
rotation,
|
|
160
|
+
file.type,
|
|
161
|
+
outputQuality,
|
|
162
|
+
)
|
|
104
163
|
const ext = blob.type === "image/png" ? "png" : "jpg"
|
|
105
164
|
const baseName = file.name.replace(/\.[^.]+$/, "")
|
|
106
165
|
const cropped = new File([blob], `${baseName}.${ext}`, {
|
|
@@ -110,23 +169,48 @@ export function CropDialog({
|
|
|
110
169
|
} finally {
|
|
111
170
|
setBusy(false)
|
|
112
171
|
}
|
|
113
|
-
}, [imageUrl, croppedAreaPixels, file, onClose, outputQuality])
|
|
172
|
+
}, [imageUrl, croppedAreaPixels, rotation, file, onClose, outputQuality])
|
|
114
173
|
|
|
115
174
|
const handleUseOriginal = useCallback(() => onClose(file), [file, onClose])
|
|
116
175
|
const handleCancel = useCallback(() => onClose(null), [onClose])
|
|
176
|
+
const handleRotate = useCallback(
|
|
177
|
+
(delta: 90 | -90) => {
|
|
178
|
+
setRotation((r) => {
|
|
179
|
+
const next = (r + delta + 360) % 360
|
|
180
|
+
return next
|
|
181
|
+
})
|
|
182
|
+
},
|
|
183
|
+
[],
|
|
184
|
+
)
|
|
117
185
|
|
|
118
|
-
// ESC kapatır
|
|
186
|
+
// ESC kapatır, R döndürür
|
|
119
187
|
useEffect(() => {
|
|
120
188
|
if (!open) return
|
|
121
189
|
const onKey = (e: KeyboardEvent) => {
|
|
122
190
|
if (e.key === "Escape") {
|
|
123
191
|
e.stopPropagation()
|
|
124
192
|
handleCancel()
|
|
193
|
+
} else if (e.key === "r" || e.key === "R") {
|
|
194
|
+
if (
|
|
195
|
+
e.target instanceof HTMLElement &&
|
|
196
|
+
(e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA")
|
|
197
|
+
) {
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
e.preventDefault()
|
|
201
|
+
handleRotate(e.shiftKey ? -90 : 90)
|
|
125
202
|
}
|
|
126
203
|
}
|
|
127
204
|
window.addEventListener("keydown", onKey)
|
|
128
205
|
return () => window.removeEventListener("keydown", onKey)
|
|
129
|
-
}, [open, handleCancel])
|
|
206
|
+
}, [open, handleCancel, handleRotate])
|
|
207
|
+
|
|
208
|
+
const outputWidth = croppedAreaPixels ? Math.round(croppedAreaPixels.width) : 0
|
|
209
|
+
const outputHeight = croppedAreaPixels ? Math.round(croppedAreaPixels.height) : 0
|
|
210
|
+
// Rotation 90° / 270° iken output dimensions swap edilir (canvas rotate
|
|
211
|
+
// sonrası kullanıcı görsel olarak swap görür).
|
|
212
|
+
const displayW = rotation % 180 === 0 ? outputWidth : outputHeight
|
|
213
|
+
const displayH = rotation % 180 === 0 ? outputHeight : outputWidth
|
|
130
214
|
|
|
131
215
|
return (
|
|
132
216
|
<AnimatePresence>
|
|
@@ -137,7 +221,6 @@ export function CropDialog({
|
|
|
137
221
|
animate={{ opacity: 1 }}
|
|
138
222
|
exit={{ opacity: 0 }}
|
|
139
223
|
transition={{ duration: 0.2 }}
|
|
140
|
-
// z-index ana MediaManager modal'ından yüksek (nested)
|
|
141
224
|
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/80 backdrop-blur-sm p-4"
|
|
142
225
|
onClick={(e) => {
|
|
143
226
|
if (e.target === e.currentTarget) handleCancel()
|
|
@@ -148,11 +231,11 @@ export function CropDialog({
|
|
|
148
231
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
149
232
|
exit={{ opacity: 0, scale: 0.98 }}
|
|
150
233
|
transition={{ duration: 0.25, ease: [0.22, 1, 0.36, 1] }}
|
|
151
|
-
className="flex h-[min(
|
|
234
|
+
className="flex h-[min(92vh,760px)] w-full max-w-5xl flex-col overflow-hidden rounded-xl border bg-background shadow-2xl"
|
|
152
235
|
>
|
|
153
236
|
{/* Header */}
|
|
154
237
|
<div className="flex items-center justify-between gap-3 border-b px-4 py-3">
|
|
155
|
-
<div className="flex flex-col">
|
|
238
|
+
<div className="flex flex-col min-w-0">
|
|
156
239
|
<span className="text-sm font-semibold">Crop image</span>
|
|
157
240
|
<span className="truncate text-xs text-muted-foreground">
|
|
158
241
|
{file.name}
|
|
@@ -178,45 +261,119 @@ export function CropDialog({
|
|
|
178
261
|
</button>
|
|
179
262
|
</div>
|
|
180
263
|
|
|
181
|
-
{/* Aspect toolbar */}
|
|
264
|
+
{/* Aspect + rotate toolbar */}
|
|
182
265
|
<div className="flex flex-wrap items-center gap-1 border-b bg-muted/20 px-3 py-2">
|
|
183
|
-
|
|
266
|
+
<div className="flex flex-1 flex-wrap items-center gap-1">
|
|
267
|
+
{ASPECT_PRESETS.map((p) => (
|
|
268
|
+
<button
|
|
269
|
+
key={p.id}
|
|
270
|
+
type="button"
|
|
271
|
+
onClick={() => setAspectId(p.id)}
|
|
272
|
+
className={cls(
|
|
273
|
+
"rounded-md px-2.5 py-1 text-xs transition-colors",
|
|
274
|
+
aspectId === p.id
|
|
275
|
+
? "bg-foreground text-background"
|
|
276
|
+
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
277
|
+
)}
|
|
278
|
+
>
|
|
279
|
+
{p.label}
|
|
280
|
+
</button>
|
|
281
|
+
))}
|
|
282
|
+
</div>
|
|
283
|
+
<div className="ms-auto flex items-center gap-1">
|
|
184
284
|
<button
|
|
185
|
-
key={p.id}
|
|
186
285
|
type="button"
|
|
187
|
-
onClick={() =>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
286
|
+
onClick={() => handleRotate(-90)}
|
|
287
|
+
title="Rotate left (Shift+R)"
|
|
288
|
+
aria-label="Rotate left"
|
|
289
|
+
className="rounded-md border px-2 py-1 text-xs text-muted-foreground hover:bg-muted/50 hover:text-foreground"
|
|
290
|
+
>
|
|
291
|
+
<RotateLeftIcon />
|
|
292
|
+
</button>
|
|
293
|
+
<button
|
|
294
|
+
type="button"
|
|
295
|
+
onClick={() => handleRotate(90)}
|
|
296
|
+
title="Rotate right (R)"
|
|
297
|
+
aria-label="Rotate right"
|
|
298
|
+
className="rounded-md border px-2 py-1 text-xs text-muted-foreground hover:bg-muted/50 hover:text-foreground"
|
|
194
299
|
>
|
|
195
|
-
|
|
300
|
+
<RotateRightIcon />
|
|
196
301
|
</button>
|
|
197
|
-
|
|
302
|
+
</div>
|
|
198
303
|
</div>
|
|
199
304
|
|
|
200
|
-
{/*
|
|
201
|
-
<div className="
|
|
202
|
-
{
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
305
|
+
{/* Body: cropper + preview sidebar */}
|
|
306
|
+
<div className="flex flex-1 min-h-0 flex-col overflow-hidden md:flex-row">
|
|
307
|
+
{/* Cropper canvas */}
|
|
308
|
+
<div className="relative flex-1 bg-black">
|
|
309
|
+
{tooLarge ? (
|
|
310
|
+
<div className="flex h-full w-full items-center justify-center p-6 text-center text-sm text-white/70">
|
|
311
|
+
Image too large to crop in browser. Upload as-is or resize
|
|
312
|
+
beforehand.
|
|
313
|
+
</div>
|
|
314
|
+
) : (
|
|
315
|
+
<Cropper
|
|
316
|
+
image={imageUrl}
|
|
317
|
+
crop={crop}
|
|
318
|
+
zoom={zoom}
|
|
319
|
+
rotation={rotation}
|
|
320
|
+
aspect={aspect}
|
|
321
|
+
onCropChange={setCrop}
|
|
322
|
+
onCropComplete={onCropComplete}
|
|
323
|
+
onZoomChange={setZoom}
|
|
324
|
+
onRotationChange={setRotation}
|
|
325
|
+
showGrid
|
|
326
|
+
objectFit="contain"
|
|
327
|
+
/>
|
|
328
|
+
)}
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
{/* Preview sidebar */}
|
|
332
|
+
<div className="flex w-full shrink-0 flex-col gap-3 border-t bg-muted/10 p-4 md:w-64 md:border-l md:border-t-0">
|
|
333
|
+
<div className="flex items-center justify-between">
|
|
334
|
+
<span className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
335
|
+
Preview
|
|
336
|
+
</span>
|
|
337
|
+
{displayW > 0 && displayH > 0 && (
|
|
338
|
+
<span className="font-mono text-[10px] text-muted-foreground">
|
|
339
|
+
{displayW}×{displayH}
|
|
340
|
+
</span>
|
|
341
|
+
)}
|
|
206
342
|
</div>
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
343
|
+
<div className="flex min-h-[140px] items-center justify-center rounded-lg border border-dashed bg-background/40 p-2">
|
|
344
|
+
{tooLarge ? (
|
|
345
|
+
<span className="text-[11px] text-muted-foreground">
|
|
346
|
+
Preview unavailable
|
|
347
|
+
</span>
|
|
348
|
+
) : croppedAreaPixels ? (
|
|
349
|
+
<canvas
|
|
350
|
+
ref={previewCanvasRef}
|
|
351
|
+
className="max-h-[220px] max-w-full rounded-sm shadow-sm"
|
|
352
|
+
/>
|
|
353
|
+
) : (
|
|
354
|
+
<span className="text-[11px] text-muted-foreground">
|
|
355
|
+
Adjust crop…
|
|
356
|
+
</span>
|
|
357
|
+
)}
|
|
358
|
+
</div>
|
|
359
|
+
<div className="flex flex-col gap-1 text-[11px] text-muted-foreground">
|
|
360
|
+
<div className="flex items-center justify-between">
|
|
361
|
+
<span>Aspect</span>
|
|
362
|
+
<span className="font-mono">
|
|
363
|
+
{ASPECT_PRESETS.find((p) => p.id === aspectId)?.label ??
|
|
364
|
+
"Free"}
|
|
365
|
+
</span>
|
|
366
|
+
</div>
|
|
367
|
+
<div className="flex items-center justify-between">
|
|
368
|
+
<span>Rotation</span>
|
|
369
|
+
<span className="font-mono">{rotation}°</span>
|
|
370
|
+
</div>
|
|
371
|
+
<div className="flex items-center justify-between">
|
|
372
|
+
<span>Zoom</span>
|
|
373
|
+
<span className="font-mono">{zoom.toFixed(2)}×</span>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
220
377
|
</div>
|
|
221
378
|
|
|
222
379
|
{/* Zoom + actions */}
|
|
@@ -251,6 +408,7 @@ export function CropDialog({
|
|
|
251
408
|
type="button"
|
|
252
409
|
onClick={() => {
|
|
253
410
|
setZoom(1)
|
|
411
|
+
setRotation(0)
|
|
254
412
|
setCrop({ x: 0, y: 0 })
|
|
255
413
|
}}
|
|
256
414
|
className="rounded-md border px-2 py-0.5 text-xs hover:bg-muted/50"
|
|
@@ -293,6 +451,68 @@ export function CropDialog({
|
|
|
293
451
|
)
|
|
294
452
|
}
|
|
295
453
|
|
|
454
|
+
/**
|
|
455
|
+
* Rotated crop'u canvas'a yaz. Rotation 0° fast-path; aksi halde önce
|
|
456
|
+
* "rotated bounding box" canvas'ı oluştur (orijinal image'in döndürülmüş
|
|
457
|
+
* versiyonu), sonra croppedAreaPixels koordinatlarında çıkar.
|
|
458
|
+
*
|
|
459
|
+
* react-easy-crop's `croppedAreaPixels` rotation-aware: koordinatlar
|
|
460
|
+
* orijinal image'i rotasyon merkezinden döndürdükten sonraki "visual"
|
|
461
|
+
* bounding box üzerinden hesaplanır — biz aynı transform'u canvas'a
|
|
462
|
+
* uygulayıp aynı koordinatlardan drawImage yapıyoruz.
|
|
463
|
+
*/
|
|
464
|
+
function drawRotatedCrop(
|
|
465
|
+
ctx: CanvasRenderingContext2D,
|
|
466
|
+
image: HTMLImageElement,
|
|
467
|
+
area: CropArea,
|
|
468
|
+
rotation: number,
|
|
469
|
+
dstW: number,
|
|
470
|
+
dstH: number,
|
|
471
|
+
): void {
|
|
472
|
+
if (rotation === 0) {
|
|
473
|
+
ctx.drawImage(
|
|
474
|
+
image,
|
|
475
|
+
area.x,
|
|
476
|
+
area.y,
|
|
477
|
+
area.width,
|
|
478
|
+
area.height,
|
|
479
|
+
0,
|
|
480
|
+
0,
|
|
481
|
+
dstW,
|
|
482
|
+
dstH,
|
|
483
|
+
)
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
// Build a rotated source canvas at the size of the rotated bounding box,
|
|
487
|
+
// then crop from it.
|
|
488
|
+
const rad = (rotation * Math.PI) / 180
|
|
489
|
+
const sin = Math.abs(Math.sin(rad))
|
|
490
|
+
const cos = Math.abs(Math.cos(rad))
|
|
491
|
+
const iw = image.naturalWidth
|
|
492
|
+
const ih = image.naturalHeight
|
|
493
|
+
const bbW = iw * cos + ih * sin
|
|
494
|
+
const bbH = iw * sin + ih * cos
|
|
495
|
+
const tmp = document.createElement("canvas")
|
|
496
|
+
tmp.width = bbW
|
|
497
|
+
tmp.height = bbH
|
|
498
|
+
const tctx = tmp.getContext("2d")
|
|
499
|
+
if (!tctx) return
|
|
500
|
+
tctx.translate(bbW / 2, bbH / 2)
|
|
501
|
+
tctx.rotate(rad)
|
|
502
|
+
tctx.drawImage(image, -iw / 2, -ih / 2)
|
|
503
|
+
ctx.drawImage(
|
|
504
|
+
tmp,
|
|
505
|
+
area.x,
|
|
506
|
+
area.y,
|
|
507
|
+
area.width,
|
|
508
|
+
area.height,
|
|
509
|
+
0,
|
|
510
|
+
0,
|
|
511
|
+
dstW,
|
|
512
|
+
dstH,
|
|
513
|
+
)
|
|
514
|
+
}
|
|
515
|
+
|
|
296
516
|
/**
|
|
297
517
|
* Canvas ile crop area'yı çıkar + Blob döndür.
|
|
298
518
|
* Output MIME: PNG ise PNG, diğerleri JPEG (transparency yoksa).
|
|
@@ -300,6 +520,7 @@ export function CropDialog({
|
|
|
300
520
|
async function getCroppedBlob(
|
|
301
521
|
imageUrl: string,
|
|
302
522
|
area: CropArea,
|
|
523
|
+
rotation: number,
|
|
303
524
|
sourceMime: string,
|
|
304
525
|
quality: number,
|
|
305
526
|
): Promise<Blob> {
|
|
@@ -309,17 +530,8 @@ async function getCroppedBlob(
|
|
|
309
530
|
canvas.height = area.height
|
|
310
531
|
const ctx = canvas.getContext("2d")
|
|
311
532
|
if (!ctx) throw new Error("Canvas 2D context unavailable")
|
|
312
|
-
ctx.
|
|
313
|
-
|
|
314
|
-
area.x,
|
|
315
|
-
area.y,
|
|
316
|
-
area.width,
|
|
317
|
-
area.height,
|
|
318
|
-
0,
|
|
319
|
-
0,
|
|
320
|
-
area.width,
|
|
321
|
-
area.height,
|
|
322
|
-
)
|
|
533
|
+
ctx.imageSmoothingQuality = "high"
|
|
534
|
+
drawRotatedCrop(ctx, image, area, rotation, area.width, area.height)
|
|
323
535
|
const outputMime = sourceMime === "image/png" ? "image/png" : "image/jpeg"
|
|
324
536
|
return new Promise<Blob>((resolve, reject) => {
|
|
325
537
|
canvas.toBlob(
|
|
@@ -342,3 +554,39 @@ function loadImage(url: string): Promise<HTMLImageElement> {
|
|
|
342
554
|
function cls(...arr: Array<string | false | null | undefined>): string {
|
|
343
555
|
return arr.filter(Boolean).join(" ")
|
|
344
556
|
}
|
|
557
|
+
|
|
558
|
+
function RotateLeftIcon() {
|
|
559
|
+
return (
|
|
560
|
+
<svg
|
|
561
|
+
viewBox="0 0 24 24"
|
|
562
|
+
fill="none"
|
|
563
|
+
stroke="currentColor"
|
|
564
|
+
strokeWidth="2"
|
|
565
|
+
strokeLinecap="round"
|
|
566
|
+
strokeLinejoin="round"
|
|
567
|
+
className="size-3.5"
|
|
568
|
+
aria-hidden="true"
|
|
569
|
+
>
|
|
570
|
+
<path d="M3 12a9 9 0 1 0 3-6.7" />
|
|
571
|
+
<path d="M3 4v5h5" />
|
|
572
|
+
</svg>
|
|
573
|
+
)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function RotateRightIcon() {
|
|
577
|
+
return (
|
|
578
|
+
<svg
|
|
579
|
+
viewBox="0 0 24 24"
|
|
580
|
+
fill="none"
|
|
581
|
+
stroke="currentColor"
|
|
582
|
+
strokeWidth="2"
|
|
583
|
+
strokeLinecap="round"
|
|
584
|
+
strokeLinejoin="round"
|
|
585
|
+
className="size-3.5"
|
|
586
|
+
aria-hidden="true"
|
|
587
|
+
>
|
|
588
|
+
<path d="M21 12a9 9 0 1 1-3-6.7" />
|
|
589
|
+
<path d="M21 4v5h-5" />
|
|
590
|
+
</svg>
|
|
591
|
+
)
|
|
592
|
+
}
|
package/src/vault/index.ts
CHANGED
|
@@ -42,6 +42,23 @@ export interface EnvCacheState {
|
|
|
42
42
|
const DEFAULT_TTL_MS = 5 * 60 * 1000
|
|
43
43
|
const DEFAULT_BASE_URL = "https://sentroy.com"
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Opt-in runtime logging — `SENTROY_ENV_DEBUG=1` set edilirse her fetch
|
|
47
|
+
* (success/fail), cache hit ve fallback satırı stdout'a yazılır. Default
|
|
48
|
+
* kapalı; migration verification + prod sorunu debug için.
|
|
49
|
+
*/
|
|
50
|
+
function isDebug(): boolean {
|
|
51
|
+
const v = readEnv("SENTROY_ENV_DEBUG")
|
|
52
|
+
return v === "1" || v === "true"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function debugLog(...parts: unknown[]): void {
|
|
56
|
+
if (isDebug()) {
|
|
57
|
+
// eslint-disable-next-line no-console
|
|
58
|
+
console.log("[env-vault]", ...parts)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
45
62
|
interface ClientOptions {
|
|
46
63
|
/** Sentroy core URL (defaults to env or https://sentroy.com). */
|
|
47
64
|
baseUrl?: string
|
|
@@ -111,12 +128,15 @@ async function fetchVariables(): Promise<EnvCacheState> {
|
|
|
111
128
|
)
|
|
112
129
|
}
|
|
113
130
|
const url = `${resolvedBaseUrl}/api/env-vault/fetch`
|
|
131
|
+
const started = Date.now()
|
|
132
|
+
debugLog(`fetching ${url}`)
|
|
114
133
|
const res = await fetch(url, {
|
|
115
134
|
headers: { Authorization: `Bearer ${resolvedApiKey}` },
|
|
116
135
|
signal: AbortSignal.timeout(fetchTimeoutMs),
|
|
117
136
|
cache: "no-store",
|
|
118
137
|
})
|
|
119
138
|
if (!res.ok) {
|
|
139
|
+
debugLog(`fetch failed: ${res.status} ${res.statusText} (${Date.now() - started}ms)`)
|
|
120
140
|
throw new Error(
|
|
121
141
|
`env-vault fetch failed: ${res.status} ${res.statusText} (url=${url})`,
|
|
122
142
|
)
|
|
@@ -131,6 +151,9 @@ async function fetchVariables(): Promise<EnvCacheState> {
|
|
|
131
151
|
if (!json.data) throw new Error("env-vault fetch: malformed response")
|
|
132
152
|
const map = new Map<string, EnvVariable>()
|
|
133
153
|
for (const v of json.data.variables) map.set(v.key, v)
|
|
154
|
+
debugLog(
|
|
155
|
+
`fetched ${map.size} var(s) from ${json.data.project}/${json.data.environment} in ${Date.now() - started}ms`,
|
|
156
|
+
)
|
|
134
157
|
return {
|
|
135
158
|
fetchedAt: Date.now(),
|
|
136
159
|
variables: map,
|
|
@@ -201,14 +224,27 @@ export async function getEnvWithFallback(
|
|
|
201
224
|
): Promise<string | undefined> {
|
|
202
225
|
// Token yoksa bypass — vault fetch denemeyelim, log spam etmeyelim.
|
|
203
226
|
const apiKey = resolvedApiKey ?? readEnv("SENTROY_ENV_API_KEY")
|
|
204
|
-
if (!apiKey)
|
|
227
|
+
if (!apiKey) {
|
|
228
|
+
const pe = readEnv(key)
|
|
229
|
+
debugLog(`${key}: no-token bypass → ${pe !== undefined ? "process.env hit" : "miss"}`)
|
|
230
|
+
return pe
|
|
231
|
+
}
|
|
205
232
|
try {
|
|
206
233
|
const v = await getEnv(key)
|
|
207
|
-
if (v !== undefined)
|
|
208
|
-
|
|
209
|
-
|
|
234
|
+
if (v !== undefined) {
|
|
235
|
+
debugLog(`${key}: vault hit`)
|
|
236
|
+
return v
|
|
237
|
+
}
|
|
238
|
+
const pe = readEnv(key)
|
|
239
|
+
debugLog(`${key}: vault miss → ${pe !== undefined ? "process.env fallback" : "undefined"}`)
|
|
240
|
+
return pe
|
|
241
|
+
} catch (err) {
|
|
242
|
+
const pe = readEnv(key)
|
|
243
|
+
debugLog(
|
|
244
|
+
`${key}: vault error (${err instanceof Error ? err.message : String(err)}) → ${pe !== undefined ? "process.env fallback" : "undefined"}`,
|
|
245
|
+
)
|
|
246
|
+
return pe
|
|
210
247
|
}
|
|
211
|
-
return readEnv(key)
|
|
212
248
|
}
|
|
213
249
|
|
|
214
250
|
/** Tüm env'leri map olarak döner (dump için kullanışlı). */
|