-
Notifications
You must be signed in to change notification settings - Fork 3
/
CanvasItem.jsx
146 lines (134 loc) · 3.71 KB
/
CanvasItem.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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
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
import * as React from "react";
import { centerToTL, degToRadian, getNewStyle, tLToCenter } from "./utils";
import Rect from "./Rect";
/**
* CanvasItem is a component that can be used for as part of a drawing application. It includes features such as resize, rotate, and drag.
*/
export default function CanvasItem({
top,
left,
width,
height,
minHeight = 30,
minWidth = 30,
rotateAngle = 0,
resizeDirection = [],
onRotate,
onResize,
onDrag,
onClick,
aspectRatio,
children,
selected,
zIndex = 0
}) {
/**
* Callback for item being rotated. Handles snapping to major angles within 4 degrees, and normalizes angle to be between 0 and 360.
* @param {number} angle Delta angle in degrees from the original rotation angle
* @param {number} startAngle Original rotation angle
*/
const handleRotate = (angle, startAngle) => {
// return if no callback
if (!onRotate) return;
// normalize angle to be between 0 and 360
let rotateAngle = Math.round(startAngle + angle);
if (rotateAngle >= 360) {
rotateAngle -= 360;
} else if (rotateAngle < 0) {
rotateAngle += 360;
}
// snap to major angles within 4 degrees
if (rotateAngle > 356 || rotateAngle < 4) {
rotateAngle = 0;
} else if (rotateAngle > 86 && rotateAngle < 94) {
rotateAngle = 90;
} else if (rotateAngle > 176 && rotateAngle < 184) {
rotateAngle = 180;
} else if (rotateAngle > 266 && rotateAngle < 274) {
rotateAngle = 270;
}
// call callback
onRotate(rotateAngle);
};
/**
* Callback for item being resized. Converts user input to a top/left origin rectangle and calls user callback.
* @param {number} length Distance of the user mouse movement
* @param {number} alpha Angle of the users mouse movement
* @param {object} rect Original rectangle
* @param {string} type Resize handle type
* @param {boolean} isShiftKey Is the shift key pressed
* @returns
*/
const handleResize = (length, alpha, rect, type, isShiftKey) => {
// return if no callback
if (!onResize) return;
// apply resize handle drag changes to the item rectangle
const beta = alpha - degToRadian(rotateAngle);
const deltaW = length * Math.cos(beta);
const deltaH = length * Math.sin(beta);
const ratio =
isShiftKey && !aspectRatio ? rect.width / rect.height : aspectRatio;
const {
position: { centerX, centerY },
size: { width, height }
} = getNewStyle(
type,
{ ...rect, rotateAngle },
deltaW,
deltaH,
ratio,
minWidth,
minHeight
);
// call callback with top/left origin rectangle
onResize(
centerToTL({
centerX,
centerY,
height,
rotateAngle,
width
}),
isShiftKey,
type
);
};
/**
* Callback for item being dragged. Converts user input to a top/left origin rectangle and calls user callback.
* @param {number} centerX New center x coordinate
* @param {number} centerY New center y coordinate
*/
const handleDrag = (centerX, centerY) => {
const { top, left } = centerToTL({
centerX,
centerY,
height,
rotateAngle,
width
});
onDrag && onDrag(top, left);
};
// convert top/left origin rectangle to center origin rectangle. it makes the math easier
const styles = tLToCenter({
height,
left,
rotateAngle,
top,
width
});
return (
<Rect
selected={selected}
styles={styles}
resizeDirection={resizeDirection}
rotatable={Boolean(onRotate)}
onResize={handleResize}
onRotate={handleRotate}
onDrag={handleDrag}
onClick={onClick}
zIndex={zIndex}
>
{children}
</Rect>
);
}