@chrono-os/image-editor-react 0.2.0
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/README.md +424 -0
- package/dist/badge-BRjFgV8X.d.cts +130 -0
- package/dist/badge-BRjFgV8X.d.ts +130 -0
- package/dist/badge.cjs +56 -0
- package/dist/badge.cjs.map +1 -0
- package/dist/badge.d.cts +3 -0
- package/dist/badge.d.ts +3 -0
- package/dist/badge.js +4 -0
- package/dist/badge.js.map +1 -0
- package/dist/chunk-P77DHS6Z.js +53 -0
- package/dist/chunk-P77DHS6Z.js.map +1 -0
- package/dist/index.cjs +1989 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +163 -0
- package/dist/index.d.ts +163 -0
- package/dist/index.js +1928 -0
- package/dist/index.js.map +1 -0
- package/dist/theme.css +30 -0
- package/package.json +63 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1989 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/image-picker.tsx
|
|
8
|
+
var BADGE_DEFAULT_POS = {
|
|
9
|
+
numero: { x: 6, y: 84 },
|
|
10
|
+
label: { x: 36, y: 88 }
|
|
11
|
+
};
|
|
12
|
+
var BADGE_BASE_CQW = { numero: 15, label: 3.5 };
|
|
13
|
+
function BadgeOverlay({ badge }) {
|
|
14
|
+
if (!badge) return null;
|
|
15
|
+
const showNumero = !!badge.numero && badge.numeroEnabled !== false;
|
|
16
|
+
const showLabel = !!badge.label && badge.labelEnabled !== false;
|
|
17
|
+
if (!showNumero && !showLabel) return null;
|
|
18
|
+
const numSizeCqw = BADGE_BASE_CQW.numero * ((badge.numeroSize ?? 100) / 100);
|
|
19
|
+
const labelSizeCqw = BADGE_BASE_CQW.label * ((badge.labelSize ?? 100) / 100);
|
|
20
|
+
const numX = badge.numeroOffsetX ?? BADGE_DEFAULT_POS.numero.x;
|
|
21
|
+
const numY = badge.numeroOffsetY ?? BADGE_DEFAULT_POS.numero.y;
|
|
22
|
+
const labelX = badge.labelOffsetX ?? BADGE_DEFAULT_POS.label.x;
|
|
23
|
+
const labelY = badge.labelOffsetY ?? BADGE_DEFAULT_POS.label.y;
|
|
24
|
+
const numeroColor = badge.numeroColor || "var(--ie-accent, #556FFF)";
|
|
25
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
26
|
+
showNumero && /* @__PURE__ */ jsxRuntime.jsx(
|
|
27
|
+
"span",
|
|
28
|
+
{
|
|
29
|
+
className: "pointer-events-none absolute z-10 whitespace-nowrap font-extrabold leading-none tracking-tight",
|
|
30
|
+
style: {
|
|
31
|
+
left: `${numX}%`,
|
|
32
|
+
top: `${numY}%`,
|
|
33
|
+
fontSize: `${numSizeCqw}cqw`,
|
|
34
|
+
color: numeroColor
|
|
35
|
+
},
|
|
36
|
+
children: badge.numero
|
|
37
|
+
}
|
|
38
|
+
),
|
|
39
|
+
showLabel && /* @__PURE__ */ jsxRuntime.jsx(
|
|
40
|
+
"span",
|
|
41
|
+
{
|
|
42
|
+
className: "pointer-events-none absolute z-10 whitespace-pre-line font-semibold leading-tight text-white",
|
|
43
|
+
style: {
|
|
44
|
+
left: `${labelX}%`,
|
|
45
|
+
top: `${labelY}%`,
|
|
46
|
+
fontSize: `${labelSizeCqw}cqw`,
|
|
47
|
+
color: badge.labelColor || void 0
|
|
48
|
+
},
|
|
49
|
+
children: badge.label
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
] });
|
|
53
|
+
}
|
|
54
|
+
function buildImageFilter(opts) {
|
|
55
|
+
const parts = [];
|
|
56
|
+
if (opts.saturation != null && opts.saturation !== 100) {
|
|
57
|
+
parts.push(`saturate(${opts.saturation}%)`);
|
|
58
|
+
}
|
|
59
|
+
if (opts.grayscale != null && opts.grayscale > 0) {
|
|
60
|
+
parts.push(`grayscale(${opts.grayscale}%)`);
|
|
61
|
+
}
|
|
62
|
+
if (opts.sepia != null && opts.sepia > 0) {
|
|
63
|
+
parts.push(`sepia(${opts.sepia}%)`);
|
|
64
|
+
if (opts.sepiaColor) {
|
|
65
|
+
const hue = hexToHueDeg(opts.sepiaColor);
|
|
66
|
+
const rotation = Math.round((hue - 35 + 360) % 360);
|
|
67
|
+
if (rotation !== 0) parts.push(`hue-rotate(${rotation}deg)`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return parts.length ? parts.join(" ") : void 0;
|
|
71
|
+
}
|
|
72
|
+
function hexToHueDeg(hex) {
|
|
73
|
+
const h = hex.replace("#", "");
|
|
74
|
+
if (h.length !== 6) return 0;
|
|
75
|
+
const r = parseInt(h.slice(0, 2), 16) / 255;
|
|
76
|
+
const g = parseInt(h.slice(2, 4), 16) / 255;
|
|
77
|
+
const b = parseInt(h.slice(4, 6), 16) / 255;
|
|
78
|
+
const max = Math.max(r, g, b);
|
|
79
|
+
const min = Math.min(r, g, b);
|
|
80
|
+
const d = max - min;
|
|
81
|
+
if (d === 0) return 0;
|
|
82
|
+
let hDeg = 0;
|
|
83
|
+
if (max === r) hDeg = (g - b) / d % 6;
|
|
84
|
+
else if (max === g) hDeg = (b - r) / d + 2;
|
|
85
|
+
else hDeg = (r - g) / d + 4;
|
|
86
|
+
return (hDeg * 60 + 360) % 360;
|
|
87
|
+
}
|
|
88
|
+
var MIN_SCALE = 0.2;
|
|
89
|
+
var MAX_SCALE = 5;
|
|
90
|
+
function computeVisibleBase(imgAspect, targetAspect) {
|
|
91
|
+
if (imgAspect > targetAspect) {
|
|
92
|
+
return { vwBase: targetAspect / imgAspect, vhBase: 1 };
|
|
93
|
+
}
|
|
94
|
+
return { vwBase: 1, vhBase: imgAspect / targetAspect };
|
|
95
|
+
}
|
|
96
|
+
function buildImageGeometry(opts) {
|
|
97
|
+
const { vwBase, vhBase } = computeVisibleBase(opts.imgAspect, opts.targetAspect);
|
|
98
|
+
const coverWFrac = vwBase < 1 ? 1 / vwBase : 1;
|
|
99
|
+
const coverHFrac = vhBase < 1 ? 1 / vhBase : 1;
|
|
100
|
+
const renderedW = opts.scale * coverWFrac;
|
|
101
|
+
const renderedH = opts.scale * coverHFrac;
|
|
102
|
+
const fx = clamp(opts.focalX, 0, 100);
|
|
103
|
+
const fy = clamp(opts.focalY, 0, 100);
|
|
104
|
+
const leftFrac = fx / 100 * (1 - renderedW);
|
|
105
|
+
const topFrac = fy / 100 * (1 - renderedH);
|
|
106
|
+
return {
|
|
107
|
+
width: `${renderedW * 100}%`,
|
|
108
|
+
height: `${renderedH * 100}%`,
|
|
109
|
+
left: `${leftFrac * 100}%`,
|
|
110
|
+
top: `${topFrac * 100}%`,
|
|
111
|
+
renderedW,
|
|
112
|
+
renderedH
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function parsePosition(raw) {
|
|
116
|
+
if (!raw) return { x: 50, y: 50 };
|
|
117
|
+
const trimmed = raw.trim().toLowerCase();
|
|
118
|
+
const keywords = {
|
|
119
|
+
left: 0,
|
|
120
|
+
center: 50,
|
|
121
|
+
right: 100,
|
|
122
|
+
top: 0,
|
|
123
|
+
bottom: 100
|
|
124
|
+
};
|
|
125
|
+
const tokens = trimmed.split(/\s+/);
|
|
126
|
+
if (tokens.length !== 2) return { x: 50, y: 50 };
|
|
127
|
+
function toNum(t, axis) {
|
|
128
|
+
if (t.endsWith("%")) {
|
|
129
|
+
const n = parseFloat(t);
|
|
130
|
+
if (!Number.isNaN(n)) return clamp(n, 0, 100);
|
|
131
|
+
}
|
|
132
|
+
if (t in keywords) {
|
|
133
|
+
const v = keywords[t];
|
|
134
|
+
if (axis === "x" && (t === "top" || t === "bottom")) return 50;
|
|
135
|
+
if (axis === "y" && (t === "left" || t === "right")) return 50;
|
|
136
|
+
return v;
|
|
137
|
+
}
|
|
138
|
+
return 50;
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
x: toNum(tokens[0], "x"),
|
|
142
|
+
y: toNum(tokens[1], "y")
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function formatPos(x, y) {
|
|
146
|
+
return `${Math.round(x)}% ${Math.round(y)}%`;
|
|
147
|
+
}
|
|
148
|
+
function clamp(v, min, max) {
|
|
149
|
+
return Math.max(min, Math.min(max, v));
|
|
150
|
+
}
|
|
151
|
+
function focalToOp(focal, visible) {
|
|
152
|
+
if (visible >= 0.999) return 50;
|
|
153
|
+
const overflow = 1 - visible;
|
|
154
|
+
const op = (focal / 100 - visible / 2) / overflow;
|
|
155
|
+
return clamp(op * 100, 0, 100);
|
|
156
|
+
}
|
|
157
|
+
function opToFocal(op, visible) {
|
|
158
|
+
if (visible >= 0.999) return 50;
|
|
159
|
+
const overflow = 1 - visible;
|
|
160
|
+
return clamp(overflow * op + visible * 50, 0, 100);
|
|
161
|
+
}
|
|
162
|
+
function ImageCropPicker({
|
|
163
|
+
imageUrl,
|
|
164
|
+
targetAspect,
|
|
165
|
+
value,
|
|
166
|
+
onChange,
|
|
167
|
+
maxHeight
|
|
168
|
+
}) {
|
|
169
|
+
const outerRef = react.useRef(null);
|
|
170
|
+
const containerRef = react.useRef(null);
|
|
171
|
+
const imgRef = react.useRef(null);
|
|
172
|
+
const [imgDims, setImgDims] = react.useState(null);
|
|
173
|
+
const [containerDims, setContainerDims] = react.useState(null);
|
|
174
|
+
const [dragMode, setDragMode] = react.useState(null);
|
|
175
|
+
const [liveCornerDrag, setLiveCornerDrag] = react.useState(null);
|
|
176
|
+
const dragStartRef = react.useRef(null);
|
|
177
|
+
const { x: opX, y: opY } = parsePosition(value.position);
|
|
178
|
+
const scale = clamp(value.scale ?? 1, MIN_SCALE, MAX_SCALE);
|
|
179
|
+
const mirror = !!value.mirror;
|
|
180
|
+
const filter = buildImageFilter({
|
|
181
|
+
grayscale: value.grayscale,
|
|
182
|
+
saturation: value.saturation,
|
|
183
|
+
sepia: value.sepia,
|
|
184
|
+
sepiaColor: value.sepiaColor
|
|
185
|
+
});
|
|
186
|
+
function onImgLoad() {
|
|
187
|
+
if (!imgRef.current) return;
|
|
188
|
+
setImgDims({
|
|
189
|
+
w: imgRef.current.naturalWidth,
|
|
190
|
+
h: imgRef.current.naturalHeight
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
react.useEffect(() => {
|
|
194
|
+
if (!imageUrl) return;
|
|
195
|
+
const probe = new Image();
|
|
196
|
+
let cancelled = false;
|
|
197
|
+
probe.onload = () => {
|
|
198
|
+
if (!cancelled) {
|
|
199
|
+
setImgDims({ w: probe.naturalWidth, h: probe.naturalHeight });
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
probe.onerror = () => {
|
|
203
|
+
};
|
|
204
|
+
probe.src = imageUrl;
|
|
205
|
+
if (probe.complete && probe.naturalWidth > 0) {
|
|
206
|
+
setImgDims({ w: probe.naturalWidth, h: probe.naturalHeight });
|
|
207
|
+
}
|
|
208
|
+
return () => {
|
|
209
|
+
cancelled = true;
|
|
210
|
+
};
|
|
211
|
+
}, [imageUrl]);
|
|
212
|
+
react.useEffect(() => {
|
|
213
|
+
function measure() {
|
|
214
|
+
if (!containerRef.current) return;
|
|
215
|
+
const r = containerRef.current.getBoundingClientRect();
|
|
216
|
+
setContainerDims({ w: r.width, h: r.height });
|
|
217
|
+
}
|
|
218
|
+
measure();
|
|
219
|
+
const ro = new ResizeObserver(measure);
|
|
220
|
+
if (containerRef.current) ro.observe(containerRef.current);
|
|
221
|
+
return () => ro.disconnect();
|
|
222
|
+
}, [imgDims]);
|
|
223
|
+
const focalX = opX;
|
|
224
|
+
const focalY = opY;
|
|
225
|
+
const overflowX = value.overflowX ?? 0;
|
|
226
|
+
const overflowY = value.overflowY ?? 0;
|
|
227
|
+
const imgAspect = imgDims ? imgDims.w / imgDims.h : 1;
|
|
228
|
+
const panelAspect = imgAspect;
|
|
229
|
+
const { vwBase, vhBase } = computeVisibleBase(imgAspect, targetAspect);
|
|
230
|
+
const coverWFrac = vwBase < 1 ? 1 / vwBase : 1;
|
|
231
|
+
const coverHFrac = vhBase < 1 ? 1 / vhBase : 1;
|
|
232
|
+
const frameWFracSrc = 1 / (scale * coverWFrac);
|
|
233
|
+
const frameHFracSrc = 1 / (scale * coverHFrac);
|
|
234
|
+
const displayFocalX = mirror ? 100 - focalX : focalX;
|
|
235
|
+
const displayFocalY = focalY;
|
|
236
|
+
const displayOverflowX = mirror ? -overflowX : overflowX;
|
|
237
|
+
const frameLeftFracSrc = displayFocalX / 100 * (1 - frameWFracSrc) - displayOverflowX * frameWFracSrc;
|
|
238
|
+
const frameTopFracSrc = displayFocalY / 100 * (1 - frameHFracSrc) - overflowY * frameHFracSrc;
|
|
239
|
+
const applyFocal = react.useCallback(
|
|
240
|
+
(newFocalX, newFocalY, newScale, newOverflow) => {
|
|
241
|
+
const s = clamp(newScale ?? scale, MIN_SCALE, MAX_SCALE);
|
|
242
|
+
const fx = clamp(newFocalX, 0, 100);
|
|
243
|
+
const fy = clamp(newFocalY, 0, 100);
|
|
244
|
+
const ovX = newOverflow?.overflowX;
|
|
245
|
+
const ovY = newOverflow?.overflowY;
|
|
246
|
+
onChange({
|
|
247
|
+
...value,
|
|
248
|
+
position: formatPos(fx, fy),
|
|
249
|
+
scale: s,
|
|
250
|
+
overflowX: ovX !== void 0 && Math.abs(ovX) > 1e-3 ? ovX : void 0,
|
|
251
|
+
overflowY: ovY !== void 0 && Math.abs(ovY) > 1e-3 ? ovY : void 0
|
|
252
|
+
});
|
|
253
|
+
},
|
|
254
|
+
[scale, value, onChange]
|
|
255
|
+
);
|
|
256
|
+
function startDrag(mode, e) {
|
|
257
|
+
if (!mode) return;
|
|
258
|
+
setDragMode(mode);
|
|
259
|
+
outerRef.current?.setPointerCapture(e.pointerId);
|
|
260
|
+
let anchorXpx;
|
|
261
|
+
let anchorYpx;
|
|
262
|
+
let cornerXpx;
|
|
263
|
+
let cornerYpx;
|
|
264
|
+
if (mode !== "move" && containerDims) {
|
|
265
|
+
const frameL = liveCornerDrag?.leftPx ?? frameLeftFracSrc * containerDims.w;
|
|
266
|
+
const frameT = liveCornerDrag?.topPx ?? frameTopFracSrc * containerDims.h;
|
|
267
|
+
const frameW = liveCornerDrag?.wPx ?? frameWFracSrc * containerDims.w;
|
|
268
|
+
const frameH = liveCornerDrag?.hPx ?? frameHFracSrc * containerDims.h;
|
|
269
|
+
switch (mode) {
|
|
270
|
+
case "nw":
|
|
271
|
+
anchorXpx = frameL + frameW;
|
|
272
|
+
anchorYpx = frameT + frameH;
|
|
273
|
+
cornerXpx = frameL;
|
|
274
|
+
cornerYpx = frameT;
|
|
275
|
+
break;
|
|
276
|
+
case "ne":
|
|
277
|
+
anchorXpx = frameL;
|
|
278
|
+
anchorYpx = frameT + frameH;
|
|
279
|
+
cornerXpx = frameL + frameW;
|
|
280
|
+
cornerYpx = frameT;
|
|
281
|
+
break;
|
|
282
|
+
case "sw":
|
|
283
|
+
anchorXpx = frameL + frameW;
|
|
284
|
+
anchorYpx = frameT;
|
|
285
|
+
cornerXpx = frameL;
|
|
286
|
+
cornerYpx = frameT + frameH;
|
|
287
|
+
break;
|
|
288
|
+
case "se":
|
|
289
|
+
anchorXpx = frameL;
|
|
290
|
+
anchorYpx = frameT;
|
|
291
|
+
cornerXpx = frameL + frameW;
|
|
292
|
+
cornerYpx = frameT + frameH;
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
dragStartRef.current = {
|
|
297
|
+
pointerX: e.clientX,
|
|
298
|
+
pointerY: e.clientY,
|
|
299
|
+
focalX,
|
|
300
|
+
focalY,
|
|
301
|
+
scale,
|
|
302
|
+
overflowX,
|
|
303
|
+
overflowY,
|
|
304
|
+
anchorXpx,
|
|
305
|
+
anchorYpx,
|
|
306
|
+
cornerXpx,
|
|
307
|
+
cornerYpx,
|
|
308
|
+
cornerDragBegun: false,
|
|
309
|
+
snapEngaged: void 0
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function handlePointerMove(e) {
|
|
313
|
+
if (!dragMode || !dragStartRef.current || !containerDims) return;
|
|
314
|
+
const start = dragStartRef.current;
|
|
315
|
+
if (dragMode === "move") {
|
|
316
|
+
if (!containerDims) return;
|
|
317
|
+
const actualFrameWSrc = liveCornerDrag && containerDims.w > 0 ? liveCornerDrag.wPx / containerDims.w : frameWFracSrc;
|
|
318
|
+
const actualFrameHSrc = liveCornerDrag && containerDims.h > 0 ? liveCornerDrag.hPx / containerDims.h : frameHFracSrc;
|
|
319
|
+
if (actualFrameWSrc <= 0 || actualFrameHSrc <= 0) return;
|
|
320
|
+
const dx = e.clientX - start.pointerX;
|
|
321
|
+
const dy = e.clientY - start.pointerY;
|
|
322
|
+
const dxTargetFrac = dx / (actualFrameWSrc * containerDims.w);
|
|
323
|
+
const dyTargetFrac = dy / (actualFrameHSrc * containerDims.h);
|
|
324
|
+
const adjDxTargetFrac = mirror ? -dxTargetFrac : dxTargetFrac;
|
|
325
|
+
let candOverflowX = start.overflowX - adjDxTargetFrac;
|
|
326
|
+
let candOverflowY = start.overflowY - dyTargetFrac;
|
|
327
|
+
const SOFT_SNAP_TOL = 0.04;
|
|
328
|
+
const snapDisabled = e.shiftKey;
|
|
329
|
+
const dispFocalX_b = mirror ? 100 - start.focalX : start.focalX;
|
|
330
|
+
const dispFocalY_b = start.focalY;
|
|
331
|
+
const dispOverflowX_cand = mirror ? -candOverflowX : candOverflowX;
|
|
332
|
+
const dispOverflowY_cand = candOverflowY;
|
|
333
|
+
const fLeftSrc = dispFocalX_b / 100 * (1 - actualFrameWSrc) - dispOverflowX_cand * actualFrameWSrc;
|
|
334
|
+
const fTopSrc = dispFocalY_b / 100 * (1 - actualFrameHSrc) - dispOverflowY_cand * actualFrameHSrc;
|
|
335
|
+
const snapTargetsX = snapDisabled ? [] : [0, 1 - actualFrameWSrc];
|
|
336
|
+
const snapTargetsY = snapDisabled ? [] : [0, 1 - actualFrameHSrc];
|
|
337
|
+
let snappedFLeftSrc = fLeftSrc;
|
|
338
|
+
let snappedFTopSrc = fTopSrc;
|
|
339
|
+
for (const t of snapTargetsX) {
|
|
340
|
+
if (Math.abs(fLeftSrc - t) < SOFT_SNAP_TOL) {
|
|
341
|
+
snappedFLeftSrc = t;
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
for (const t of snapTargetsY) {
|
|
346
|
+
if (Math.abs(fTopSrc - t) < SOFT_SNAP_TOL) {
|
|
347
|
+
snappedFTopSrc = t;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (snappedFLeftSrc !== fLeftSrc && actualFrameWSrc > 1e-3) {
|
|
352
|
+
const snappedDispOverflowX = (dispFocalX_b / 100 * (1 - actualFrameWSrc) - snappedFLeftSrc) / actualFrameWSrc;
|
|
353
|
+
candOverflowX = mirror ? -snappedDispOverflowX : snappedDispOverflowX;
|
|
354
|
+
}
|
|
355
|
+
if (snappedFTopSrc !== fTopSrc && actualFrameHSrc > 1e-3) {
|
|
356
|
+
const snappedDispOverflowY = (dispFocalY_b / 100 * (1 - actualFrameHSrc) - snappedFTopSrc) / actualFrameHSrc;
|
|
357
|
+
candOverflowY = snappedDispOverflowY;
|
|
358
|
+
}
|
|
359
|
+
const MIN_FRAME_OVERLAP = 0.05;
|
|
360
|
+
const dispOverflowX_clamp = mirror ? -candOverflowX : candOverflowX;
|
|
361
|
+
const dispOverflowY_clamp = candOverflowY;
|
|
362
|
+
let clampedFLeftSrc = dispFocalX_b / 100 * (1 - actualFrameWSrc) - dispOverflowX_clamp * actualFrameWSrc;
|
|
363
|
+
let clampedFTopSrc = dispFocalY_b / 100 * (1 - actualFrameHSrc) - dispOverflowY_clamp * actualFrameHSrc;
|
|
364
|
+
const minFLeft = -(actualFrameWSrc - MIN_FRAME_OVERLAP);
|
|
365
|
+
const maxFLeft = 1 - MIN_FRAME_OVERLAP;
|
|
366
|
+
const minFTop = -(actualFrameHSrc - MIN_FRAME_OVERLAP);
|
|
367
|
+
const maxFTop = 1 - MIN_FRAME_OVERLAP;
|
|
368
|
+
clampedFLeftSrc = clamp(clampedFLeftSrc, minFLeft, maxFLeft);
|
|
369
|
+
clampedFTopSrc = clamp(clampedFTopSrc, minFTop, maxFTop);
|
|
370
|
+
if (actualFrameWSrc > 1e-3) {
|
|
371
|
+
const clampedDispOverflowX = (dispFocalX_b / 100 * (1 - actualFrameWSrc) - clampedFLeftSrc) / actualFrameWSrc;
|
|
372
|
+
candOverflowX = mirror ? -clampedDispOverflowX : clampedDispOverflowX;
|
|
373
|
+
}
|
|
374
|
+
if (actualFrameHSrc > 1e-3) {
|
|
375
|
+
const clampedDispOverflowY = (dispFocalY_b / 100 * (1 - actualFrameHSrc) - clampedFTopSrc) / actualFrameHSrc;
|
|
376
|
+
candOverflowY = clampedDispOverflowY;
|
|
377
|
+
}
|
|
378
|
+
const finalFrameLeftPx = clampedFLeftSrc * containerDims.w;
|
|
379
|
+
const finalFrameTopPx = clampedFTopSrc * containerDims.h;
|
|
380
|
+
const frameWPxOverride = liveCornerDrag?.wPx ?? frameWFracSrc * containerDims.w;
|
|
381
|
+
const frameHPxOverride = liveCornerDrag?.hPx ?? frameHFracSrc * containerDims.h;
|
|
382
|
+
setLiveCornerDrag({
|
|
383
|
+
leftPx: finalFrameLeftPx,
|
|
384
|
+
topPx: finalFrameTopPx,
|
|
385
|
+
wPx: frameWPxOverride,
|
|
386
|
+
hPx: frameHPxOverride
|
|
387
|
+
});
|
|
388
|
+
applyFocal(start.focalX, start.focalY, scale, {
|
|
389
|
+
overflowX: candOverflowX,
|
|
390
|
+
overflowY: candOverflowY
|
|
391
|
+
});
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (start.anchorXpx === void 0 || start.anchorYpx === void 0) return;
|
|
395
|
+
if (start.cornerXpx === void 0 || start.cornerYpx === void 0) return;
|
|
396
|
+
if (!containerRef.current) return;
|
|
397
|
+
if (!start.cornerDragBegun) {
|
|
398
|
+
const initialDx = e.clientX - start.pointerX;
|
|
399
|
+
const initialDy = e.clientY - start.pointerY;
|
|
400
|
+
if (Math.abs(initialDx) < 4 && Math.abs(initialDy) < 4) return;
|
|
401
|
+
start.cornerDragBegun = true;
|
|
402
|
+
start.pointerX = e.clientX;
|
|
403
|
+
start.pointerY = e.clientY;
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const dragDx = e.clientX - start.pointerX;
|
|
407
|
+
const dragDy = e.clientY - start.pointerY;
|
|
408
|
+
const pointerXpx = start.cornerXpx + dragDx;
|
|
409
|
+
const pointerYpx = start.cornerYpx + dragDy;
|
|
410
|
+
const freeW = Math.abs(pointerXpx - start.anchorXpx);
|
|
411
|
+
const freeH = Math.abs(pointerYpx - start.anchorYpx);
|
|
412
|
+
if (freeW < 4 || freeH < 4) return;
|
|
413
|
+
let lockedW;
|
|
414
|
+
let lockedH;
|
|
415
|
+
if (freeH * targetAspect <= freeW) {
|
|
416
|
+
lockedH = freeH;
|
|
417
|
+
lockedW = freeH * targetAspect;
|
|
418
|
+
} else {
|
|
419
|
+
lockedW = freeW;
|
|
420
|
+
lockedH = freeW / targetAspect;
|
|
421
|
+
}
|
|
422
|
+
const minFrameW = containerDims.w / (MAX_SCALE * coverWFrac);
|
|
423
|
+
const maxFrameW = containerDims.w / (MIN_SCALE * coverWFrac);
|
|
424
|
+
if (lockedW < minFrameW) {
|
|
425
|
+
lockedW = minFrameW;
|
|
426
|
+
lockedH = lockedW / targetAspect;
|
|
427
|
+
}
|
|
428
|
+
if (lockedW > maxFrameW) {
|
|
429
|
+
lockedW = maxFrameW;
|
|
430
|
+
lockedH = lockedW / targetAspect;
|
|
431
|
+
}
|
|
432
|
+
const SNAP_ENGAGE_PX = 4;
|
|
433
|
+
const SNAP_DISENGAGE_PX = 12;
|
|
434
|
+
const cornerSnapDisabled = e.shiftKey;
|
|
435
|
+
const snapTargetW = dragMode === "nw" || dragMode === "sw" ? start.anchorXpx : containerDims.w - start.anchorXpx;
|
|
436
|
+
const snapTargetH = dragMode === "nw" || dragMode === "ne" ? start.anchorYpx : containerDims.h - start.anchorYpx;
|
|
437
|
+
const canSnapW = !cornerSnapDisabled && snapTargetW >= minFrameW && snapTargetW <= maxFrameW;
|
|
438
|
+
const canSnapH = !cornerSnapDisabled && snapTargetH * targetAspect >= minFrameW && snapTargetH * targetAspect <= maxFrameW;
|
|
439
|
+
const distW = Math.abs(lockedW - snapTargetW);
|
|
440
|
+
const distH = Math.abs(lockedH - snapTargetH);
|
|
441
|
+
const engageThreshold = SNAP_ENGAGE_PX;
|
|
442
|
+
const disengageThreshold = SNAP_DISENGAGE_PX;
|
|
443
|
+
if (cornerSnapDisabled) {
|
|
444
|
+
start.snapEngaged = void 0;
|
|
445
|
+
} else if (start.snapEngaged === "w") {
|
|
446
|
+
if (canSnapW && distW < disengageThreshold) {
|
|
447
|
+
lockedW = snapTargetW;
|
|
448
|
+
lockedH = lockedW / targetAspect;
|
|
449
|
+
} else {
|
|
450
|
+
start.snapEngaged = void 0;
|
|
451
|
+
}
|
|
452
|
+
} else if (start.snapEngaged === "h") {
|
|
453
|
+
if (canSnapH && distH < disengageThreshold) {
|
|
454
|
+
lockedH = snapTargetH;
|
|
455
|
+
lockedW = lockedH * targetAspect;
|
|
456
|
+
} else {
|
|
457
|
+
start.snapEngaged = void 0;
|
|
458
|
+
}
|
|
459
|
+
} else {
|
|
460
|
+
const approachingW = start.prevDistW !== void 0 && start.prevDistW >= engageThreshold && distW < engageThreshold;
|
|
461
|
+
const approachingH = start.prevDistH !== void 0 && start.prevDistH >= engageThreshold && distH < engageThreshold;
|
|
462
|
+
if (canSnapW && approachingW) {
|
|
463
|
+
lockedW = snapTargetW;
|
|
464
|
+
lockedH = lockedW / targetAspect;
|
|
465
|
+
start.snapEngaged = "w";
|
|
466
|
+
} else if (canSnapH && approachingH) {
|
|
467
|
+
lockedH = snapTargetH;
|
|
468
|
+
lockedW = lockedH * targetAspect;
|
|
469
|
+
start.snapEngaged = "h";
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
start.prevDistW = distW;
|
|
473
|
+
start.prevDistH = distH;
|
|
474
|
+
const newFrameLPx = pointerXpx >= start.anchorXpx ? start.anchorXpx : start.anchorXpx - lockedW;
|
|
475
|
+
const newFrameTPx = pointerYpx >= start.anchorYpx ? start.anchorYpx : start.anchorYpx - lockedH;
|
|
476
|
+
const newFrameWSrc = lockedW / containerDims.w;
|
|
477
|
+
const newFrameHSrc = lockedH / containerDims.h;
|
|
478
|
+
const newScale = clamp(
|
|
479
|
+
1 / (newFrameWSrc * coverWFrac),
|
|
480
|
+
MIN_SCALE,
|
|
481
|
+
MAX_SCALE
|
|
482
|
+
);
|
|
483
|
+
const newFrameLSrc = newFrameLPx / containerDims.w;
|
|
484
|
+
const newFrameTSrc = newFrameTPx / containerDims.h;
|
|
485
|
+
const startDispFocalX = mirror ? 100 - start.focalX : start.focalX;
|
|
486
|
+
const startDispFocalY = start.focalY;
|
|
487
|
+
const newDispOverflowX = newFrameWSrc > 1e-3 ? (startDispFocalX / 100 * (1 - newFrameWSrc) - newFrameLSrc) / newFrameWSrc : start.overflowX;
|
|
488
|
+
const newDispOverflowY = newFrameHSrc > 1e-3 ? (startDispFocalY / 100 * (1 - newFrameHSrc) - newFrameTSrc) / newFrameHSrc : start.overflowY;
|
|
489
|
+
const newOverflowX = mirror ? -newDispOverflowX : newDispOverflowX;
|
|
490
|
+
const newOverflowY = newDispOverflowY;
|
|
491
|
+
const MIN_FRAME_OVERLAP_CORNER = 0.05;
|
|
492
|
+
const dispOverflowX_corner = mirror ? -newOverflowX : newOverflowX;
|
|
493
|
+
const dispOverflowY_corner = newOverflowY;
|
|
494
|
+
let cornerClampedFLeftSrc = startDispFocalX / 100 * (1 - newFrameWSrc) - dispOverflowX_corner * newFrameWSrc;
|
|
495
|
+
let cornerClampedFTopSrc = startDispFocalY / 100 * (1 - newFrameHSrc) - dispOverflowY_corner * newFrameHSrc;
|
|
496
|
+
const minFLeft_c = -(newFrameWSrc - MIN_FRAME_OVERLAP_CORNER);
|
|
497
|
+
const maxFLeft_c = 1 - MIN_FRAME_OVERLAP_CORNER;
|
|
498
|
+
const minFTop_c = -(newFrameHSrc - MIN_FRAME_OVERLAP_CORNER);
|
|
499
|
+
const maxFTop_c = 1 - MIN_FRAME_OVERLAP_CORNER;
|
|
500
|
+
cornerClampedFLeftSrc = clamp(cornerClampedFLeftSrc, minFLeft_c, maxFLeft_c);
|
|
501
|
+
cornerClampedFTopSrc = clamp(cornerClampedFTopSrc, minFTop_c, maxFTop_c);
|
|
502
|
+
let finalOverflowX = newOverflowX;
|
|
503
|
+
let finalOverflowY = newOverflowY;
|
|
504
|
+
if (newFrameWSrc > 1e-3) {
|
|
505
|
+
const clampedDispOverflowX_c = (startDispFocalX / 100 * (1 - newFrameWSrc) - cornerClampedFLeftSrc) / newFrameWSrc;
|
|
506
|
+
finalOverflowX = mirror ? -clampedDispOverflowX_c : clampedDispOverflowX_c;
|
|
507
|
+
}
|
|
508
|
+
if (newFrameHSrc > 1e-3) {
|
|
509
|
+
const clampedDispOverflowY_c = (startDispFocalY / 100 * (1 - newFrameHSrc) - cornerClampedFTopSrc) / newFrameHSrc;
|
|
510
|
+
finalOverflowY = clampedDispOverflowY_c;
|
|
511
|
+
}
|
|
512
|
+
setLiveCornerDrag({
|
|
513
|
+
leftPx: newFrameLPx,
|
|
514
|
+
topPx: newFrameTPx,
|
|
515
|
+
wPx: lockedW,
|
|
516
|
+
hPx: lockedH
|
|
517
|
+
});
|
|
518
|
+
applyFocal(start.focalX, start.focalY, newScale, {
|
|
519
|
+
overflowX: finalOverflowX,
|
|
520
|
+
overflowY: finalOverflowY
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
const nudgeFrame = react.useCallback(
|
|
524
|
+
(deltaXpx, deltaYpx) => {
|
|
525
|
+
if (!containerDims) return;
|
|
526
|
+
const actualFrameWSrc = liveCornerDrag && containerDims.w > 0 ? liveCornerDrag.wPx / containerDims.w : frameWFracSrc;
|
|
527
|
+
const actualFrameHSrc = liveCornerDrag && containerDims.h > 0 ? liveCornerDrag.hPx / containerDims.h : frameHFracSrc;
|
|
528
|
+
if (actualFrameWSrc <= 0 || actualFrameHSrc <= 0) return;
|
|
529
|
+
const dxTargetFrac = deltaXpx / (actualFrameWSrc * containerDims.w);
|
|
530
|
+
const dyTargetFrac = deltaYpx / (actualFrameHSrc * containerDims.h);
|
|
531
|
+
const adjDxTargetFrac = mirror ? -dxTargetFrac : dxTargetFrac;
|
|
532
|
+
let candOverflowX = overflowX - adjDxTargetFrac;
|
|
533
|
+
let candOverflowY = overflowY - dyTargetFrac;
|
|
534
|
+
const MIN_FRAME_OVERLAP = 0.05;
|
|
535
|
+
const dispFocalX_b = mirror ? 100 - focalX : focalX;
|
|
536
|
+
const dispFocalY_b = focalY;
|
|
537
|
+
const dispOverflowX_clamp = mirror ? -candOverflowX : candOverflowX;
|
|
538
|
+
const dispOverflowY_clamp = candOverflowY;
|
|
539
|
+
let clampedFLeftSrc = dispFocalX_b / 100 * (1 - actualFrameWSrc) - dispOverflowX_clamp * actualFrameWSrc;
|
|
540
|
+
let clampedFTopSrc = dispFocalY_b / 100 * (1 - actualFrameHSrc) - dispOverflowY_clamp * actualFrameHSrc;
|
|
541
|
+
const minFLeft = -(actualFrameWSrc - MIN_FRAME_OVERLAP);
|
|
542
|
+
const maxFLeft = 1 - MIN_FRAME_OVERLAP;
|
|
543
|
+
const minFTop = -(actualFrameHSrc - MIN_FRAME_OVERLAP);
|
|
544
|
+
const maxFTop = 1 - MIN_FRAME_OVERLAP;
|
|
545
|
+
clampedFLeftSrc = clamp(clampedFLeftSrc, minFLeft, maxFLeft);
|
|
546
|
+
clampedFTopSrc = clamp(clampedFTopSrc, minFTop, maxFTop);
|
|
547
|
+
if (actualFrameWSrc > 1e-3) {
|
|
548
|
+
const clampedDispOverflowX = (dispFocalX_b / 100 * (1 - actualFrameWSrc) - clampedFLeftSrc) / actualFrameWSrc;
|
|
549
|
+
candOverflowX = mirror ? -clampedDispOverflowX : clampedDispOverflowX;
|
|
550
|
+
}
|
|
551
|
+
if (actualFrameHSrc > 1e-3) {
|
|
552
|
+
const clampedDispOverflowY = (dispFocalY_b / 100 * (1 - actualFrameHSrc) - clampedFTopSrc) / actualFrameHSrc;
|
|
553
|
+
candOverflowY = clampedDispOverflowY;
|
|
554
|
+
}
|
|
555
|
+
const frameWPxOverride = liveCornerDrag?.wPx ?? frameWFracSrc * containerDims.w;
|
|
556
|
+
const frameHPxOverride = liveCornerDrag?.hPx ?? frameHFracSrc * containerDims.h;
|
|
557
|
+
setLiveCornerDrag({
|
|
558
|
+
leftPx: clampedFLeftSrc * containerDims.w,
|
|
559
|
+
topPx: clampedFTopSrc * containerDims.h,
|
|
560
|
+
wPx: frameWPxOverride,
|
|
561
|
+
hPx: frameHPxOverride
|
|
562
|
+
});
|
|
563
|
+
applyFocal(focalX, focalY, scale, {
|
|
564
|
+
overflowX: candOverflowX,
|
|
565
|
+
overflowY: candOverflowY
|
|
566
|
+
});
|
|
567
|
+
},
|
|
568
|
+
[
|
|
569
|
+
containerDims,
|
|
570
|
+
liveCornerDrag,
|
|
571
|
+
frameWFracSrc,
|
|
572
|
+
frameHFracSrc,
|
|
573
|
+
mirror,
|
|
574
|
+
overflowX,
|
|
575
|
+
overflowY,
|
|
576
|
+
focalX,
|
|
577
|
+
focalY,
|
|
578
|
+
scale,
|
|
579
|
+
applyFocal
|
|
580
|
+
]
|
|
581
|
+
);
|
|
582
|
+
react.useEffect(() => {
|
|
583
|
+
if (!imageUrl) return;
|
|
584
|
+
function onKey(e) {
|
|
585
|
+
const ae = document.activeElement;
|
|
586
|
+
if (ae) {
|
|
587
|
+
const tag = ae.tagName;
|
|
588
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || ae.isContentEditable) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
const step = e.shiftKey ? 10 : 1;
|
|
593
|
+
switch (e.key) {
|
|
594
|
+
case "ArrowLeft":
|
|
595
|
+
e.preventDefault();
|
|
596
|
+
nudgeFrame(-step, 0);
|
|
597
|
+
break;
|
|
598
|
+
case "ArrowRight":
|
|
599
|
+
e.preventDefault();
|
|
600
|
+
nudgeFrame(step, 0);
|
|
601
|
+
break;
|
|
602
|
+
case "ArrowUp":
|
|
603
|
+
e.preventDefault();
|
|
604
|
+
nudgeFrame(0, -step);
|
|
605
|
+
break;
|
|
606
|
+
case "ArrowDown":
|
|
607
|
+
e.preventDefault();
|
|
608
|
+
nudgeFrame(0, step);
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
window.addEventListener("keydown", onKey);
|
|
613
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
614
|
+
}, [imageUrl, nudgeFrame]);
|
|
615
|
+
function handlePointerUp(e) {
|
|
616
|
+
setDragMode(null);
|
|
617
|
+
dragStartRef.current = null;
|
|
618
|
+
try {
|
|
619
|
+
outerRef.current?.releasePointerCapture(e.pointerId);
|
|
620
|
+
} catch {
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (!imageUrl) return null;
|
|
624
|
+
const backdropBg = (() => {
|
|
625
|
+
const c = value.backdropColor;
|
|
626
|
+
if (c === "transparent") return void 0;
|
|
627
|
+
const op = (value.backdropOpacity ?? 100) / 100;
|
|
628
|
+
const hex = c && c !== "transparent" ? c : "#13203B";
|
|
629
|
+
const h = hex.replace("#", "");
|
|
630
|
+
if (h.length !== 6) return hex;
|
|
631
|
+
const r = parseInt(h.slice(0, 2), 16);
|
|
632
|
+
const g = parseInt(h.slice(2, 4), 16);
|
|
633
|
+
const b = parseInt(h.slice(4, 6), 16);
|
|
634
|
+
return `rgba(${r}, ${g}, ${b}, ${op})`;
|
|
635
|
+
})();
|
|
636
|
+
const editorH = maxHeight ?? 520;
|
|
637
|
+
const PAD = 48;
|
|
638
|
+
const MAX_SOURCE_BOX_W = 544;
|
|
639
|
+
let sourceBoxH = Math.max(120, editorH - PAD * 2);
|
|
640
|
+
let sourceBoxW = sourceBoxH * panelAspect;
|
|
641
|
+
if (sourceBoxW > MAX_SOURCE_BOX_W) {
|
|
642
|
+
sourceBoxW = MAX_SOURCE_BOX_W;
|
|
643
|
+
sourceBoxH = sourceBoxW / panelAspect;
|
|
644
|
+
}
|
|
645
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
646
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[10px] font-semibold uppercase leading-snug tracking-wider text-neutral-500", children: [
|
|
647
|
+
"Arraste o quadro e puxe os cantos para posicionar",
|
|
648
|
+
/* @__PURE__ */ jsxRuntime.jsx("br", {}),
|
|
649
|
+
"Setas = 1px \xB7 Shift+Setas = 10px \xB7 Shift ao arrastar = sem snap"
|
|
650
|
+
] }),
|
|
651
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
652
|
+
"div",
|
|
653
|
+
{
|
|
654
|
+
ref: outerRef,
|
|
655
|
+
className: "relative mx-auto select-none rounded-md border-2 border-white/30 shadow-[0_0_0_1px_rgba(0,0,0,0.5)]",
|
|
656
|
+
style: {
|
|
657
|
+
height: editorH,
|
|
658
|
+
width: sourceBoxW + PAD * 2,
|
|
659
|
+
maxWidth: "100%",
|
|
660
|
+
padding: PAD,
|
|
661
|
+
boxSizing: "border-box",
|
|
662
|
+
backgroundColor: backdropBg,
|
|
663
|
+
touchAction: "none",
|
|
664
|
+
overflow: "hidden",
|
|
665
|
+
display: "flex",
|
|
666
|
+
alignItems: "center",
|
|
667
|
+
justifyContent: "center"
|
|
668
|
+
},
|
|
669
|
+
onPointerMove: handlePointerMove,
|
|
670
|
+
onPointerUp: handlePointerUp,
|
|
671
|
+
onPointerCancel: handlePointerUp,
|
|
672
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
673
|
+
"div",
|
|
674
|
+
{
|
|
675
|
+
ref: containerRef,
|
|
676
|
+
className: "relative",
|
|
677
|
+
style: {
|
|
678
|
+
height: sourceBoxH,
|
|
679
|
+
width: sourceBoxW,
|
|
680
|
+
overflow: "visible",
|
|
681
|
+
boxShadow: "0 0 0 1px rgba(255,255,255,0.18)"
|
|
682
|
+
},
|
|
683
|
+
children: [
|
|
684
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
685
|
+
"img",
|
|
686
|
+
{
|
|
687
|
+
ref: imgRef,
|
|
688
|
+
src: imageUrl,
|
|
689
|
+
alt: "",
|
|
690
|
+
onLoad: onImgLoad,
|
|
691
|
+
className: "pointer-events-none absolute inset-0 h-full w-full",
|
|
692
|
+
draggable: false,
|
|
693
|
+
style: {
|
|
694
|
+
objectFit: "contain",
|
|
695
|
+
objectPosition: "center",
|
|
696
|
+
transform: mirror ? "scaleX(-1)" : void 0,
|
|
697
|
+
transformOrigin: "center center",
|
|
698
|
+
filter
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
),
|
|
702
|
+
imgDims && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
703
|
+
"div",
|
|
704
|
+
{
|
|
705
|
+
className: "absolute pointer-events-auto",
|
|
706
|
+
style: {
|
|
707
|
+
// Durante corner drag ativo, render usa pixel ABSOLUTOS do
|
|
708
|
+
// liveCornerDrag → anchor fica EXATAMENTE preso sem drift
|
|
709
|
+
// da math state-based. Fora do corner drag, math normal.
|
|
710
|
+
left: liveCornerDrag && containerDims ? `${liveCornerDrag.leftPx / containerDims.w * 100}%` : `${frameLeftFracSrc * 100}%`,
|
|
711
|
+
top: liveCornerDrag && containerDims ? `${liveCornerDrag.topPx / containerDims.h * 100}%` : `${frameTopFracSrc * 100}%`,
|
|
712
|
+
width: liveCornerDrag && containerDims ? `${liveCornerDrag.wPx / containerDims.w * 100}%` : `${frameWFracSrc * 100}%`,
|
|
713
|
+
height: liveCornerDrag && containerDims ? `${liveCornerDrag.hPx / containerDims.h * 100}%` : `${frameHFracSrc * 100}%`,
|
|
714
|
+
cursor: "move",
|
|
715
|
+
boxShadow: "inset 0 0 0 2px white, inset 0 0 0 3px rgba(0,0,0,0.5), 0 0 8px rgba(0,0,0,0.4)"
|
|
716
|
+
},
|
|
717
|
+
onPointerDown: (e) => startDrag("move", e),
|
|
718
|
+
children: [
|
|
719
|
+
/* @__PURE__ */ jsxRuntime.jsx(CornerHandle, { pos: "nw", onStart: (e) => startDrag("nw", e) }),
|
|
720
|
+
/* @__PURE__ */ jsxRuntime.jsx(CornerHandle, { pos: "ne", onStart: (e) => startDrag("ne", e) }),
|
|
721
|
+
/* @__PURE__ */ jsxRuntime.jsx(CornerHandle, { pos: "sw", onStart: (e) => startDrag("sw", e) }),
|
|
722
|
+
/* @__PURE__ */ jsxRuntime.jsx(CornerHandle, { pos: "se", onStart: (e) => startDrag("se", e) })
|
|
723
|
+
]
|
|
724
|
+
}
|
|
725
|
+
)
|
|
726
|
+
]
|
|
727
|
+
}
|
|
728
|
+
)
|
|
729
|
+
}
|
|
730
|
+
)
|
|
731
|
+
] });
|
|
732
|
+
}
|
|
733
|
+
function CornerHandle({
|
|
734
|
+
pos,
|
|
735
|
+
onStart
|
|
736
|
+
}) {
|
|
737
|
+
const offset = "-8px";
|
|
738
|
+
const cornerStyle = {
|
|
739
|
+
position: "absolute",
|
|
740
|
+
width: 16,
|
|
741
|
+
height: 16,
|
|
742
|
+
borderRadius: 3,
|
|
743
|
+
background: "white",
|
|
744
|
+
border: "1px solid rgba(0,0,0,0.55)",
|
|
745
|
+
boxShadow: "0 1px 3px rgba(0,0,0,0.4)",
|
|
746
|
+
touchAction: "none"
|
|
747
|
+
};
|
|
748
|
+
if (pos === "nw") {
|
|
749
|
+
cornerStyle.top = offset;
|
|
750
|
+
cornerStyle.left = offset;
|
|
751
|
+
cornerStyle.cursor = "nwse-resize";
|
|
752
|
+
} else if (pos === "ne") {
|
|
753
|
+
cornerStyle.top = offset;
|
|
754
|
+
cornerStyle.right = offset;
|
|
755
|
+
cornerStyle.cursor = "nesw-resize";
|
|
756
|
+
} else if (pos === "sw") {
|
|
757
|
+
cornerStyle.bottom = offset;
|
|
758
|
+
cornerStyle.left = offset;
|
|
759
|
+
cornerStyle.cursor = "nesw-resize";
|
|
760
|
+
} else {
|
|
761
|
+
cornerStyle.bottom = offset;
|
|
762
|
+
cornerStyle.right = offset;
|
|
763
|
+
cornerStyle.cursor = "nwse-resize";
|
|
764
|
+
}
|
|
765
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
766
|
+
"div",
|
|
767
|
+
{
|
|
768
|
+
style: cornerStyle,
|
|
769
|
+
onPointerDown: (e) => {
|
|
770
|
+
e.stopPropagation();
|
|
771
|
+
onStart(e);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
var DEFAULT_SEPIA_COLOR = "#704214";
|
|
777
|
+
function defaultResolveUrl(url) {
|
|
778
|
+
return url;
|
|
779
|
+
}
|
|
780
|
+
function computeImageDisplayStyle(opts) {
|
|
781
|
+
const { position, scale, mirror, imgDims, targetAspect, filter } = opts;
|
|
782
|
+
const overflowX = opts.overflowX ?? 0;
|
|
783
|
+
const overflowY = opts.overflowY ?? 0;
|
|
784
|
+
const focal = parsePosition(position);
|
|
785
|
+
const imgAspect = imgDims ? imgDims.w / imgDims.h : 1;
|
|
786
|
+
let coverWFrac = 1;
|
|
787
|
+
let coverHFrac = 1;
|
|
788
|
+
if (imgAspect > targetAspect) {
|
|
789
|
+
coverWFrac = imgAspect / targetAspect;
|
|
790
|
+
} else {
|
|
791
|
+
coverHFrac = targetAspect / imgAspect;
|
|
792
|
+
}
|
|
793
|
+
const renderedW = scale * coverWFrac;
|
|
794
|
+
const renderedH = scale * coverHFrac;
|
|
795
|
+
const leftFrac = focal.x / 100 * (1 - renderedW) + overflowX;
|
|
796
|
+
const topFrac = focal.y / 100 * (1 - renderedH) + overflowY;
|
|
797
|
+
return {
|
|
798
|
+
position: "absolute",
|
|
799
|
+
display: "block",
|
|
800
|
+
// Override Tailwind/normalize reset (max-width:100%; height:auto) que
|
|
801
|
+
// estavam clampando a imagem em 100% do container e distorcendo o aspect.
|
|
802
|
+
maxWidth: "none",
|
|
803
|
+
maxHeight: "none",
|
|
804
|
+
width: `${renderedW * 100}%`,
|
|
805
|
+
height: `${renderedH * 100}%`,
|
|
806
|
+
left: `${leftFrac * 100}%`,
|
|
807
|
+
top: `${topFrac * 100}%`,
|
|
808
|
+
transform: mirror ? "scaleX(-1)" : void 0,
|
|
809
|
+
transformOrigin: "center center",
|
|
810
|
+
filter
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
function resolveBackdropBg(crop) {
|
|
814
|
+
if (!crop) return void 0;
|
|
815
|
+
const c = crop.backdropColor;
|
|
816
|
+
if (c === "transparent") return void 0;
|
|
817
|
+
const op = (crop.backdropOpacity ?? 100) / 100;
|
|
818
|
+
const hex = c && c !== "transparent" ? c : "#13203B";
|
|
819
|
+
const h = hex.replace("#", "");
|
|
820
|
+
if (h.length !== 6) return hex;
|
|
821
|
+
const r = parseInt(h.slice(0, 2), 16);
|
|
822
|
+
const g = parseInt(h.slice(2, 4), 16);
|
|
823
|
+
const b = parseInt(h.slice(4, 6), 16);
|
|
824
|
+
return `rgba(${r}, ${g}, ${b}, ${op})`;
|
|
825
|
+
}
|
|
826
|
+
var ASPECT_RATIO = {
|
|
827
|
+
"1/1": 1,
|
|
828
|
+
"3/4": 3 / 4,
|
|
829
|
+
"16/9": 16 / 9,
|
|
830
|
+
"4/3": 4 / 3
|
|
831
|
+
};
|
|
832
|
+
function bytesHuman(n) {
|
|
833
|
+
if (n < 1024) return `${n}B`;
|
|
834
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(0)}KB`;
|
|
835
|
+
return `${(n / (1024 * 1024)).toFixed(1)}MB`;
|
|
836
|
+
}
|
|
837
|
+
function computeEditModalSize(imageDims, targetAspect) {
|
|
838
|
+
if (typeof window === "undefined") {
|
|
839
|
+
return {
|
|
840
|
+
style: { width: "1100px", maxWidth: "calc(100vw - 32px)" },
|
|
841
|
+
editorMaxHeight: 480
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
const vw = window.innerWidth;
|
|
845
|
+
const vh = window.innerHeight;
|
|
846
|
+
const bodyHeightAvail = Math.max(280, vh * 0.9 - 146);
|
|
847
|
+
const editorMaxHeight = Math.min(bodyHeightAvail, 520);
|
|
848
|
+
const sourceAspect = imageDims ? imageDims.w / imageDims.h : 4 / 3;
|
|
849
|
+
const sourceBoxH = Math.max(120, editorMaxHeight - 96);
|
|
850
|
+
const sourceBoxW = sourceBoxH * sourceAspect;
|
|
851
|
+
const editorWidth = Math.min(sourceBoxW + 96, 640);
|
|
852
|
+
const previewColWidth = targetAspect >= 1 ? 240 : 200;
|
|
853
|
+
const editColWidth = 208;
|
|
854
|
+
const gapsAndPadding = 32 + 40;
|
|
855
|
+
const total = previewColWidth + editorWidth + editColWidth + gapsAndPadding;
|
|
856
|
+
const clamped = Math.max(720, Math.min(1300, total));
|
|
857
|
+
const final = Math.min(clamped, vw - 32);
|
|
858
|
+
return {
|
|
859
|
+
style: { width: `${Math.round(final)}px` },
|
|
860
|
+
editorMaxHeight: Math.round(editorMaxHeight)
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
function probeImageDims(url) {
|
|
864
|
+
return new Promise((resolve) => {
|
|
865
|
+
if (typeof window === "undefined") {
|
|
866
|
+
resolve(null);
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const img = new window.Image();
|
|
870
|
+
img.onload = () => resolve({ w: img.naturalWidth, h: img.naturalHeight });
|
|
871
|
+
img.onerror = () => resolve(null);
|
|
872
|
+
img.src = url;
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
function Modal({
|
|
876
|
+
open,
|
|
877
|
+
onClose,
|
|
878
|
+
title,
|
|
879
|
+
children,
|
|
880
|
+
widthClass = "max-w-3xl w-full",
|
|
881
|
+
widthStyle,
|
|
882
|
+
footer
|
|
883
|
+
}) {
|
|
884
|
+
react.useEffect(() => {
|
|
885
|
+
if (!open) return;
|
|
886
|
+
function onKey(e) {
|
|
887
|
+
if (e.key === "Escape") onClose();
|
|
888
|
+
}
|
|
889
|
+
window.addEventListener("keydown", onKey);
|
|
890
|
+
const prev = document.body.style.overflow;
|
|
891
|
+
document.body.style.overflow = "hidden";
|
|
892
|
+
return () => {
|
|
893
|
+
window.removeEventListener("keydown", onKey);
|
|
894
|
+
document.body.style.overflow = prev;
|
|
895
|
+
};
|
|
896
|
+
}, [open, onClose]);
|
|
897
|
+
if (!open) return null;
|
|
898
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
899
|
+
"div",
|
|
900
|
+
{
|
|
901
|
+
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4",
|
|
902
|
+
onMouseDown: (e) => {
|
|
903
|
+
if (e.target === e.currentTarget) onClose();
|
|
904
|
+
},
|
|
905
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
906
|
+
"div",
|
|
907
|
+
{
|
|
908
|
+
className: `${widthStyle ? "" : widthClass} flex max-h-[90vh] flex-col overflow-hidden rounded-lg bg-white shadow-2xl`,
|
|
909
|
+
style: widthStyle,
|
|
910
|
+
children: [
|
|
911
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center justify-between border-b border-neutral-200 px-5 py-3", children: [
|
|
912
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold uppercase tracking-wider text-neutral-700", children: title }),
|
|
913
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
914
|
+
"button",
|
|
915
|
+
{
|
|
916
|
+
type: "button",
|
|
917
|
+
onClick: onClose,
|
|
918
|
+
className: "rounded-full p-1 text-neutral-500 hover:bg-neutral-100 hover:text-neutral-900",
|
|
919
|
+
"aria-label": "Fechar",
|
|
920
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6L6 18M6 6l12 12" }) })
|
|
921
|
+
}
|
|
922
|
+
)
|
|
923
|
+
] }),
|
|
924
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 items-center justify-center overflow-auto p-5", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full", children }) }),
|
|
925
|
+
footer && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 border-t border-neutral-200 bg-neutral-50 px-5 py-3", children: footer })
|
|
926
|
+
]
|
|
927
|
+
}
|
|
928
|
+
)
|
|
929
|
+
}
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
function Collapsible({
|
|
933
|
+
label,
|
|
934
|
+
defaultOpen = false,
|
|
935
|
+
children
|
|
936
|
+
}) {
|
|
937
|
+
const [open, setOpen] = react.useState(defaultOpen);
|
|
938
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md border border-neutral-200 bg-white", children: [
|
|
939
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
940
|
+
"button",
|
|
941
|
+
{
|
|
942
|
+
type: "button",
|
|
943
|
+
onClick: () => setOpen((v) => !v),
|
|
944
|
+
className: "flex w-full items-center justify-between px-3 py-2 text-[11px] font-semibold uppercase tracking-wider text-neutral-700 hover:text-neutral-900",
|
|
945
|
+
children: [
|
|
946
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: label }),
|
|
947
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
948
|
+
"svg",
|
|
949
|
+
{
|
|
950
|
+
width: "14",
|
|
951
|
+
height: "14",
|
|
952
|
+
viewBox: "0 0 24 24",
|
|
953
|
+
fill: "none",
|
|
954
|
+
stroke: "currentColor",
|
|
955
|
+
strokeWidth: "2.5",
|
|
956
|
+
strokeLinecap: "round",
|
|
957
|
+
strokeLinejoin: "round",
|
|
958
|
+
className: `transition-transform duration-200 ${open ? "rotate-180" : ""}`,
|
|
959
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "6 9 12 15 18 9" })
|
|
960
|
+
}
|
|
961
|
+
)
|
|
962
|
+
]
|
|
963
|
+
}
|
|
964
|
+
),
|
|
965
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
966
|
+
"div",
|
|
967
|
+
{
|
|
968
|
+
className: "grid transition-[grid-template-rows] duration-200 ease-out",
|
|
969
|
+
style: { gridTemplateRows: open ? "1fr" : "0fr" },
|
|
970
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-neutral-200 p-3", children }) })
|
|
971
|
+
}
|
|
972
|
+
)
|
|
973
|
+
] });
|
|
974
|
+
}
|
|
975
|
+
function EditControls({
|
|
976
|
+
crop,
|
|
977
|
+
onChange
|
|
978
|
+
}) {
|
|
979
|
+
const saturation = crop.saturation ?? 100;
|
|
980
|
+
const sepia = crop.sepia ?? 0;
|
|
981
|
+
const sepiaColor = crop.sepiaColor ?? DEFAULT_SEPIA_COLOR;
|
|
982
|
+
const mirror = !!crop.mirror;
|
|
983
|
+
const backdropColor = crop.backdropColor;
|
|
984
|
+
const backdropOpacity = crop.backdropOpacity;
|
|
985
|
+
const { x: opX, y: opY } = parsePosition(crop.position);
|
|
986
|
+
function update(patch) {
|
|
987
|
+
onChange({ ...crop, ...patch });
|
|
988
|
+
}
|
|
989
|
+
function toggleMirror() {
|
|
990
|
+
update({
|
|
991
|
+
mirror: !mirror,
|
|
992
|
+
position: formatPos(100 - opX, opY)
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 rounded-md border border-neutral-200 bg-neutral-50 p-3", children: [
|
|
996
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-neutral-500", children: "Edi\xE7\xE3o" }),
|
|
997
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
998
|
+
"button",
|
|
999
|
+
{
|
|
1000
|
+
type: "button",
|
|
1001
|
+
onClick: toggleMirror,
|
|
1002
|
+
className: `w-full whitespace-nowrap rounded-md border px-3 py-1.5 text-xs font-semibold transition-colors ${mirror ? "border-[color:var(--ie-accent,#556FFF)] bg-[color:var(--ie-accent,#556FFF)] text-white" : "border-neutral-300 bg-white text-neutral-700 hover:border-neutral-500"}`,
|
|
1003
|
+
children: mirror ? "\u21C6 Espelhada" : "\u21C6 Espelhar"
|
|
1004
|
+
}
|
|
1005
|
+
),
|
|
1006
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Collapsible, { label: "Cor de fundo", defaultOpen: true, children: [
|
|
1007
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1008
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-1 flex items-center justify-between text-[11px] text-neutral-700", children: [
|
|
1009
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "backdrop-opacity", className: "font-semibold", children: "Opacidade" }),
|
|
1010
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono tabular-nums text-neutral-500", children: [
|
|
1011
|
+
backdropOpacity ?? 100,
|
|
1012
|
+
"%"
|
|
1013
|
+
] })
|
|
1014
|
+
] }),
|
|
1015
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1016
|
+
"input",
|
|
1017
|
+
{
|
|
1018
|
+
id: "backdrop-opacity",
|
|
1019
|
+
type: "range",
|
|
1020
|
+
min: 0,
|
|
1021
|
+
max: 100,
|
|
1022
|
+
step: 5,
|
|
1023
|
+
value: backdropOpacity ?? 100,
|
|
1024
|
+
onChange: (e) => update({ backdropOpacity: Number(e.target.value) }),
|
|
1025
|
+
className: "w-full accent-[color:var(--ie-accent,#556FFF)]"
|
|
1026
|
+
}
|
|
1027
|
+
)
|
|
1028
|
+
] }),
|
|
1029
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
|
|
1030
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1031
|
+
"label",
|
|
1032
|
+
{
|
|
1033
|
+
htmlFor: "backdrop-color",
|
|
1034
|
+
className: "text-[10px] font-semibold text-neutral-700",
|
|
1035
|
+
children: "Cor:"
|
|
1036
|
+
}
|
|
1037
|
+
),
|
|
1038
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1039
|
+
"input",
|
|
1040
|
+
{
|
|
1041
|
+
id: "backdrop-color",
|
|
1042
|
+
type: "color",
|
|
1043
|
+
value: backdropColor && backdropColor !== "transparent" ? backdropColor : "#13203B",
|
|
1044
|
+
onChange: (e) => update({ backdropColor: e.target.value }),
|
|
1045
|
+
className: "h-6 w-8 cursor-pointer rounded border border-neutral-300 p-0",
|
|
1046
|
+
"aria-label": "Cor de fundo"
|
|
1047
|
+
}
|
|
1048
|
+
),
|
|
1049
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1050
|
+
"input",
|
|
1051
|
+
{
|
|
1052
|
+
type: "text",
|
|
1053
|
+
value: backdropColor && backdropColor !== "transparent" ? backdropColor : "#13203B",
|
|
1054
|
+
onChange: (e) => {
|
|
1055
|
+
const v = e.target.value;
|
|
1056
|
+
if (/^#[0-9a-fA-F]{0,6}$/.test(v)) update({ backdropColor: v });
|
|
1057
|
+
},
|
|
1058
|
+
className: "w-20 rounded border border-neutral-300 px-2 py-1 font-mono text-[10px] uppercase",
|
|
1059
|
+
placeholder: "#13203B"
|
|
1060
|
+
}
|
|
1061
|
+
)
|
|
1062
|
+
] }),
|
|
1063
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1064
|
+
"button",
|
|
1065
|
+
{
|
|
1066
|
+
type: "button",
|
|
1067
|
+
onClick: () => update({ backdropColor: void 0, backdropOpacity: void 0 }),
|
|
1068
|
+
className: "mt-3 w-full rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-neutral-700 transition-colors hover:border-neutral-500",
|
|
1069
|
+
children: "Resetar fundo"
|
|
1070
|
+
}
|
|
1071
|
+
)
|
|
1072
|
+
] }),
|
|
1073
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Collapsible, { label: "Filtros", defaultOpen: true, children: [
|
|
1074
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1075
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-1 flex items-center justify-between text-[11px] text-neutral-700", children: [
|
|
1076
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "filter-saturation", className: "font-semibold", children: "Satura\xE7\xE3o" }),
|
|
1077
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono tabular-nums text-neutral-500", children: [
|
|
1078
|
+
saturation,
|
|
1079
|
+
"%"
|
|
1080
|
+
] })
|
|
1081
|
+
] }),
|
|
1082
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1083
|
+
"input",
|
|
1084
|
+
{
|
|
1085
|
+
id: "filter-saturation",
|
|
1086
|
+
type: "range",
|
|
1087
|
+
min: 0,
|
|
1088
|
+
max: 200,
|
|
1089
|
+
step: 5,
|
|
1090
|
+
value: saturation,
|
|
1091
|
+
onChange: (e) => update({ saturation: Number(e.target.value) }),
|
|
1092
|
+
className: "w-full accent-[color:var(--ie-accent,#556FFF)]"
|
|
1093
|
+
}
|
|
1094
|
+
),
|
|
1095
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-0.5 flex justify-between text-[9px] text-neutral-400", children: [
|
|
1096
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Sem cor" }),
|
|
1097
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Normal" }),
|
|
1098
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Vibrante" })
|
|
1099
|
+
] })
|
|
1100
|
+
] }),
|
|
1101
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3", children: [
|
|
1102
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-1 flex items-center justify-between text-[11px] text-neutral-700", children: [
|
|
1103
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "filter-sepia", className: "font-semibold", children: "S\xE9pia" }),
|
|
1104
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono tabular-nums text-neutral-500", children: [
|
|
1105
|
+
sepia,
|
|
1106
|
+
"%"
|
|
1107
|
+
] })
|
|
1108
|
+
] }),
|
|
1109
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1110
|
+
"input",
|
|
1111
|
+
{
|
|
1112
|
+
id: "filter-sepia",
|
|
1113
|
+
type: "range",
|
|
1114
|
+
min: 0,
|
|
1115
|
+
max: 100,
|
|
1116
|
+
step: 5,
|
|
1117
|
+
value: sepia,
|
|
1118
|
+
onChange: (e) => update({ sepia: Number(e.target.value) }),
|
|
1119
|
+
className: "w-full accent-[color:var(--ie-accent,#556FFF)]"
|
|
1120
|
+
}
|
|
1121
|
+
),
|
|
1122
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
|
|
1123
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1124
|
+
"label",
|
|
1125
|
+
{
|
|
1126
|
+
htmlFor: "filter-sepia-color",
|
|
1127
|
+
className: "text-[10px] font-semibold text-neutral-700",
|
|
1128
|
+
children: "Cor:"
|
|
1129
|
+
}
|
|
1130
|
+
),
|
|
1131
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1132
|
+
"input",
|
|
1133
|
+
{
|
|
1134
|
+
id: "filter-sepia-color",
|
|
1135
|
+
type: "color",
|
|
1136
|
+
value: sepiaColor,
|
|
1137
|
+
onChange: (e) => update({ sepiaColor: e.target.value }),
|
|
1138
|
+
className: "h-6 w-8 cursor-pointer rounded border border-neutral-300 p-0",
|
|
1139
|
+
"aria-label": "Cor do tint s\xE9pia"
|
|
1140
|
+
}
|
|
1141
|
+
),
|
|
1142
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1143
|
+
"input",
|
|
1144
|
+
{
|
|
1145
|
+
type: "text",
|
|
1146
|
+
value: sepiaColor,
|
|
1147
|
+
onChange: (e) => {
|
|
1148
|
+
const v = e.target.value;
|
|
1149
|
+
if (/^#[0-9a-fA-F]{0,6}$/.test(v)) update({ sepiaColor: v });
|
|
1150
|
+
},
|
|
1151
|
+
className: "w-20 rounded border border-neutral-300 px-2 py-1 font-mono text-[10px] uppercase",
|
|
1152
|
+
placeholder: "#704214"
|
|
1153
|
+
}
|
|
1154
|
+
)
|
|
1155
|
+
] })
|
|
1156
|
+
] }),
|
|
1157
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1158
|
+
"button",
|
|
1159
|
+
{
|
|
1160
|
+
type: "button",
|
|
1161
|
+
onClick: () => update({
|
|
1162
|
+
saturation: void 0,
|
|
1163
|
+
sepia: void 0,
|
|
1164
|
+
sepiaColor: void 0
|
|
1165
|
+
}),
|
|
1166
|
+
className: "mt-3 w-full rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-neutral-700 hover:border-neutral-500",
|
|
1167
|
+
children: "Resetar filtros"
|
|
1168
|
+
}
|
|
1169
|
+
)
|
|
1170
|
+
] })
|
|
1171
|
+
] });
|
|
1172
|
+
}
|
|
1173
|
+
function ImagePicker({
|
|
1174
|
+
value,
|
|
1175
|
+
onChange,
|
|
1176
|
+
uploadAdapter,
|
|
1177
|
+
label = "IMAGEM",
|
|
1178
|
+
hint,
|
|
1179
|
+
aspect = "3/4",
|
|
1180
|
+
crop,
|
|
1181
|
+
onCropChange,
|
|
1182
|
+
badge,
|
|
1183
|
+
extraCards,
|
|
1184
|
+
presets
|
|
1185
|
+
}) {
|
|
1186
|
+
const resolveUrl = uploadAdapter.resolveUrl ?? defaultResolveUrl;
|
|
1187
|
+
const canEdit = !!onCropChange && !!value;
|
|
1188
|
+
const currentPos = crop?.position ?? "center center";
|
|
1189
|
+
const currentScale = crop?.scale ?? 1;
|
|
1190
|
+
const currentMirror = !!crop?.mirror;
|
|
1191
|
+
const currentFilter = buildImageFilter({
|
|
1192
|
+
grayscale: crop?.grayscale,
|
|
1193
|
+
saturation: crop?.saturation,
|
|
1194
|
+
sepia: crop?.sepia,
|
|
1195
|
+
sepiaColor: crop?.sepiaColor
|
|
1196
|
+
});
|
|
1197
|
+
const [panel, setPanel] = react.useState(null);
|
|
1198
|
+
const [cropSnapshot, setCropSnapshot] = react.useState(null);
|
|
1199
|
+
const [urlSnapshot, setUrlSnapshot] = react.useState(null);
|
|
1200
|
+
const [imageDims, setImageDims] = react.useState(null);
|
|
1201
|
+
const [editModalSize, setEditModalSize] = react.useState(null);
|
|
1202
|
+
const [tab, setTab] = react.useState("images");
|
|
1203
|
+
const [uploads, setUploads] = react.useState([]);
|
|
1204
|
+
const [loading, setLoading] = react.useState(false);
|
|
1205
|
+
const [error, setError] = react.useState(null);
|
|
1206
|
+
const [progressMsg, setProgressMsg] = react.useState(null);
|
|
1207
|
+
const fileInputRef = react.useRef(null);
|
|
1208
|
+
const onCropChangeRef = react.useRef(onCropChange);
|
|
1209
|
+
react.useEffect(() => {
|
|
1210
|
+
onCropChangeRef.current = onCropChange;
|
|
1211
|
+
}, [onCropChange]);
|
|
1212
|
+
const uploadAdapterRef = react.useRef(uploadAdapter);
|
|
1213
|
+
react.useEffect(() => {
|
|
1214
|
+
uploadAdapterRef.current = uploadAdapter;
|
|
1215
|
+
}, [uploadAdapter]);
|
|
1216
|
+
react.useEffect(() => {
|
|
1217
|
+
void refreshUploads();
|
|
1218
|
+
}, []);
|
|
1219
|
+
async function refreshUploads() {
|
|
1220
|
+
setLoading(true);
|
|
1221
|
+
try {
|
|
1222
|
+
const items = await uploadAdapterRef.current.list();
|
|
1223
|
+
setUploads(items);
|
|
1224
|
+
} catch {
|
|
1225
|
+
setUploads([]);
|
|
1226
|
+
}
|
|
1227
|
+
setLoading(false);
|
|
1228
|
+
}
|
|
1229
|
+
async function handleFile(file) {
|
|
1230
|
+
setError(null);
|
|
1231
|
+
setProgressMsg(`Otimizando ${file.name}...`);
|
|
1232
|
+
try {
|
|
1233
|
+
const result = await uploadAdapterRef.current.upload(file);
|
|
1234
|
+
setUploads((prev) => [result, ...prev]);
|
|
1235
|
+
const dimsTxt = result.width && result.height ? ` (${result.width}\xD7${result.height} WebP)` : "";
|
|
1236
|
+
setProgressMsg(
|
|
1237
|
+
`${file.name} \u2192 ${bytesHuman(result.sizeBytes)}${dimsTxt}`
|
|
1238
|
+
);
|
|
1239
|
+
setTab("images");
|
|
1240
|
+
window.setTimeout(() => {
|
|
1241
|
+
setProgressMsg(null);
|
|
1242
|
+
void pickAndOpenEdit(result.url);
|
|
1243
|
+
}, 900);
|
|
1244
|
+
} catch (e) {
|
|
1245
|
+
const msg = e instanceof Error ? e.message : "Falha ao subir imagem";
|
|
1246
|
+
setError(msg);
|
|
1247
|
+
setProgressMsg(null);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
async function handleDelete(filename) {
|
|
1251
|
+
if (!confirm(`Remover "${filename}"?`)) return;
|
|
1252
|
+
const ok = await uploadAdapterRef.current.delete(filename);
|
|
1253
|
+
if (!ok) {
|
|
1254
|
+
setError("Falha ao remover");
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
setUploads((prev) => prev.filter((u) => u.filename !== filename));
|
|
1258
|
+
}
|
|
1259
|
+
async function pickAndOpenEdit(url) {
|
|
1260
|
+
const prevUrl = value;
|
|
1261
|
+
const prevAspectRatio = imageDims ? imageDims.w / imageDims.h : void 0;
|
|
1262
|
+
const prevCrop = crop ? { ...crop } : {};
|
|
1263
|
+
const dims = await probeImageDims(resolveUrl(url));
|
|
1264
|
+
const aspectRatio = dims ? dims.w / dims.h : void 0;
|
|
1265
|
+
setUrlSnapshot(prevUrl ? { url: prevUrl, aspectRatio: prevAspectRatio } : null);
|
|
1266
|
+
setCropSnapshot(prevCrop);
|
|
1267
|
+
onChange(url, aspectRatio);
|
|
1268
|
+
if (dims) setImageDims(dims);
|
|
1269
|
+
setEditModalSize(computeEditModalSize(dims, ASPECT_RATIO[aspect]));
|
|
1270
|
+
setPanel("edit");
|
|
1271
|
+
}
|
|
1272
|
+
function handlePickPreset(url) {
|
|
1273
|
+
void pickAndOpenEdit(url);
|
|
1274
|
+
}
|
|
1275
|
+
function handlePickUpload(url) {
|
|
1276
|
+
void pickAndOpenEdit(url);
|
|
1277
|
+
}
|
|
1278
|
+
function openEdit() {
|
|
1279
|
+
setUrlSnapshot(null);
|
|
1280
|
+
setCropSnapshot(crop ?? {});
|
|
1281
|
+
setEditModalSize(computeEditModalSize(imageDims, ASPECT_RATIO[aspect]));
|
|
1282
|
+
setPanel("edit");
|
|
1283
|
+
}
|
|
1284
|
+
function confirmEdit() {
|
|
1285
|
+
setUrlSnapshot(null);
|
|
1286
|
+
setCropSnapshot(null);
|
|
1287
|
+
setEditModalSize(null);
|
|
1288
|
+
setPanel(null);
|
|
1289
|
+
}
|
|
1290
|
+
function cancelEdit() {
|
|
1291
|
+
const snapshot = cropSnapshot;
|
|
1292
|
+
const urlSnap = urlSnapshot;
|
|
1293
|
+
if (urlSnap && urlSnap.url !== value) {
|
|
1294
|
+
onChange(urlSnap.url, urlSnap.aspectRatio);
|
|
1295
|
+
if (snapshot) {
|
|
1296
|
+
setTimeout(() => onCropChangeRef.current?.(snapshot), 0);
|
|
1297
|
+
}
|
|
1298
|
+
} else if (snapshot && onCropChange) {
|
|
1299
|
+
onCropChange(snapshot);
|
|
1300
|
+
}
|
|
1301
|
+
setUrlSnapshot(null);
|
|
1302
|
+
setCropSnapshot(null);
|
|
1303
|
+
setEditModalSize(null);
|
|
1304
|
+
setPanel(null);
|
|
1305
|
+
}
|
|
1306
|
+
const aspectClass = {
|
|
1307
|
+
"1/1": "aspect-square",
|
|
1308
|
+
"3/4": "aspect-[3/4]",
|
|
1309
|
+
"16/9": "aspect-video",
|
|
1310
|
+
"4/3": "aspect-[4/3]"
|
|
1311
|
+
}[aspect];
|
|
1312
|
+
const previewSrc = value ? resolveUrl(value) : null;
|
|
1313
|
+
react.useEffect(() => {
|
|
1314
|
+
if (!previewSrc) {
|
|
1315
|
+
setImageDims(null);
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
let cancelled = false;
|
|
1319
|
+
const img = new window.Image();
|
|
1320
|
+
img.onload = () => {
|
|
1321
|
+
if (!cancelled) {
|
|
1322
|
+
setImageDims({ w: img.naturalWidth, h: img.naturalHeight });
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
img.src = previewSrc;
|
|
1326
|
+
return () => {
|
|
1327
|
+
cancelled = true;
|
|
1328
|
+
};
|
|
1329
|
+
}, [previewSrc]);
|
|
1330
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4 rounded-md border border-neutral-200 bg-neutral-50 p-4", children: [
|
|
1331
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1332
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold uppercase tracking-wider text-neutral-500", children: label }),
|
|
1333
|
+
hint && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-neutral-500", children: hint })
|
|
1334
|
+
] }),
|
|
1335
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1336
|
+
"div",
|
|
1337
|
+
{
|
|
1338
|
+
className: "grid grid-cols-1 gap-3 md:grid-cols-[var(--ip-cols)]",
|
|
1339
|
+
style: {
|
|
1340
|
+
"--ip-cols": extraCards && extraCards.length > 0 ? `220px ${Array(extraCards.length).fill("minmax(0, 1fr)").join(" ")}` : "1fr"
|
|
1341
|
+
},
|
|
1342
|
+
children: [
|
|
1343
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 rounded-md border border-neutral-200 bg-white p-3", children: [
|
|
1344
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1345
|
+
"div",
|
|
1346
|
+
{
|
|
1347
|
+
className: `${aspectClass} relative mx-auto w-full max-w-[180px] overflow-hidden rounded-md border border-neutral-200 bg-neutral-50`,
|
|
1348
|
+
style: {
|
|
1349
|
+
containerType: "inline-size",
|
|
1350
|
+
backgroundColor: previewSrc ? resolveBackdropBg(crop) : void 0
|
|
1351
|
+
},
|
|
1352
|
+
children: previewSrc ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1353
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1354
|
+
"img",
|
|
1355
|
+
{
|
|
1356
|
+
src: previewSrc,
|
|
1357
|
+
alt: "",
|
|
1358
|
+
style: computeImageDisplayStyle({
|
|
1359
|
+
position: currentPos,
|
|
1360
|
+
scale: currentScale,
|
|
1361
|
+
mirror: currentMirror,
|
|
1362
|
+
imgDims: imageDims,
|
|
1363
|
+
targetAspect: ASPECT_RATIO[aspect],
|
|
1364
|
+
filter: currentFilter,
|
|
1365
|
+
overflowX: crop?.overflowX,
|
|
1366
|
+
overflowY: crop?.overflowY
|
|
1367
|
+
})
|
|
1368
|
+
}
|
|
1369
|
+
),
|
|
1370
|
+
/* @__PURE__ */ jsxRuntime.jsx(BadgeOverlay, { badge, size: "sm" })
|
|
1371
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full w-full items-center justify-center text-xs text-neutral-400", children: "sem imagem" })
|
|
1372
|
+
}
|
|
1373
|
+
),
|
|
1374
|
+
!value && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center text-xs text-neutral-400", children: "Nenhuma imagem selecionada" }),
|
|
1375
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto flex w-full max-w-[180px] flex-col gap-2", children: [
|
|
1376
|
+
canEdit && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1377
|
+
"button",
|
|
1378
|
+
{
|
|
1379
|
+
type: "button",
|
|
1380
|
+
onClick: openEdit,
|
|
1381
|
+
className: "flex w-full items-center justify-center gap-2 rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-700 hover:border-[color:var(--ie-accent,#556FFF)] hover:text-[color:var(--ie-accent,#556FFF)]",
|
|
1382
|
+
children: [
|
|
1383
|
+
/* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" }) }),
|
|
1384
|
+
"Editar imagem"
|
|
1385
|
+
]
|
|
1386
|
+
}
|
|
1387
|
+
),
|
|
1388
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1389
|
+
"button",
|
|
1390
|
+
{
|
|
1391
|
+
type: "button",
|
|
1392
|
+
onClick: () => setPanel("change"),
|
|
1393
|
+
className: "flex w-full items-center justify-center gap-2 rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-700 hover:border-[color:var(--ie-accent,#556FFF)] hover:text-[color:var(--ie-accent,#556FFF)]",
|
|
1394
|
+
children: [
|
|
1395
|
+
/* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
1396
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m16 3 4 4-4 4" }),
|
|
1397
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 7H4" }),
|
|
1398
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m8 21-4-4 4-4" }),
|
|
1399
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 17h16" })
|
|
1400
|
+
] }),
|
|
1401
|
+
value ? "Trocar imagem" : "Selecionar imagem"
|
|
1402
|
+
]
|
|
1403
|
+
}
|
|
1404
|
+
),
|
|
1405
|
+
value && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1406
|
+
"button",
|
|
1407
|
+
{
|
|
1408
|
+
type: "button",
|
|
1409
|
+
onClick: () => {
|
|
1410
|
+
if (confirm("Remover a imagem selecionada?")) onChange("");
|
|
1411
|
+
},
|
|
1412
|
+
className: "flex w-full items-center justify-center gap-2 rounded-md border border-red-300 bg-white px-3 py-1.5 text-xs font-semibold text-red-600 hover:border-red-500 hover:bg-red-50",
|
|
1413
|
+
children: [
|
|
1414
|
+
/* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
1415
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 6h18" }),
|
|
1416
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }),
|
|
1417
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }),
|
|
1418
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "10", x2: "10", y1: "11", y2: "17" }),
|
|
1419
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "14", x2: "14", y1: "11", y2: "17" })
|
|
1420
|
+
] }),
|
|
1421
|
+
"Remover imagem"
|
|
1422
|
+
]
|
|
1423
|
+
}
|
|
1424
|
+
)
|
|
1425
|
+
] })
|
|
1426
|
+
] }),
|
|
1427
|
+
extraCards?.map((card, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1428
|
+
"div",
|
|
1429
|
+
{
|
|
1430
|
+
className: "min-w-0 rounded-md border border-neutral-200 bg-white p-4",
|
|
1431
|
+
children: card
|
|
1432
|
+
},
|
|
1433
|
+
i
|
|
1434
|
+
))
|
|
1435
|
+
]
|
|
1436
|
+
}
|
|
1437
|
+
),
|
|
1438
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1439
|
+
Modal,
|
|
1440
|
+
{
|
|
1441
|
+
open: panel === "edit",
|
|
1442
|
+
onClose: cancelEdit,
|
|
1443
|
+
title: "Editar imagem",
|
|
1444
|
+
widthStyle: editModalSize?.style,
|
|
1445
|
+
footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
1446
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1447
|
+
"button",
|
|
1448
|
+
{
|
|
1449
|
+
type: "button",
|
|
1450
|
+
onClick: cancelEdit,
|
|
1451
|
+
className: "rounded-md border border-neutral-300 bg-white px-5 py-2 text-xs font-semibold uppercase tracking-wider text-neutral-700 hover:border-neutral-400 hover:text-neutral-900",
|
|
1452
|
+
children: "Cancelar"
|
|
1453
|
+
}
|
|
1454
|
+
),
|
|
1455
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1456
|
+
"button",
|
|
1457
|
+
{
|
|
1458
|
+
type: "button",
|
|
1459
|
+
onClick: confirmEdit,
|
|
1460
|
+
className: "rounded-md bg-[color:var(--ie-accent,#556FFF)] px-5 py-2 text-xs font-semibold uppercase tracking-wider text-white hover:bg-[color:var(--ie-accent-hover,#3957E2)]",
|
|
1461
|
+
children: "Confirmar"
|
|
1462
|
+
}
|
|
1463
|
+
)
|
|
1464
|
+
] }),
|
|
1465
|
+
children: canEdit && previewSrc && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 md:flex-row md:items-start", children: [
|
|
1466
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "md:shrink-0", children: [
|
|
1467
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-neutral-500", children: "Preview" }),
|
|
1468
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1469
|
+
"div",
|
|
1470
|
+
{
|
|
1471
|
+
className: "relative mt-2 overflow-hidden rounded-md border border-neutral-200 shadow-sm",
|
|
1472
|
+
style: {
|
|
1473
|
+
width: `${ASPECT_RATIO[aspect] >= 1 ? 240 : 200}px`,
|
|
1474
|
+
aspectRatio: ASPECT_RATIO[aspect],
|
|
1475
|
+
backgroundColor: resolveBackdropBg(crop),
|
|
1476
|
+
containerType: "inline-size"
|
|
1477
|
+
},
|
|
1478
|
+
children: [
|
|
1479
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1480
|
+
"img",
|
|
1481
|
+
{
|
|
1482
|
+
src: previewSrc,
|
|
1483
|
+
alt: "",
|
|
1484
|
+
style: computeImageDisplayStyle({
|
|
1485
|
+
position: crop?.position ?? "center center",
|
|
1486
|
+
scale: crop?.scale ?? 1,
|
|
1487
|
+
mirror: !!crop?.mirror,
|
|
1488
|
+
imgDims: imageDims,
|
|
1489
|
+
targetAspect: ASPECT_RATIO[aspect],
|
|
1490
|
+
filter: buildImageFilter({
|
|
1491
|
+
grayscale: crop?.grayscale,
|
|
1492
|
+
saturation: crop?.saturation,
|
|
1493
|
+
sepia: crop?.sepia,
|
|
1494
|
+
sepiaColor: crop?.sepiaColor
|
|
1495
|
+
}),
|
|
1496
|
+
overflowX: crop?.overflowX,
|
|
1497
|
+
overflowY: crop?.overflowY
|
|
1498
|
+
})
|
|
1499
|
+
}
|
|
1500
|
+
),
|
|
1501
|
+
/* @__PURE__ */ jsxRuntime.jsx(BadgeOverlay, { badge, size: "md" })
|
|
1502
|
+
]
|
|
1503
|
+
}
|
|
1504
|
+
),
|
|
1505
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-[10px] text-neutral-500", children: aspect === "16/9" ? "Aspect 16:9 (fundo)" : "Aspect 3:4 (foto)" })
|
|
1506
|
+
] }),
|
|
1507
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1508
|
+
ImageCropPicker,
|
|
1509
|
+
{
|
|
1510
|
+
imageUrl: previewSrc,
|
|
1511
|
+
targetAspect: ASPECT_RATIO[aspect],
|
|
1512
|
+
value: crop ?? {},
|
|
1513
|
+
onChange: (next) => onCropChange?.(next),
|
|
1514
|
+
maxHeight: editModalSize?.editorMaxHeight
|
|
1515
|
+
}
|
|
1516
|
+
) }),
|
|
1517
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "md:w-52 md:shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1518
|
+
EditControls,
|
|
1519
|
+
{
|
|
1520
|
+
crop: crop ?? {},
|
|
1521
|
+
onChange: (next) => onCropChange?.(next)
|
|
1522
|
+
}
|
|
1523
|
+
) })
|
|
1524
|
+
] })
|
|
1525
|
+
}
|
|
1526
|
+
),
|
|
1527
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1528
|
+
Modal,
|
|
1529
|
+
{
|
|
1530
|
+
open: panel === "change",
|
|
1531
|
+
onClose: () => setPanel(null),
|
|
1532
|
+
title: value ? "Trocar imagem" : "Selecionar imagem",
|
|
1533
|
+
widthClass: "max-w-3xl w-full",
|
|
1534
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
1535
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1 border-b border-neutral-200", children: [
|
|
1536
|
+
["images", `Imagens (${uploads.length})`],
|
|
1537
|
+
["url", "URL manual"]
|
|
1538
|
+
].map(([id, name]) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1539
|
+
"button",
|
|
1540
|
+
{
|
|
1541
|
+
type: "button",
|
|
1542
|
+
onClick: () => setTab(id),
|
|
1543
|
+
className: `px-3 py-2 text-xs font-semibold uppercase tracking-wider transition-colors ${tab === id ? "border-b-2 border-[color:var(--ie-accent,#556FFF)] text-[color:var(--ie-accent,#556FFF)]" : "text-neutral-500 hover:text-neutral-700"}`,
|
|
1544
|
+
children: name
|
|
1545
|
+
},
|
|
1546
|
+
id
|
|
1547
|
+
)) }),
|
|
1548
|
+
progressMsg && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-800", children: progressMsg }),
|
|
1549
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-800", children: error }),
|
|
1550
|
+
tab === "images" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
1551
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1552
|
+
"div",
|
|
1553
|
+
{
|
|
1554
|
+
onDragOver: (e) => e.preventDefault(),
|
|
1555
|
+
onDrop: (e) => {
|
|
1556
|
+
e.preventDefault();
|
|
1557
|
+
const file = e.dataTransfer.files[0];
|
|
1558
|
+
if (file) void handleFile(file);
|
|
1559
|
+
},
|
|
1560
|
+
className: "flex flex-col items-center justify-center gap-2 rounded-md border-2 border-dashed border-neutral-300 bg-neutral-50 px-4 py-5 text-center",
|
|
1561
|
+
children: [
|
|
1562
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-neutral-600", children: "Arraste uma imagem aqui ou" }),
|
|
1563
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1564
|
+
"button",
|
|
1565
|
+
{
|
|
1566
|
+
type: "button",
|
|
1567
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1568
|
+
className: "rounded-md bg-[color:var(--ie-accent,#556FFF)] px-4 py-2 text-xs font-semibold uppercase tracking-wider text-white hover:bg-[color:var(--ie-accent-hover,#3957E2)]",
|
|
1569
|
+
children: "Selecionar arquivo"
|
|
1570
|
+
}
|
|
1571
|
+
),
|
|
1572
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-neutral-500", children: "JPG, PNG, WebP ou AVIF \xB7 M\xE1x 15MB \xB7 Otimizado pra WebP automaticamente" }),
|
|
1573
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1574
|
+
"input",
|
|
1575
|
+
{
|
|
1576
|
+
ref: fileInputRef,
|
|
1577
|
+
type: "file",
|
|
1578
|
+
accept: "image/jpeg,image/png,image/webp,image/avif,image/gif",
|
|
1579
|
+
className: "hidden",
|
|
1580
|
+
onChange: (e) => {
|
|
1581
|
+
const file = e.target.files?.[0];
|
|
1582
|
+
if (file) void handleFile(file);
|
|
1583
|
+
e.target.value = "";
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
)
|
|
1587
|
+
]
|
|
1588
|
+
}
|
|
1589
|
+
),
|
|
1590
|
+
loading && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-neutral-500", children: "Carregando subidas..." }),
|
|
1591
|
+
uploads.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1592
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mb-2 text-[10px] font-semibold uppercase tracking-wider text-neutral-500", children: [
|
|
1593
|
+
"Subidas (",
|
|
1594
|
+
uploads.length,
|
|
1595
|
+
")"
|
|
1596
|
+
] }),
|
|
1597
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-2 md:grid-cols-4", children: uploads.map((up) => {
|
|
1598
|
+
const selected = value === up.url;
|
|
1599
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1600
|
+
"div",
|
|
1601
|
+
{
|
|
1602
|
+
className: `group relative overflow-hidden rounded-md border ${selected ? "border-[color:var(--ie-accent,#556FFF)] ring-2 ring-[color:var(--ie-accent,#556FFF)]" : "border-neutral-200"}`,
|
|
1603
|
+
children: [
|
|
1604
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1605
|
+
"button",
|
|
1606
|
+
{
|
|
1607
|
+
type: "button",
|
|
1608
|
+
onClick: () => handlePickUpload(up.url),
|
|
1609
|
+
className: "block w-full text-left",
|
|
1610
|
+
title: up.filename,
|
|
1611
|
+
children: [
|
|
1612
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-square w-full bg-neutral-50", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1613
|
+
"img",
|
|
1614
|
+
{
|
|
1615
|
+
src: resolveUrl(up.url),
|
|
1616
|
+
alt: up.filename,
|
|
1617
|
+
className: "h-full w-full object-cover",
|
|
1618
|
+
loading: "lazy"
|
|
1619
|
+
}
|
|
1620
|
+
) }),
|
|
1621
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate px-2 py-1 text-[10px] text-neutral-600", children: bytesHuman(up.sizeBytes) })
|
|
1622
|
+
]
|
|
1623
|
+
}
|
|
1624
|
+
),
|
|
1625
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1626
|
+
"button",
|
|
1627
|
+
{
|
|
1628
|
+
type: "button",
|
|
1629
|
+
onClick: () => void handleDelete(up.filename),
|
|
1630
|
+
className: "absolute right-1 top-1 rounded-full bg-black/60 px-2 py-0.5 text-[10px] text-white opacity-0 transition-opacity hover:bg-red-600 group-hover:opacity-100",
|
|
1631
|
+
title: "Remover",
|
|
1632
|
+
children: "\u2715"
|
|
1633
|
+
}
|
|
1634
|
+
)
|
|
1635
|
+
]
|
|
1636
|
+
},
|
|
1637
|
+
up.filename
|
|
1638
|
+
);
|
|
1639
|
+
}) })
|
|
1640
|
+
] }),
|
|
1641
|
+
presets && presets.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1642
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-[10px] font-semibold uppercase tracking-wider text-neutral-500", children: "Do site" }),
|
|
1643
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-2 md:grid-cols-4", children: presets.map((img) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1644
|
+
"button",
|
|
1645
|
+
{
|
|
1646
|
+
type: "button",
|
|
1647
|
+
onClick: () => handlePickPreset(img.url),
|
|
1648
|
+
className: `group overflow-hidden rounded-md border text-left transition-all ${value === img.url ? "border-[color:var(--ie-accent,#556FFF)] ring-2 ring-[color:var(--ie-accent,#556FFF)]" : "border-neutral-200 hover:border-neutral-400"}`,
|
|
1649
|
+
title: img.label,
|
|
1650
|
+
children: [
|
|
1651
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-square w-full bg-neutral-50", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1652
|
+
"img",
|
|
1653
|
+
{
|
|
1654
|
+
src: resolveUrl(img.url),
|
|
1655
|
+
alt: img.label,
|
|
1656
|
+
className: "h-full w-full object-cover",
|
|
1657
|
+
loading: "lazy"
|
|
1658
|
+
}
|
|
1659
|
+
) }),
|
|
1660
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate px-2 py-1 text-[10px] text-neutral-600 group-hover:text-neutral-900", children: img.label })
|
|
1661
|
+
]
|
|
1662
|
+
},
|
|
1663
|
+
img.url
|
|
1664
|
+
)) })
|
|
1665
|
+
] })
|
|
1666
|
+
] }),
|
|
1667
|
+
tab === "url" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
|
|
1668
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1669
|
+
"input",
|
|
1670
|
+
{
|
|
1671
|
+
type: "text",
|
|
1672
|
+
value,
|
|
1673
|
+
onChange: async (e) => {
|
|
1674
|
+
const url = e.target.value;
|
|
1675
|
+
if (url) {
|
|
1676
|
+
const dims = await probeImageDims(resolveUrl(url));
|
|
1677
|
+
const aspectRatio = dims ? dims.w / dims.h : void 0;
|
|
1678
|
+
onChange(url, aspectRatio);
|
|
1679
|
+
} else {
|
|
1680
|
+
onChange(url);
|
|
1681
|
+
}
|
|
1682
|
+
},
|
|
1683
|
+
placeholder: "/uploads/...webp ou https://...",
|
|
1684
|
+
className: "w-full rounded-md border border-neutral-300 px-3 py-2 text-sm focus:border-[color:var(--ie-accent,#556FFF)] focus:outline-none"
|
|
1685
|
+
}
|
|
1686
|
+
),
|
|
1687
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-neutral-500", children: "Caminhos relativos buscam no dom\xEDnio atual; URLs absolutas funcionam tal qual." }),
|
|
1688
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1689
|
+
"button",
|
|
1690
|
+
{
|
|
1691
|
+
type: "button",
|
|
1692
|
+
onClick: () => setPanel(null),
|
|
1693
|
+
className: "rounded-md bg-[color:var(--ie-accent,#556FFF)] px-4 py-2 text-xs font-semibold uppercase tracking-wider text-white hover:bg-[color:var(--ie-accent-hover,#3957E2)]",
|
|
1694
|
+
children: "Concluir"
|
|
1695
|
+
}
|
|
1696
|
+
) })
|
|
1697
|
+
] })
|
|
1698
|
+
] })
|
|
1699
|
+
}
|
|
1700
|
+
)
|
|
1701
|
+
] });
|
|
1702
|
+
}
|
|
1703
|
+
function SliderRow({
|
|
1704
|
+
label,
|
|
1705
|
+
min,
|
|
1706
|
+
max,
|
|
1707
|
+
step,
|
|
1708
|
+
value,
|
|
1709
|
+
fallback,
|
|
1710
|
+
format,
|
|
1711
|
+
isCustom,
|
|
1712
|
+
onChange,
|
|
1713
|
+
onReset
|
|
1714
|
+
}) {
|
|
1715
|
+
const current = value ?? fallback;
|
|
1716
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1717
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-20 shrink-0 text-xs font-semibold uppercase tracking-wider text-neutral-600", children: label }),
|
|
1718
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1719
|
+
"input",
|
|
1720
|
+
{
|
|
1721
|
+
type: "range",
|
|
1722
|
+
min,
|
|
1723
|
+
max,
|
|
1724
|
+
step,
|
|
1725
|
+
value: current,
|
|
1726
|
+
onChange: (e) => onChange(Number(e.target.value)),
|
|
1727
|
+
className: "min-w-0 flex-1 accent-[color:var(--ie-accent,#556FFF)]"
|
|
1728
|
+
}
|
|
1729
|
+
),
|
|
1730
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-12 shrink-0 text-right font-mono text-xs tabular-nums text-neutral-500", children: format(current) }),
|
|
1731
|
+
isCustom ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1732
|
+
"button",
|
|
1733
|
+
{
|
|
1734
|
+
type: "button",
|
|
1735
|
+
onClick: onReset,
|
|
1736
|
+
className: "w-10 shrink-0 text-xs text-neutral-500 hover:text-neutral-900 hover:underline",
|
|
1737
|
+
title: "Resetar para o padr\xE3o",
|
|
1738
|
+
children: "reset"
|
|
1739
|
+
}
|
|
1740
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-10 shrink-0", "aria-hidden": "true" })
|
|
1741
|
+
] });
|
|
1742
|
+
}
|
|
1743
|
+
var DEFAULT_SEED_COLORS = ["#FFFFFF", "#000000"];
|
|
1744
|
+
function BadgePartControls({
|
|
1745
|
+
color,
|
|
1746
|
+
defaultColor,
|
|
1747
|
+
size,
|
|
1748
|
+
offsetX,
|
|
1749
|
+
offsetY,
|
|
1750
|
+
defaultPosX,
|
|
1751
|
+
defaultPosY,
|
|
1752
|
+
onColorChange,
|
|
1753
|
+
onSizeChange,
|
|
1754
|
+
onOffsetXChange,
|
|
1755
|
+
onOffsetYChange,
|
|
1756
|
+
recentColors,
|
|
1757
|
+
recentKind = "text",
|
|
1758
|
+
seedColors = DEFAULT_SEED_COLORS,
|
|
1759
|
+
sizeMin = 50,
|
|
1760
|
+
sizeMax = 500
|
|
1761
|
+
}) {
|
|
1762
|
+
const effectiveColor = color || defaultColor;
|
|
1763
|
+
const valueUp = effectiveColor.toUpperCase();
|
|
1764
|
+
const seen = /* @__PURE__ */ new Set([valueUp]);
|
|
1765
|
+
const combined = [];
|
|
1766
|
+
const recents = recentColors?.colors[recentKind] ?? [];
|
|
1767
|
+
for (const hex of [...recents, ...seedColors]) {
|
|
1768
|
+
const up = hex.toUpperCase();
|
|
1769
|
+
if (!seen.has(up)) {
|
|
1770
|
+
seen.add(up);
|
|
1771
|
+
combined.push(up);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
const swatches = combined.slice(0, 5);
|
|
1775
|
+
function commitColor() {
|
|
1776
|
+
if (color && /^#[0-9a-fA-F]{6}$/.test(color)) {
|
|
1777
|
+
recentColors?.addColor(color, recentKind);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
function applySwatch(hex) {
|
|
1781
|
+
onColorChange(hex);
|
|
1782
|
+
recentColors?.addColor(hex, recentKind);
|
|
1783
|
+
}
|
|
1784
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
1785
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
1786
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1787
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-20 shrink-0 text-xs font-semibold uppercase tracking-wider text-neutral-600", children: "Cor" }),
|
|
1788
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1789
|
+
"input",
|
|
1790
|
+
{
|
|
1791
|
+
type: "color",
|
|
1792
|
+
value: effectiveColor,
|
|
1793
|
+
onChange: (e) => onColorChange(e.target.value),
|
|
1794
|
+
onBlur: commitColor,
|
|
1795
|
+
className: "h-8 w-10 shrink-0 cursor-pointer rounded border border-neutral-300 p-0",
|
|
1796
|
+
"aria-label": "Cor"
|
|
1797
|
+
}
|
|
1798
|
+
),
|
|
1799
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1800
|
+
"input",
|
|
1801
|
+
{
|
|
1802
|
+
type: "text",
|
|
1803
|
+
value: effectiveColor,
|
|
1804
|
+
onChange: (e) => {
|
|
1805
|
+
const v = e.target.value;
|
|
1806
|
+
if (/^#[0-9a-fA-F]{0,6}$/.test(v)) onColorChange(v);
|
|
1807
|
+
},
|
|
1808
|
+
onBlur: commitColor,
|
|
1809
|
+
className: "min-w-0 flex-1 rounded border border-neutral-300 px-2 py-1.5 font-mono text-xs uppercase",
|
|
1810
|
+
placeholder: defaultColor
|
|
1811
|
+
}
|
|
1812
|
+
),
|
|
1813
|
+
color !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1814
|
+
"button",
|
|
1815
|
+
{
|
|
1816
|
+
type: "button",
|
|
1817
|
+
onClick: () => onColorChange(void 0),
|
|
1818
|
+
className: "w-10 shrink-0 text-xs text-neutral-500 hover:text-neutral-900 hover:underline",
|
|
1819
|
+
title: "Resetar para o padr\xE3o",
|
|
1820
|
+
children: "reset"
|
|
1821
|
+
}
|
|
1822
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-10 shrink-0", "aria-hidden": "true" })
|
|
1823
|
+
] }),
|
|
1824
|
+
swatches.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-1.5 pl-[92px]", children: [
|
|
1825
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] uppercase tracking-wider text-neutral-400", children: "Recentes" }),
|
|
1826
|
+
swatches.map((hex) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1827
|
+
"button",
|
|
1828
|
+
{
|
|
1829
|
+
type: "button",
|
|
1830
|
+
onClick: () => applySwatch(hex),
|
|
1831
|
+
className: "h-5 w-5 shrink-0 rounded border border-neutral-300 transition-transform hover:scale-125",
|
|
1832
|
+
style: { backgroundColor: hex },
|
|
1833
|
+
title: hex,
|
|
1834
|
+
"aria-label": `Aplicar ${hex}`
|
|
1835
|
+
},
|
|
1836
|
+
hex
|
|
1837
|
+
))
|
|
1838
|
+
] })
|
|
1839
|
+
] }),
|
|
1840
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1841
|
+
SliderRow,
|
|
1842
|
+
{
|
|
1843
|
+
label: "Tamanho",
|
|
1844
|
+
min: sizeMin,
|
|
1845
|
+
max: sizeMax,
|
|
1846
|
+
step: 5,
|
|
1847
|
+
value: size,
|
|
1848
|
+
fallback: 100,
|
|
1849
|
+
format: (v) => `${v}%`,
|
|
1850
|
+
isCustom: size !== void 0,
|
|
1851
|
+
onChange: onSizeChange,
|
|
1852
|
+
onReset: () => onSizeChange(void 0)
|
|
1853
|
+
}
|
|
1854
|
+
),
|
|
1855
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1856
|
+
SliderRow,
|
|
1857
|
+
{
|
|
1858
|
+
label: "Pos. X",
|
|
1859
|
+
min: 0,
|
|
1860
|
+
max: 100,
|
|
1861
|
+
step: 0.5,
|
|
1862
|
+
value: offsetX,
|
|
1863
|
+
fallback: defaultPosX,
|
|
1864
|
+
format: (v) => `${v.toFixed(1)}%`,
|
|
1865
|
+
isCustom: offsetX !== void 0,
|
|
1866
|
+
onChange: onOffsetXChange,
|
|
1867
|
+
onReset: () => onOffsetXChange(void 0)
|
|
1868
|
+
}
|
|
1869
|
+
),
|
|
1870
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1871
|
+
SliderRow,
|
|
1872
|
+
{
|
|
1873
|
+
label: "Pos. Y",
|
|
1874
|
+
min: 0,
|
|
1875
|
+
max: 100,
|
|
1876
|
+
step: 0.5,
|
|
1877
|
+
value: offsetY,
|
|
1878
|
+
fallback: defaultPosY,
|
|
1879
|
+
format: (v) => `${v.toFixed(1)}%`,
|
|
1880
|
+
isCustom: offsetY !== void 0,
|
|
1881
|
+
onChange: onOffsetYChange,
|
|
1882
|
+
onReset: () => onOffsetYChange(void 0)
|
|
1883
|
+
}
|
|
1884
|
+
)
|
|
1885
|
+
] });
|
|
1886
|
+
}
|
|
1887
|
+
function TextField({
|
|
1888
|
+
label,
|
|
1889
|
+
value,
|
|
1890
|
+
onChange,
|
|
1891
|
+
placeholder
|
|
1892
|
+
}) {
|
|
1893
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block", children: [
|
|
1894
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-xs font-semibold uppercase tracking-wider text-neutral-700 mb-1", children: label }),
|
|
1895
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1896
|
+
"input",
|
|
1897
|
+
{
|
|
1898
|
+
type: "text",
|
|
1899
|
+
className: "block w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-[color:var(--ie-accent,#556FFF)] focus:ring-2 focus:ring-blue-200",
|
|
1900
|
+
value: value ?? "",
|
|
1901
|
+
onChange: (e) => onChange(e.target.value),
|
|
1902
|
+
placeholder
|
|
1903
|
+
}
|
|
1904
|
+
)
|
|
1905
|
+
] });
|
|
1906
|
+
}
|
|
1907
|
+
function BadgeEditorCard({
|
|
1908
|
+
title,
|
|
1909
|
+
value,
|
|
1910
|
+
onChange,
|
|
1911
|
+
defaultColor,
|
|
1912
|
+
defaultPosX,
|
|
1913
|
+
defaultPosY,
|
|
1914
|
+
enabledLabel = "Exibir",
|
|
1915
|
+
textPlaceholder,
|
|
1916
|
+
recentColors,
|
|
1917
|
+
recentKind = "text",
|
|
1918
|
+
seedColors,
|
|
1919
|
+
sizeMin,
|
|
1920
|
+
sizeMax
|
|
1921
|
+
}) {
|
|
1922
|
+
const enabled = value.enabled !== false;
|
|
1923
|
+
function patch(partial) {
|
|
1924
|
+
onChange({ ...value, ...partial });
|
|
1925
|
+
}
|
|
1926
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
1927
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold uppercase tracking-wider text-neutral-500", children: title }),
|
|
1928
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm text-neutral-700", children: [
|
|
1929
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1930
|
+
"input",
|
|
1931
|
+
{
|
|
1932
|
+
type: "checkbox",
|
|
1933
|
+
checked: enabled,
|
|
1934
|
+
onChange: (e) => patch({ enabled: e.target.checked ? void 0 : false }),
|
|
1935
|
+
className: "h-4 w-4 accent-[color:var(--ie-accent,#556FFF)]"
|
|
1936
|
+
}
|
|
1937
|
+
),
|
|
1938
|
+
enabledLabel
|
|
1939
|
+
] }),
|
|
1940
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1941
|
+
TextField,
|
|
1942
|
+
{
|
|
1943
|
+
label: "Texto",
|
|
1944
|
+
value: value.text,
|
|
1945
|
+
onChange: (v) => patch({ text: v }),
|
|
1946
|
+
placeholder: textPlaceholder
|
|
1947
|
+
}
|
|
1948
|
+
),
|
|
1949
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1950
|
+
BadgePartControls,
|
|
1951
|
+
{
|
|
1952
|
+
color: value.color,
|
|
1953
|
+
defaultColor,
|
|
1954
|
+
size: value.size,
|
|
1955
|
+
offsetX: value.offsetX,
|
|
1956
|
+
offsetY: value.offsetY,
|
|
1957
|
+
defaultPosX,
|
|
1958
|
+
defaultPosY,
|
|
1959
|
+
onColorChange: (v) => patch({ color: v }),
|
|
1960
|
+
onSizeChange: (v) => patch({ size: v }),
|
|
1961
|
+
onOffsetXChange: (v) => patch({ offsetX: v }),
|
|
1962
|
+
onOffsetYChange: (v) => patch({ offsetY: v }),
|
|
1963
|
+
recentColors,
|
|
1964
|
+
recentKind,
|
|
1965
|
+
seedColors,
|
|
1966
|
+
sizeMin,
|
|
1967
|
+
sizeMax
|
|
1968
|
+
}
|
|
1969
|
+
)
|
|
1970
|
+
] });
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
exports.BadgeEditorCard = BadgeEditorCard;
|
|
1974
|
+
exports.BadgeOverlay = BadgeOverlay;
|
|
1975
|
+
exports.BadgePartControls = BadgePartControls;
|
|
1976
|
+
exports.ImageCropPicker = ImageCropPicker;
|
|
1977
|
+
exports.ImagePicker = ImagePicker;
|
|
1978
|
+
exports.MAX_SCALE = MAX_SCALE;
|
|
1979
|
+
exports.MIN_SCALE = MIN_SCALE;
|
|
1980
|
+
exports.SliderRow = SliderRow;
|
|
1981
|
+
exports.buildImageFilter = buildImageFilter;
|
|
1982
|
+
exports.buildImageGeometry = buildImageGeometry;
|
|
1983
|
+
exports.computeVisibleBase = computeVisibleBase;
|
|
1984
|
+
exports.focalToOp = focalToOp;
|
|
1985
|
+
exports.formatPos = formatPos;
|
|
1986
|
+
exports.opToFocal = opToFocal;
|
|
1987
|
+
exports.parsePosition = parsePosition;
|
|
1988
|
+
//# sourceMappingURL=index.cjs.map
|
|
1989
|
+
//# sourceMappingURL=index.cjs.map
|