@antscorp/antsomi-ui 1.3.5-beta.261 → 1.3.5-beta.263

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.
Files changed (27) hide show
  1. package/es/components/common/ConfigProvider/GlobalStyle.js +5 -0
  2. package/es/components/molecules/ImageEditor/ImageEditor.d.ts +18 -0
  3. package/es/components/molecules/ImageEditor/ImageEditor.js +774 -0
  4. package/es/components/molecules/ImageEditor/index.d.ts +1 -7
  5. package/es/components/molecules/ImageEditor/index.js +1 -555
  6. package/es/components/molecules/ImageEditor/reducer.d.ts +1 -1
  7. package/es/components/molecules/ImageEditor/styled.d.ts +4 -3
  8. package/es/components/molecules/ImageEditor/styled.js +12 -6
  9. package/es/components/molecules/ImageResizeTuiEditor/ImageResizeTuiEditor.d.ts +12 -0
  10. package/es/components/molecules/ImageResizeTuiEditor/ImageResizeTuiEditor.js +73 -0
  11. package/es/components/molecules/ImageResizeTuiEditor/index.d.ts +1 -0
  12. package/es/components/molecules/ImageResizeTuiEditor/index.js +1 -0
  13. package/es/components/molecules/ImageResizeTuiEditor/types.d.ts +5 -0
  14. package/es/components/molecules/ImageResizeTuiEditor/types.js +1 -0
  15. package/es/components/molecules/TemplateSaveAs/TemplateSaveAs.d.ts +4 -0
  16. package/es/components/molecules/TemplateSaveAs/TemplateSaveAs.js +5 -3
  17. package/es/components/molecules/TemplateSaveAs/components/ImageSlider/BigImage.d.ts +2 -0
  18. package/es/components/molecules/TemplateSaveAs/components/ImageSlider/BigImage.js +12 -6
  19. package/es/components/molecules/TemplateSaveAs/components/ImageSlider/ImageSlider.d.ts +5 -0
  20. package/es/components/molecules/TemplateSaveAs/components/ImageSlider/ImageSlider.js +92 -8
  21. package/es/components/molecules/TemplateSaveAs/components/ImageSlider/styled.js +70 -5
  22. package/es/components/molecules/TemplateSaveAs/styled.js +22 -0
  23. package/es/components/molecules/index.d.ts +2 -0
  24. package/es/components/molecules/index.js +1 -0
  25. package/es/providers/ConfigProvider/GlobalStyle.js +5 -0
  26. package/es/test.js +4 -4
  27. package/package.json +2 -1
