Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
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
import type { KonvaEventObject } from "konva/lib/Node";
import type { Box, Transformer } from "konva/lib/shapes/Transformer";
import type { WorkingArea } from "./types";
 
// define several math function
export const getCorner = (pivotX: number, pivotY: number, diffX: number, diffY: number, angle: number) => {
  const distance = Math.sqrt(diffX * diffX + diffY * diffY);
 
  /// find angle from pivot to corner
  angle += Math.atan2(diffY, diffX);
 
  /// get new x and y and round it off to integer
  const x = pivotX + distance * Math.cos(angle);
  const y = pivotY + distance * Math.sin(angle);
 
  return { x, y };
};
 
export const getClientRect = (rotatedBox: Box) => {
  const { x, y, width, height } = rotatedBox;
  const rad = rotatedBox.rotation;
 
  const p1 = getCorner(x, y, 0, 0, rad);
  const p2 = getCorner(x, y, width, 0, rad);
  const p3 = getCorner(x, y, width, height, rad);
  const p4 = getCorner(x, y, 0, height, rad);
 
  const minX = Math.min(p1.x, p2.x, p3.x, p4.x);
  const minY = Math.min(p1.y, p2.y, p3.y, p4.y);
  const maxX = Math.max(p1.x, p2.x, p3.x, p4.x);
  const maxY = Math.max(p1.y, p2.y, p3.y, p4.y);
 
  return {
    x: minX,
    y: minY,
    width: maxX - minX,
    height: maxY - minY,
  };
};
 
export const getCommonBBox = (
  boxes: {
    x: number;
    y: number;
    width: number;
    height: number;
  }[],
) => {
  let minX = Number.POSITIVE_INFINITY;
  let minY = Number.POSITIVE_INFINITY;
  let maxX = Number.NEGATIVE_INFINITY;
  let maxY = Number.NEGATIVE_INFINITY;
 
  boxes.forEach((box) => {
    minX = Math.min(minX, box.x);
    minY = Math.min(minY, box.y);
    maxX = Math.max(maxX, box.x + box.width);
    maxY = Math.max(maxY, box.y + box.height);
  });
 
  return {
    x: minX,
    y: minY,
    width: maxX - minX,
    height: maxY - minY,
  };
};
 
export const createBoundingBoxGetter =
  (workingArea: WorkingArea, enabled = true) =>
  (oldBox: Box, newBox: Box) => {
    if (!enabled) return newBox;
 
    const box = getClientRect(newBox);
    const result = { ...newBox };
 
    const edgeReached = [
      box.x <= workingArea.x, // x0
      box.y <= workingArea.y, // y0
      box.x + box.width >= workingArea.x + workingArea.width, // x1
      box.y + box.height >= workingArea.y + workingArea.height, // y1
    ];
 
    // If any edge is caught, stop the movement
    if (edgeReached.some(Boolean)) {
      return oldBox;
    }
 
    return result;
  };
 
export const createOnDragMoveHandler = (workingArea: WorkingArea, enabled = true) =>
  function (this: Transformer, e: KonvaEventObject<DragEvent>) {
    if (!enabled) return;
 
    const nodes = this?.nodes ? this.nodes() : [e.target];
    const boxes = nodes.map((node) => node.getClientRect());
    const box = getCommonBBox(boxes);
 
    nodes.forEach((shape) => {
      const absPos = shape.getAbsolutePosition();
      // where are shapes inside bounding box of all shapes?
      const offsetX = box.x - workingArea.x - absPos.x;
      const offsetY = box.y - workingArea.y - absPos.y;
 
      // we total box goes outside of viewport, we need to move absolute position of shape
      const newAbsPos = { ...absPos };
 
      if (box.x - workingArea.x < 0) {
        newAbsPos.x = -offsetX;
      }
      if (box.y - workingArea.y < 0) {
        newAbsPos.y = -offsetY;
      }
      if (box.x - workingArea.x + box.width > workingArea.width) {
        newAbsPos.x = workingArea.width - box.width - offsetX;
      }
      if (box.y - workingArea.y + box.height > workingArea.height) {
        newAbsPos.y = workingArea.height - box.height - offsetY;
      }
      shape.setAbsolutePosition(newAbsPos);
    });
  };