@orioro/react-image-crop 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # Template react lib
@@ -0,0 +1,2 @@
1
+ /// <reference types="react" />
2
+ export declare const CheckeredBg: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ import { ImageCropDialogProps } from '../types';
3
+ export declare function ImageCropDialog(props: ImageCropDialogProps): React.JSX.Element;
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ export declare function InstructionsOverlay({ active, style, }: {
3
+ active?: boolean;
4
+ style?: Record<string, any>;
5
+ }): React.JSX.Element;
@@ -0,0 +1,2 @@
1
+ import { DialogSpec } from '@orioro/react-ui-core';
2
+ export declare const IMAGE_CROP_DIALOG_SPEC: DialogSpec;
@@ -0,0 +1,3 @@
1
+ export * from './ImageCropDialog';
2
+ export * from './onSubmitApplyCrop';
3
+ export * from './dialogSpec';
@@ -0,0 +1,7 @@
1
+ import { Merge } from 'type-fest';
2
+ import { ImageCropDialogProps } from '../types';
3
+ import { ApplyImageCropOptions } from '../util';
4
+ export declare function onSubmitApplyCrop({ onSuccess, onError, ...options }: Merge<Pick<ApplyImageCropOptions, 'maxWidth' | 'maxHeight' | 'background'>, {
5
+ onSuccess: (file: File) => any;
6
+ onError: (error: any) => any;
7
+ }>): ImageCropDialogProps['onSubmit'];
@@ -0,0 +1,3 @@
1
+ export * from './CheckeredBg';
2
+ export * from './ImageCropDialog';
3
+ export * from './util';
package/dist/index.mjs ADDED
@@ -0,0 +1,909 @@
1
+ import styled from 'styled-components';
2
+ import React, { useCallback, useState, useEffect, useMemo } from 'react';
3
+ import { Dialog, Slider, IconButton } from '@radix-ui/themes';
4
+ import { withDefaults, Flex, Box, mergeable, Button, pickPromptUIProps, usePromptUI, SingleFileInput, LoadingOverlay, CANCELLED } from '@orioro/react-ui-core';
5
+ import Cropper from 'react-easy-crop';
6
+ import { useDebounce } from 'react-use';
7
+ import canvasSize from 'canvas-size';
8
+ import Compressor from 'compressorjs';
9
+ import { Icon } from '@mdi/react';
10
+ import { mdiMouseScrollWheel, mdiArrowAll, mdiGestureSpread, mdiGestureSwipe, mdiFlipHorizontal, mdiFlipVertical, mdiHelpCircleOutline } from '@mdi/js';
11
+
12
+ function _typeof(o) {
13
+ "@babel/helpers - typeof";
14
+
15
+ return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
16
+ return typeof o;
17
+ } : function (o) {
18
+ return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
19
+ }, _typeof(o);
20
+ }
21
+
22
+ var _assign = function __assign() {
23
+ _assign = Object.assign || function __assign(t) {
24
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
25
+ s = arguments[i];
26
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
27
+ }
28
+ return t;
29
+ };
30
+ return _assign.apply(this, arguments);
31
+ };
32
+ function __rest(s, e) {
33
+ var t = {};
34
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
35
+ if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
36
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
37
+ }
38
+ return t;
39
+ }
40
+ function __awaiter(thisArg, _arguments, P, generator) {
41
+ function adopt(value) {
42
+ return value instanceof P ? value : new P(function (resolve) {
43
+ resolve(value);
44
+ });
45
+ }
46
+ return new (P || (P = Promise))(function (resolve, reject) {
47
+ function fulfilled(value) {
48
+ try {
49
+ step(generator.next(value));
50
+ } catch (e) {
51
+ reject(e);
52
+ }
53
+ }
54
+ function rejected(value) {
55
+ try {
56
+ step(generator["throw"](value));
57
+ } catch (e) {
58
+ reject(e);
59
+ }
60
+ }
61
+ function step(result) {
62
+ result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
63
+ }
64
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
65
+ });
66
+ }
67
+ function __generator(thisArg, body) {
68
+ var _ = {
69
+ label: 0,
70
+ sent: function sent() {
71
+ if (t[0] & 1) throw t[1];
72
+ return t[1];
73
+ },
74
+ trys: [],
75
+ ops: []
76
+ },
77
+ f,
78
+ y,
79
+ t,
80
+ g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
81
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function () {
82
+ return this;
83
+ }), g;
84
+ function verb(n) {
85
+ return function (v) {
86
+ return step([n, v]);
87
+ };
88
+ }
89
+ function step(op) {
90
+ if (f) throw new TypeError("Generator is already executing.");
91
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
92
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
93
+ if (y = 0, t) op = [op[0] & 2, t.value];
94
+ switch (op[0]) {
95
+ case 0:
96
+ case 1:
97
+ t = op;
98
+ break;
99
+ case 4:
100
+ _.label++;
101
+ return {
102
+ value: op[1],
103
+ done: false
104
+ };
105
+ case 5:
106
+ _.label++;
107
+ y = op[1];
108
+ op = [0];
109
+ continue;
110
+ case 7:
111
+ op = _.ops.pop();
112
+ _.trys.pop();
113
+ continue;
114
+ default:
115
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
116
+ _ = 0;
117
+ continue;
118
+ }
119
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
120
+ _.label = op[1];
121
+ break;
122
+ }
123
+ if (op[0] === 6 && _.label < t[1]) {
124
+ _.label = t[1];
125
+ t = op;
126
+ break;
127
+ }
128
+ if (t && _.label < t[2]) {
129
+ _.label = t[2];
130
+ _.ops.push(op);
131
+ break;
132
+ }
133
+ if (t[2]) _.ops.pop();
134
+ _.trys.pop();
135
+ continue;
136
+ }
137
+ op = body.call(thisArg, _);
138
+ } catch (e) {
139
+ op = [6, e];
140
+ y = 0;
141
+ } finally {
142
+ f = t = 0;
143
+ }
144
+ if (op[0] & 5) throw op[1];
145
+ return {
146
+ value: op[0] ? op[1] : void 0,
147
+ done: true
148
+ };
149
+ }
150
+ }
151
+ function __makeTemplateObject(cooked, raw) {
152
+ if (Object.defineProperty) {
153
+ Object.defineProperty(cooked, "raw", {
154
+ value: raw
155
+ });
156
+ } else {
157
+ cooked.raw = raw;
158
+ }
159
+ return cooked;
160
+ }
161
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
162
+ var e = new Error(message);
163
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
164
+ };
165
+
166
+ var CheckeredBg = styled.div(templateObject_1$2 || (templateObject_1$2 = __makeTemplateObject(["\n font-size: 24px;\n --backgroundColor: rgba(239, 239, 239);\n --squareColor: rgba(204, 204, 204);\n --squareSize: 2em;\n\n background-color: var(--backgroundColor);\n background-image: linear-gradient(\n 45deg,\n var(--squareColor) 25%,\n transparent 25%\n ),\n linear-gradient(135deg, var(--squareColor) 25%, transparent 25%),\n linear-gradient(45deg, transparent 75%, var(--squareColor) 75%),\n linear-gradient(135deg, transparent 75%, var(--squareColor) 75%);\n background-size: calc(2 * var(--squareSize)) calc(2 * var(--squareSize));\n background-position:\n 0 0,\n var(--squareSize) 0,\n var(--squareSize) calc(-1 * var(--squareSize)),\n 0 calc(-1 * var(--squareSize));\n\n > img {\n display: block;\n width: 100%;\n height: 100%;\n }\n"], ["\n font-size: 24px;\n --backgroundColor: rgba(239, 239, 239);\n --squareColor: rgba(204, 204, 204);\n --squareSize: 2em;\n\n background-color: var(--backgroundColor);\n background-image: linear-gradient(\n 45deg,\n var(--squareColor) 25%,\n transparent 25%\n ),\n linear-gradient(135deg, var(--squareColor) 25%, transparent 25%),\n linear-gradient(45deg, transparent 75%, var(--squareColor) 75%),\n linear-gradient(135deg, transparent 75%, var(--squareColor) 75%);\n background-size: calc(2 * var(--squareSize)) calc(2 * var(--squareSize));\n background-position:\n 0 0,\n var(--squareSize) 0,\n var(--squareSize) calc(-1 * var(--squareSize)),\n 0 calc(-1 * var(--squareSize));\n\n > img {\n display: block;\n width: 100%;\n height: 100%;\n }\n"])));
167
+ var templateObject_1$2;
168
+
169
+ function toImageUrl(src) {
170
+ if (typeof src === 'string') {
171
+ return src;
172
+ } else if (src instanceof File || src instanceof Blob) {
173
+ return URL.createObjectURL(src);
174
+ } else {
175
+ return src.url;
176
+ }
177
+ }
178
+ function toImageFile(src) {
179
+ return __awaiter(this, void 0, void 0, function () {
180
+ var url, response, blob, file;
181
+ return __generator(this, function (_a) {
182
+ switch (_a.label) {
183
+ case 0:
184
+ if (!(src instanceof File)) return [3 /*break*/, 1];
185
+ return [2 /*return*/, src];
186
+ case 1:
187
+ if (!(src instanceof Blob)) return [3 /*break*/, 2];
188
+ return [2 /*return*/, new File([src], 'image', {
189
+ type: src.type
190
+ })];
191
+ case 2:
192
+ url = typeof src === 'string' ? src : src.url;
193
+ return [4 /*yield*/, fetch(url)];
194
+ case 3:
195
+ response = _a.sent();
196
+ if (!response.ok) {
197
+ throw new Error("Failed to fetch ".concat(url, ": ").concat(response.statusText));
198
+ }
199
+ return [4 /*yield*/, response.blob()
200
+ // Step 3: Create a File object from the Blob
201
+ ];
202
+ case 4:
203
+ blob = _a.sent();
204
+ file = new File([blob], inferImageName(src), {
205
+ type: blob.type
206
+ });
207
+ return [2 /*return*/, file];
208
+ }
209
+ });
210
+ });
211
+ }
212
+ function inferImageName(src) {
213
+ if (typeof src === 'string') {
214
+ var parts = src.split(/s*\/s*/g);
215
+ return parts[parts.length - 1];
216
+ } else if (src instanceof File) {
217
+ return src.name;
218
+ } else if (src === null || src instanceof Blob) {
219
+ return 'image';
220
+ } else if (_typeof(src) === 'object') {
221
+ return inferImageName(src.url);
222
+ } else {
223
+ return 'image';
224
+ }
225
+ }
226
+ function canvasToBlobPromise(canvas, mimeType) {
227
+ if (mimeType === void 0) {
228
+ mimeType = 'image/png';
229
+ }
230
+ return new Promise(function (resolve, reject) {
231
+ canvas.toBlob(function (file) {
232
+ if (file) {
233
+ resolve(file);
234
+ } else {
235
+ reject(new Error('Failed to create blob'));
236
+ }
237
+ }, mimeType);
238
+ });
239
+ }
240
+ var createImage = function createImage(src) {
241
+ return __awaiter(void 0, void 0, void 0, function () {
242
+ var url;
243
+ return __generator(this, function (_a) {
244
+ switch (_a.label) {
245
+ case 0:
246
+ return [4 /*yield*/, toImageUrl(src)];
247
+ case 1:
248
+ url = _a.sent();
249
+ if (!url) {
250
+ throw new Error('Error retrieving image url');
251
+ }
252
+ return [2 /*return*/, new Promise(function (resolve, reject) {
253
+ var image = new Image();
254
+ image.addEventListener('load', function () {
255
+ return resolve(image);
256
+ });
257
+ image.addEventListener('error', function (error) {
258
+ return reject(error);
259
+ });
260
+ image.setAttribute('crossOrigin', 'anonymous'); // needed to avoid cross-origin issues on CodeSandbox
261
+ image.src = url;
262
+ })];
263
+ }
264
+ });
265
+ });
266
+ };
267
+ function drawCanvas(_a) {
268
+ var width = _a.width,
269
+ height = _a.height,
270
+ _b = _a.background,
271
+ background = _b === void 0 ? 'transparent' : _b;
272
+ var canvas = document.createElement('canvas');
273
+ var ctx = canvas.getContext('2d');
274
+ if (!ctx) {
275
+ throw new Error('Could not retrieve canvas context');
276
+ }
277
+ canvas.width = width;
278
+ canvas.height = height;
279
+ if (background === 'transparent') {
280
+ ctx.clearRect(0, 0, width, height);
281
+ } else {
282
+ ctx.fillStyle = background;
283
+ ctx.fillRect(0, 0, width, height);
284
+ ctx.fillStyle = '#000';
285
+ }
286
+ return {
287
+ canvas: canvas,
288
+ ctx: ctx
289
+ };
290
+ }
291
+
292
+ function getRadianAngle(degreeValue) {
293
+ return degreeValue * Math.PI / 180;
294
+ }
295
+ /**
296
+ * Returns the new bounding area of a rotated rectangle.
297
+ */
298
+ function computeRotationSize(width, height, rotation) {
299
+ var rotRad = getRadianAngle(rotation);
300
+ return {
301
+ width: Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height),
302
+ height: Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height)
303
+ };
304
+ }
305
+ function _scaleProperties(obj, factor) {
306
+ return Object.fromEntries(Object.entries(obj).map(function (_a) {
307
+ var key = _a[0],
308
+ value = _a[1];
309
+ return [key, factor * value];
310
+ }));
311
+ }
312
+ /**
313
+ * This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop
314
+ */
315
+ function applyImageCrop(src, _a) {
316
+ var area = _a.area,
317
+ _b = _a.flipHorizontal,
318
+ flipHorizontal = _b === void 0 ? false : _b,
319
+ _c = _a.flipVertical,
320
+ flipVertical = _c === void 0 ? false : _c,
321
+ _d = _a.rotation,
322
+ rotation = _d === void 0 ? 0 : _d,
323
+ _e = _a.background,
324
+ background = _e === void 0 ? 'transparent' : _e,
325
+ _f = _a.mimeType,
326
+ mimeType = _f === void 0 ? 'image/png' : _f,
327
+ _g = _a.maxWidth,
328
+ maxWidth = _g === void 0 ? Infinity : _g,
329
+ _h = _a.maxHeight,
330
+ maxHeight = _h === void 0 ? Infinity : _h;
331
+ return __awaiter(this, void 0, void 0, function () {
332
+ var image, rotRad, _j, bBoxWidth, bBoxHeight, canvasMaxSize, finalMaxWidth, finalMaxHeight, canvasMaxSizeScaleFactor, dimensions, transferCanvas, cropCanvas;
333
+ return __generator(this, function (_k) {
334
+ switch (_k.label) {
335
+ case 0:
336
+ return [4 /*yield*/, createImage(src)];
337
+ case 1:
338
+ image = _k.sent();
339
+ rotRad = getRadianAngle(rotation);
340
+ _j = computeRotationSize(image.width, image.height, rotation), bBoxWidth = _j.width, bBoxHeight = _j.height;
341
+ return [4 /*yield*/, canvasSize.maxArea()];
342
+ case 2:
343
+ canvasMaxSize = _k.sent();
344
+ finalMaxWidth = Math.min(maxWidth, canvasMaxSize.width);
345
+ finalMaxHeight = Math.min(maxHeight, canvasMaxSize.height);
346
+ canvasMaxSizeScaleFactor = Math.min(1, finalMaxWidth / bBoxWidth, finalMaxHeight / bBoxHeight, finalMaxWidth / area.width, finalMaxHeight / area.height);
347
+ dimensions = {
348
+ //
349
+ // Scaled image dimensions
350
+ //
351
+ image: _scaleProperties({
352
+ width: image.width,
353
+ height: image.height
354
+ }, canvasMaxSizeScaleFactor),
355
+ //
356
+ // The canvas on which rotation operation will be executed
357
+ //
358
+ transferCanvas: _scaleProperties({
359
+ width: bBoxWidth,
360
+ height: bBoxHeight
361
+ }, canvasMaxSizeScaleFactor),
362
+ //
363
+ // Crop area
364
+ //
365
+ area: _scaleProperties(area, canvasMaxSizeScaleFactor)
366
+ };
367
+ transferCanvas = drawCanvas({
368
+ width: dimensions.transferCanvas.width,
369
+ height: dimensions.transferCanvas.height,
370
+ background: background
371
+ });
372
+ // translate canvas context to a central location to allow rotating and flipping around the center
373
+ transferCanvas.ctx.translate(dimensions.transferCanvas.width / 2, dimensions.transferCanvas.height / 2);
374
+ transferCanvas.ctx.rotate(rotRad);
375
+ transferCanvas.ctx.scale(flipHorizontal ? -1 : 1, flipVertical ? -1 : 1);
376
+ transferCanvas.ctx.translate(-dimensions.image.width / 2, -dimensions.image.height / 2);
377
+ // Draw rotated image onto transfer canvas
378
+ transferCanvas.ctx.drawImage(image, 0,
379
+ // source x
380
+ 0,
381
+ // source y
382
+ dimensions.image.width,
383
+ // source width
384
+ dimensions.image.height);
385
+ cropCanvas = drawCanvas({
386
+ width: dimensions.area.width,
387
+ height: dimensions.area.height,
388
+ background: background
389
+ });
390
+ // Draw the cropped image onto the new canvas
391
+ cropCanvas.ctx.drawImage(transferCanvas.canvas,
392
+ // prevent negative positions (bug detected on iOS)
393
+ dimensions.area.x < 0 ? 0 : dimensions.area.x,
394
+ // source x
395
+ dimensions.area.y < 0 ? 0 : dimensions.area.y,
396
+ // source y
397
+ dimensions.area.width,
398
+ // source width
399
+ dimensions.area.height,
400
+ // source height
401
+ // transfer negative positions to drawing target (bug detected on iOS)
402
+ dimensions.area.x < 0 ? -1 * dimensions.area.x : 0,
403
+ // target x
404
+ dimensions.area.y < 0 ? -1 * dimensions.area.y : 0,
405
+ // target y
406
+ dimensions.area.width,
407
+ // target width
408
+ dimensions.area.height);
409
+ // Export as blob
410
+ return [2 /*return*/, canvasToBlobPromise(cropCanvas.canvas, mimeType)];
411
+ }
412
+ });
413
+ });
414
+ }
415
+
416
+ function compressImage(input, options) {
417
+ if (options === void 0) {
418
+ options = {};
419
+ }
420
+ return __awaiter(this, void 0, void 0, function () {
421
+ return __generator(this, function (_a) {
422
+ return [2 /*return*/, new Promise(function (resolve, reject) {
423
+ new Compressor(input, _assign({
424
+ success: function success(output) {
425
+ if (input instanceof File) {
426
+ if (output instanceof Blob) {
427
+ resolve(new File([output], input.name, {
428
+ type: input.type
429
+ }));
430
+ } else {
431
+ resolve(output);
432
+ }
433
+ } else {
434
+ resolve(output);
435
+ }
436
+ },
437
+ error: function error(_error) {
438
+ reject(_error);
439
+ }
440
+ }, options));
441
+ })];
442
+ });
443
+ });
444
+ }
445
+
446
+ var InstructionBlock = withDefaults(styled(Flex)(templateObject_1$1 || (templateObject_1$1 = __makeTemplateObject(["\n width: 100px;\n font-size: 1rem;\n text-align: center;\n "], ["\n width: 100px;\n font-size: 1rem;\n text-align: center;\n "]))), {
447
+ direction: 'column',
448
+ alignItems: 'center',
449
+ gap: '3'
450
+ });
451
+ var InstructionsLayout = withDefaults(Flex, {
452
+ direction: 'row',
453
+ gap: '5',
454
+ height: '100%',
455
+ alignItems: 'center',
456
+ justifyContent: 'center'
457
+ });
458
+ var MouseInstructions = styled.div(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n height: 100%;\n display: none;\n\n @media (pointer: fine) {\n display: block;\n }\n"], ["\n height: 100%;\n display: none;\n\n @media (pointer: fine) {\n display: block;\n }\n"])));
459
+ var TouchInstructions = styled.div(templateObject_3 || (templateObject_3 = __makeTemplateObject(["\n height: 100%;\n display: none;\n\n @media (pointer: coarse) {\n display: block;\n }\n"], ["\n height: 100%;\n display: none;\n\n @media (pointer: coarse) {\n display: block;\n }\n"])));
460
+ function InstructionsOverlay(_a) {
461
+ var _b = _a.active,
462
+ active = _b === void 0 ? true : _b,
463
+ _c = _a.style,
464
+ style = _c === void 0 ? {} : _c;
465
+ return /*#__PURE__*/React.createElement(Box, {
466
+ style: _assign({
467
+ position: 'absolute',
468
+ zIndex: 2,
469
+ top: 0,
470
+ left: 0,
471
+ right: 0,
472
+ bottom: 0,
473
+ transition: 'opacity .5s ease, backdrop-filter .5s ease',
474
+ opacity: active ? 1 : 0,
475
+ backdropFilter: active ? 'blur(10px)' : 'none',
476
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
477
+ color: 'white',
478
+ pointerEvents: 'none'
479
+ }, style)
480
+ }, /*#__PURE__*/React.createElement(MouseInstructions, null, /*#__PURE__*/React.createElement(InstructionsLayout, null, /*#__PURE__*/React.createElement(InstructionBlock, null, /*#__PURE__*/React.createElement(Icon, {
481
+ path: mdiMouseScrollWheel,
482
+ size: 1.5
483
+ }), /*#__PURE__*/React.createElement("div", null, "Use scroll para zoom")), /*#__PURE__*/React.createElement(InstructionBlock, null, /*#__PURE__*/React.createElement(Icon, {
484
+ path: mdiArrowAll,
485
+ size: 1.5
486
+ }), /*#__PURE__*/React.createElement("div", null, "Arraste para reposicionar")))), /*#__PURE__*/React.createElement(TouchInstructions, null, /*#__PURE__*/React.createElement(InstructionsLayout, null, /*#__PURE__*/React.createElement(InstructionBlock, null, /*#__PURE__*/React.createElement(Icon, {
487
+ path: mdiGestureSpread,
488
+ size: 1.5
489
+ }), /*#__PURE__*/React.createElement("div", null, "Use dois dedos para zoom")), /*#__PURE__*/React.createElement(InstructionBlock, null, /*#__PURE__*/React.createElement(Icon, {
490
+ path: mdiGestureSwipe,
491
+ size: 1.5
492
+ }), /*#__PURE__*/React.createElement("div", null, "Arraste para reposicionar")))));
493
+ }
494
+ var templateObject_1$1, templateObject_2, templateObject_3;
495
+
496
+ var CropperContainer = styled.div(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n position: relative;\n height: 400px;\n"], ["\n position: relative;\n height: 400px;\n"])));
497
+ var SupportButton = mergeable(Button, {
498
+ variant: 'soft',
499
+ color: 'gray',
500
+ type: 'button'
501
+ });
502
+ var Submit = mergeable(Button, {
503
+ type: 'submit'
504
+ });
505
+ function Control(_a) {
506
+ var label = _a.label,
507
+ children = _a.children;
508
+ return /*#__PURE__*/React.createElement(Flex, {
509
+ direction: "column",
510
+ gap: "2"
511
+ }, /*#__PURE__*/React.createElement("label", {
512
+ style: {
513
+ fontWeight: 'bold',
514
+ fontSize: '.9rem'
515
+ }
516
+ }, label), children);
517
+ }
518
+ function initialValue(_a) {
519
+ var src = _a.src,
520
+ background = _a.background;
521
+ return {
522
+ src: src,
523
+ background: background,
524
+ originalSize: undefined,
525
+ area: undefined,
526
+ areaPercentage: undefined,
527
+ rotation: 0,
528
+ zoom: 1,
529
+ flipHorizontal: false,
530
+ flipVertical: false
531
+ };
532
+ }
533
+ var CENTER_CROP_POSITION = {
534
+ x: 0,
535
+ y: 0
536
+ };
537
+ function ImageCropDialog(props) {
538
+ var _this = this;
539
+ var _a = pickPromptUIProps(_assign({}, props)),
540
+ promptUIProps = _a[0],
541
+ _b = _a[1],
542
+ _c = _b.open,
543
+ open = _c === void 0 ? false : _c,
544
+ _d = _b.src,
545
+ src = _d === void 0 ? null : _d,
546
+ _e = _b.background,
547
+ background = _e === void 0 ? 'transparent' : _e,
548
+ _f = _b.aspectRatio,
549
+ aspectRatio = _f === void 0 ? 1 : _f,
550
+ _g = _b.loading,
551
+ loading = _g === void 0 ? 'Carregando' : _g,
552
+ _h = _b.submit,
553
+ submit = _h === void 0 ? 'Enviar' : _h,
554
+ _j = _b.cancel,
555
+ cancel = _j === void 0 ? 'Cancelar' : _j,
556
+ _k = _b.zoom,
557
+ enableZoom = _k === void 0 ? false : _k,
558
+ _l = _b.minZoom,
559
+ minZoom = _l === void 0 ? 0.3 : _l,
560
+ _m = _b.maxZoom,
561
+ maxZoom = _m === void 0 ? 5 : _m,
562
+ _o = _b.rotation,
563
+ enableRotation = _o === void 0 ? false : _o,
564
+ _p = _b.flip,
565
+ enableFlip = _p === void 0 ? false : _p,
566
+ _q = _b.restrictPosition,
567
+ restrictPosition = _q === void 0 ? true : _q;
568
+ var promptUI = usePromptUI(_assign(_assign({}, promptUIProps), {
569
+ active: open,
570
+ initialValue: initialValue({
571
+ src: src,
572
+ background: background
573
+ })
574
+ }));
575
+ var currentState = promptUI.value;
576
+ var onCropComplete = useCallback(function (areaPercentage, area) {
577
+ promptUI.setValue(_assign(_assign({}, currentState), {
578
+ area: area,
579
+ areaPercentage: areaPercentage
580
+ }));
581
+ }, [promptUI.value]);
582
+ var _r = useState(false),
583
+ openReady = _r[0],
584
+ setOpenReady = _r[1];
585
+ useEffect(function () {
586
+ if (open) {
587
+ // Re-center
588
+ _setCropPos(CENTER_CROP_POSITION);
589
+ //
590
+ // Wait for dialog scale transition to be ready before
591
+ // rendering cropper: react-easy-crop has trouble
592
+ // rendering when inside elements that animate size (transform: scale)
593
+ //
594
+ // https://github.com/ValentinH/react-easy-crop/issues/428
595
+ // https://github.com/ValentinH/react-easy-crop/issues/409
596
+ // https://github.com/ValentinH/react-easy-crop/issues/267
597
+ // https://github.com/ValentinH/react-easy-crop/issues/400
598
+ //
599
+ var readyDelayTimeout_1 = setTimeout(function () {
600
+ return setOpenReady(true);
601
+ }, 200);
602
+ return function () {
603
+ return clearTimeout(readyDelayTimeout_1);
604
+ };
605
+ } else {
606
+ setOpenReady(false);
607
+ }
608
+ }, [open]);
609
+ var _srcImageUrl = useMemo(function () {
610
+ return currentState.src ? toImageUrl(currentState.src) : null;
611
+ }, [currentState.src]);
612
+ //
613
+ // Zoom and rotation are controlled simultaneously in mobile,
614
+ // store in separate states in order to be capable of handling
615
+ // their simultaneous changes
616
+ //
617
+ var _s = useState(1),
618
+ _zoom = _s[0],
619
+ _setZoom = _s[1];
620
+ var _t = useState(0),
621
+ _rotation = _t[0],
622
+ _setRotation = _t[1];
623
+ var _u = useState(CENTER_CROP_POSITION),
624
+ _cropPos = _u[0],
625
+ _setCropPos = _u[1];
626
+ useDebounce(function () {
627
+ promptUI.setValue(_assign(_assign({}, currentState), {
628
+ zoom: _zoom,
629
+ rotation: _rotation
630
+ }));
631
+ }, 200, [_zoom, _rotation]);
632
+ var EFFECTIVE_MIN_ZOOM = useMemo(function () {
633
+ return restrictPosition ? Math.max(1, minZoom) : minZoom;
634
+ }, [minZoom, restrictPosition]);
635
+ var _v = useState(false),
636
+ showInstructions = _v[0],
637
+ setShowInstructions = _v[1];
638
+ // //
639
+ // // https://github.com/ValentinH/react-easy-crop/issues/435
640
+ // //
641
+ // const _finalCropPos = useMemo(
642
+ // () => (restrictPosition && _zoom < 1 ? CENTER_CROP_POSITION : _cropPos),
643
+ // [_cropPos, _zoom, restrictPosition],
644
+ // )
645
+ //
646
+ // Resets all modifications
647
+ //
648
+ function _resetChanges() {
649
+ _setCropPos({
650
+ x: 0,
651
+ y: 0
652
+ });
653
+ _setZoom(1);
654
+ _setRotation(0);
655
+ }
656
+ //
657
+ // Resets all modifications redefines image
658
+ //
659
+ function _clear() {
660
+ _resetChanges();
661
+ promptUI.setValue(initialValue({
662
+ src: null,
663
+ background: background
664
+ }));
665
+ setShowInstructions(false);
666
+ }
667
+ useEffect(function () {
668
+ if (!open) {
669
+ _clear();
670
+ } else {
671
+ setShowInstructions(true);
672
+ var instructionsTimeout_1 = setTimeout(function () {
673
+ return setShowInstructions(false);
674
+ }, 2000);
675
+ return function () {
676
+ return clearTimeout(instructionsTimeout_1);
677
+ };
678
+ }
679
+ }, [open]);
680
+ return /*#__PURE__*/React.createElement(Dialog.Root, {
681
+ open: open
682
+ }, /*#__PURE__*/React.createElement(Dialog.Content, {
683
+ onEscapeKeyDown: promptUI.cancel,
684
+ onPointerDownOutside: promptUI.cancel
685
+ }, /*#__PURE__*/React.createElement("form", {
686
+ onSubmit: function onSubmit(e) {
687
+ return __awaiter(_this, void 0, void 0, function () {
688
+ return __generator(this, function (_a) {
689
+ e.preventDefault();
690
+ promptUI.submit();
691
+ return [2 /*return*/];
692
+ });
693
+ });
694
+ }
695
+ }, /*#__PURE__*/React.createElement(Flex, {
696
+ direction: "column",
697
+ gap: "3"
698
+ }, !Boolean(currentState.src) && ( /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SingleFileInput, {
699
+ value: currentState.src,
700
+ onSetValue: function onSetValue(file) {
701
+ return promptUI.setValue(_assign(_assign({}, currentState), {
702
+ src: file
703
+ }));
704
+ },
705
+ accept: "image/*"
706
+ }))), _srcImageUrl && ( /*#__PURE__*/React.createElement(Flex, {
707
+ direction: "column"
708
+ }, /*#__PURE__*/React.createElement(CheckeredBg, null, /*#__PURE__*/React.createElement(CropperContainer, {
709
+ style: {
710
+ backgroundColor: background
711
+ }
712
+ }, openReady ? ( /*#__PURE__*/React.createElement(Cropper, {
713
+ image: _srcImageUrl,
714
+ transform: ["translate(".concat(_cropPos.x, "px, ").concat(_cropPos.y, "px)"), "rotateY(".concat(currentState.flipHorizontal ? 180 : 0, "deg)"), "rotateX(".concat(currentState.flipVertical ? 180 : 0, "deg)"), "rotateZ(".concat(_rotation, "deg)"), "scale(".concat(_zoom, ")")].join(' '),
715
+ crop: _cropPos,
716
+ onCropChange: _setCropPos,
717
+ aspect: aspectRatio,
718
+ onCropComplete: onCropComplete,
719
+ zoom: _zoom,
720
+ minZoom: EFFECTIVE_MIN_ZOOM,
721
+ maxZoom: maxZoom,
722
+ onZoomChange: function onZoomChange(zoom) {
723
+ return _setZoom(zoom);
724
+ },
725
+ rotation: _rotation,
726
+ onRotationChange: function onRotationChange(rotation) {
727
+ return _setRotation(rotation % 360);
728
+ },
729
+ onMediaLoaded: function onMediaLoaded(mediaSize) {
730
+ return promptUI.setValue(_assign(_assign({}, currentState), {
731
+ originalSize: {
732
+ width: mediaSize.naturalWidth,
733
+ height: mediaSize.naturalHeight
734
+ }
735
+ }));
736
+ },
737
+ restrictPosition: restrictPosition,
738
+ objectFit: props.objectFit
739
+ })) : ( /*#__PURE__*/React.createElement(LoadingOverlay, {
740
+ message: loading
741
+ })), promptUI.isLoading && ( /*#__PURE__*/React.createElement(LoadingOverlay, {
742
+ message: "Processando"
743
+ })), /*#__PURE__*/React.createElement(InstructionsOverlay, {
744
+ active: showInstructions
745
+ }))), (enableZoom || enableRotation) && ( /*#__PURE__*/React.createElement(Flex, {
746
+ direction: {
747
+ xs: 'column',
748
+ md: 'row'
749
+ },
750
+ // alignItems="center"
751
+ justifyContent: "center",
752
+ gap: "5",
753
+ mb: "4"
754
+ }, enableRotation && ( /*#__PURE__*/React.createElement(Control, {
755
+ label: "Rota\xE7\xE3o: ".concat(_rotation, "\xB0")
756
+ }, /*#__PURE__*/React.createElement(Slider, {
757
+ style: {
758
+ width: 200
759
+ },
760
+ min: -359,
761
+ step: 1,
762
+ max: 359,
763
+ value: [_rotation],
764
+ onValueChange: function onValueChange(value) {
765
+ return _setRotation(value[0] % 360);
766
+ },
767
+ size: "1"
768
+ }))), enableZoom && ( /*#__PURE__*/React.createElement(Control, {
769
+ label: "Zoom: ".concat(_zoom.toFixed(2), "x")
770
+ }, /*#__PURE__*/React.createElement(Slider, {
771
+ style: {
772
+ width: 200
773
+ },
774
+ min: EFFECTIVE_MIN_ZOOM,
775
+ step: 0.01,
776
+ max: maxZoom,
777
+ value: [_zoom],
778
+ onValueChange: function onValueChange(value) {
779
+ return _setZoom(value[0]);
780
+ },
781
+ size: "1"
782
+ }))), enableFlip && ( /*#__PURE__*/React.createElement(Control, {
783
+ label: "Sentido:"
784
+ }, /*#__PURE__*/React.createElement(Flex, {
785
+ direction: "row",
786
+ gap: "3"
787
+ }, /*#__PURE__*/React.createElement(IconButton, {
788
+ size: "2",
789
+ type: "button",
790
+ variant: currentState.flipHorizontal ? 'outline' : 'soft',
791
+ onClick: function onClick() {
792
+ return promptUI.setValue(_assign(_assign({}, currentState), {
793
+ flipHorizontal: !currentState.flipHorizontal
794
+ }));
795
+ }
796
+ }, /*#__PURE__*/React.createElement(Icon, {
797
+ path: mdiFlipHorizontal,
798
+ size: 1
799
+ })), /*#__PURE__*/React.createElement(IconButton, {
800
+ size: "2",
801
+ type: "button",
802
+ variant: currentState.flipVertical ? 'outline' : 'soft',
803
+ onClick: function onClick() {
804
+ return promptUI.setValue(_assign(_assign({}, currentState), {
805
+ flipVertical: !currentState.flipVertical
806
+ }));
807
+ }
808
+ }, /*#__PURE__*/React.createElement(Icon, {
809
+ path: mdiFlipVertical,
810
+ size: 1
811
+ }))))))))), /*#__PURE__*/React.createElement(Flex, {
812
+ direction: "row",
813
+ justifyContent: "space-between"
814
+ }, /*#__PURE__*/React.createElement(IconButton, {
815
+ type: "button",
816
+ radius: "full",
817
+ onClick: function onClick() {
818
+ return setShowInstructions(!showInstructions);
819
+ },
820
+ onMouseEnter: function onMouseEnter() {
821
+ return setShowInstructions(true);
822
+ },
823
+ onMouseLeave: function onMouseLeave() {
824
+ return setShowInstructions(false);
825
+ },
826
+ variant: "ghost"
827
+ }, /*#__PURE__*/React.createElement(Icon, {
828
+ path: mdiHelpCircleOutline,
829
+ size: "16px"
830
+ })), /*#__PURE__*/React.createElement(Flex, {
831
+ direction: "row",
832
+ justifyContent: "flex-end"
833
+ }, !promptUI.isLoading && ( /*#__PURE__*/React.createElement(React.Fragment, null, _srcImageUrl && promptUI.hasUnsubmittedChanges && ( /*#__PURE__*/React.createElement(SupportButton, {
834
+ type: "button",
835
+ onClick: _resetChanges
836
+ }, "Redefinir")), /*#__PURE__*/React.createElement(Dialog.Close, _assign({}, promptUI.cancelProps), /*#__PURE__*/React.createElement(SupportButton, {
837
+ children: cancel
838
+ })))), /*#__PURE__*/React.createElement(Dialog.Close, null, /*#__PURE__*/React.createElement(Submit, _assign({}, promptUI.submitProps, {
839
+ loading: promptUI.isLoading
840
+ }), submit))))))));
841
+ }
842
+ var templateObject_1;
843
+
844
+ function onSubmitApplyCrop(_a) {
845
+ var onSuccess = _a.onSuccess,
846
+ onError = _a.onError,
847
+ options = __rest(_a, ["onSuccess", "onError"]);
848
+ return function (imageCropState) {
849
+ return __awaiter(this, void 0, void 0, function () {
850
+ var src, cropOptions, croppedImageBlob, err_1;
851
+ return __generator(this, function (_a) {
852
+ switch (_a.label) {
853
+ case 0:
854
+ src = imageCropState.src, cropOptions = __rest(imageCropState, ["src"]);
855
+ _a.label = 1;
856
+ case 1:
857
+ _a.trys.push([1, 3,, 4]);
858
+ return [4 /*yield*/, applyImageCrop(src, _assign(_assign({}, cropOptions), options))];
859
+ case 2:
860
+ croppedImageBlob = _a.sent();
861
+ if (!croppedImageBlob) {
862
+ throw new Error('Error generating cropped image');
863
+ }
864
+ return [3 /*break*/, 4];
865
+ case 3:
866
+ err_1 = _a.sent();
867
+ onError(err_1);
868
+ return [2 /*return*/];
869
+ case 4:
870
+ return [4 /*yield*/, onSuccess(new File([croppedImageBlob], inferImageName(src), {
871
+ type: croppedImageBlob.type
872
+ }))];
873
+ case 5:
874
+ _a.sent();
875
+ return [2 /*return*/];
876
+ }
877
+ });
878
+ });
879
+ };
880
+ }
881
+
882
+ var IMAGE_CROP_DIALOG_SPEC = [ImageCropDialog, {
883
+ getProps: function getProps(_a, propsOrImage) {
884
+ var resolve = _a.resolve,
885
+ reject = _a.reject;
886
+ if (propsOrImage === void 0) {
887
+ propsOrImage = {};
888
+ }
889
+ var isImage = propsOrImage instanceof File || propsOrImage instanceof Blob || typeof propsOrImage === 'string';
890
+ var baseProps = {
891
+ onCancel: function onCancel() {
892
+ return resolve(CANCELLED);
893
+ },
894
+ onSubmit: onSubmitApplyCrop(_assign({
895
+ onSuccess: resolve,
896
+ onError: reject
897
+ }, isImage ? {} : {
898
+ maxHeight: propsOrImage.maxHeight,
899
+ maxWidth: propsOrImage.maxWidth,
900
+ background: propsOrImage.background
901
+ }))
902
+ };
903
+ return isImage ? _assign({
904
+ src: propsOrImage
905
+ }, baseProps) : _assign(_assign({}, propsOrImage), baseProps);
906
+ }
907
+ }];
908
+
909
+ export { CheckeredBg, IMAGE_CROP_DIALOG_SPEC, ImageCropDialog, applyImageCrop, canvasToBlobPromise, compressImage, computeRotationSize, createImage, drawCanvas, getRadianAngle, inferImageName, onSubmitApplyCrop, toImageFile, toImageUrl };
@@ -0,0 +1,31 @@
1
+ import type { Area } from 'react-easy-crop';
2
+ export type FileRepresentation = {
3
+ url: string;
4
+ [key: string]: any;
5
+ };
6
+ export type ImageSpec = string | FileRepresentation | File | Blob;
7
+ export type ImageCropDialogProps = {
8
+ open?: boolean;
9
+ aspectRatio?: number;
10
+ rotation?: boolean;
11
+ zoom?: boolean;
12
+ minZoom?: number;
13
+ maxZoom?: number;
14
+ flip?: boolean;
15
+ background?: string;
16
+ src?: ImageSpec;
17
+ submit?: any;
18
+ onSubmit: (result: ImageCropState) => any;
19
+ cancel?: any;
20
+ onCancel?: () => any;
21
+ };
22
+ export type ImageCropState = {
23
+ src: ImageSpec;
24
+ area: Area;
25
+ areaPercentage: Area;
26
+ rotation: number;
27
+ zoom: number;
28
+ flipHorizontal: boolean;
29
+ flipVertical: boolean;
30
+ background: string;
31
+ };
@@ -0,0 +1,24 @@
1
+ import { ImageCropState } from '../types';
2
+ import { Area } from 'react-easy-crop';
3
+ export declare function getRadianAngle(degreeValue: number): number;
4
+ /**
5
+ * Returns the new bounding area of a rotated rectangle.
6
+ */
7
+ export declare function computeRotationSize(width: number, height: number, rotation: number): {
8
+ width: number;
9
+ height: number;
10
+ };
11
+ export type ApplyImageCropOptions = {
12
+ area: Area;
13
+ rotation?: number;
14
+ flipHorizontal?: boolean;
15
+ flipVertical?: boolean;
16
+ background?: string;
17
+ mimeType?: 'image/png' | 'image/jpeg' | 'image/webp';
18
+ maxWidth?: number;
19
+ maxHeight?: number;
20
+ };
21
+ /**
22
+ * This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop
23
+ */
24
+ export declare function applyImageCrop(src: ImageCropState['src'], { area, flipHorizontal, flipVertical, rotation, background, mimeType, maxWidth, maxHeight, }: ApplyImageCropOptions): Promise<Blob>;
@@ -0,0 +1,4 @@
1
+ import Compressor from 'compressorjs';
2
+ type CompressorOptions = ConstructorParameters<typeof Compressor>[1];
3
+ export declare function compressImage(input: File | Blob, options?: CompressorOptions): Promise<File | Blob>;
4
+ export {};
@@ -0,0 +1,3 @@
1
+ export * from './misc';
2
+ export * from './applyImageCrop';
3
+ export * from './compressImage';
@@ -0,0 +1,14 @@
1
+ import { SrcImage } from '../types';
2
+ export declare function toImageUrl(src: SrcImage): string;
3
+ export declare function toImageFile(src: SrcImage): Promise<File>;
4
+ export declare function inferImageName(src: SrcImage): string;
5
+ export declare function canvasToBlobPromise(canvas: HTMLCanvasElement, mimeType?: string): Promise<Blob>;
6
+ export declare const createImage: (src: SrcImage) => Promise<HTMLImageElement>;
7
+ export declare function drawCanvas({ width, height, background, }: {
8
+ width: number;
9
+ height: number;
10
+ background: string;
11
+ }): {
12
+ canvas: HTMLCanvasElement;
13
+ ctx: CanvasRenderingContext2D;
14
+ };
@@ -0,0 +1,20 @@
1
+ import { ImageCropState } from '../types';
2
+ import { Merge } from 'type-fest';
3
+ export declare function getRadianAngle(degreeValue: number): number;
4
+ type ImageSize = {
5
+ width: number;
6
+ height: number;
7
+ };
8
+ /**
9
+ * Returns the new bounding area of a rotated rectangle.
10
+ */
11
+ export declare function computeRotatedImageSize(size: ImageSize, rotation: number): ImageSize;
12
+ /**
13
+ * This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop
14
+ */
15
+ export declare function applyImageCrop(src: ImageCropState['src'], { area, rotation, zoom, flip, background, mimeType, }: Merge<Pick<ImageCropState, 'area' | 'rotation' | 'flip'>, {
16
+ background?: 'transparent' | string;
17
+ zoom?: number;
18
+ mimeType?: 'image/png' | 'image/jpeg' | 'image/webp';
19
+ }>): Promise<Blob>;
20
+ export {};
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@orioro/react-image-crop",
3
+ "version": "0.0.1",
4
+ "packageManager": "yarn@4.0.2",
5
+ "type": "module",
6
+ "main": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": "./dist/index.mjs",
10
+ "./*": "./dist/*"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "license": "ISC",
19
+ "scripts": {
20
+ "build": "rm -rf dist && rollup --config ./rollup.config.mjs",
21
+ "storybook": "storybook dev -p 6006",
22
+ "build-storybook": "storybook build"
23
+ },
24
+ "devDependencies": {
25
+ "@orioro/dev": "workspace:^",
26
+ "@types/canvas-size": "^1.2.2",
27
+ "rollup": "^4.13.0",
28
+ "storybook": "^8.0.0"
29
+ },
30
+ "dependencies": {
31
+ "@mdi/js": "^7.4.47",
32
+ "@mdi/react": "^1.6.1",
33
+ "@orioro/react-ui-core": "workspace:^",
34
+ "@radix-ui/themes": "^3.0.1",
35
+ "canvas-size": "^2.0.0",
36
+ "compressorjs": "^1.2.1",
37
+ "react": "^18.2.0",
38
+ "react-easy-crop": "^5.1.0",
39
+ "react-use": "^17.5.1",
40
+ "styled-components": "^6.1.8",
41
+ "type-fest": "^4.26.1"
42
+ }
43
+ }