@@ -0,0 +1,774 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ /* eslint-disable default-case */
11
+ import React, { useCallback, useEffect, useImperativeHandle, useReducer, useRef, useState, } from 'react';
12
+ import Icon from '@antscorp/icons';
13
+ import { Flex } from 'antd';
14
+ import * as Konva from 'konva';
15
+ import { cloneDeep } from 'lodash';
16
+ import { SketchPicker } from 'react-color';
17
+ import { Arrow as KonvaArrow, Circle as KonvaCircle, Image as KonvaImage, Line as KonvaLine, Rect as KonvaRect, Layer, Stage, Transformer, } from 'react-konva';
18
+ import { v4 as uuidV4 } from 'uuid';
19
+ import { Button, Popover } from '../../atoms';
20
+ import ModalShortcut from './components/ModalShortcut';
21
+ import { useDropFile, useKeyEventCanvas } from '@antscorp/antsomi-ui/es/hooks';
22
+ import { DEFAULT_TRANSFORM, DrawAction, PAINT_OPTIONS, ShapeObject } from './constants';
23
+ import { initialReducer, reducer } from './reducer';
24
+ import { downloadURI, getTransformData } from './utils';
25
+ import { StageWrapperStyled } from './styled';
26
+ const SNAP_THRESHOLD = 5;
27
+ export const ImageEditor = React.forwardRef((props, ref) => {
28
+ const { defaultValue, className, width, height, hideAction, imageUrl = '', showSelection, } = props;
29
+ const [color, setColor] = useState('#000');
30
+ const [drawAction, setDrawAction] = useState(DrawAction.Select);
31
+ const [cropZone, setCropZone] = useState();
32
+ const [selectedId, setSelectedId] = useState();
33
+ const [selectedShape, setSelectedShape] = useState();
34
+ const [openHelper, setOpenHelper] = useState(false);
35
+ // NOTE: use for snap line feature
36
+ const [hLines, setHLines] = useState([]);
37
+ const [vLines, setVLines] = useState([]);
38
+ // TODO: START - handle selection
39
+ const [selectionPosition, setSelectionPosition] = useState({
40
+ x: 0,
41
+ y: 0,
42
+ width: 0,
43
+ height: 0,
44
+ visible: false,
45
+ });
46
+ // Konva.default.Util.haveIntersection
47
+ const [state, dispatch] = useReducer(reducer, defaultValue
48
+ ? { history: [defaultValue], historyStep: 0, current: defaultValue }
49
+ : initialReducer);
50
+ const { current: { arrows, circles, imageCoordinate, rectangles, scribbles, image }, } = state;
51
+ // const [imageFromUrl] = useImage(imageUrl);
52
+ // console.log(imageUrl, '🚀 ~ ImageEditor ~ imageFromUrl:', imageFromUrl);
53
+ const stageRef = useRef(null);
54
+ const transformerRef = useRef(null);
55
+ const selectionRectangleRef = useRef(null);
56
+ const isPaintRef = useRef(false);
57
+ // NOTE: format canvas action (as methods) to ref with forwardRef and useImperativeHandle
58
+ useImperativeHandle(ref, () => {
59
+ return {
60
+ getBase64(options) {
61
+ var _a, _b;
62
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
63
+ return (_b = stageRef === null || stageRef === void 0 ? void 0 : stageRef.current) === null || _b === void 0 ? void 0 : _b.toDataURL(options);
64
+ },
65
+ replaceImageWithUrl(imageUrl) {
66
+ changeImage(imageUrl);
67
+ },
68
+ getCurrentState() {
69
+ return state.current;
70
+ },
71
+ };
72
+ }, [state]);
73
+ const changeImage = useCallback((imageUrl) => {
74
+ const image = new Image();
75
+ image.src = imageUrl;
76
+ image.crossOrigin = 'Anonymous';
77
+ image.decode().then(() => {
78
+ const originalWidth = image.width;
79
+ const originalHeight = image.height;
80
+ const ratio = originalWidth / originalHeight;
81
+ image.width = originalWidth > originalHeight ? width : height * ratio;
82
+ // image.width = originalWidth > originalHeight ? width * 0.8 : height * 0.96 * ratio;
83
+ image.height = originalWidth > originalHeight ? width / ratio : height;
84
+ // image.height = originalWidth > originalHeight ? (width * 0.8) / ratio : height * 0.96;
85
+ dispatch({
86
+ type: 'SET_NEW_STATE',
87
+ payload: {
88
+ image,
89
+ imageCoordinate: Object.assign(Object.assign({}, imageCoordinate), { id: uuidV4(), x: (width - image.width) / 2, y: (height - image.height) / 2, width: image.width, height: image.height }),
90
+ saveHistory: true,
91
+ },
92
+ });
93
+ });
94
+ }, [height, width, imageCoordinate]);
95
+ useEffect(() => {
96
+ imageUrl && changeImage(imageUrl);
97
+ }, [imageUrl]);
98
+ const onImportImageSelect = useCallback((e) => {
99
+ var _a;
100
+ if ((_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0]) {
101
+ changeImage(URL.createObjectURL(e.target.files[0]));
102
+ }
103
+ e.target.files = null;
104
+ }, [changeImage]);
105
+ const fileRef = useRef(null);
106
+ const onImportImageClick = useCallback(() => {
107
+ var _a;
108
+ (_a = fileRef === null || fileRef === void 0 ? void 0 : fileRef.current) === null || _a === void 0 ? void 0 : _a.click();
109
+ }, []);
110
+ const onExportClick = useCallback(() => {
111
+ var _a;
112
+ const dataUri = (_a = stageRef === null || stageRef === void 0 ? void 0 : stageRef.current) === null || _a === void 0 ? void 0 : _a.toDataURL();
113
+ downloadURI(dataUri, 'image.png');
114
+ }, []);
115
+ const onClear = useCallback(() => {
116
+ var _a;
117
+ dispatch({ type: 'SET_RECTANGLES', payload: { rectangles: [] } });
118
+ dispatch({ type: 'SET_CIRCLES', payload: { circles: [] } });
119
+ dispatch({ type: 'SET_SCRIBBLES', payload: { scribbles: [] } });
120
+ dispatch({ type: 'SET_ARROWS', payload: { arrows: [] } });
121
+ dispatch({ type: 'SET_IMAGE', payload: { image: undefined, saveHistory: true } });
122
+ setCropZone(undefined);
123
+ setDrawAction(DrawAction.Select);
124
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
125
+ }, []);
126
+ const removeOrDuplicateShape = useCallback((isDuplicate) => {
127
+ var _a;
128
+ if (!selectedShape)
129
+ return;
130
+ switch (selectedShape) {
131
+ case ShapeObject.Arrows: {
132
+ const currSelect = arrows.findIndex(item => item.id === selectedId);
133
+ if (isDuplicate)
134
+ dispatch({
135
+ type: 'SET_ARROWS',
136
+ payload: {
137
+ saveHistory: true,
138
+ arrows: [
139
+ ...arrows,
140
+ Object.assign(Object.assign({}, arrows[currSelect]), { points: arrows[currSelect].points.map((item, idx) => idx % 2 === 0 ? item + 25 : item + 10), id: uuidV4() }),
141
+ ],
142
+ },
143
+ });
144
+ else {
145
+ const cloneObject = cloneDeep(arrows);
146
+ cloneObject.splice(currSelect, 1);
147
+ dispatch({ type: 'SET_ARROWS', payload: { saveHistory: true, arrows: cloneObject } });
148
+ }
149
+ break;
150
+ }
151
+ case ShapeObject.Scribbles: {
152
+ const currSelect = scribbles.findIndex(item => item.id === selectedId);
153
+ if (isDuplicate) {
154
+ dispatch({
155
+ type: 'SET_SCRIBBLES',
156
+ payload: {
157
+ saveHistory: true,
158
+ scribbles: [
159
+ ...scribbles,
160
+ Object.assign(Object.assign({}, scribbles[currSelect]), { points: scribbles[currSelect].points.map(item => item + 25), id: uuidV4() }),
161
+ ],
162
+ },
163
+ });
164
+ }
165
+ else {
166
+ const cloneObject = cloneDeep(scribbles);
167
+ cloneObject.splice(currSelect, 1);
168
+ dispatch({
169
+ type: 'SET_SCRIBBLES',
170
+ payload: { saveHistory: true, scribbles: cloneObject },
171
+ });
172
+ }
173
+ break;
174
+ }
175
+ case ShapeObject.Rectangles: {
176
+ const currSelect = rectangles.findIndex(item => item.id === selectedId);
177
+ if (isDuplicate) {
178
+ dispatch({
179
+ type: 'SET_RECTANGLES',
180
+ payload: {
181
+ saveHistory: true,
182
+ rectangles: [
183
+ ...rectangles,
184
+ Object.assign(Object.assign({}, rectangles[currSelect]), { x: rectangles[currSelect].x + 25, y: rectangles[currSelect].y + 25, id: uuidV4() }),
185
+ ],
186
+ },
187
+ });
188
+ }
189
+ else {
190
+ const cloneObject = cloneDeep(rectangles);
191
+ cloneObject.splice(currSelect, 1);
192
+ dispatch({
193
+ type: 'SET_RECTANGLES',
194
+ payload: { saveHistory: true, rectangles: cloneObject },
195
+ });
196
+ }
197
+ break;
198
+ }
199
+ case ShapeObject.Circles: {
200
+ const currSelect = circles.findIndex(item => item.id === selectedId);
201
+ if (isDuplicate) {
202
+ dispatch({
203
+ type: 'SET_CIRCLES',
204
+ payload: {
205
+ saveHistory: true,
206
+ circles: [
207
+ ...circles,
208
+ Object.assign(Object.assign({}, circles[currSelect]), { x: circles[currSelect].x + 25, y: circles[currSelect].y + 25, id: uuidV4() }),
209
+ ],
210
+ },
211
+ });
212
+ }
213
+ else {
214
+ const cloneObject = cloneDeep(circles);
215
+ cloneObject.splice(currSelect, 1);
216
+ dispatch({
217
+ type: 'SET_CIRCLES',
218
+ payload: { saveHistory: true, circles: cloneObject },
219
+ });
220
+ }
221
+ break;
222
+ }
223
+ case ShapeObject.Image: {
224
+ if (isDuplicate)
225
+ return;
226
+ dispatch({ type: 'SET_IMAGE', payload: { image: undefined, saveHistory: true } });
227
+ break;
228
+ }
229
+ }
230
+ setSelectedId(undefined);
231
+ setSelectedShape(undefined);
232
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
233
+ }, [arrows, circles, rectangles, scribbles, selectedId, selectedShape]);
234
+ useKeyEventCanvas('keydown', event => {
235
+ var _a, _b;
236
+ event.preventDefault();
237
+ switch (true) {
238
+ case event.code === 'Delete':
239
+ removeOrDuplicateShape();
240
+ break;
241
+ case event.code === 'KeyD' && event.ctrlKey:
242
+ removeOrDuplicateShape(true);
243
+ break;
244
+ case event.code === 'KeyZ' && event.ctrlKey && !event.shiftKey:
245
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
246
+ dispatch({ type: 'UNDO' });
247
+ break;
248
+ case event.code === 'KeyZ' && event.ctrlKey && event.shiftKey:
249
+ (_b = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _b === void 0 ? void 0 : _b.nodes([]);
250
+ dispatch({ type: 'REDO' });
251
+ break;
252
+ case event.code === 'KeyV' && event.ctrlKey:
253
+ navigator.clipboard.read().then(clipboardContents => {
254
+ if (!clipboardContents[0].types.includes('image/png'))
255
+ return;
256
+ clipboardContents[0]
257
+ .getType('image/png')
258
+ .then(blob => changeImage(URL.createObjectURL(blob)));
259
+ });
260
+ break;
261
+ default:
262
+ break;
263
+ }
264
+ });
265
+ useDropFile('#image-editor', e => {
266
+ var _a, _b;
267
+ if (!((_a = e === null || e === void 0 ? void 0 : e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files[0]))
268
+ return;
269
+ changeImage(URL.createObjectURL((_b = e === null || e === void 0 ? void 0 : e.dataTransfer) === null || _b === void 0 ? void 0 : _b.files[0]));
270
+ });
271
+ const onStageMouseUp = useCallback(() => {
272
+ var _a, _b, _c;
273
+ isPaintRef.current = false;
274
+ if (drawAction !== DrawAction.Select)
275
+ dispatch({ type: 'SAVE_HISTORY' });
276
+ // NOTE: Guard check showSelection is falsy =>don't run code below
277
+ if (!showSelection)
278
+ return;
279
+ if (selectionPosition.visible) {
280
+ setSelectionPosition(prev => (Object.assign(Object.assign({}, prev), { visible: false })));
281
+ const shapes = ((_a = stageRef.current) === null || _a === void 0 ? void 0 : _a.find('.shape')) || [];
282
+ const box = (_b = selectionRectangleRef.current) === null || _b === void 0 ? void 0 : _b.getClientRect();
283
+ const selected = shapes.filter(shape => Konva.default.Util.haveIntersection(box, shape.getClientRect()));
284
+ (_c = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _c === void 0 ? void 0 : _c.nodes(selected);
285
+ }
286
+ }, [drawAction, selectionPosition.visible]);
287
+ const currentShapeRef = useRef();
288
+ const onStageMouseDown = useCallback(() => {
289
+ if (drawAction !== DrawAction.Crop)
290
+ setCropZone(undefined);
291
+ // if (drawAction === DrawAction.Select) return;
292
+ isPaintRef.current = true;
293
+ const stage = stageRef === null || stageRef === void 0 ? void 0 : stageRef.current;
294
+ const pos = stage === null || stage === void 0 ? void 0 : stage.getPointerPosition();
295
+ const x = (pos === null || pos === void 0 ? void 0 : pos.x) || 0;
296
+ const y = (pos === null || pos === void 0 ? void 0 : pos.y) || 0;
297
+ const id = uuidV4();
298
+ currentShapeRef.current = id;
299
+ switch (drawAction) {
300
+ case DrawAction.Select: {
301
+ setSelectionPosition({ x, y, width: 1, height: 1, visible: false });
302
+ break;
303
+ }
304
+ case DrawAction.Scribble: {
305
+ dispatch({
306
+ type: 'SET_SCRIBBLES',
307
+ payload: {
308
+ scribbles: [...scribbles, Object.assign({ id, points: [x, y], color }, DEFAULT_TRANSFORM)],
309
+ },
310
+ });
311
+ break;
312
+ }
313
+ case DrawAction.Circle: {
314
+ dispatch({
315
+ type: 'SET_CIRCLES',
316
+ payload: {
317
+ circles: [...circles, Object.assign({ id, x, y, radius: 1, color }, DEFAULT_TRANSFORM)],
318
+ },
319
+ });
320
+ break;
321
+ }
322
+ case DrawAction.Rectangle: {
323
+ dispatch({
324
+ type: 'SET_RECTANGLES',
325
+ payload: {
326
+ rectangles: [
327
+ ...rectangles,
328
+ Object.assign({ id, x, y, width: 1, height: 1, color }, DEFAULT_TRANSFORM),
329
+ ],
330
+ },
331
+ });
332
+ break;
333
+ }
334
+ case DrawAction.Crop: {
335
+ setCropZone({ id, x, y, width: 1, height: 1 });
336
+ break;
337
+ }
338
+ case DrawAction.Arrow: {
339
+ dispatch({
340
+ type: 'SET_ARROWS',
341
+ payload: {
342
+ arrows: [...arrows, Object.assign({ id, points: [x, y, x, y], color }, DEFAULT_TRANSFORM)],
343
+ },
344
+ });
345
+ break;
346
+ }
347
+ }
348
+ }, [drawAction, color, arrows, circles, rectangles, scribbles]);
349
+ const onStageMouseMove = useCallback(() => {
350
+ if (!isPaintRef.current)
351
+ return;
352
+ const stage = stageRef === null || stageRef === void 0 ? void 0 : stageRef.current;
353
+ const id = currentShapeRef.current;
354
+ const pos = stage === null || stage === void 0 ? void 0 : stage.getPointerPosition();
355
+ const x = (pos === null || pos === void 0 ? void 0 : pos.x) || 0;
356
+ const y = (pos === null || pos === void 0 ? void 0 : pos.y) || 0;
357
+ switch (drawAction) {
358
+ case DrawAction.Select: {
359
+ // setSelectionVisible(true);
360
+ setSelectionPosition(prev => (Object.assign(Object.assign({}, prev), { width: x - prev.x, height: y - prev.y, visible: Math.abs(x - prev.x) > 4 || Math.abs(y - prev.y) > 4 })));
361
+ break;
362
+ }
363
+ case DrawAction.Scribble: {
364
+ dispatch({
365
+ type: 'SET_SCRIBBLES',
366
+ payload: {
367
+ scribbles: scribbles === null || scribbles === void 0 ? void 0 : scribbles.map(prevScribble => prevScribble.id === id
368
+ ? Object.assign(Object.assign({}, prevScribble), { points: [...prevScribble.points, x, y] }) : prevScribble),
369
+ },
370
+ });
371
+ break;
372
+ }
373
+ case DrawAction.Circle: {
374
+ dispatch({
375
+ type: 'SET_CIRCLES',
376
+ payload: {
377
+ circles: circles === null || circles === void 0 ? void 0 : circles.map(prevCircle => prevCircle.id === id
378
+ ? Object.assign(Object.assign({}, prevCircle), { radius: Math.pow((Math.pow((x - prevCircle.x), 2) + Math.pow((y - prevCircle.y), 2)), 0.5) }) : prevCircle),
379
+ },
380
+ });
381
+ break;
382
+ }
383
+ case DrawAction.Rectangle: {
384
+ dispatch({
385
+ type: 'SET_RECTANGLES',
386
+ payload: {
387
+ rectangles: rectangles === null || rectangles === void 0 ? void 0 : rectangles.map(prevRectangle => prevRectangle.id === id
388
+ ? Object.assign(Object.assign({}, prevRectangle), { height: y - prevRectangle.y, width: x - prevRectangle.x }) : prevRectangle),
389
+ },
390
+ });
391
+ break;
392
+ }
393
+ case DrawAction.Crop: {
394
+ setCropZone(prevRectangle => prevRectangle
395
+ ? Object.assign(Object.assign({}, prevRectangle), { height: y - prevRectangle.y, width: x - prevRectangle.x }) : prevRectangle);
396
+ break;
397
+ }
398
+ case DrawAction.Arrow: {
399
+ dispatch({
400
+ type: 'SET_ARROWS',
401
+ payload: {
402
+ arrows: arrows.map(prevArrow => prevArrow.id === id
403
+ ? Object.assign(Object.assign({}, prevArrow), { points: [prevArrow.points[0], prevArrow.points[1], x, y] }) : prevArrow),
404
+ },
405
+ });
406
+ break;
407
+ }
408
+ }
409
+ }, [drawAction, arrows, circles, rectangles, scribbles]);
410
+ const onStageMouseLeave = useCallback(() => {
411
+ if (selectionPosition.visible) {
412
+ setSelectionPosition(prev => (Object.assign(Object.assign({}, prev), { visible: false })));
413
+ }
414
+ }, [selectionPosition]);
415
+ const onApplyCropClick = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
416
+ var _a;
417
+ if (!cropZone)
418
+ return;
419
+ const dataUri = yield ((_a = stageRef === null || stageRef === void 0 ? void 0 : stageRef.current) === null || _a === void 0 ? void 0 : _a.toDataURL({
420
+ x: (cropZone === null || cropZone === void 0 ? void 0 : cropZone.x) + 2,
421
+ y: (cropZone === null || cropZone === void 0 ? void 0 : cropZone.y) + 2,
422
+ width: (cropZone === null || cropZone === void 0 ? void 0 : cropZone.width) - 4,
423
+ height: (cropZone === null || cropZone === void 0 ? void 0 : cropZone.height) - 4,
424
+ }));
425
+ onClear();
426
+ dataUri && changeImage(dataUri);
427
+ }), [cropZone, changeImage, onClear]);
428
+ const onShapeClick = useCallback((e, id, shape) => {
429
+ var _a;
430
+ if (drawAction !== DrawAction.Select)
431
+ return;
432
+ const { currentTarget } = e || {};
433
+ setSelectedId(id);
434
+ setSelectedShape(shape);
435
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([currentTarget]);
436
+ }, [drawAction]);
437
+ const isDraggable = drawAction === DrawAction.Select;
438
+ const onBgClick = useCallback(() => {
439
+ var _a;
440
+ if (selectionPosition.visible)
441
+ return;
442
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
443
+ }, [selectionPosition.visible]);
444
+ // const getSnapLines = (excludedShape) => {
445
+ const getSnapLines = useCallback(() => {
446
+ const stage = stageRef.current;
447
+ if (!stage)
448
+ return;
449
+ // Center snap lines
450
+ const vertical = [stage.width() / 2];
451
+ const horizontal = [stage.height() / 2];
452
+ // NOTE: 💥💥💥 Don't remove the code below (placeholder for generate snap lines between objects to others)
453
+ // stage.find('.shape').forEach(shape => {
454
+ // if (shape === excludedShape) return;
455
+ // const box = shape.getClientRect({ relativeTo: stage });
456
+ // vertical.push(box.x, box.x + box.width, box.x + box.width / 2);
457
+ // horizontal.push(box.y, box.y + box.height, box.y + box.height / 2);
458
+ // });
459
+ // NOTE: 💥💥💥 Don't remove the code above
460
+ return {
461
+ vertical,
462
+ horizontal,
463
+ };
464
+ }, []);
465
+ const getShapeSnappingEdges = useCallback(() => {
466
+ var _a, _b;
467
+ const stage = stageRef.current;
468
+ const tr = transformerRef.current;
469
+ if (!tr || !stage)
470
+ return;
471
+ const box = ((_a = stage.findOne('.back')) === null || _a === void 0 ? void 0 : _a.getClientRect({ relativeTo: stage })) || {
472
+ x: 0,
473
+ y: 0,
474
+ height: 0,
475
+ width: 0,
476
+ };
477
+ const absPos = ((_b = stage.findOne('.back')) === null || _b === void 0 ? void 0 : _b.absolutePosition()) || { x: 0, y: 0 };
478
+ return {
479
+ vertical: [
480
+ // Left vertical edge
481
+ {
482
+ guide: box.x,
483
+ offset: absPos.x - box.x,
484
+ snap: 'start',
485
+ },
486
+ // Center vertical edge
487
+ {
488
+ guide: box.x + box.width / 2,
489
+ offset: absPos.x - box.x - box.width / 2,
490
+ snap: 'center',
491
+ },
492
+ // Right vertical edge
493
+ {
494
+ guide: box.x + box.width,
495
+ offset: absPos.x - box.x - box.width,
496
+ snap: 'end',
497
+ },
498
+ ],
499
+ horizontal: [
500
+ // Top horizontal edge
501
+ {
502
+ guide: box.y,
503
+ offset: absPos.y - box.y,
504
+ snap: 'start',
505
+ },
506
+ // Center horizontal edge
507
+ {
508
+ guide: box.y + box.height / 2,
509
+ offset: absPos.y - box.y - box.height / 2,
510
+ snap: 'center',
511
+ },
512
+ // Bottom horizontal edge
513
+ {
514
+ guide: box.y + box.height,
515
+ offset: absPos.y - box.y - box.height,
516
+ snap: 'end',
517
+ },
518
+ ],
519
+ };
520
+ }, []);
521
+ const getClosestSnapLines = (possibleSnapLines, shapeSnappingEdges) => {
522
+ const getAllSnapLines = direction => {
523
+ const result = [];
524
+ possibleSnapLines[direction].forEach(snapLine => {
525
+ shapeSnappingEdges[direction].forEach(snappingEdge => {
526
+ const diff = Math.abs(snapLine - snappingEdge.guide);
527
+ // If the distance between the line and the shape is less than the threshold, we will consider it a snapping point.
528
+ if (diff > SNAP_THRESHOLD)
529
+ return;
530
+ const { snap, offset } = snappingEdge;
531
+ result.push({ snapLine, diff, snap, offset });
532
+ });
533
+ });
534
+ return result;
535
+ };
536
+ const resultV = getAllSnapLines('vertical');
537
+ const resultH = getAllSnapLines('horizontal');
538
+ const closestSnapLines = [];
539
+ const getSnapLine = ({ snapLine, offset, snap }, orientation) => {
540
+ return { snapLine, offset, orientation, snap };
541
+ };
542
+ // find closest vertical and horizontal snappping lines
543
+ const [minV] = resultV.sort((a, b) => a.diff - b.diff);
544
+ const [minH] = resultH.sort((a, b) => a.diff - b.diff);
545
+ if (minV)
546
+ closestSnapLines.push(getSnapLine(minV, 'V'));
547
+ if (minH)
548
+ closestSnapLines.push(getSnapLine(minH, 'H'));
549
+ return closestSnapLines;
550
+ };
551
+ const drawLines = (lines = []) => {
552
+ if (lines.length > 0) {
553
+ const lineStyle = {
554
+ stroke: 'rgb(255, 81, 12)',
555
+ strokeWidth: 1,
556
+ name: 'guid-line',
557
+ dash: [4, 6],
558
+ };
559
+ const hLines = [];
560
+ const vLines = [];
561
+ lines.forEach((l) => {
562
+ if (l.orientation === 'H') {
563
+ const line = Object.assign({ points: [-6000, 0, 6000, 0], x: 0, y: l.snapLine }, lineStyle);
564
+ hLines.push(line);
565
+ }
566
+ else if (l.orientation === 'V') {
567
+ const line = Object.assign({ points: [0, -6000, 0, 6000], x: l.snapLine, y: 0 }, lineStyle);
568
+ vLines.push(line);
569
+ }
570
+ });
571
+ // Set state
572
+ setHLines(hLines);
573
+ setVLines(vLines);
574
+ }
575
+ };
576
+ const onTransformerMove = useCallback((target) => {
577
+ // const target = transformerRef.current;
578
+ if (!target)
579
+ return;
580
+ // const [selectedNode] = target.getNodes();
581
+ // if (!selectedNode) return;
582
+ const possibleSnappingLines = getSnapLines();
583
+ // const possibleSnappingLines = getSnapLines(selectedNode);
584
+ const selectedShapeSnappingEdges = getShapeSnappingEdges();
585
+ const closestSnapLines = getClosestSnapLines(possibleSnappingLines, selectedShapeSnappingEdges);
586
+ // Do nothing if no snapping lines
587
+ if (closestSnapLines.length === 0) {
588
+ setHLines([]);
589
+ setVLines([]);
590
+ return;
591
+ }
592
+ // draw the lines
593
+ drawLines(closestSnapLines);
594
+ const orgAbsPos = target.absolutePosition();
595
+ const absPos = target.absolutePosition();
596
+ // Find new position
597
+ closestSnapLines.forEach(l => {
598
+ const position = l.snapLine + l.offset;
599
+ if (l.orientation === 'V') {
600
+ absPos.x = position;
601
+ }
602
+ else if (l.orientation === 'H') {
603
+ absPos.y = position;
604
+ }
605
+ });
606
+ // calculate the difference between original and new position
607
+ const vecDiff = {
608
+ x: orgAbsPos.x - absPos.x,
609
+ y: orgAbsPos.y - absPos.y,
610
+ };
611
+ // apply the difference to the selected shape.
612
+ const nodeAbsPos = target.getAbsolutePosition();
613
+ // const nodeAbsPos = selectedNode.getAbsolutePosition();
614
+ const newPos = {
615
+ x: nodeAbsPos.x - vecDiff.x,
616
+ y: nodeAbsPos.y - vecDiff.y,
617
+ };
618
+ target.setAbsolutePosition(newPos);
619
+ // selectedNode.setAbsolutePosition(newPos);
620
+ }, []);
621
+ return (React.createElement("div", { style: { width: `${width}px` } },
622
+ React.createElement(Flex, { justify: "space-between", align: "center", style: { marginBottom: '6px', display: hideAction ? 'none' : 'flex' } },
623
+ React.createElement(Flex, { gap: 4 },
624
+ PAINT_OPTIONS.map(({ id, label, icon }) => (React.createElement(Button, { key: id, "aria-label": label, icon: React.createElement(Icon, { type: icon, style: { fontSize: '16px' } }), onClick: () => {
625
+ var _a;
626
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
627
+ setCropZone(undefined);
628
+ setDrawAction(id);
629
+ }, type: id === drawAction ? 'primary' : 'default' }))),
630
+ React.createElement(Popover, { content: React.createElement(SketchPicker, { color: color, onChangeComplete: selectedColor => setColor(selectedColor.hex) }) },
631
+ React.createElement("div", { style: {
632
+ backgroundColor: color,
633
+ height: '30px',
634
+ width: '30px',
635
+ borderRadius: '3px',
636
+ cursor: 'pointer',
637
+ } })),
638
+ React.createElement(Button, { "aria-label": "Clear", icon: React.createElement(Icon, { type: "icon-ants-remove-circle", style: { fontSize: '16px' } }), onClick: onClear })),
639
+ React.createElement(Flex, { gap: 4, align: "center", style: { height: '100%' } },
640
+ React.createElement(Button, { "aria-label": "Help", icon: React.createElement(Icon, { type: "icon-ants-help", style: { fontSize: '16px' } }), onClick: () => setOpenHelper(true) }),
641
+ React.createElement("input", { type: "file", ref: fileRef, onChange: onImportImageSelect, style: { display: 'none' }, accept: "image/*" }),
642
+ React.createElement(Button, { onClick: onImportImageClick }, "Import Image"),
643
+ React.createElement(Button, { onClick: onExportClick }, "Export"))),
644
+ hideAction ? null : (React.createElement(Flex, { gap: 4 },
645
+ React.createElement(Button, { onClick: () => {
646
+ var _a;
647
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
648
+ setDrawAction(DrawAction.Crop);
649
+ }, type: DrawAction.Crop === drawAction ? 'primary' : 'default' }, "Crop"),
650
+ DrawAction.Crop === drawAction ? (React.createElement(React.Fragment, null,
651
+ React.createElement(Button, { onClick: () => setCropZone(undefined), danger: true }, "Cancel Crop"),
652
+ React.createElement(Button, { onClick: () => onApplyCropClick() }, "Apply Crop"))) : null,
653
+ React.createElement(Button, { onClick: () => {
654
+ var _a;
655
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
656
+ dispatch({ type: 'UNDO' });
657
+ }, style: { marginLeft: 'auto' } }, "Undo"),
658
+ React.createElement(Button, { onClick: () => {
659
+ var _a;
660
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
661
+ dispatch({ type: 'REDO' });
662
+ } }, "Redo"))),
663
+ React.createElement(StageWrapperStyled, { "$width": width, "$height": height, className: className || '' },
664
+ React.createElement(Stage, { id: "image-editor", width: width, height: height, ref: stageRef, onMouseUp: onStageMouseUp, onMouseDown: onStageMouseDown, onMouseMove: onStageMouseMove, onMouseLeave: onStageMouseLeave },
665
+ React.createElement(Layer, null,
666
+ React.createElement(KonvaRect, { x: 0, y: 0, height: width, width: height, fill: "transparent", id: "bg", onClick: onBgClick }),
667
+ image && (React.createElement(KonvaImage, Object.assign({ image: image }, imageCoordinate, { name: "shape", onClick: e => onShapeClick(e, imageCoordinate.id, ShapeObject.Image), onTap: e => onShapeClick(e, imageCoordinate.id, ShapeObject.Image), draggable: isDraggable, onDragEnd: ({ target: { attrs } }) => {
668
+ if (isDraggable)
669
+ dispatch({
670
+ type: 'SET_IMAGE_COORDINATE',
671
+ payload: {
672
+ imageCoordinate: Object.assign(Object.assign({}, imageCoordinate), { x: attrs.x, y: attrs.y }),
673
+ saveHistory: true,
674
+ },
675
+ });
676
+ }, onTransformEnd: e => {
677
+ dispatch({
678
+ type: 'SET_IMAGE_COORDINATE',
679
+ payload: {
680
+ imageCoordinate: Object.assign(Object.assign(Object.assign({}, imageCoordinate), { width: e.target.width(), height: e.target.height() }), getTransformData(e)),
681
+ saveHistory: true,
682
+ },
683
+ });
684
+ } }))),
685
+ arrows.map(arrow => (React.createElement(KonvaArrow, Object.assign({ key: arrow.id }, arrow, { name: "shape", fill: arrow.color, stroke: arrow.color, strokeWidth: 4, onClick: e => onShapeClick(e, arrow.id, ShapeObject.Arrows), onTap: e => onShapeClick(e, arrow.id, ShapeObject.Arrows), draggable: isDraggable, strokeScaleEnabled: false, onDragEnd: ({ target: { attrs } }) => {
686
+ if (isDraggable)
687
+ dispatch({
688
+ type: 'SET_ARROWS',
689
+ payload: {
690
+ arrows: arrows.map(item => item.id === arrow.id ? Object.assign(Object.assign({}, item), { x: attrs.x, y: attrs.y }) : item),
691
+ saveHistory: true,
692
+ },
693
+ });
694
+ }, onTransformEnd: e => {
695
+ dispatch({
696
+ type: 'SET_ARROWS',
697
+ payload: {
698
+ arrows: arrows.map(item => item.id === arrow.id
699
+ ? Object.assign(Object.assign(Object.assign({}, item), { points: e.target.attrs.points }), getTransformData(e)) : item),
700
+ saveHistory: true,
701
+ },
702
+ });
703
+ } })))),
704
+ rectangles.map(rectangle => (React.createElement(KonvaRect, Object.assign({ key: rectangle.id }, rectangle, { name: "shape", stroke: rectangle === null || rectangle === void 0 ? void 0 : rectangle.color, strokeWidth: 4, onClick: e => onShapeClick(e, rectangle.id, ShapeObject.Rectangles), onTap: e => onShapeClick(e, rectangle.id, ShapeObject.Rectangles), draggable: isDraggable, strokeScaleEnabled: false, onDragEnd: ({ target: { attrs } }) => {
705
+ if (isDraggable)
706
+ dispatch({
707
+ type: 'SET_RECTANGLES',
708
+ payload: {
709
+ rectangles: rectangles.map(item => item.id === rectangle.id ? Object.assign(Object.assign({}, item), { x: attrs.x, y: attrs.y }) : item),
710
+ saveHistory: true,
711
+ },
712
+ });
713
+ }, onTransformEnd: e => {
714
+ dispatch({
715
+ type: 'SET_RECTANGLES',
716
+ payload: {
717
+ rectangles: rectangles.map(item => item.id === rectangle.id
718
+ ? Object.assign(Object.assign(Object.assign({}, item), { width: e.target.width(), height: e.target.height() }), getTransformData(e)) : item),
719
+ saveHistory: true,
720
+ },
721
+ });
722
+ } })))),
723
+ circles.map(circle => (React.createElement(KonvaCircle, Object.assign({ key: circle.id }, circle, { name: "shape", stroke: circle === null || circle === void 0 ? void 0 : circle.color, strokeWidth: 4, onClick: e => onShapeClick(e, circle.id, ShapeObject.Circles), onTap: e => onShapeClick(e, circle.id, ShapeObject.Circles), draggable: isDraggable, strokeScaleEnabled: false, onDragEnd: ({ target: { attrs } }) => {
724
+ if (isDraggable)
725
+ dispatch({
726
+ type: 'SET_CIRCLES',
727
+ payload: {
728
+ circles: circles.map(item => item.id === circle.id ? Object.assign(Object.assign({}, item), { x: attrs.x, y: attrs.y }) : item),
729
+ saveHistory: true,
730
+ },
731
+ });
732
+ }, onTransformEnd: e => {
733
+ dispatch({
734
+ type: 'SET_CIRCLES',
735
+ payload: {
736
+ circles: circles.map(item => item.id === circle.id
737
+ ? Object.assign(Object.assign(Object.assign({}, item), { radius: e.target.attrs.radius }), getTransformData(e)) : item),
738
+ saveHistory: true,
739
+ },
740
+ });
741
+ } })))),
742
+ scribbles.map(scribble => (React.createElement(KonvaLine, Object.assign({ key: scribble.id }, scribble, { name: "shape", lineCap: "round", lineJoin: "round", stroke: scribble === null || scribble === void 0 ? void 0 : scribble.color, strokeWidth: 4, onClick: e => onShapeClick(e, scribble.id, ShapeObject.Scribbles), onTap: e => onShapeClick(e, scribble.id, ShapeObject.Scribbles), draggable: isDraggable, strokeScaleEnabled: false, onDragEnd: ({ target: { attrs } }) => {
743
+ if (isDraggable)
744
+ dispatch({
745
+ type: 'SET_SCRIBBLES',
746
+ payload: {
747
+ scribbles: scribbles.map(item => item.id === scribble.id ? Object.assign(Object.assign({}, item), { x: attrs.x, y: attrs.y }) : item),
748
+ saveHistory: true,
749
+ },
750
+ });
751
+ }, onTransformEnd: e => {
752
+ dispatch({
753
+ type: 'SET_SCRIBBLES',
754
+ payload: {
755
+ scribbles: scribbles.map(item => item.id === scribble.id
756
+ ? Object.assign(Object.assign(Object.assign({}, item), { points: e.target.attrs.points }), getTransformData(e)) : item),
757
+ saveHistory: true,
758
+ },
759
+ });
760
+ } })))),
761
+ cropZone ? (React.createElement(KonvaRect, Object.assign({}, cropZone, { dashEnabled: true, dash: [6], stroke: "red", strokeWidth: 2 }))) : null,
762
+ showSelection ? (React.createElement(KonvaRect, Object.assign({}, selectionPosition, { ref: selectionRectangleRef, fill: "rgba(0,0,200,0.4)" }))) : null,
763
+ React.createElement(Transformer, { ref: transformerRef, ignoreStroke: true, onDragMove: e => onTransformerMove(e.target), onDragEnd: () => {
764
+ setHLines([]);
765
+ setVLines([]);
766
+ } }),
767
+ hLines.map((item, i) => (React.createElement(KonvaLine, Object.assign({ key: i }, item)))),
768
+ vLines.map((item, i) => (React.createElement(KonvaLine, Object.assign({ key: i }, item)))))),
769
+ !hideAction && DrawAction.Select === drawAction && selectedId ? (React.createElement(Flex, { style: { marginLeft: '16px' } },
770
+ React.createElement(Flex, { vertical: true, gap: 4 },
771
+ React.createElement(Button, { onClick: () => removeOrDuplicateShape(), icon: React.createElement(Icon, { type: "icon-ants-outline-delete" }), danger: true }),
772
+ React.createElement(Button, { onClick: () => removeOrDuplicateShape(true), icon: React.createElement(Icon, { type: "icon-ants-copy-report" }) })))) : null,
773
+ React.createElement(ModalShortcut, { open: openHelper, onCancel: () => setOpenHelper(false) }))));
774
+ });