react-native-expo-cropper 1.2.36 → 1.2.38
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/babel.config.js +6 -0
- package/dist/CustomCamera.js +343 -245
- package/dist/ImageCropper.js +1700 -513
- package/dist/ImageCropperStyles.js +217 -217
- package/dist/ImageMaskProcessor.js +177 -0
- package/dist/ImageProcessor.js +40 -52
- package/package.json +5 -5
- package/src/CustomCamera.js +92 -18
- package/src/ImageCropper.js +1039 -170
package/dist/ImageCropper.js
CHANGED
|
@@ -1,514 +1,1701 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
4
|
-
Object.defineProperty(exports, "__esModule", {
|
|
5
|
-
value: true
|
|
6
|
-
});
|
|
7
|
-
exports["default"] = void 0;
|
|
8
|
-
var _ImageCropperStyles = _interopRequireDefault(require("./ImageCropperStyles"));
|
|
9
|
-
var _react = _interopRequireWildcard(require("react"));
|
|
10
|
-
var _reactNative = require("react-native");
|
|
11
|
-
var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg"));
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
var
|
|
15
|
-
var
|
|
16
|
-
var
|
|
17
|
-
var
|
|
18
|
-
function
|
|
19
|
-
function
|
|
20
|
-
function
|
|
21
|
-
function
|
|
22
|
-
function
|
|
23
|
-
function
|
|
24
|
-
function
|
|
25
|
-
function
|
|
26
|
-
function
|
|
27
|
-
function
|
|
28
|
-
function
|
|
29
|
-
function
|
|
30
|
-
function
|
|
31
|
-
function
|
|
32
|
-
function
|
|
33
|
-
function
|
|
34
|
-
function
|
|
35
|
-
function
|
|
36
|
-
function
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
var
|
|
40
|
-
var onConfirm = _ref.onConfirm,
|
|
41
|
-
openCameraFirst = _ref.openCameraFirst,
|
|
42
|
-
initialImage = _ref.initialImage,
|
|
43
|
-
addheight = _ref.addheight;
|
|
44
|
-
var _useState = (0, _react.useState)(null),
|
|
45
|
-
_useState2 = _slicedToArray(_useState, 2),
|
|
46
|
-
image = _useState2[0],
|
|
47
|
-
setImage = _useState2[1];
|
|
48
|
-
var _useState3 = (0, _react.useState)([]),
|
|
49
|
-
_useState4 = _slicedToArray(_useState3, 2),
|
|
50
|
-
points = _useState4[0],
|
|
51
|
-
setPoints = _useState4[1];
|
|
52
|
-
var _useState5 = (0, _react.useState)(false),
|
|
53
|
-
_useState6 = _slicedToArray(_useState5, 2),
|
|
54
|
-
showResult = _useState6[0],
|
|
55
|
-
setShowResult = _useState6[1];
|
|
56
|
-
var _useState7 = (0, _react.useState)(false),
|
|
57
|
-
_useState8 = _slicedToArray(_useState7, 2),
|
|
58
|
-
showCustomCamera = _useState8[0],
|
|
59
|
-
setShowCustomCamera = _useState8[1];
|
|
60
|
-
var viewRef = (0, _react.useRef)(null);
|
|
61
|
-
var
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
var
|
|
68
|
-
var
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
var
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
var
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
var
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
var
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports["default"] = void 0;
|
|
8
|
+
var _ImageCropperStyles = _interopRequireDefault(require("./ImageCropperStyles"));
|
|
9
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
10
|
+
var _reactNative = require("react-native");
|
|
11
|
+
var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg"));
|
|
12
|
+
var _CustomCamera = _interopRequireDefault(require("./CustomCamera"));
|
|
13
|
+
var ImageManipulator = _interopRequireWildcard(require("expo-image-manipulator"));
|
|
14
|
+
var _vectorIcons = require("@expo/vector-icons");
|
|
15
|
+
var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
|
|
16
|
+
var _ImageMaskProcessor = require("./ImageMaskProcessor");
|
|
17
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t3 in e) "default" !== _t3 && {}.hasOwnProperty.call(e, _t3) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t3)) && (i.get || i.set) ? o(f, _t3, i) : f[_t3] = e[_t3]); return f; })(e, t); }
|
|
18
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
|
|
19
|
+
function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
|
|
20
|
+
function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
|
|
21
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
22
|
+
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
23
|
+
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
|
|
24
|
+
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
25
|
+
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
|
|
26
|
+
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
|
|
27
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
28
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
29
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
30
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
|
31
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
32
|
+
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
33
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
34
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
35
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
36
|
+
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
37
|
+
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
38
|
+
var ImageCropper = function ImageCropper(_ref) {
|
|
39
|
+
var _cameraFrameData$curr3;
|
|
40
|
+
var onConfirm = _ref.onConfirm,
|
|
41
|
+
openCameraFirst = _ref.openCameraFirst,
|
|
42
|
+
initialImage = _ref.initialImage,
|
|
43
|
+
addheight = _ref.addheight;
|
|
44
|
+
var _useState = (0, _react.useState)(null),
|
|
45
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
46
|
+
image = _useState2[0],
|
|
47
|
+
setImage = _useState2[1];
|
|
48
|
+
var _useState3 = (0, _react.useState)([]),
|
|
49
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
|
50
|
+
points = _useState4[0],
|
|
51
|
+
setPoints = _useState4[1];
|
|
52
|
+
var _useState5 = (0, _react.useState)(false),
|
|
53
|
+
_useState6 = _slicedToArray(_useState5, 2),
|
|
54
|
+
showResult = _useState6[0],
|
|
55
|
+
setShowResult = _useState6[1];
|
|
56
|
+
var _useState7 = (0, _react.useState)(false),
|
|
57
|
+
_useState8 = _slicedToArray(_useState7, 2),
|
|
58
|
+
showCustomCamera = _useState8[0],
|
|
59
|
+
setShowCustomCamera = _useState8[1];
|
|
60
|
+
var viewRef = (0, _react.useRef)(null);
|
|
61
|
+
var maskViewRef = (0, _react.useRef)(null); // Ref pour la vue de masque (invisible)
|
|
62
|
+
var sourceImageUri = (0, _react.useRef)(null); // keep original image URI (full-res) for upload
|
|
63
|
+
var cameraFrameData = (0, _react.useRef)(null); // ✅ Store green frame coordinates from camera
|
|
64
|
+
|
|
65
|
+
// ✅ RÉFÉRENTIEL UNIQUE : Wrapper commun 9/16 (identique à CustomCamera)
|
|
66
|
+
// Ce wrapper est utilisé dans CustomCamera ET ImageCropper pour garantir pixel-perfect matching
|
|
67
|
+
var commonWrapperRef = (0, _react.useRef)(null);
|
|
68
|
+
var commonWrapperLayout = (0, _react.useRef)({
|
|
69
|
+
x: 0,
|
|
70
|
+
y: 0,
|
|
71
|
+
width: 0,
|
|
72
|
+
height: 0
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// ✅ REFACTORISATION : Séparation claire entre dimensions originales et affichage
|
|
76
|
+
// Dimensions réelles de l'image originale (pixels)
|
|
77
|
+
var originalImageDimensions = (0, _react.useRef)({
|
|
78
|
+
width: 0,
|
|
79
|
+
height: 0
|
|
80
|
+
});
|
|
81
|
+
// Dimensions et position d'affichage à l'écran (pour le calcul des points de crop)
|
|
82
|
+
var displayedImageLayout = (0, _react.useRef)({
|
|
83
|
+
x: 0,
|
|
84
|
+
y: 0,
|
|
85
|
+
width: 0,
|
|
86
|
+
height: 0
|
|
87
|
+
});
|
|
88
|
+
// Conserver imageMeasure pour compatibilité avec le code existant (utilisé pour SVG overlay)
|
|
89
|
+
var imageMeasure = (0, _react.useRef)({
|
|
90
|
+
x: 0,
|
|
91
|
+
y: 0,
|
|
92
|
+
width: 0,
|
|
93
|
+
height: 0
|
|
94
|
+
});
|
|
95
|
+
// ✅ imageDisplayRect : Rectangle réel de l'image affichée (quand resizeMode='contain') à l'intérieur du wrapper commun
|
|
96
|
+
// C'est la zone où l'image est réellement visible (avec letterboxing si nécessaire)
|
|
97
|
+
// Les points de crop DOIVENT rester dans cette zone pour éviter de cropper hors de l'image
|
|
98
|
+
var imageDisplayRect = (0, _react.useRef)({
|
|
99
|
+
x: 0,
|
|
100
|
+
y: 0,
|
|
101
|
+
width: 0,
|
|
102
|
+
height: 0
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ✅ COMPATIBILITÉ : displayedContentRect reste pour le code existant, mais pointe vers imageDisplayRect
|
|
106
|
+
var displayedContentRect = imageDisplayRect;
|
|
107
|
+
|
|
108
|
+
// ✅ RÉFÉRENTIEL UNIQUE : Calculer imageDisplayRect à l'intérieur du wrapper commun
|
|
109
|
+
// - CAMERA: use "cover" mode → image fills wrapper, imageDisplayRect = full wrapper (same as preview)
|
|
110
|
+
// - GALLERY: use "contain" mode → imageDisplayRect = letterboxed area
|
|
111
|
+
var updateImageDisplayRect = function updateImageDisplayRect(wrapperWidth, wrapperHeight) {
|
|
112
|
+
var iw = originalImageDimensions.current.width;
|
|
113
|
+
var ih = originalImageDimensions.current.height;
|
|
114
|
+
|
|
115
|
+
// ✅ CAMERA IMAGE: Use full wrapper so green frame and white frame show same content
|
|
116
|
+
if (cameraFrameData.current && cameraFrameData.current.greenFrame && wrapperWidth > 0 && wrapperHeight > 0) {
|
|
117
|
+
imageDisplayRect.current = {
|
|
118
|
+
x: 0,
|
|
119
|
+
y: 0,
|
|
120
|
+
width: wrapperWidth,
|
|
121
|
+
height: wrapperHeight
|
|
122
|
+
};
|
|
123
|
+
console.log("✅ Image display rect (COVER mode for camera - full wrapper):", imageDisplayRect.current);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
console.log("🔄 updateImageDisplayRect called:", {
|
|
127
|
+
originalDimensions: {
|
|
128
|
+
width: iw,
|
|
129
|
+
height: ih
|
|
130
|
+
},
|
|
131
|
+
wrapperDimensions: {
|
|
132
|
+
width: wrapperWidth,
|
|
133
|
+
height: wrapperHeight
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
if (iw > 0 && ih > 0 && wrapperWidth > 0 && wrapperHeight > 0) {
|
|
137
|
+
// Calculer comment l'image s'affiche en "contain" dans le wrapper (gallery)
|
|
138
|
+
var scale = Math.min(wrapperWidth / iw, wrapperHeight / ih);
|
|
139
|
+
var imageDisplayWidth = iw * scale;
|
|
140
|
+
var imageDisplayHeight = ih * scale;
|
|
141
|
+
var offsetX = (wrapperWidth - imageDisplayWidth) / 2;
|
|
142
|
+
var offsetY = (wrapperHeight - imageDisplayHeight) / 2;
|
|
143
|
+
imageDisplayRect.current = {
|
|
144
|
+
x: offsetX,
|
|
145
|
+
y: offsetY,
|
|
146
|
+
width: imageDisplayWidth,
|
|
147
|
+
height: imageDisplayHeight
|
|
148
|
+
};
|
|
149
|
+
console.log("✅ Image display rect (contain in wrapper) calculated:", {
|
|
150
|
+
wrapper: {
|
|
151
|
+
width: wrapperWidth,
|
|
152
|
+
height: wrapperHeight
|
|
153
|
+
},
|
|
154
|
+
imageDisplayRect: imageDisplayRect.current,
|
|
155
|
+
scale: scale.toFixed(4)
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (wrapperWidth > 0 && wrapperHeight > 0) {
|
|
160
|
+
imageDisplayRect.current = {
|
|
161
|
+
x: 0,
|
|
162
|
+
y: 0,
|
|
163
|
+
width: wrapperWidth,
|
|
164
|
+
height: wrapperHeight
|
|
165
|
+
};
|
|
166
|
+
console.log("⚠️ Using wrapper dimensions as fallback (original dimensions not available yet):", imageDisplayRect.current);
|
|
167
|
+
} else {
|
|
168
|
+
imageDisplayRect.current = {
|
|
169
|
+
x: 0,
|
|
170
|
+
y: 0,
|
|
171
|
+
width: 0,
|
|
172
|
+
height: 0
|
|
173
|
+
};
|
|
174
|
+
console.warn("❌ Cannot calculate imageDisplayRect: missing dimensions");
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// ✅ COMPATIBILITÉ : Alias pour le code existant
|
|
179
|
+
var updateDisplayedContentRect = updateImageDisplayRect;
|
|
180
|
+
var selectedPointIndex = (0, _react.useRef)(null);
|
|
181
|
+
var lastTap = (0, _react.useRef)(null);
|
|
182
|
+
|
|
183
|
+
// ✅ CRITICAL: Guard to prevent double crop box initialization
|
|
184
|
+
// This ensures crop box is initialized only once, especially for camera images
|
|
185
|
+
var hasInitializedCropBox = (0, _react.useRef)(false);
|
|
186
|
+
var imageSource = (0, _react.useRef)(null); // 'camera' | 'gallery' | null
|
|
187
|
+
|
|
188
|
+
// ✅ FREE DRAG: Store initial touch position and point position for delta-based movement
|
|
189
|
+
var initialTouchPosition = (0, _react.useRef)(null); // { x, y } - initial touch position when drag starts
|
|
190
|
+
var initialPointPosition = (0, _react.useRef)(null); // { x, y } - initial point position when drag starts
|
|
191
|
+
var lastTouchPosition = (0, _react.useRef)(null); // { x, y } - last touch position for incremental delta calculation
|
|
192
|
+
|
|
193
|
+
// ✅ CRITICAL: dragBase stores the VISUAL position (can be overshoot) during drag
|
|
194
|
+
// This ensures smooth continuous drag without "dead zones" at boundaries
|
|
195
|
+
// dragBase is updated with applied position (overshoot) after each movement
|
|
196
|
+
// and is used as base for next delta calculation
|
|
197
|
+
var dragBase = (0, _react.useRef)(null); // { x, y } - visual position during drag (can be overshoot)
|
|
198
|
+
|
|
199
|
+
// ✅ NEW APPROACH: touchOffset stores the initial offset between point and touch
|
|
200
|
+
// This eliminates delta accumulation issues and "dead zones"
|
|
201
|
+
// Once set at drag start, it remains constant throughout the drag
|
|
202
|
+
var touchOffset = (0, _react.useRef)(null); // { x, y } - offset = pointPosition - touchPosition
|
|
203
|
+
|
|
204
|
+
// ✅ Track if point was clamped in previous frame (to detect transition)
|
|
205
|
+
var wasClampedLastFrame = (0, _react.useRef)({
|
|
206
|
+
x: false,
|
|
207
|
+
y: false
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Angle de rotation accumulé (pour éviter les rotations multiples)
|
|
211
|
+
var rotationAngle = (0, _react.useRef)(0);
|
|
212
|
+
|
|
213
|
+
// États pour la vue de masque temporaire
|
|
214
|
+
var _useState9 = (0, _react.useState)(null),
|
|
215
|
+
_useState0 = _slicedToArray(_useState9, 2),
|
|
216
|
+
maskImageUri = _useState0[0],
|
|
217
|
+
setMaskImageUri = _useState0[1];
|
|
218
|
+
var _useState1 = (0, _react.useState)([]),
|
|
219
|
+
_useState10 = _slicedToArray(_useState1, 2),
|
|
220
|
+
maskPoints = _useState10[0],
|
|
221
|
+
setMaskPoints = _useState10[1];
|
|
222
|
+
var _useState11 = (0, _react.useState)({
|
|
223
|
+
width: 0,
|
|
224
|
+
height: 0
|
|
225
|
+
}),
|
|
226
|
+
_useState12 = _slicedToArray(_useState11, 2),
|
|
227
|
+
maskDimensions = _useState12[0],
|
|
228
|
+
setMaskDimensions = _useState12[1];
|
|
229
|
+
var _useState13 = (0, _react.useState)(false),
|
|
230
|
+
_useState14 = _slicedToArray(_useState13, 2),
|
|
231
|
+
showMaskView = _useState14[0],
|
|
232
|
+
setShowMaskView = _useState14[1];
|
|
233
|
+
var _useState15 = (0, _react.useState)(false),
|
|
234
|
+
_useState16 = _slicedToArray(_useState15, 2),
|
|
235
|
+
isLoading = _useState16[0],
|
|
236
|
+
setIsLoading = _useState16[1];
|
|
237
|
+
var _useState17 = (0, _react.useState)(false),
|
|
238
|
+
_useState18 = _slicedToArray(_useState17, 2),
|
|
239
|
+
showFullScreenCapture = _useState18[0],
|
|
240
|
+
setShowFullScreenCapture = _useState18[1];
|
|
241
|
+
var _useState19 = (0, _react.useState)(false),
|
|
242
|
+
_useState20 = _slicedToArray(_useState19, 2),
|
|
243
|
+
isRotating = _useState20[0],
|
|
244
|
+
setIsRotating = _useState20[1];
|
|
245
|
+
var lastValidPosition = (0, _react.useRef)(null);
|
|
246
|
+
var insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
|
|
247
|
+
|
|
248
|
+
// ✅ NEW ARCH: mobile does NOT export the final crop.
|
|
249
|
+
|
|
250
|
+
// No view-shot / captureRef / bitmap masking on device.
|
|
251
|
+
var enableMask = false;
|
|
252
|
+
var enableRotation = false; // rotation would require careful coord transforms; keep off for pixel-perfect pipeline.
|
|
253
|
+
|
|
254
|
+
(0, _react.useEffect)(function () {
|
|
255
|
+
if (openCameraFirst) {
|
|
256
|
+
setShowCustomCamera(true);
|
|
257
|
+
} else if (initialImage) {
|
|
258
|
+
setImage(initialImage);
|
|
259
|
+
sourceImageUri.current = initialImage;
|
|
260
|
+
// ✅ CRITICAL: Reset points when loading a new image from gallery
|
|
261
|
+
// This ensures the crop box will be automatically initialized
|
|
262
|
+
setPoints([]);
|
|
263
|
+
rotationAngle.current = 0;
|
|
264
|
+
// Clear camera frame data for gallery images
|
|
265
|
+
cameraFrameData.current = null;
|
|
266
|
+
// ✅ CRITICAL: Reset initialization guard for new image
|
|
267
|
+
hasInitializedCropBox.current = false;
|
|
268
|
+
imageSource.current = null;
|
|
269
|
+
}
|
|
270
|
+
}, [openCameraFirst, initialImage]);
|
|
271
|
+
|
|
272
|
+
// ✅ REFACTORISATION : Stocker uniquement les dimensions originales (pas de calcul théorique)
|
|
273
|
+
// ✅ CRITICAL FIX: Single source of truth for image dimensions
|
|
274
|
+
|
|
275
|
+
(0, _react.useEffect)(function () {
|
|
276
|
+
if (!image) {
|
|
277
|
+
originalImageDimensions.current = {
|
|
278
|
+
width: 0,
|
|
279
|
+
height: 0
|
|
280
|
+
};
|
|
281
|
+
hasInitializedCropBox.current = false;
|
|
282
|
+
imageSource.current = null;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (!sourceImageUri.current) {
|
|
286
|
+
sourceImageUri.current = image;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ✅ CRITICAL FIX #1: If we have capturedImageSize from camera, use it as SINGLE SOURCE OF TRUTH
|
|
290
|
+
// takePictureAsync returns physical dimensions, while Image.getSize() may return EXIF-oriented dimensions
|
|
291
|
+
// DO NOT call Image.getSize() for camera images - it can return swapped dimensions on Android
|
|
292
|
+
if (cameraFrameData.current && cameraFrameData.current.capturedImageSize) {
|
|
293
|
+
var _cameraFrameData$curr = cameraFrameData.current.capturedImageSize,
|
|
294
|
+
capturedWidth = _cameraFrameData$curr.width,
|
|
295
|
+
capturedHeight = _cameraFrameData$curr.height;
|
|
296
|
+
originalImageDimensions.current = {
|
|
297
|
+
width: capturedWidth,
|
|
298
|
+
height: capturedHeight
|
|
299
|
+
};
|
|
300
|
+
imageSource.current = 'camera';
|
|
301
|
+
hasInitializedCropBox.current = false; // Reset guard for new camera image
|
|
302
|
+
|
|
303
|
+
console.log("✅ Using captured image dimensions from takePictureAsync (SINGLE SOURCE OF TRUTH):", {
|
|
304
|
+
width: capturedWidth,
|
|
305
|
+
height: capturedHeight,
|
|
306
|
+
source: 'takePictureAsync',
|
|
307
|
+
note: 'Image.getSize() will NOT be called for camera images'
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// ✅ CRITICAL: Recalculate imageDisplayRect immediately with camera dimensions
|
|
311
|
+
var wrapper = commonWrapperLayout.current;
|
|
312
|
+
if (wrapper.width > 0 && wrapper.height > 0) {
|
|
313
|
+
updateImageDisplayRect(wrapper.width, wrapper.height);
|
|
314
|
+
|
|
315
|
+
// ✅ CRITICAL FIX #2: Initialize crop box immediately when cameraFrameData is available
|
|
316
|
+
// This ensures camera images are initialized from greenFrame BEFORE any other initialization
|
|
317
|
+
if (cameraFrameData.current && cameraFrameData.current.greenFrame && !hasInitializedCropBox.current) {
|
|
318
|
+
console.log("✅ Initializing crop box from cameraFrameData (immediate in useEffect):", {
|
|
319
|
+
hasGreenFrame: !!cameraFrameData.current.greenFrame,
|
|
320
|
+
wrapper: wrapper,
|
|
321
|
+
originalDimensions: originalImageDimensions.current
|
|
322
|
+
});
|
|
323
|
+
initializeCropBox();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return; // ✅ CRITICAL: Exit early - DO NOT call Image.getSize()
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ✅ FALLBACK: Use Image.getSize() ONLY for gallery images (no cameraFrameData)
|
|
330
|
+
// BUT: Check again right before calling to avoid race condition
|
|
331
|
+
if (cameraFrameData.current && cameraFrameData.current.capturedImageSize) {
|
|
332
|
+
console.log("⚠️ cameraFrameData exists, skipping Image.getSize() call");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ✅ CRITICAL: Also check imageSource - if it's 'camera', don't call Image.getSize()
|
|
337
|
+
if (imageSource.current === 'camera') {
|
|
338
|
+
console.log("⚠️ imageSource is 'camera', skipping Image.getSize() call");
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ✅ CRITICAL: Set imageSource to 'gallery' ONLY if we're sure it's not a camera image
|
|
343
|
+
// Don't set it yet - we'll set it in the callback after verifying
|
|
344
|
+
hasInitializedCropBox.current = false; // Reset guard for new image
|
|
345
|
+
|
|
346
|
+
_reactNative.Image.getSize(image, function (imgWidth, imgHeight) {
|
|
347
|
+
// ✅ CRITICAL SAFETY #1: Check if cameraFrameData appeared while Image.getSize() was resolving
|
|
348
|
+
// This is the PRIMARY check - cameraFrameData takes precedence
|
|
349
|
+
if (cameraFrameData.current && cameraFrameData.current.capturedImageSize) {
|
|
350
|
+
console.warn("⚠️ Image.getSize() resolved but cameraFrameData exists - IGNORING Image.getSize() result to prevent dimension swap");
|
|
351
|
+
console.warn("⚠️ Camera dimensions (correct):", cameraFrameData.current.capturedImageSize);
|
|
352
|
+
console.warn("⚠️ Image.getSize() dimensions (potentially swapped):", {
|
|
353
|
+
width: imgWidth,
|
|
354
|
+
height: imgHeight
|
|
355
|
+
});
|
|
356
|
+
return; // ✅ CRITICAL: Exit early - do NOT update dimensions or initialize crop box
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ✅ CRITICAL SAFETY #2: Check imageSource (should be 'camera' if cameraFrameData was set)
|
|
360
|
+
if (imageSource.current === 'camera') {
|
|
361
|
+
console.warn("⚠️ Image.getSize() resolved but imageSource is 'camera' - IGNORING Image.getSize() result");
|
|
362
|
+
console.warn("⚠️ Image.getSize() dimensions (potentially swapped):", {
|
|
363
|
+
width: imgWidth,
|
|
364
|
+
height: imgHeight
|
|
365
|
+
});
|
|
366
|
+
return; // ✅ CRITICAL: Exit early - do NOT update dimensions or initialize crop box
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ✅ CRITICAL SAFETY #3: Check if crop box was already initialized (from camera)
|
|
370
|
+
if (hasInitializedCropBox.current) {
|
|
371
|
+
console.warn("⚠️ Image.getSize() resolved but crop box already initialized - IGNORING result to prevent double initialization");
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ✅ SAFE: This is a gallery image, proceed with Image.getSize() result
|
|
376
|
+
imageSource.current = 'gallery';
|
|
377
|
+
originalImageDimensions.current = {
|
|
378
|
+
width: imgWidth,
|
|
379
|
+
height: imgHeight
|
|
380
|
+
};
|
|
381
|
+
console.log("✅ Image dimensions from Image.getSize() (gallery image):", {
|
|
382
|
+
width: imgWidth,
|
|
383
|
+
height: imgHeight,
|
|
384
|
+
platform: _reactNative.Platform.OS,
|
|
385
|
+
pixelRatio: _reactNative.PixelRatio.get(),
|
|
386
|
+
uri: image,
|
|
387
|
+
source: 'Image.getSize()'
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// ✅ RÉFÉRENTIEL UNIQUE : Recalculer imageDisplayRect dans le wrapper commun
|
|
391
|
+
// dès qu'on connaît la taille originale de l'image
|
|
392
|
+
var wrapper = commonWrapperLayout.current;
|
|
393
|
+
if (wrapper.width > 0 && wrapper.height > 0) {
|
|
394
|
+
updateImageDisplayRect(wrapper.width, wrapper.height);
|
|
395
|
+
// ✅ IMPORTANT: pour les images de la galerie (pas de cameraFrameData),
|
|
396
|
+
// initialiser automatiquement le cadre blanc (70% du wrapper) une fois que
|
|
397
|
+
// nous connaissons à la fois le wrapper et les dimensions originales.
|
|
398
|
+
// ✅ CRITICAL: Guard against double initialization
|
|
399
|
+
if (!hasInitializedCropBox.current && points.length === 0 && imageSource.current === 'gallery') {
|
|
400
|
+
initializeCropBox();
|
|
401
|
+
hasInitializedCropBox.current = true;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}, function (error) {
|
|
405
|
+
console.error("Error getting image size:", error);
|
|
406
|
+
});
|
|
407
|
+
}, [image]);
|
|
408
|
+
|
|
409
|
+
// Le cadre blanc doit être calculé sur le MÊME wrapper que le cadre vert (9/16)
|
|
410
|
+
// Ensuite, on restreint les points pour qu'ils restent dans imageDisplayRect (image visible)
|
|
411
|
+
var initializeCropBox = function initializeCropBox() {
|
|
412
|
+
// ✅ CRITICAL FIX #2: Guard against double initialization
|
|
413
|
+
if (hasInitializedCropBox.current) {
|
|
414
|
+
console.log("⚠️ Crop box already initialized, skipping duplicate initialization");
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ✅ CRITICAL: Ensure common wrapper layout is available
|
|
419
|
+
var wrapper = commonWrapperLayout.current;
|
|
420
|
+
if (wrapper.width === 0 || wrapper.height === 0) {
|
|
421
|
+
console.warn("Cannot initialize crop box: common wrapper layout not ready");
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ✅ CRITICAL: Ensure imageDisplayRect is available (zone réelle de l'image dans le wrapper)
|
|
426
|
+
var imageRect = imageDisplayRect.current;
|
|
427
|
+
if (imageRect.width === 0 || imageRect.height === 0) {
|
|
428
|
+
// Recalculer si nécessaire
|
|
429
|
+
if (originalImageDimensions.current.width > 0 && originalImageDimensions.current.height > 0) {
|
|
430
|
+
updateImageDisplayRect(wrapper.width, wrapper.height);
|
|
431
|
+
imageRect = imageDisplayRect.current;
|
|
432
|
+
} else {
|
|
433
|
+
console.warn("Cannot initialize crop box: imageDisplayRect not available (original dimensions missing)");
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ✅ CRITICAL FIX: Calculate crop box as percentage of VISIBLE IMAGE AREA (imageDisplayRect)
|
|
439
|
+
// NOT the wrapper. This ensures the crop box is truly 80% of the image, not 80% of wrapper then clamped.
|
|
440
|
+
// Calculate absolute position of imageDisplayRect within wrapper
|
|
441
|
+
var imageRectX = wrapper.x + imageRect.x;
|
|
442
|
+
var imageRectY = wrapper.y + imageRect.y;
|
|
443
|
+
|
|
444
|
+
// ✅ PRIORITY RULE #3: IF image comes from camera → Use EXACT green frame coordinates
|
|
445
|
+
// Image is displayed in "cover" mode (full wrapper), so green frame coords = white frame coords
|
|
446
|
+
if (cameraFrameData.current && cameraFrameData.current.greenFrame && originalImageDimensions.current.width > 0) {
|
|
447
|
+
var greenFrame = cameraFrameData.current.greenFrame;
|
|
448
|
+
|
|
449
|
+
// ✅ Image fills wrapper (cover mode), so use green frame position directly in wrapper space
|
|
450
|
+
var _boxX = wrapper.x + greenFrame.x;
|
|
451
|
+
var _boxY = wrapper.y + greenFrame.y;
|
|
452
|
+
var _boxWidth = greenFrame.width;
|
|
453
|
+
var _boxHeight = greenFrame.height;
|
|
454
|
+
|
|
455
|
+
// ✅ SAFETY: Validate calculated coordinates before creating points
|
|
456
|
+
var _isValidCoordinate = function _isValidCoordinate(val) {
|
|
457
|
+
return typeof val === 'number' && isFinite(val) && !isNaN(val);
|
|
458
|
+
};
|
|
459
|
+
if (!_isValidCoordinate(_boxX) || !_isValidCoordinate(_boxY) || !_isValidCoordinate(_boxWidth) || !_isValidCoordinate(_boxHeight)) {
|
|
460
|
+
console.warn("⚠️ Invalid coordinates calculated for crop box, skipping initialization");
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ✅ Create points using EXACT greenFrame coordinates (mapped to ImageCropper wrapper)
|
|
465
|
+
var _newPoints = [{
|
|
466
|
+
x: _boxX,
|
|
467
|
+
y: _boxY
|
|
468
|
+
},
|
|
469
|
+
// Top-left
|
|
470
|
+
{
|
|
471
|
+
x: _boxX + _boxWidth,
|
|
472
|
+
y: _boxY
|
|
473
|
+
},
|
|
474
|
+
// Top-right
|
|
475
|
+
{
|
|
476
|
+
x: _boxX + _boxWidth,
|
|
477
|
+
y: _boxY + _boxHeight
|
|
478
|
+
},
|
|
479
|
+
// Bottom-right
|
|
480
|
+
{
|
|
481
|
+
x: _boxX,
|
|
482
|
+
y: _boxY + _boxHeight
|
|
483
|
+
} // Bottom-left
|
|
484
|
+
];
|
|
485
|
+
|
|
486
|
+
// ✅ SAFETY: Validate all points before setting
|
|
487
|
+
var _validPoints = _newPoints.filter(function (p) {
|
|
488
|
+
return _isValidCoordinate(p.x) && _isValidCoordinate(p.y);
|
|
489
|
+
});
|
|
490
|
+
if (_validPoints.length !== _newPoints.length) {
|
|
491
|
+
console.warn("⚠️ Some points have invalid coordinates, skipping initialization");
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
console.log("✅ Initializing crop box for camera image (COVER MODE - exact green frame):", {
|
|
495
|
+
greenFrame: {
|
|
496
|
+
x: greenFrame.x,
|
|
497
|
+
y: greenFrame.y,
|
|
498
|
+
width: greenFrame.width,
|
|
499
|
+
height: greenFrame.height
|
|
500
|
+
},
|
|
501
|
+
whiteFrame: {
|
|
502
|
+
x: _boxX.toFixed(2),
|
|
503
|
+
y: _boxY.toFixed(2),
|
|
504
|
+
width: _boxWidth.toFixed(2),
|
|
505
|
+
height: _boxHeight.toFixed(2)
|
|
506
|
+
},
|
|
507
|
+
note: "Image in cover mode - white frame = same position/size as green frame, same content"
|
|
508
|
+
});
|
|
509
|
+
setPoints(_newPoints);
|
|
510
|
+
hasInitializedCropBox.current = true; // ✅ CRITICAL: Mark as initialized
|
|
511
|
+
// ✅ CRITICAL: DO NOT nullify cameraFrameData here - keep it for Image.getSize() callback check
|
|
512
|
+
// It will be cleared when loading a new image
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// ✅ PRIORITY RULE #3: DEFAULT logic ONLY for gallery images (NOT camera)
|
|
517
|
+
// If we reach here and imageSource is 'camera', something went wrong
|
|
518
|
+
if (imageSource.current === 'camera') {
|
|
519
|
+
console.warn("⚠️ Camera image but no greenFrame found - this should not happen");
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// ✅ DEFAULT: Crop box (70% of VISIBLE IMAGE AREA - centered) - ONLY for gallery images
|
|
524
|
+
var boxWidth = imageRect.width * 0.70; // 70% of visible image width
|
|
525
|
+
var boxHeight = imageRect.height * 0.70; // 70% of visible image height
|
|
526
|
+
var boxX = imageRectX + (imageRect.width - boxWidth) / 2; // Centered in image area
|
|
527
|
+
var boxY = imageRectY + (imageRect.height - boxHeight) / 2; // Centered in image area
|
|
528
|
+
|
|
529
|
+
// ✅ SAFETY: Validate calculated coordinates before creating points
|
|
530
|
+
var isValidCoordinate = function isValidCoordinate(val) {
|
|
531
|
+
return typeof val === 'number' && isFinite(val) && !isNaN(val);
|
|
532
|
+
};
|
|
533
|
+
if (!isValidCoordinate(boxX) || !isValidCoordinate(boxY) || !isValidCoordinate(boxWidth) || !isValidCoordinate(boxHeight)) {
|
|
534
|
+
console.warn("⚠️ Invalid coordinates calculated for default crop box, skipping initialization");
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
var newPoints = [{
|
|
538
|
+
x: boxX,
|
|
539
|
+
y: boxY
|
|
540
|
+
}, {
|
|
541
|
+
x: boxX + boxWidth,
|
|
542
|
+
y: boxY
|
|
543
|
+
}, {
|
|
544
|
+
x: boxX + boxWidth,
|
|
545
|
+
y: boxY + boxHeight
|
|
546
|
+
}, {
|
|
547
|
+
x: boxX,
|
|
548
|
+
y: boxY + boxHeight
|
|
549
|
+
}];
|
|
550
|
+
|
|
551
|
+
// ✅ SAFETY: Validate all points before setting
|
|
552
|
+
var validPoints = newPoints.filter(function (p) {
|
|
553
|
+
return isValidCoordinate(p.x) && isValidCoordinate(p.y);
|
|
554
|
+
});
|
|
555
|
+
if (validPoints.length !== newPoints.length) {
|
|
556
|
+
console.warn("⚠️ Some points have invalid coordinates in default crop box, skipping initialization");
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
console.log("✅ Initializing crop box (default - 70% of visible image area, gallery only):", {
|
|
560
|
+
wrapper: {
|
|
561
|
+
width: wrapper.width,
|
|
562
|
+
height: wrapper.height
|
|
563
|
+
},
|
|
564
|
+
imageDisplayRect: imageRect,
|
|
565
|
+
boxInImage: {
|
|
566
|
+
x: boxX,
|
|
567
|
+
y: boxY,
|
|
568
|
+
width: boxWidth,
|
|
569
|
+
height: boxHeight
|
|
570
|
+
},
|
|
571
|
+
points: newPoints
|
|
572
|
+
});
|
|
573
|
+
setPoints(newPoints);
|
|
574
|
+
hasInitializedCropBox.current = true; // ✅ CRITICAL: Mark as initialized
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// ✅ RÉFÉRENTIEL UNIQUE : Callback pour mettre à jour le layout du wrapper commun
|
|
578
|
+
// Ce wrapper a exactement les mêmes dimensions que le wrapper de CustomCamera (9/16, width = screenWidth)
|
|
579
|
+
var onCommonWrapperLayout = function onCommonWrapperLayout(e) {
|
|
580
|
+
var layout = e.nativeEvent.layout;
|
|
581
|
+
commonWrapperLayout.current = {
|
|
582
|
+
x: layout.x,
|
|
583
|
+
y: layout.y,
|
|
584
|
+
width: layout.width,
|
|
585
|
+
height: layout.height
|
|
586
|
+
};
|
|
587
|
+
console.log("✅ Common wrapper layout updated:", commonWrapperLayout.current);
|
|
588
|
+
|
|
589
|
+
// ✅ Recalculer imageDisplayRect dès que le wrapper est prêt
|
|
590
|
+
if (originalImageDimensions.current.width > 0 && originalImageDimensions.current.height > 0) {
|
|
591
|
+
updateImageDisplayRect(layout.width, layout.height);
|
|
592
|
+
|
|
593
|
+
// ✅ CRITICAL FIX #2: Initialize crop box ONLY if not already initialized
|
|
594
|
+
// For camera images: initialize ONLY from greenFrame (already done when cameraFrameData was set)
|
|
595
|
+
if (!hasInitializedCropBox.current && points.length === 0) {
|
|
596
|
+
// ✅ CRITICAL: Only initialize for gallery images here
|
|
597
|
+
// Camera images should be initialized when cameraFrameData is set, not here
|
|
598
|
+
if (imageSource.current !== 'camera') {
|
|
599
|
+
initializeCropBox();
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
// ✅ REFACTORISATION : Mettre à jour les dimensions d'affichage et les dimensions pour SVG
|
|
606
|
+
var onImageLayout = function onImageLayout(e) {
|
|
607
|
+
var layout = e.nativeEvent.layout;
|
|
608
|
+
|
|
609
|
+
// Stocker les dimensions d'affichage réelles (pour conversion de coordonnées)
|
|
610
|
+
displayedImageLayout.current = {
|
|
611
|
+
x: layout.x,
|
|
612
|
+
y: layout.y,
|
|
613
|
+
width: layout.width,
|
|
614
|
+
height: layout.height
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
// Conserver aussi dans imageMeasure pour compatibilité avec SVG overlay
|
|
618
|
+
imageMeasure.current = {
|
|
619
|
+
x: layout.x,
|
|
620
|
+
y: layout.y,
|
|
621
|
+
width: layout.width,
|
|
622
|
+
height: layout.height
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
// ✅ Si l'image vient de la caméra et que les dimensions originales ne sont pas encore définies,
|
|
626
|
+
// les initialiser à partir de cameraFrameData AVANT de calculer le contentRect.
|
|
627
|
+
if (originalImageDimensions.current.width === 0 && cameraFrameData.current && cameraFrameData.current.capturedImageSize) {
|
|
628
|
+
var _cameraFrameData$curr2 = cameraFrameData.current.capturedImageSize,
|
|
629
|
+
width = _cameraFrameData$curr2.width,
|
|
630
|
+
height = _cameraFrameData$curr2.height;
|
|
631
|
+
originalImageDimensions.current = {
|
|
632
|
+
width: width,
|
|
633
|
+
height: height
|
|
634
|
+
};
|
|
635
|
+
console.log("✅ originalImageDimensions initialisées depuis cameraFrameData dans onImageLayout:", {
|
|
636
|
+
width: width,
|
|
637
|
+
height: height
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// ✅ RÉFÉRENTIEL UNIQUE : Recalculer imageDisplayRect dans le wrapper commun
|
|
642
|
+
// Si le wrapper commun n'est pas encore prêt, on attendra onCommonWrapperLayout
|
|
643
|
+
var wrapper = commonWrapperLayout.current;
|
|
644
|
+
if (wrapper.width > 0 && wrapper.height > 0) {
|
|
645
|
+
updateImageDisplayRect(wrapper.width, wrapper.height);
|
|
646
|
+
}
|
|
647
|
+
console.log("Displayed image layout updated:", {
|
|
648
|
+
width: layout.width,
|
|
649
|
+
height: layout.height,
|
|
650
|
+
x: layout.x,
|
|
651
|
+
y: layout.y
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
// ✅ CRITICAL FIX #2: Do NOT initialize crop box in onImageLayout for camera images
|
|
655
|
+
// Camera images should be initialized ONLY when cameraFrameData is set (in useEffect)
|
|
656
|
+
// Gallery images can be initialized here if not already done
|
|
657
|
+
if (wrapper.width > 0 && wrapper.height > 0 && layout.width > 0 && layout.height > 0 && !hasInitializedCropBox.current && points.length === 0 && originalImageDimensions.current.width > 0 && originalImageDimensions.current.height > 0) {
|
|
658
|
+
// ✅ CRITICAL: Only initialize for gallery images here
|
|
659
|
+
// Camera images should be initialized when cameraFrameData is set, not here
|
|
660
|
+
if (imageSource.current !== 'camera') {
|
|
661
|
+
initializeCropBox();
|
|
662
|
+
} else if (cameraFrameData.current && cameraFrameData.current.greenFrame) {
|
|
663
|
+
// ✅ For camera images, initialize ONLY if greenFrame is available
|
|
664
|
+
initializeCropBox();
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
var createPath = function createPath() {
|
|
669
|
+
if (points.length < 1) return '';
|
|
670
|
+
var path = "M ".concat(points[0].x, " ").concat(points[0].y, " ");
|
|
671
|
+
points.forEach(function (point) {
|
|
672
|
+
return path += "L ".concat(point.x, " ").concat(point.y, " ");
|
|
673
|
+
});
|
|
674
|
+
return path + 'Z';
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
// ✅ Helper function: Find closest point on a line segment to a tap point
|
|
678
|
+
var findClosestPointOnLine = function findClosestPointOnLine(tapX, tapY, lineStartX, lineStartY, lineEndX, lineEndY) {
|
|
679
|
+
var dx = lineEndX - lineStartX;
|
|
680
|
+
var dy = lineEndY - lineStartY;
|
|
681
|
+
var lengthSquared = dx * dx + dy * dy;
|
|
682
|
+
if (lengthSquared === 0) {
|
|
683
|
+
// Line segment is a point
|
|
684
|
+
return {
|
|
685
|
+
x: lineStartX,
|
|
686
|
+
y: lineStartY,
|
|
687
|
+
distance: Math.sqrt(Math.pow(tapX - lineStartX, 2) + Math.pow(tapY - lineStartY, 2))
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Calculate projection parameter t (0 to 1)
|
|
692
|
+
var t = Math.max(0, Math.min(1, ((tapX - lineStartX) * dx + (tapY - lineStartY) * dy) / lengthSquared));
|
|
693
|
+
|
|
694
|
+
// Calculate closest point on line segment
|
|
695
|
+
var closestX = lineStartX + t * dx;
|
|
696
|
+
var closestY = lineStartY + t * dy;
|
|
697
|
+
|
|
698
|
+
// Calculate distance from tap to closest point
|
|
699
|
+
var distance = Math.sqrt(Math.pow(tapX - closestX, 2) + Math.pow(tapY - closestY, 2));
|
|
700
|
+
return {
|
|
701
|
+
x: closestX,
|
|
702
|
+
y: closestY,
|
|
703
|
+
distance: distance,
|
|
704
|
+
t: t
|
|
705
|
+
};
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// ✅ Helper function: Check if tap is near any line segment and find closest point
|
|
709
|
+
var findClosestPointOnFrame = function findClosestPointOnFrame(tapX, tapY) {
|
|
710
|
+
var lineTolerance = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 30;
|
|
711
|
+
if (points.length < 2) return null;
|
|
712
|
+
var closestPoint = null;
|
|
713
|
+
var minDistance = Infinity;
|
|
714
|
+
var insertIndex = -1;
|
|
715
|
+
|
|
716
|
+
// Check each line segment (closed polygon: last point connects to first)
|
|
717
|
+
for (var i = 0; i < points.length; i++) {
|
|
718
|
+
var start = points[i];
|
|
719
|
+
var end = points[(i + 1) % points.length];
|
|
720
|
+
var result = findClosestPointOnLine(tapX, tapY, start.x, start.y, end.x, end.y);
|
|
721
|
+
if (result.distance < minDistance && result.distance < lineTolerance) {
|
|
722
|
+
minDistance = result.distance;
|
|
723
|
+
closestPoint = {
|
|
724
|
+
x: result.x,
|
|
725
|
+
y: result.y
|
|
726
|
+
};
|
|
727
|
+
// Insert after the start point of this segment
|
|
728
|
+
insertIndex = i + 1;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return closestPoint ? {
|
|
732
|
+
point: closestPoint,
|
|
733
|
+
insertIndex: insertIndex
|
|
734
|
+
} : null;
|
|
735
|
+
};
|
|
736
|
+
var handleTap = function handleTap(e) {
|
|
737
|
+
if (!image || showResult) return;
|
|
738
|
+
var now = Date.now();
|
|
739
|
+
var _e$nativeEvent = e.nativeEvent,
|
|
740
|
+
tapX = _e$nativeEvent.locationX,
|
|
741
|
+
tapY = _e$nativeEvent.locationY;
|
|
742
|
+
|
|
743
|
+
// ✅ RÉFÉRENTIEL UNIQUE : Utiliser imageDisplayRect (zone réelle de l'image dans le wrapper)
|
|
744
|
+
// Les coordonnées du tap sont relatives au wrapper commun
|
|
745
|
+
var imageRect = imageDisplayRect.current;
|
|
746
|
+
var wrapper = commonWrapperLayout.current;
|
|
747
|
+
|
|
748
|
+
// Recalculate if not available
|
|
749
|
+
if (imageRect.width === 0 || imageRect.height === 0) {
|
|
750
|
+
if (wrapper.width > 0 && wrapper.height > 0 && originalImageDimensions.current.width > 0) {
|
|
751
|
+
updateImageDisplayRect(wrapper.width, wrapper.height);
|
|
752
|
+
imageRect = imageDisplayRect.current;
|
|
753
|
+
}
|
|
754
|
+
// If still not available, use wrapper as fallback
|
|
755
|
+
if (imageRect.width === 0 || imageRect.height === 0) {
|
|
756
|
+
if (wrapper.width > 0 && wrapper.height > 0) {
|
|
757
|
+
imageRect = {
|
|
758
|
+
x: wrapper.x,
|
|
759
|
+
y: wrapper.y,
|
|
760
|
+
width: wrapper.width,
|
|
761
|
+
height: wrapper.height
|
|
762
|
+
};
|
|
763
|
+
} else {
|
|
764
|
+
console.warn("⚠️ Cannot handle tap: wrapper or imageDisplayRect not available");
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// ✅ Clamp to real displayed image content (imageDisplayRect dans le wrapper)
|
|
771
|
+
// Les coordonnées tapX/tapY sont relatives au wrapper commun
|
|
772
|
+
var imageRectX = wrapper.x + imageRect.x;
|
|
773
|
+
var imageRectY = wrapper.y + imageRect.y;
|
|
774
|
+
var _x$y$width$height = {
|
|
775
|
+
x: imageRectX,
|
|
776
|
+
y: imageRectY,
|
|
777
|
+
width: imageRect.width,
|
|
778
|
+
height: imageRect.height
|
|
779
|
+
},
|
|
780
|
+
cx = _x$y$width$height.x,
|
|
781
|
+
cy = _x$y$width$height.y,
|
|
782
|
+
cw = _x$y$width$height.width,
|
|
783
|
+
ch = _x$y$width$height.height;
|
|
784
|
+
// ✅ Larger select radius for easier point selection (especially on touch screens)
|
|
785
|
+
var selectRadius = 50; // Increased from 28 to 50 for better UX
|
|
786
|
+
|
|
787
|
+
// ✅ CRITICAL: Check for existing point selection FIRST (using raw tap coordinates)
|
|
788
|
+
// Don't clamp tapX/Y for point selection - points can be anywhere in wrapper now
|
|
789
|
+
var index = points.findIndex(function (p) {
|
|
790
|
+
return Math.abs(p.x - tapX) < selectRadius && Math.abs(p.y - tapY) < selectRadius;
|
|
791
|
+
});
|
|
792
|
+
if (index !== -1) {
|
|
793
|
+
// ✅ Point found - select it for dragging
|
|
794
|
+
selectedPointIndex.current = index;
|
|
795
|
+
|
|
796
|
+
// Store initial positions
|
|
797
|
+
initialTouchPosition.current = {
|
|
798
|
+
x: tapX,
|
|
799
|
+
y: tapY
|
|
800
|
+
};
|
|
801
|
+
lastTouchPosition.current = {
|
|
802
|
+
x: tapX,
|
|
803
|
+
y: tapY
|
|
804
|
+
};
|
|
805
|
+
initialPointPosition.current = _objectSpread({}, points[index]);
|
|
806
|
+
lastValidPosition.current = _objectSpread({}, points[index]);
|
|
807
|
+
|
|
808
|
+
// Calculate offset between point and touch at drag start
|
|
809
|
+
touchOffset.current = {
|
|
810
|
+
x: points[index].x - tapX,
|
|
811
|
+
y: points[index].y - tapY
|
|
812
|
+
};
|
|
813
|
+
console.log("🎯 DRAG START - Offset calculated:", {
|
|
814
|
+
pointX: points[index].x.toFixed(2),
|
|
815
|
+
pointY: points[index].y.toFixed(2),
|
|
816
|
+
touchX: tapX.toFixed(2),
|
|
817
|
+
touchY: tapY.toFixed(2),
|
|
818
|
+
offsetX: touchOffset.current.x.toFixed(2),
|
|
819
|
+
offsetY: touchOffset.current.y.toFixed(2)
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
// Disable parent ScrollView scrolling when dragging
|
|
823
|
+
try {
|
|
824
|
+
var _findScrollView = function findScrollView(node) {
|
|
825
|
+
if (!node) return null;
|
|
826
|
+
if (node._component && node._component.setNativeProps) {
|
|
827
|
+
node._component.setNativeProps({
|
|
828
|
+
scrollEnabled: false
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
return _findScrollView(node._owner || node._parent);
|
|
832
|
+
};
|
|
833
|
+
} catch (e) {
|
|
834
|
+
// Ignore errors
|
|
835
|
+
}
|
|
836
|
+
} else {
|
|
837
|
+
// ✅ No point found - check if double-tap on a line to create new point
|
|
838
|
+
var isDoubleTap = lastTap.current && now - lastTap.current < 300;
|
|
839
|
+
if (isDoubleTap && points.length >= 2) {
|
|
840
|
+
// Find closest point on frame lines
|
|
841
|
+
var lineResult = findClosestPointOnFrame(tapX, tapY, 30); // 30px tolerance
|
|
842
|
+
|
|
843
|
+
if (lineResult) {
|
|
844
|
+
var point = lineResult.point,
|
|
845
|
+
insertIndex = lineResult.insertIndex;
|
|
846
|
+
|
|
847
|
+
// Check if a point already exists very close to this position
|
|
848
|
+
var exists = points.some(function (p) {
|
|
849
|
+
return Math.abs(p.x - point.x) < selectRadius && Math.abs(p.y - point.y) < selectRadius;
|
|
850
|
+
});
|
|
851
|
+
if (!exists) {
|
|
852
|
+
// Insert new point at the correct position in the polygon
|
|
853
|
+
var newPoints = _toConsumableArray(points);
|
|
854
|
+
newPoints.splice(insertIndex, 0, point);
|
|
855
|
+
setPoints(newPoints);
|
|
856
|
+
console.log("✅ New point created on frame line:", {
|
|
857
|
+
tap: {
|
|
858
|
+
x: tapX.toFixed(2),
|
|
859
|
+
y: tapY.toFixed(2)
|
|
860
|
+
},
|
|
861
|
+
newPoint: {
|
|
862
|
+
x: point.x.toFixed(2),
|
|
863
|
+
y: point.y.toFixed(2)
|
|
864
|
+
},
|
|
865
|
+
insertIndex: insertIndex,
|
|
866
|
+
totalPoints: newPoints.length
|
|
867
|
+
});
|
|
868
|
+
lastTap.current = null; // Reset to prevent triple-tap
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
lastTap.current = now;
|
|
875
|
+
};
|
|
876
|
+
var handleMove = function handleMove(e) {
|
|
877
|
+
if (showResult || selectedPointIndex.current === null) return;
|
|
878
|
+
|
|
879
|
+
// ✅ FREE DRAG: Use delta-based movement for smooth, unconstrained dragging
|
|
880
|
+
|
|
881
|
+
var nativeEvent = e.nativeEvent;
|
|
882
|
+
var currentX = nativeEvent.locationX;
|
|
883
|
+
var currentY = nativeEvent.locationY;
|
|
884
|
+
|
|
885
|
+
// ✅ Validate coordinates
|
|
886
|
+
if (currentX === undefined || currentY === undefined || isNaN(currentX) || isNaN(currentY)) {
|
|
887
|
+
console.warn("⚠️ Cannot get touch coordinates", {
|
|
888
|
+
locationX: nativeEvent.locationX,
|
|
889
|
+
locationY: nativeEvent.locationY
|
|
890
|
+
});
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// This is more reliable when ScrollView affects coordinate updates
|
|
895
|
+
var deltaX, deltaY;
|
|
896
|
+
if (lastTouchPosition.current) {
|
|
897
|
+
// Calculate incremental delta from last touch position
|
|
898
|
+
deltaX = currentX - lastTouchPosition.current.x;
|
|
899
|
+
deltaY = currentY - lastTouchPosition.current.y;
|
|
900
|
+
} else if (initialTouchPosition.current) {
|
|
901
|
+
// Fallback to absolute delta if lastTouchPosition not set
|
|
902
|
+
deltaX = currentX - initialTouchPosition.current.x;
|
|
903
|
+
deltaY = currentY - initialTouchPosition.current.y;
|
|
904
|
+
} else {
|
|
905
|
+
console.warn("⚠️ No touch position reference available");
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// Les coordonnées de mouvement sont relatives au wrapper commun
|
|
910
|
+
var imageRect = imageDisplayRect.current;
|
|
911
|
+
var wrapper = commonWrapperLayout.current;
|
|
912
|
+
|
|
913
|
+
// Recalculate if not available
|
|
914
|
+
if (imageRect.width === 0 || imageRect.height === 0) {
|
|
915
|
+
if (wrapper.width > 0 && wrapper.height > 0 && originalImageDimensions.current.width > 0) {
|
|
916
|
+
updateImageDisplayRect(wrapper.width, wrapper.height);
|
|
917
|
+
imageRect = imageDisplayRect.current;
|
|
918
|
+
}
|
|
919
|
+
// If still not available, use wrapper as fallback
|
|
920
|
+
if (imageRect.width === 0 || imageRect.height === 0) {
|
|
921
|
+
if (wrapper.width > 0 && wrapper.height > 0) {
|
|
922
|
+
imageRect = {
|
|
923
|
+
x: wrapper.x,
|
|
924
|
+
y: wrapper.y,
|
|
925
|
+
width: wrapper.width,
|
|
926
|
+
height: wrapper.height
|
|
927
|
+
};
|
|
928
|
+
} else {
|
|
929
|
+
console.warn("⚠️ Cannot move point: wrapper or imageDisplayRect not available");
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// ✅ CRITICAL: Calculate absolute bounds of imageDisplayRect within the wrapper
|
|
936
|
+
// imageRect is relative to wrapper, so we need to add wrapper offset
|
|
937
|
+
var imageRectX = wrapper.x + imageRect.x;
|
|
938
|
+
var imageRectY = wrapper.y + imageRect.y;
|
|
939
|
+
var contentRect = {
|
|
940
|
+
x: imageRectX,
|
|
941
|
+
y: imageRectY,
|
|
942
|
+
width: imageRect.width,
|
|
943
|
+
height: imageRect.height
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
// ✅ FREE DRAG: Ensure initial positions are set
|
|
947
|
+
if (!initialPointPosition.current) {
|
|
948
|
+
var currentPoint = points[selectedPointIndex.current];
|
|
949
|
+
if (currentPoint && typeof currentPoint.x === 'number' && typeof currentPoint.y === 'number') {
|
|
950
|
+
initialPointPosition.current = _objectSpread({}, currentPoint);
|
|
951
|
+
} else {
|
|
952
|
+
console.warn("⚠️ No point found for selected index or invalid point data");
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// ✅ NEW APPROACH: Use touchOffset to map touch position directly to point position
|
|
958
|
+
// This eliminates delta accumulation and "dead zone" issues completely
|
|
959
|
+
if (!touchOffset.current) {
|
|
960
|
+
console.warn("⚠️ touchOffset not initialized, cannot move point");
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// ✅ DIRECT MAPPING: newPosition = touchPosition + offset
|
|
965
|
+
// No delta accumulation, no zone morte
|
|
966
|
+
var newX = currentX + touchOffset.current.x;
|
|
967
|
+
var newY = currentY + touchOffset.current.y;
|
|
968
|
+
|
|
969
|
+
// ✅ SEPARATE DRAG BOUNDS vs CROP BOUNDS
|
|
970
|
+
var cx = contentRect.x,
|
|
971
|
+
cy = contentRect.y,
|
|
972
|
+
cw = contentRect.width,
|
|
973
|
+
ch = contentRect.height;
|
|
974
|
+
|
|
975
|
+
// ✅ STRICT BOUNDS: For final crop safety (imageDisplayRect)
|
|
976
|
+
var strictMinX = cx;
|
|
977
|
+
var strictMaxX = cx + cw;
|
|
978
|
+
var strictMinY = cy;
|
|
979
|
+
var strictMaxY = cy + ch;
|
|
980
|
+
|
|
981
|
+
// ✅ DRAG BOUNDS: Allow movement ANYWHERE in wrapper during drag
|
|
982
|
+
// Points can move freely across the entire screen for maximum flexibility
|
|
983
|
+
// They will be clamped to imageDisplayRect only on release for safe cropping
|
|
984
|
+
var wrapperRect = wrapper;
|
|
985
|
+
var overshootMinX = wrapperRect.x;
|
|
986
|
+
var overshootMaxX = wrapperRect.x + wrapperRect.width;
|
|
987
|
+
var overshootMinY = wrapperRect.y;
|
|
988
|
+
var overshootMaxY = wrapperRect.y + wrapperRect.height;
|
|
989
|
+
|
|
990
|
+
// ✅ DRAG BOUNDS: Clamp ONLY to overshootBounds during drag (NOT strictBounds)
|
|
991
|
+
var dragX = Math.max(overshootMinX, Math.min(newX, overshootMaxX));
|
|
992
|
+
var dragY = Math.max(overshootMinY, Math.min(newY, overshootMaxY));
|
|
993
|
+
|
|
994
|
+
// ✅ UPDATE POINT: Use drag bounds (overshoot) - allows visual freedom
|
|
995
|
+
var updatedPoint = {
|
|
996
|
+
x: dragX,
|
|
997
|
+
y: dragY
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
// ✅ CRITICAL: Detect if point is AT overshoot boundary (not just clamped)
|
|
1001
|
+
// Check if point is exactly at overshootMin/Max (within 1px tolerance)
|
|
1002
|
+
var isAtOvershootMinX = Math.abs(dragX - overshootMinX) < 1;
|
|
1003
|
+
var isAtOvershootMaxX = Math.abs(dragX - overshootMaxX) < 1;
|
|
1004
|
+
var isAtOvershootMinY = Math.abs(dragY - overshootMinY) < 1;
|
|
1005
|
+
var isAtOvershootMaxY = Math.abs(dragY - overshootMaxY) < 1;
|
|
1006
|
+
var isAtBoundaryX = isAtOvershootMinX || isAtOvershootMaxX;
|
|
1007
|
+
var isAtBoundaryY = isAtOvershootMinY || isAtOvershootMaxY;
|
|
1008
|
+
|
|
1009
|
+
// Only recalculate offset when FIRST hitting boundary (transition free → boundary)
|
|
1010
|
+
var justHitBoundaryX = isAtBoundaryX && !wasClampedLastFrame.current.x;
|
|
1011
|
+
var justHitBoundaryY = isAtBoundaryY && !wasClampedLastFrame.current.y;
|
|
1012
|
+
if (justHitBoundaryX || justHitBoundaryY) {
|
|
1013
|
+
// Point JUST hit overshoot boundary - recalculate offset once
|
|
1014
|
+
var newOffsetX = justHitBoundaryX ? dragX - currentX : touchOffset.current.x;
|
|
1015
|
+
var newOffsetY = justHitBoundaryY ? dragY - currentY : touchOffset.current.y;
|
|
1016
|
+
touchOffset.current = {
|
|
1017
|
+
x: newOffsetX,
|
|
1018
|
+
y: newOffsetY
|
|
1019
|
+
};
|
|
1020
|
+
console.log("✅ OFFSET RECALCULATED (hit boundary):", {
|
|
1021
|
+
axis: justHitBoundaryX ? 'X' : 'Y',
|
|
1022
|
+
touchY: currentY.toFixed(2),
|
|
1023
|
+
dragY: dragY.toFixed(2),
|
|
1024
|
+
newOffsetY: touchOffset.current.y.toFixed(2),
|
|
1025
|
+
note: "First contact with boundary - offset locked"
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// Update boundary state for next frame
|
|
1030
|
+
wasClampedLastFrame.current = {
|
|
1031
|
+
x: isAtBoundaryX,
|
|
1032
|
+
y: isAtBoundaryY
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
// ✅ DEBUG: Log when in overshoot zone (only when not at boundary)
|
|
1036
|
+
var isInOvershootY = dragY < strictMinY || dragY > strictMaxY;
|
|
1037
|
+
if (isInOvershootY && !isAtBoundaryY) {
|
|
1038
|
+
console.log("🎯 IN OVERSHOOT ZONE:", {
|
|
1039
|
+
touchY: currentY.toFixed(2),
|
|
1040
|
+
appliedY: dragY.toFixed(2),
|
|
1041
|
+
overshootRange: "".concat(overshootMinY.toFixed(2), " - ").concat(overshootMaxY.toFixed(2)),
|
|
1042
|
+
strictRange: "".concat(strictMinY.toFixed(2), " - ").concat(strictMaxY.toFixed(2))
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// ✅ Update lastValidPosition ONLY if point is within strictBounds
|
|
1047
|
+
var isStrictlyValid = dragX >= strictMinX && dragX <= strictMaxX && dragY >= strictMinY && dragY <= strictMaxY;
|
|
1048
|
+
if (isStrictlyValid) {
|
|
1049
|
+
lastValidPosition.current = updatedPoint;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// ✅ Update lastTouchPosition for next frame (simple tracking)
|
|
1053
|
+
lastTouchPosition.current = {
|
|
1054
|
+
x: currentX,
|
|
1055
|
+
y: currentY
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
// ✅ DEBUG: Log the point update before setPoints
|
|
1059
|
+
console.log("📍 UPDATING POINT:", {
|
|
1060
|
+
index: selectedPointIndex.current,
|
|
1061
|
+
newX: updatedPoint.x.toFixed(2),
|
|
1062
|
+
newY: updatedPoint.y.toFixed(2),
|
|
1063
|
+
touchX: currentX.toFixed(2),
|
|
1064
|
+
touchY: currentY.toFixed(2),
|
|
1065
|
+
offsetX: touchOffset.current.x.toFixed(2),
|
|
1066
|
+
offsetY: touchOffset.current.y.toFixed(2)
|
|
1067
|
+
});
|
|
1068
|
+
setPoints(function (prev) {
|
|
1069
|
+
var _prev$pointIndex, _newPoints$pointIndex, _prev$pointIndex2, _newPoints$pointIndex2;
|
|
1070
|
+
// ✅ SAFETY: Ensure prev is a valid array
|
|
1071
|
+
if (!Array.isArray(prev) || prev.length === 0) {
|
|
1072
|
+
return prev;
|
|
1073
|
+
}
|
|
1074
|
+
var pointIndex = selectedPointIndex.current;
|
|
1075
|
+
// ✅ SAFETY: Validate pointIndex
|
|
1076
|
+
if (pointIndex === null || pointIndex === undefined || pointIndex < 0 || pointIndex >= prev.length) {
|
|
1077
|
+
return prev;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// ✅ SAFETY: Filter out any invalid points and update the selected one
|
|
1081
|
+
var newPoints = prev.map(function (p, i) {
|
|
1082
|
+
if (i === pointIndex) {
|
|
1083
|
+
return updatedPoint;
|
|
1084
|
+
}
|
|
1085
|
+
// ✅ SAFETY: Ensure existing points are valid
|
|
1086
|
+
if (p && typeof p.x === 'number' && typeof p.y === 'number') {
|
|
1087
|
+
return p;
|
|
1088
|
+
}
|
|
1089
|
+
// If point is invalid, return a default point (shouldn't happen, but safety first)
|
|
1090
|
+
return {
|
|
1091
|
+
x: 0,
|
|
1092
|
+
y: 0
|
|
1093
|
+
};
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
// ✅ DEBUG: Log the state update
|
|
1097
|
+
console.log("✅ STATE UPDATED:", {
|
|
1098
|
+
index: pointIndex,
|
|
1099
|
+
oldY: (_prev$pointIndex = prev[pointIndex]) === null || _prev$pointIndex === void 0 ? void 0 : _prev$pointIndex.y.toFixed(2),
|
|
1100
|
+
newY: (_newPoints$pointIndex = newPoints[pointIndex]) === null || _newPoints$pointIndex === void 0 ? void 0 : _newPoints$pointIndex.y.toFixed(2),
|
|
1101
|
+
changed: Math.abs(((_prev$pointIndex2 = prev[pointIndex]) === null || _prev$pointIndex2 === void 0 ? void 0 : _prev$pointIndex2.y) - ((_newPoints$pointIndex2 = newPoints[pointIndex]) === null || _newPoints$pointIndex2 === void 0 ? void 0 : _newPoints$pointIndex2.y)) > 0.01
|
|
1102
|
+
});
|
|
1103
|
+
return newPoints;
|
|
1104
|
+
});
|
|
1105
|
+
};
|
|
1106
|
+
var handleRelease = function handleRelease() {
|
|
1107
|
+
var wasDragging = selectedPointIndex.current !== null;
|
|
1108
|
+
|
|
1109
|
+
// ✅ CRITICAL: Reset drag state when drag ends
|
|
1110
|
+
touchOffset.current = null;
|
|
1111
|
+
wasClampedLastFrame.current = {
|
|
1112
|
+
x: false,
|
|
1113
|
+
y: false
|
|
1114
|
+
};
|
|
1115
|
+
|
|
1116
|
+
// ✅ VISUAL OVERSHOOT: Clamp points back to imageDisplayRect when drag ends
|
|
1117
|
+
// This ensures final crop is always within valid image bounds
|
|
1118
|
+
if (wasDragging && selectedPointIndex.current !== null) {
|
|
1119
|
+
var wrapper = commonWrapperLayout.current;
|
|
1120
|
+
var imageRect = imageDisplayRect.current;
|
|
1121
|
+
|
|
1122
|
+
// Recalculate imageDisplayRect if needed
|
|
1123
|
+
if (imageRect.width === 0 || imageRect.height === 0) {
|
|
1124
|
+
if (wrapper.width > 0 && wrapper.height > 0 && originalImageDimensions.current.width > 0) {
|
|
1125
|
+
updateImageDisplayRect(wrapper.width, wrapper.height);
|
|
1126
|
+
imageRect = imageDisplayRect.current;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
if (imageRect.width > 0 && imageRect.height > 0) {
|
|
1130
|
+
var imageRectX = wrapper.x + imageRect.x;
|
|
1131
|
+
var imageRectY = wrapper.y + imageRect.y;
|
|
1132
|
+
var imageRectMaxX = imageRectX + imageRect.width;
|
|
1133
|
+
var imageRectMaxY = imageRectY + imageRect.height;
|
|
1134
|
+
|
|
1135
|
+
// Clamp the dragged point back to strict image bounds
|
|
1136
|
+
setPoints(function (prev) {
|
|
1137
|
+
// ✅ SAFETY: Ensure prev is a valid array
|
|
1138
|
+
if (!Array.isArray(prev) || prev.length === 0) {
|
|
1139
|
+
return prev;
|
|
1140
|
+
}
|
|
1141
|
+
var pointIndex = selectedPointIndex.current;
|
|
1142
|
+
// ✅ SAFETY: Validate pointIndex and ensure point exists
|
|
1143
|
+
if (pointIndex === null || pointIndex === undefined || pointIndex < 0 || pointIndex >= prev.length) {
|
|
1144
|
+
return prev;
|
|
1145
|
+
}
|
|
1146
|
+
var point = prev[pointIndex];
|
|
1147
|
+
// ✅ SAFETY: Ensure point exists and has valid x/y properties
|
|
1148
|
+
if (!point || typeof point.x !== 'number' || typeof point.y !== 'number') {
|
|
1149
|
+
return prev;
|
|
1150
|
+
}
|
|
1151
|
+
var clampedPoint = {
|
|
1152
|
+
x: Math.max(imageRectX, Math.min(point.x, imageRectMaxX)),
|
|
1153
|
+
y: Math.max(imageRectY, Math.min(point.y, imageRectMaxY))
|
|
1154
|
+
};
|
|
1155
|
+
|
|
1156
|
+
// Only update if point was outside bounds
|
|
1157
|
+
if (point.x !== clampedPoint.x || point.y !== clampedPoint.y) {
|
|
1158
|
+
console.log("🔒 Clamping point back to image bounds on release:", {
|
|
1159
|
+
before: {
|
|
1160
|
+
x: point.x.toFixed(2),
|
|
1161
|
+
y: point.y.toFixed(2)
|
|
1162
|
+
},
|
|
1163
|
+
after: {
|
|
1164
|
+
x: clampedPoint.x.toFixed(2),
|
|
1165
|
+
y: clampedPoint.y.toFixed(2)
|
|
1166
|
+
},
|
|
1167
|
+
bounds: {
|
|
1168
|
+
minX: imageRectX.toFixed(2),
|
|
1169
|
+
maxX: imageRectMaxX.toFixed(2),
|
|
1170
|
+
minY: imageRectY.toFixed(2),
|
|
1171
|
+
maxY: imageRectMaxY.toFixed(2)
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
return prev.map(function (p, i) {
|
|
1175
|
+
return i === pointIndex ? clampedPoint : p;
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
return prev;
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// ✅ FREE DRAG: Clear initial positions when drag ends
|
|
1184
|
+
initialTouchPosition.current = null;
|
|
1185
|
+
initialPointPosition.current = null;
|
|
1186
|
+
lastValidPosition.current = null;
|
|
1187
|
+
selectedPointIndex.current = null;
|
|
1188
|
+
|
|
1189
|
+
// ✅ CRITICAL: Re-enable parent ScrollView scrolling when drag ends
|
|
1190
|
+
if (wasDragging) {
|
|
1191
|
+
try {
|
|
1192
|
+
// Re-enable scrolling after a short delay to avoid conflicts
|
|
1193
|
+
setTimeout(function () {
|
|
1194
|
+
// ScrollView will be re-enabled automatically when responder is released
|
|
1195
|
+
}, 100);
|
|
1196
|
+
} catch (e) {
|
|
1197
|
+
// Ignore errors
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
var handleReset = function handleReset() {
|
|
1202
|
+
// setPoints([]);
|
|
1203
|
+
hasInitializedCropBox.current = false; // ✅ CRITICAL: Reset guard to allow reinitialization
|
|
1204
|
+
initializeCropBox();
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
// ✅ REFACTORISATION : Stocker l'angle de rotation au lieu de modifier l'image immédiatement
|
|
1208
|
+
// La rotation sera appliquée uniquement lors du crop final pour éviter les interpolations multiples
|
|
1209
|
+
var rotatePreviewImage = /*#__PURE__*/function () {
|
|
1210
|
+
var _ref2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(degrees) {
|
|
1211
|
+
var rotated, _t;
|
|
1212
|
+
return _regenerator().w(function (_context) {
|
|
1213
|
+
while (1) switch (_context.p = _context.n) {
|
|
1214
|
+
case 0:
|
|
1215
|
+
if (!(!image || isRotating)) {
|
|
1216
|
+
_context.n = 1;
|
|
1217
|
+
break;
|
|
1218
|
+
}
|
|
1219
|
+
return _context.a(2);
|
|
1220
|
+
case 1:
|
|
1221
|
+
setIsRotating(true);
|
|
1222
|
+
_context.p = 2;
|
|
1223
|
+
// ✅ CORRECTION : appliquer la rotation de façon incrémentale sur le fichier (pas cumulée sur un fichier déjà roté).
|
|
1224
|
+
// L'ancienne version rotait l'image déjà rotée par l'angle total, ce qui donnait des rotations incorrectes (90 + 180 => 270).
|
|
1225
|
+
rotationAngle.current = (rotationAngle.current + degrees) % 360;
|
|
1226
|
+
_context.n = 3;
|
|
1227
|
+
return ImageManipulator.manipulateAsync(image, [{
|
|
1228
|
+
rotate: degrees
|
|
1229
|
+
}], {
|
|
1230
|
+
compress: 1,
|
|
1231
|
+
// Qualité maximale (pas de compression)
|
|
1232
|
+
format: ImageManipulator.SaveFormat.PNG // Format sans perte
|
|
1233
|
+
});
|
|
1234
|
+
case 3:
|
|
1235
|
+
rotated = _context.v;
|
|
1236
|
+
// Update image pour l'aperçu - onImageLayout will call initializeCropBox automatically
|
|
1237
|
+
setImage(rotated.uri);
|
|
1238
|
+
console.log("Rotation applied (preview increment):", degrees, "degrees; accumulated:", rotationAngle.current);
|
|
1239
|
+
_context.n = 5;
|
|
1240
|
+
break;
|
|
1241
|
+
case 4:
|
|
1242
|
+
_context.p = 4;
|
|
1243
|
+
_t = _context.v;
|
|
1244
|
+
console.error("Error rotating image:", _t);
|
|
1245
|
+
alert("Error rotating image");
|
|
1246
|
+
case 5:
|
|
1247
|
+
_context.p = 5;
|
|
1248
|
+
setIsRotating(false);
|
|
1249
|
+
return _context.f(5);
|
|
1250
|
+
case 6:
|
|
1251
|
+
return _context.a(2);
|
|
1252
|
+
}
|
|
1253
|
+
}, _callee, null, [[2, 4, 5, 6]]);
|
|
1254
|
+
}));
|
|
1255
|
+
return function rotatePreviewImage(_x) {
|
|
1256
|
+
return _ref2.apply(this, arguments);
|
|
1257
|
+
};
|
|
1258
|
+
}();
|
|
1259
|
+
|
|
1260
|
+
// Helper function to wait for multiple render cycles (works on iOS)
|
|
1261
|
+
var waitForRender = function waitForRender() {
|
|
1262
|
+
var cycles = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 5;
|
|
1263
|
+
return new Promise(function (resolve) {
|
|
1264
|
+
var count = 0;
|
|
1265
|
+
var _tick = function tick() {
|
|
1266
|
+
count++;
|
|
1267
|
+
if (count >= cycles) {
|
|
1268
|
+
resolve();
|
|
1269
|
+
} else {
|
|
1270
|
+
setImmediate(_tick);
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
setImmediate(_tick);
|
|
1274
|
+
});
|
|
1275
|
+
};
|
|
1276
|
+
return /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
1277
|
+
style: _ImageCropperStyles["default"].container
|
|
1278
|
+
}, showCustomCamera ? /*#__PURE__*/_react["default"].createElement(_CustomCamera["default"], {
|
|
1279
|
+
onPhotoCaptured: function onPhotoCaptured(uri, frameData) {
|
|
1280
|
+
// ✅ CRITICAL FIX: Store green frame coordinates for coordinate conversion
|
|
1281
|
+
if (frameData && frameData.greenFrame) {
|
|
1282
|
+
cameraFrameData.current = {
|
|
1283
|
+
greenFrame: frameData.greenFrame,
|
|
1284
|
+
capturedImageSize: frameData.capturedImageSize
|
|
1285
|
+
};
|
|
1286
|
+
// ✅ CRITICAL: Set imageSource to 'camera' IMMEDIATELY to prevent Image.getSize() from being called
|
|
1287
|
+
imageSource.current = 'camera';
|
|
1288
|
+
hasInitializedCropBox.current = false; // Reset guard for new camera image
|
|
1289
|
+
console.log("✅ Camera frame data received:", cameraFrameData.current);
|
|
1290
|
+
}
|
|
1291
|
+
setImage(uri);
|
|
1292
|
+
setShowCustomCamera(false);
|
|
1293
|
+
// ✅ CORRECTION : Réinitialiser les points et l'angle de rotation quand une nouvelle photo est capturée
|
|
1294
|
+
setPoints([]);
|
|
1295
|
+
rotationAngle.current = 0;
|
|
1296
|
+
// ✅ CRITICAL: initializeCropBox will be called automatically when image layout is ready
|
|
1297
|
+
// The green frame coordinates are stored in cameraFrameData.current and will be used
|
|
1298
|
+
},
|
|
1299
|
+
onCancel: function onCancel() {
|
|
1300
|
+
return setShowCustomCamera(false);
|
|
1301
|
+
}
|
|
1302
|
+
}) : /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, !showResult && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
1303
|
+
style: image ? _ImageCropperStyles["default"].buttonContainer : _ImageCropperStyles["default"].centerButtonsContainer
|
|
1304
|
+
}, image && _reactNative.Platform.OS === 'android' && /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
|
|
1305
|
+
style: _ImageCropperStyles["default"].iconButton,
|
|
1306
|
+
onPress: function onPress() {
|
|
1307
|
+
return enableRotation && rotatePreviewImage(90);
|
|
1308
|
+
},
|
|
1309
|
+
disabled: isRotating
|
|
1310
|
+
}, /*#__PURE__*/_react["default"].createElement(_vectorIcons.Ionicons, {
|
|
1311
|
+
name: "sync",
|
|
1312
|
+
size: 24,
|
|
1313
|
+
color: "white"
|
|
1314
|
+
}))), image && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
|
|
1315
|
+
style: _ImageCropperStyles["default"].button,
|
|
1316
|
+
onPress: handleReset
|
|
1317
|
+
}, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
|
|
1318
|
+
style: _ImageCropperStyles["default"].buttonText
|
|
1319
|
+
}, "Reset")), image && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
|
|
1320
|
+
style: _ImageCropperStyles["default"].button,
|
|
1321
|
+
onPress: /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
|
|
1322
|
+
var actualImageWidth, actualImageHeight, layout, contentRect, displayedWidth, displayedHeight, isCoverMode, scale, coverOffsetX, coverOffsetY, scaledWidth, scaledHeight, scaleX, scaleY, originalUri, cropMeta, imagePoints, minX, minY, maxX, maxY, cropX, cropY, cropEndX, cropEndY, cropWidth, cropHeight, bbox, polygon, name, _t2;
|
|
1323
|
+
return _regenerator().w(function (_context2) {
|
|
1324
|
+
while (1) switch (_context2.p = _context2.n) {
|
|
1325
|
+
case 0:
|
|
1326
|
+
setIsLoading(true);
|
|
1327
|
+
_context2.p = 1;
|
|
1328
|
+
console.log("=== Starting pixel-perfect metadata export (no bitmap crop on mobile) ===");
|
|
1329
|
+
|
|
1330
|
+
// ✅ REFACTORISATION : Utiliser les dimensions stockées (plus efficace)
|
|
1331
|
+
actualImageWidth = originalImageDimensions.current.width;
|
|
1332
|
+
actualImageHeight = originalImageDimensions.current.height; // Vérifier que les dimensions sont valides
|
|
1333
|
+
if (!(actualImageWidth === 0 || actualImageHeight === 0)) {
|
|
1334
|
+
_context2.n = 2;
|
|
1335
|
+
break;
|
|
1336
|
+
}
|
|
1337
|
+
throw new Error("Original image dimensions not available. Please wait for image to load.");
|
|
1338
|
+
case 2:
|
|
1339
|
+
console.log("Original image dimensions:", {
|
|
1340
|
+
width: actualImageWidth,
|
|
1341
|
+
height: actualImageHeight
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
// ✅ CORRECTION : Utiliser le rectangle de contenu réel (contain)
|
|
1345
|
+
// ✅ CRITICAL: Recalculate displayedContentRect if not available
|
|
1346
|
+
layout = displayedImageLayout.current;
|
|
1347
|
+
console.log("🔍 Checking displayedContentRect before crop:", {
|
|
1348
|
+
displayedContentRect: displayedContentRect.current,
|
|
1349
|
+
displayedImageLayout: layout,
|
|
1350
|
+
originalImageDimensions: originalImageDimensions.current
|
|
1351
|
+
});
|
|
1352
|
+
if (layout.width > 0 && layout.height > 0) {
|
|
1353
|
+
updateDisplayedContentRect(layout.width, layout.height);
|
|
1354
|
+
}
|
|
1355
|
+
contentRect = displayedContentRect.current;
|
|
1356
|
+
displayedWidth = contentRect.width;
|
|
1357
|
+
displayedHeight = contentRect.height; // Vérifier que les dimensions d'affichage sont valides
|
|
1358
|
+
if (!(displayedWidth === 0 || displayedHeight === 0)) {
|
|
1359
|
+
_context2.n = 4;
|
|
1360
|
+
break;
|
|
1361
|
+
}
|
|
1362
|
+
if (!(layout.width > 0 && layout.height > 0)) {
|
|
1363
|
+
_context2.n = 3;
|
|
1364
|
+
break;
|
|
1365
|
+
}
|
|
1366
|
+
console.warn("⚠️ displayedContentRect not available, using displayedImageLayout as fallback");
|
|
1367
|
+
// Use layout dimensions as fallback (assuming no letterboxing)
|
|
1368
|
+
contentRect = {
|
|
1369
|
+
x: layout.x,
|
|
1370
|
+
y: layout.y,
|
|
1371
|
+
width: layout.width,
|
|
1372
|
+
height: layout.height
|
|
1373
|
+
};
|
|
1374
|
+
displayedWidth = contentRect.width;
|
|
1375
|
+
displayedHeight = contentRect.height;
|
|
1376
|
+
// Update the ref for consistency
|
|
1377
|
+
displayedContentRect.current = contentRect;
|
|
1378
|
+
_context2.n = 4;
|
|
1379
|
+
break;
|
|
1380
|
+
case 3:
|
|
1381
|
+
throw new Error("Displayed image dimensions not available. Image may not be laid out yet. Please wait a moment and try again.");
|
|
1382
|
+
case 4:
|
|
1383
|
+
console.log("✅ Using contentRect for crop:", contentRect);
|
|
1384
|
+
console.log("Displayed image dimensions:", {
|
|
1385
|
+
width: displayedWidth,
|
|
1386
|
+
height: displayedHeight
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
// ✅ CAMERA (cover mode): scale = max, image fills wrapper, has offset
|
|
1390
|
+
// ✅ GALLERY (contain mode): scale = min, uniform
|
|
1391
|
+
isCoverMode = !!(cameraFrameData.current && cameraFrameData.current.greenFrame);
|
|
1392
|
+
coverOffsetX = 0, coverOffsetY = 0;
|
|
1393
|
+
if (isCoverMode) {
|
|
1394
|
+
scale = Math.max(displayedWidth / actualImageWidth, displayedHeight / actualImageHeight);
|
|
1395
|
+
scaledWidth = actualImageWidth * scale;
|
|
1396
|
+
scaledHeight = actualImageHeight * scale;
|
|
1397
|
+
coverOffsetX = (scaledWidth - displayedWidth) / 2;
|
|
1398
|
+
coverOffsetY = (scaledHeight - displayedHeight) / 2;
|
|
1399
|
+
console.log("Scale factor (COVER mode for camera):", {
|
|
1400
|
+
scale: scale,
|
|
1401
|
+
coverOffsetX: coverOffsetX,
|
|
1402
|
+
coverOffsetY: coverOffsetY
|
|
1403
|
+
});
|
|
1404
|
+
} else {
|
|
1405
|
+
scaleX = actualImageWidth / displayedWidth;
|
|
1406
|
+
scaleY = actualImageHeight / displayedHeight;
|
|
1407
|
+
if (Math.abs(scaleX - scaleY) > 0.01) {
|
|
1408
|
+
console.warn("Scale mismatch detected! This may cause incorrect crop coordinates.", {
|
|
1409
|
+
scaleX: scaleX,
|
|
1410
|
+
scaleY: scaleY,
|
|
1411
|
+
actualImageWidth: actualImageWidth,
|
|
1412
|
+
actualImageHeight: actualImageHeight,
|
|
1413
|
+
displayedWidth: displayedWidth,
|
|
1414
|
+
displayedHeight: displayedHeight
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
scale = scaleX;
|
|
1418
|
+
console.log("Scale factor (contain, uniform):", {
|
|
1419
|
+
scale: scale,
|
|
1420
|
+
contentRect: contentRect
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
originalUri = sourceImageUri.current || image;
|
|
1424
|
+
cropMeta = null;
|
|
1425
|
+
if (points.length > 0) {
|
|
1426
|
+
try {
|
|
1427
|
+
console.log("Calculating crop boundaries from points...");
|
|
1428
|
+
console.log("Points (display coordinates):", points);
|
|
1429
|
+
console.log("Content rect (offsets):", contentRect);
|
|
1430
|
+
|
|
1431
|
+
// ✅ Conversion display -> image px (contain or cover)
|
|
1432
|
+
imagePoints = points.map(function (point) {
|
|
1433
|
+
var clampedX, clampedY, origX, origY;
|
|
1434
|
+
if (isCoverMode) {
|
|
1435
|
+
// Cover: display = wrapper, scale = max, image centered with offset
|
|
1436
|
+
clampedX = Math.max(0, Math.min(point.x, contentRect.width));
|
|
1437
|
+
clampedY = Math.max(0, Math.min(point.y, contentRect.height));
|
|
1438
|
+
origX = (clampedX + coverOffsetX) / scale;
|
|
1439
|
+
origY = (clampedY + coverOffsetY) / scale;
|
|
1440
|
+
} else {
|
|
1441
|
+
clampedX = Math.max(contentRect.x, Math.min(point.x, contentRect.x + contentRect.width));
|
|
1442
|
+
clampedY = Math.max(contentRect.y, Math.min(point.y, contentRect.y + contentRect.height));
|
|
1443
|
+
origX = (clampedX - contentRect.x) * scale;
|
|
1444
|
+
origY = (clampedY - contentRect.y) * scale;
|
|
1445
|
+
}
|
|
1446
|
+
var finalX = Math.max(0, Math.min(origX, actualImageWidth));
|
|
1447
|
+
var finalY = Math.max(0, Math.min(origY, actualImageHeight));
|
|
1448
|
+
return {
|
|
1449
|
+
x: finalX,
|
|
1450
|
+
y: finalY
|
|
1451
|
+
};
|
|
1452
|
+
});
|
|
1453
|
+
console.log("Converted image points (original coordinates):", imagePoints);
|
|
1454
|
+
|
|
1455
|
+
// Calculer la bounding box : min X, min Y, max X, max Y
|
|
1456
|
+
minX = Math.min.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
|
|
1457
|
+
return p.x;
|
|
1458
|
+
})));
|
|
1459
|
+
minY = Math.min.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
|
|
1460
|
+
return p.y;
|
|
1461
|
+
})));
|
|
1462
|
+
maxX = Math.max.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
|
|
1463
|
+
return p.x;
|
|
1464
|
+
})));
|
|
1465
|
+
maxY = Math.max.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
|
|
1466
|
+
return p.y;
|
|
1467
|
+
}))); // ✅ CORRECTION : arrondi "conservateur" (floor origin + ceil end)
|
|
1468
|
+
// évite de rogner des pixels et réduit le risque de crop plus petit (perte de détails).
|
|
1469
|
+
cropX = Math.max(0, Math.floor(minX));
|
|
1470
|
+
cropY = Math.max(0, Math.floor(minY));
|
|
1471
|
+
cropEndX = Math.min(actualImageWidth, Math.ceil(maxX));
|
|
1472
|
+
cropEndY = Math.min(actualImageHeight, Math.ceil(maxY));
|
|
1473
|
+
cropWidth = Math.max(0, cropEndX - cropX);
|
|
1474
|
+
cropHeight = Math.max(0, cropEndY - cropY);
|
|
1475
|
+
console.log("Crop parameters (pixel-perfect):", {
|
|
1476
|
+
x: cropX,
|
|
1477
|
+
y: cropY,
|
|
1478
|
+
width: cropWidth,
|
|
1479
|
+
height: cropHeight,
|
|
1480
|
+
imageWidth: actualImageWidth,
|
|
1481
|
+
imageHeight: actualImageHeight,
|
|
1482
|
+
boundingBox: {
|
|
1483
|
+
minX: minX,
|
|
1484
|
+
minY: minY,
|
|
1485
|
+
maxX: maxX,
|
|
1486
|
+
maxY: maxY
|
|
1487
|
+
}
|
|
1488
|
+
});
|
|
1489
|
+
if (cropWidth > 0 && cropHeight > 0) {
|
|
1490
|
+
// 1) bbox in ORIGINAL image pixel coords
|
|
1491
|
+
bbox = {
|
|
1492
|
+
x: cropX,
|
|
1493
|
+
y: cropY,
|
|
1494
|
+
width: cropWidth,
|
|
1495
|
+
height: cropHeight
|
|
1496
|
+
}; // 2) polygon points relative to bbox (still in ORIGINAL pixel grid)
|
|
1497
|
+
polygon = imagePoints.map(function (point) {
|
|
1498
|
+
return {
|
|
1499
|
+
x: point.x - cropX,
|
|
1500
|
+
y: point.y - cropY
|
|
1501
|
+
};
|
|
1502
|
+
});
|
|
1503
|
+
cropMeta = {
|
|
1504
|
+
bbox: bbox,
|
|
1505
|
+
polygon: polygon,
|
|
1506
|
+
rotation: 0,
|
|
1507
|
+
imageSize: {
|
|
1508
|
+
width: actualImageWidth,
|
|
1509
|
+
height: actualImageHeight
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1512
|
+
console.log("Crop meta ready:", cropMeta);
|
|
1513
|
+
} else {
|
|
1514
|
+
console.warn("Invalid crop dimensions, cannot export crop meta");
|
|
1515
|
+
}
|
|
1516
|
+
} catch (cropError) {
|
|
1517
|
+
console.error("Error computing crop meta:", cropError);
|
|
1518
|
+
}
|
|
1519
|
+
} else {
|
|
1520
|
+
console.log("No crop points defined, using original image");
|
|
1521
|
+
}
|
|
1522
|
+
name = "IMAGE XTK".concat(Date.now());
|
|
1523
|
+
if (onConfirm) {
|
|
1524
|
+
console.log("Calling onConfirm with:", originalUri, name, cropMeta);
|
|
1525
|
+
onConfirm(originalUri, name, cropMeta);
|
|
1526
|
+
}
|
|
1527
|
+
_context2.n = 6;
|
|
1528
|
+
break;
|
|
1529
|
+
case 5:
|
|
1530
|
+
_context2.p = 5;
|
|
1531
|
+
_t2 = _context2.v;
|
|
1532
|
+
console.error("Erreur lors du crop :", _t2);
|
|
1533
|
+
alert("Erreur lors du crop ! " + _t2.message);
|
|
1534
|
+
case 6:
|
|
1535
|
+
_context2.p = 6;
|
|
1536
|
+
setShowResult(false);
|
|
1537
|
+
setIsLoading(false);
|
|
1538
|
+
setShowFullScreenCapture(false);
|
|
1539
|
+
return _context2.f(6);
|
|
1540
|
+
case 7:
|
|
1541
|
+
return _context2.a(2);
|
|
1542
|
+
}
|
|
1543
|
+
}, _callee2, null, [[1, 5, 6, 7]]);
|
|
1544
|
+
}))
|
|
1545
|
+
}, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
|
|
1546
|
+
style: _ImageCropperStyles["default"].buttonText
|
|
1547
|
+
}, "Confirm"))), image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
1548
|
+
style: {
|
|
1549
|
+
width: _reactNative.Dimensions.get('window').width,
|
|
1550
|
+
aspectRatio: 9 / 16,
|
|
1551
|
+
borderRadius: 30,
|
|
1552
|
+
overflow: 'hidden',
|
|
1553
|
+
alignItems: 'center',
|
|
1554
|
+
justifyContent: 'center',
|
|
1555
|
+
position: 'relative',
|
|
1556
|
+
backgroundColor: 'black'
|
|
1557
|
+
},
|
|
1558
|
+
ref: commonWrapperRef,
|
|
1559
|
+
onLayout: onCommonWrapperLayout
|
|
1560
|
+
}, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
1561
|
+
ref: viewRef,
|
|
1562
|
+
collapsable: false,
|
|
1563
|
+
style: _reactNative.StyleSheet.absoluteFill,
|
|
1564
|
+
onStartShouldSetResponder: function onStartShouldSetResponder() {
|
|
1565
|
+
return true;
|
|
1566
|
+
},
|
|
1567
|
+
onMoveShouldSetResponder: function onMoveShouldSetResponder(evt, gestureState) {
|
|
1568
|
+
// ✅ CRITICAL: Always capture movement when a point is selected
|
|
1569
|
+
// This ensures vertical movement is captured correctly
|
|
1570
|
+
if (selectedPointIndex.current !== null) {
|
|
1571
|
+
return true;
|
|
1572
|
+
}
|
|
1573
|
+
// ✅ CRITICAL: Capture ANY movement immediately (even 0px) to prevent ScrollView interception
|
|
1574
|
+
// This is especially important for vertical movement which ScrollView tries to intercept
|
|
1575
|
+
// We return true for ANY movement to ensure we capture it before ScrollView
|
|
1576
|
+
var hasMovement = Math.abs(gestureState.dx) > 0 || Math.abs(gestureState.dy) > 0;
|
|
1577
|
+
if (hasMovement && Math.abs(gestureState.dy) > 5) {
|
|
1578
|
+
console.log("🔄 Vertical movement detected in responder:", {
|
|
1579
|
+
dx: gestureState.dx.toFixed(2),
|
|
1580
|
+
dy: gestureState.dy.toFixed(2),
|
|
1581
|
+
selectedPoint: selectedPointIndex.current
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
return true;
|
|
1585
|
+
},
|
|
1586
|
+
onResponderGrant: function onResponderGrant(e) {
|
|
1587
|
+
// ✅ CRITICAL: Grant responder immediately to prevent ScrollView from intercepting
|
|
1588
|
+
// This ensures we capture all movement, especially vertical
|
|
1589
|
+
// Handle tap to select point if needed
|
|
1590
|
+
if (selectedPointIndex.current === null) {
|
|
1591
|
+
handleTap(e);
|
|
1592
|
+
}
|
|
1593
|
+
},
|
|
1594
|
+
onResponderStart: handleTap,
|
|
1595
|
+
onResponderMove: function onResponderMove(e) {
|
|
1596
|
+
// ✅ CRITICAL: Always handle move events to ensure smooth movement in all directions
|
|
1597
|
+
// This is called for every move event, ensuring vertical movement is captured
|
|
1598
|
+
// handleMove now uses incremental delta calculation which is more reliable
|
|
1599
|
+
handleMove(e);
|
|
1600
|
+
},
|
|
1601
|
+
onResponderRelease: handleRelease,
|
|
1602
|
+
onResponderTerminationRequest: function onResponderTerminationRequest() {
|
|
1603
|
+
// ✅ CRITICAL: Never allow termination when dragging a point
|
|
1604
|
+
// This prevents ScrollView from stealing the responder during vertical movement
|
|
1605
|
+
return selectedPointIndex.current === null;
|
|
1606
|
+
}
|
|
1607
|
+
// ✅ CRITICAL: Prevent parent ScrollView from intercepting touches
|
|
1608
|
+
// Capture responder BEFORE parent ScrollView can intercept
|
|
1609
|
+
,
|
|
1610
|
+
onStartShouldSetResponderCapture: function onStartShouldSetResponderCapture() {
|
|
1611
|
+
// Always capture start events
|
|
1612
|
+
return true;
|
|
1613
|
+
},
|
|
1614
|
+
onMoveShouldSetResponderCapture: function onMoveShouldSetResponderCapture(evt, gestureState) {
|
|
1615
|
+
// ✅ CRITICAL: Always capture movement events before parent ScrollView
|
|
1616
|
+
// This is essential for vertical movement which ScrollView tries to intercept
|
|
1617
|
+
// Especially important when a point is selected or when there's any movement
|
|
1618
|
+
if (selectedPointIndex.current !== null) {
|
|
1619
|
+
return true;
|
|
1620
|
+
}
|
|
1621
|
+
// Also capture if there's any movement to prevent ScrollView from intercepting
|
|
1622
|
+
var hasMovement = Math.abs(gestureState.dx) > 0 || Math.abs(gestureState.dy) > 0;
|
|
1623
|
+
if (hasMovement && Math.abs(gestureState.dy) > 2) {
|
|
1624
|
+
console.log("🔄 Capturing vertical movement before ScrollView:", {
|
|
1625
|
+
dy: gestureState.dy.toFixed(2)
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
return hasMovement;
|
|
1629
|
+
}
|
|
1630
|
+
// ✅ CRITICAL: Prevent ScrollView from scrolling by stopping propagation
|
|
1631
|
+
,
|
|
1632
|
+
onResponderReject: function onResponderReject() {
|
|
1633
|
+
console.warn("⚠️ Responder rejected - ScrollView may intercept");
|
|
1634
|
+
}
|
|
1635
|
+
}, /*#__PURE__*/_react["default"].createElement(_reactNative.Image, {
|
|
1636
|
+
source: {
|
|
1637
|
+
uri: image
|
|
1638
|
+
},
|
|
1639
|
+
style: _ImageCropperStyles["default"].image,
|
|
1640
|
+
resizeMode: (_cameraFrameData$curr3 = cameraFrameData.current) !== null && _cameraFrameData$curr3 !== void 0 && _cameraFrameData$curr3.greenFrame ? 'cover' : 'contain',
|
|
1641
|
+
onLayout: onImageLayout
|
|
1642
|
+
}), /*#__PURE__*/_react["default"].createElement(_reactNativeSvg["default"], {
|
|
1643
|
+
style: _ImageCropperStyles["default"].overlay,
|
|
1644
|
+
pointerEvents: "none"
|
|
1645
|
+
}, function () {
|
|
1646
|
+
// ✅ Use wrapper dimensions for SVG path (wrapper coordinates)
|
|
1647
|
+
var wrapperWidth = commonWrapperLayout.current.width || _reactNative.Dimensions.get('window').width;
|
|
1648
|
+
var wrapperHeight = commonWrapperLayout.current.height || _reactNative.Dimensions.get('window').width * 16 / 9;
|
|
1649
|
+
return /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
|
|
1650
|
+
d: "M 0 0 H ".concat(wrapperWidth, " V ").concat(wrapperHeight, " H 0 Z ").concat(createPath()),
|
|
1651
|
+
fill: showResult ? 'white' : 'rgba(0, 0, 0, 0.8)',
|
|
1652
|
+
fillRule: "evenodd"
|
|
1653
|
+
}), !showResult && points.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
|
|
1654
|
+
d: createPath(),
|
|
1655
|
+
fill: "transparent",
|
|
1656
|
+
stroke: "white",
|
|
1657
|
+
strokeWidth: 2
|
|
1658
|
+
}), !showResult && points.map(function (point, index) {
|
|
1659
|
+
return /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Circle, {
|
|
1660
|
+
key: index,
|
|
1661
|
+
cx: point.x,
|
|
1662
|
+
cy: point.y,
|
|
1663
|
+
r: 10,
|
|
1664
|
+
fill: "white"
|
|
1665
|
+
});
|
|
1666
|
+
}));
|
|
1667
|
+
}())))), showMaskView && maskImageUri && maskPoints.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
1668
|
+
style: {
|
|
1669
|
+
position: 'absolute',
|
|
1670
|
+
left: -maskDimensions.width - 100,
|
|
1671
|
+
// Hors écran mais pas trop loin
|
|
1672
|
+
top: -maskDimensions.height - 100,
|
|
1673
|
+
width: maskDimensions.width,
|
|
1674
|
+
height: maskDimensions.height,
|
|
1675
|
+
opacity: 1,
|
|
1676
|
+
// Opacité normale pour la capture
|
|
1677
|
+
pointerEvents: 'none',
|
|
1678
|
+
zIndex: 9999,
|
|
1679
|
+
// Z-index élevé pour s'assurer qu'elle est au-dessus
|
|
1680
|
+
overflow: 'hidden'
|
|
1681
|
+
},
|
|
1682
|
+
collapsable: false
|
|
1683
|
+
}, /*#__PURE__*/_react["default"].createElement(_ImageMaskProcessor.MaskView, {
|
|
1684
|
+
ref: maskViewRef,
|
|
1685
|
+
imageUri: maskImageUri,
|
|
1686
|
+
points: maskPoints,
|
|
1687
|
+
width: maskDimensions.width,
|
|
1688
|
+
height: maskDimensions.height
|
|
1689
|
+
})), /*#__PURE__*/_react["default"].createElement(_reactNative.Modal, {
|
|
1690
|
+
visible: isLoading,
|
|
1691
|
+
transparent: true,
|
|
1692
|
+
animationType: "fade"
|
|
1693
|
+
}, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
|
|
1694
|
+
style: _ImageCropperStyles["default"].loadingOverlay
|
|
1695
|
+
}, /*#__PURE__*/_react["default"].createElement(_reactNative.Image, {
|
|
1696
|
+
source: require('../src/assets/loadingCamera.gif'),
|
|
1697
|
+
style: _ImageCropperStyles["default"].loadingGif,
|
|
1698
|
+
resizeMode: "contain"
|
|
1699
|
+
}))));
|
|
1700
|
+
};
|
|
514
1701
|
var _default = exports["default"] = ImageCropper;
|