jellies-draw 0.2.1 → 0.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jellies-draw",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "A drawer for jellies",
5
5
  "private": false,
6
6
  "main": "./src/index.js",
@@ -202,34 +202,35 @@ export default {
202
202
 
203
203
  const target = e.target;
204
204
 
205
- // Reset cursor to default when over stage
206
205
  if (target === this.stage) {
207
206
  this.stage.container().style.cursor = 'default';
208
207
  return;
209
208
  }
210
209
 
211
- // Handle cursor for nodes
212
210
  if (target.hasName('node')) {
213
- if (this._shouldShowMoveCursor(target)) {
214
- this.stage.container().style.cursor = 'move';
215
- } else {
216
- this.stage.container().style.cursor = 'default';
211
+ const mousePos = this.stage.getPointerPosition();
212
+ const canInteract = this._canInteractWithNode(target, mousePos);
213
+
214
+ this.stage.container().style.cursor = canInteract ? 'move' : 'default';
215
+
216
+ if (!canInteract) {
217
+ target.draggable(false);
218
+ } else if (!Properties.isUsingDrawingTool) {
219
+ target.draggable(true);
217
220
  }
218
221
  }
219
222
  },
220
- _shouldShowMoveCursor(node) {
221
- // Always show move cursor for non-shape nodes
222
- if (!['Rect', 'Circle', 'Ellipse'].includes(node.className)) {
223
+ _canInteractWithNode(node, mousePos) {
224
+ const selectedNodes = Transformer._nodes();
225
+ if (selectedNodes.includes(node)) {
223
226
  return true;
224
227
  }
225
228
 
226
- // Show move cursor for filled shapes
227
- if (node.attrs.fill && node.attrs.fill !== 'transparent') {
229
+ if (!['Rect', 'Ellipse'].includes(node.className) ||
230
+ (node.attrs.fill && node.attrs.fill !== 'transparent')) {
228
231
  return true;
229
232
  }
230
-
231
- // For unfilled shapes, only show move cursor near border
232
- const mousePos = this.stage.getPointerPosition();
233
+
233
234
  return Transformer._isNearBorder(node, mousePos);
234
235
  }
235
236
  }
@@ -2,6 +2,8 @@ import Konva from 'konva'
2
2
  import Properties from '../properties'
3
3
  import Canvas from '../canvas'
4
4
  import Histories from '../histories'
5
+ import AngleSnap from './helper/angleSnap'
6
+
5
7
  export default {
6
8
  temporalShape: null,
7
9
  startX: 0,
@@ -24,9 +26,16 @@ export default {
24
26
  this.temporalShape = newArrow
25
27
  Canvas.layer.add(this.temporalShape)
26
28
  },
27
- change({ offsetX, offsetY }) {
29
+ change({ offsetX, offsetY, shiftKey }) {
28
30
  if (Canvas.isDrawing) {
29
- this.temporalShape.points([this.startX, this.startY, offsetX, offsetY]);
31
+ const endPoint = AngleSnap.getSnappedEndPoint(
32
+ this.startX,
33
+ this.startY,
34
+ offsetX,
35
+ offsetY,
36
+ shiftKey
37
+ );
38
+ this.temporalShape.points([this.startX, this.startY, endPoint.x, endPoint.y]);
30
39
  }
31
40
  },
32
41
  finish() {
@@ -2,6 +2,8 @@ import Konva from 'konva'
2
2
  import Properties from '../properties'
3
3
  import Canvas from '../canvas'
4
4
  import Histories from '../histories'
5
+ import ShapeConstraint from './helper/shapeConstraint'
6
+
5
7
  export default {
6
8
  temporalShape: null,
7
9
  startX: 0,
@@ -26,12 +28,19 @@ export default {
26
28
  this.temporalShape = newEllipse
27
29
  Canvas.layer.add(this.temporalShape)
28
30
  },
29
- change({ offsetX, offsetY }) {
31
+ change({ offsetX, offsetY, shiftKey }) {
30
32
  if (Canvas.isDrawing) {
33
+ const { width, height } = ShapeConstraint.getConstrainedSize(
34
+ this.startX,
35
+ this.startY,
36
+ offsetX,
37
+ offsetY,
38
+ shiftKey
39
+ );
31
40
  this.temporalShape.x((offsetX + this.startX) / 2);
32
41
  this.temporalShape.y((offsetY + this.startY) / 2);
33
- this.temporalShape.radiusX(Math.abs(offsetX - this.startX) / 2);
34
- this.temporalShape.radiusY(Math.abs(offsetY - this.startY) / 2);
42
+ this.temporalShape.radiusX(Math.abs(width) / 2);
43
+ this.temporalShape.radiusY(Math.abs(height) / 2);
35
44
  }
36
45
  },
37
46
  finish() {
@@ -107,7 +107,6 @@ export default {
107
107
  case 'Line':
108
108
  case 'Arrow':
109
109
  return this._checkLineIntersection(node, point)
110
- case 'Circle':
111
110
  case 'Ellipse':
112
111
  return this._checkEllipseIntersection(node, point)
113
112
  case 'Rect':
@@ -0,0 +1,29 @@
1
+ export default {
2
+ getAngle(startX, startY, endX, endY) {
3
+ return Math.atan2(endY - startY, endX - startX);
4
+ },
5
+
6
+ snapAngle(angle) {
7
+ let degrees = (angle * 180) / Math.PI;
8
+ degrees = (degrees + 360) % 360;
9
+ const snappedDegrees = Math.round(degrees / 15) * 15;
10
+ return (snappedDegrees * Math.PI) / 180;
11
+ },
12
+
13
+ getSnappedEndPoint(startX, startY, endX, endY, isShiftPressed) {
14
+ if (!isShiftPressed) {
15
+ return { x: endX, y: endY };
16
+ }
17
+
18
+ const angle = this.getAngle(startX, startY, endX, endY);
19
+ const snappedAngle = this.snapAngle(angle);
20
+ const distance = Math.sqrt(
21
+ Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2)
22
+ );
23
+
24
+ return {
25
+ x: startX + distance * Math.cos(snappedAngle),
26
+ y: startY + distance * Math.sin(snappedAngle)
27
+ };
28
+ }
29
+ }
@@ -0,0 +1,19 @@
1
+ export default {
2
+ getConstrainedSize(startX, startY, currentX, currentY, isShiftPressed) {
3
+ if (!isShiftPressed) {
4
+ return {
5
+ width: currentX - startX,
6
+ height: currentY - startY
7
+ };
8
+ }
9
+
10
+ const width = currentX - startX;
11
+ const height = currentY - startY;
12
+ const size = Math.max(Math.abs(width), Math.abs(height));
13
+
14
+ return {
15
+ width: size * Math.sign(width),
16
+ height: size * Math.sign(height)
17
+ };
18
+ }
19
+ }
@@ -2,6 +2,8 @@ import Konva from 'konva'
2
2
  import Properties from '../properties'
3
3
  import Canvas from '../canvas'
4
4
  import Histories from '../histories'
5
+ import AngleSnap from './helper/angleSnap'
6
+
5
7
  export default {
6
8
  temporalShape: null,
7
9
  startX: 0,
@@ -20,9 +22,16 @@ export default {
20
22
  this.temporalShape = newLine
21
23
  Canvas.layer.add(this.temporalShape)
22
24
  },
23
- change({ offsetX, offsetY }) {
25
+ change({ offsetX, offsetY, shiftKey }) {
24
26
  if (Canvas.isDrawing) {
25
- this.temporalShape.points([this.startX, this.startY, offsetX, offsetY]);
27
+ const endPoint = AngleSnap.getSnappedEndPoint(
28
+ this.startX,
29
+ this.startY,
30
+ offsetX,
31
+ offsetY,
32
+ shiftKey
33
+ );
34
+ this.temporalShape.points([this.startX, this.startY, endPoint.x, endPoint.y]);
26
35
  }
27
36
  },
28
37
  finish() {
@@ -2,6 +2,8 @@ import Konva from 'konva'
2
2
  import Properties from '../properties'
3
3
  import Canvas from '../canvas'
4
4
  import Histories from '../histories'
5
+ import ShapeConstraint from './helper/shapeConstraint'
6
+
5
7
  export default {
6
8
  temporalShape: null,
7
9
  startX: 0,
@@ -26,10 +28,17 @@ export default {
26
28
  this.temporalShape = newRectangle
27
29
  Canvas.layer.add(this.temporalShape)
28
30
  },
29
- change({ offsetX, offsetY }) {
31
+ change({ offsetX, offsetY, shiftKey }) {
30
32
  if (Canvas.isDrawing) {
31
- this.temporalShape.width(offsetX - this.startX)
32
- this.temporalShape.height(offsetY - this.startY)
33
+ const { width, height } = ShapeConstraint.getConstrainedSize(
34
+ this.startX,
35
+ this.startY,
36
+ offsetX,
37
+ offsetY,
38
+ shiftKey
39
+ );
40
+ this.temporalShape.width(width);
41
+ this.temporalShape.height(height);
33
42
  }
34
43
  },
35
44
  finish() {
@@ -11,6 +11,15 @@ export default {
11
11
  },
12
12
  initialize() {
13
13
  this.transformer = new Konva.Transformer();
14
+
15
+ // 创建一个透明的拖动区域
16
+ this.dragArea = new Konva.Rect({
17
+ fill: 'transparent',
18
+ draggable: true,
19
+ listening: true
20
+ });
21
+
22
+ Canvas.layer.add(this.dragArea);
14
23
  Canvas.layer.add(this.transformer);
15
24
  },
16
25
  copySelectedNodes() {
@@ -68,20 +77,22 @@ export default {
68
77
  this._selectLinearNode(targetNodes[0]);
69
78
  } else {
70
79
  this._nodes(targetNodes);
71
- if (targetNodes.length === 1) {
72
- this.transformer.enabledAnchors(['top-left', 'top-center', 'top-right', 'middle-right', 'middle-left', 'bottom-left', 'bottom-center', 'bottom-right']);
80
+ if (targetNodes.length > 0) {
81
+ this._updateDragArea(targetNodes);
82
+ this._updateTransformerAnchors(targetNodes);
73
83
  } else {
74
- this.transformer.enabledAnchors(['top-left', 'top-right', 'bottom-left', 'bottom-right']);
84
+ this.dragArea.visible(false);
75
85
  }
76
- Properties.isTextNodesSelected = this._hasSelectedOnlyTextNodes()
86
+ Properties.isTextNodesSelected = this._hasSelectedOnlyTextNodes();
77
87
  }
78
88
  Properties.setProperties(this._getCommonPropertiesOfSelectedNodes());
79
89
  },
80
90
  deselectAllNodes() {
91
+ this.dragArea.visible(false);
81
92
  this.selectNodes([]);
82
93
  if (this.linearTransformer.node) {
83
94
  this.linearTransformer.node.destroy();
84
- this._removeLinearTransformer()
95
+ this._removeLinearTransformer();
85
96
  }
86
97
  },
87
98
  selectAllNodes() {
@@ -217,7 +228,7 @@ export default {
217
228
  return false;
218
229
  },
219
230
  _shouldSelectNode(node) {
220
- if ((node.className === 'Rect' || node.className === 'Circle' || node.className === 'Ellipse') &&
231
+ if ((node.className === 'Rect' || node.className === 'Ellipse') &&
221
232
  (!node.attrs.fill || node.attrs.fill === 'transparent')) {
222
233
  const stage = Canvas.stage;
223
234
  const mousePos = stage.getPointerPosition();
@@ -226,33 +237,64 @@ export default {
226
237
  return true;
227
238
  },
228
239
  _isNearBorder(node, point) {
229
- const localPoint = node.getAbsoluteTransform().copy().invert().point(point);
230
- const threshold = Properties.strokeWidth * 2;
240
+ const transform = node.getAbsoluteTransform().copy();
241
+ const localPoint = transform.invert().point(point);
242
+
243
+ const threshold = node.attrs.strokeWidth * 2;
231
244
 
232
245
  if (node.className === 'Rect') {
233
246
  const width = node.width();
234
247
  const height = node.height();
235
248
 
236
- return (Math.abs(localPoint.x) < threshold ||
237
- Math.abs(localPoint.x - width) < threshold ||
238
- Math.abs(localPoint.y) < threshold ||
239
- Math.abs(localPoint.y - height) < threshold) &&
240
- localPoint.x >= -threshold &&
241
- localPoint.x <= width + threshold &&
242
- localPoint.y >= -threshold &&
243
- localPoint.y <= height + threshold;
244
- } else if (node.className === 'Circle' || node.className === 'Ellipse') {
245
- const radiusX = node.radiusX ? node.radiusX() : node.radius();
246
- const radiusY = node.radiusY ? node.radiusY() : node.radius();
249
+ const nearHorizontalEdge = (localPoint.y >= -threshold && localPoint.y <= threshold) ||
250
+ (localPoint.y >= height - threshold && localPoint.y <= height + threshold);
251
+ const nearVerticalEdge = (localPoint.x >= -threshold && localPoint.x <= threshold) ||
252
+ (localPoint.x >= width - threshold && localPoint.x <= width + threshold);
253
+
254
+ return (nearHorizontalEdge && localPoint.x >= -threshold && localPoint.x <= width + threshold) ||
255
+ (nearVerticalEdge && localPoint.y >= -threshold && localPoint.y <= height + threshold);
256
+ }
257
+
258
+ if (node.className === 'Ellipse') {
259
+ const radiusX = node.radiusX();
260
+ const radiusY = node.radiusY();
247
261
 
248
262
  const normalizedX = localPoint.x / radiusX;
249
263
  const normalizedY = localPoint.y / radiusY;
264
+ const distanceFromCenter = Math.sqrt(normalizedX * normalizedX + normalizedY * normalizedY);
250
265
 
251
- const distanceFromCenter = Math.sqrt(
252
- normalizedX * normalizedX + normalizedY * normalizedY
253
- );
254
266
  return Math.abs(distanceFromCenter - 1) < threshold / Math.min(radiusX, radiusY);
255
267
  }
268
+
256
269
  return true;
270
+ },
271
+ _updateDragArea(targetNodes) {
272
+ const box = this.transformer.getClientRect();
273
+ this.dragArea.setAttrs({
274
+ x: box.x,
275
+ y: box.y,
276
+ width: box.width,
277
+ height: box.height,
278
+ visible: true
279
+ });
280
+
281
+ this.dragArea.off('dragmove');
282
+ this.dragArea.on('dragmove', () => {
283
+ const dx = this.dragArea.x() - box.x;
284
+ const dy = this.dragArea.y() - box.y;
285
+ targetNodes.forEach(node => {
286
+ node.x(node.x() + dx);
287
+ node.y(node.y() + dy);
288
+ });
289
+ box.x = this.dragArea.x();
290
+ box.y = this.dragArea.y();
291
+ });
292
+ },
293
+ _updateTransformerAnchors(targetNodes) {
294
+ if (targetNodes.length === 1) {
295
+ this.transformer.enabledAnchors(['top-left', 'top-center', 'top-right', 'middle-right', 'middle-left', 'bottom-left', 'bottom-center', 'bottom-right']);
296
+ } else {
297
+ this.transformer.enabledAnchors(['top-left', 'top-right', 'bottom-left', 'bottom-right']);
298
+ }
257
299
  }
258
300
  }