seat-editor 2.1.2 → 3.0.0
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/dist/app/constant.d.ts +232 -0
- package/dist/app/constant.js +3683 -3045
- package/dist/app/new-board/page.js +5 -6
- package/dist/app/only-view/chair.d.ts +1 -0
- package/dist/app/only-view/chair.js +4 -0
- package/dist/app/only-view/constant.d.ts +22 -2
- package/dist/app/only-view/constant.js +4 -4
- package/dist/app/only-view/page.js +74 -37
- package/dist/app/only-view/user.d.ts +1 -0
- package/dist/app/only-view/user.js +4 -0
- package/dist/components/layer-v3/index.d.ts +23 -4
- package/dist/components/layer-v3/index.js +329 -146
- package/dist/components/layer-v4/index.d.ts +20 -0
- package/dist/components/layer-v4/index.js +445 -0
- package/dist/components/lib/index.d.ts +1 -1
- package/dist/components/lib/index.js +1 -1
- package/dist/features/board/index.js +1 -1
- package/dist/features/board-v2/index.js +1 -1
- package/dist/features/board-v3/board-slice.d.ts +1 -0
- package/dist/features/board-v3/board-slice.js +26 -3
- package/dist/features/board-v3/constant.d.ts +5 -0
- package/dist/features/board-v3/constant.js +5 -0
- package/dist/features/board-v3/index copy.d.ts +47 -0
- package/dist/features/board-v3/index copy.js +2073 -0
- package/dist/features/board-v3/index.js +1409 -647
- package/dist/features/board-v3/polygon.d.ts +28 -0
- package/dist/features/board-v3/polygon.js +109 -0
- package/dist/features/board-v3/rect.d.ts +9 -0
- package/dist/features/board-v3/rect.js +152 -0
- package/dist/features/board-v3/resize-element.d.ts +12 -0
- package/dist/features/board-v3/resize-element.js +40 -0
- package/dist/features/board-v3/utils.d.ts +162 -0
- package/dist/features/board-v3/utils.js +787 -0
- package/dist/features/package/index.js +1 -1
- package/dist/features/panel/index.js +130 -20
- package/dist/features/panel/panel-slice.d.ts +5 -0
- package/dist/features/panel/panel-slice.js +15 -0
- package/dist/features/panel/select-tool.js +11 -1
- package/dist/features/panel/selected-group.d.ts +2 -0
- package/dist/features/panel/selected-group.js +7 -0
- package/dist/features/panel/table-seat-square.d.ts +2 -0
- package/dist/features/panel/table-seat-square.js +9 -0
- package/dist/features/side-tool/index.js +13 -6
- package/dist/features/view-only/index.js +0 -1
- package/dist/features/view-only-2/index.js +0 -1
- package/dist/features/view-only-3/index.d.ts +68 -0
- package/dist/features/view-only-3/index.js +510 -0
- package/dist/features/view-only-3/utils.d.ts +1 -0
- package/dist/features/view-only-3/utils.js +3 -0
- package/dist/seat-editor.css +1 -1
- package/dist/utils/constant.d.ts +1 -0
- package/dist/utils/constant.js +11 -0
- package/dist/utils/format.d.ts +2 -0
- package/dist/utils/format.js +29 -0
- package/package.json +3 -1
- package/dist/features/view/index.d.ts +0 -19
- package/dist/features/view/index.js +0 -221
|
@@ -0,0 +1,2073 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
4
|
+
import { TransformWrapper, TransformComponent, } from "react-zoom-pan-pinch";
|
|
5
|
+
import { useAppSelector, useAppDispatch } from "../../hooks/use-redux";
|
|
6
|
+
import Layers from "../../components/layer-v3";
|
|
7
|
+
import { isEmpty, throttle } from "lodash";
|
|
8
|
+
import ModalPreview from "../../components/modal-preview";
|
|
9
|
+
import LayerView from "../view-only-2";
|
|
10
|
+
import { isEqual, debounce } from "lodash";
|
|
11
|
+
import { ZoomIn, ZoomOut } from "lucide-react";
|
|
12
|
+
import { Button } from "antd";
|
|
13
|
+
import { getAttributeElement, getAttributeElements } from "./resize-element";
|
|
14
|
+
const EXPAND_THRESHOLD = 200;
|
|
15
|
+
const EXPAND_AMOUNT = 1000;
|
|
16
|
+
const toolElement = ["square", "circle", "table-seat-circle"];
|
|
17
|
+
const idSelectionBoxGhost = "selection-box-ghost";
|
|
18
|
+
const nameShapeSelectionBoxGhost = "selection-box";
|
|
19
|
+
function matrixToXYWH({ x0, y0, w0, h0, scaleX, scaleY, a0x, a0y }) {
|
|
20
|
+
const widthReal = w0 * scaleX;
|
|
21
|
+
const heightReal = h0 * scaleY;
|
|
22
|
+
const xReal = a0x + (x0 - a0x) * scaleX;
|
|
23
|
+
const yReal = a0y + (y0 - a0y) * scaleY;
|
|
24
|
+
return { x: xReal, y: yReal, width: widthReal, height: heightReal };
|
|
25
|
+
}
|
|
26
|
+
const BoardTemplate = ({ onSelectComponent, viewOnly }) => {
|
|
27
|
+
var _a;
|
|
28
|
+
const dispatch = useAppDispatch();
|
|
29
|
+
const theme = useAppSelector((state) => state.theme);
|
|
30
|
+
const transformRef = useRef(null);
|
|
31
|
+
const instance = (_a = transformRef === null || transformRef === void 0 ? void 0 : transformRef.current) === null || _a === void 0 ? void 0 : _a.instance;
|
|
32
|
+
const context = instance === null || instance === void 0 ? void 0 : instance.getContext();
|
|
33
|
+
const containerRef = useRef(null);
|
|
34
|
+
const releaseGroupRef = useRef(false);
|
|
35
|
+
const dragGhostRef = useRef(false);
|
|
36
|
+
const dataSetGhost = useRef(null);
|
|
37
|
+
const ghostRef = useRef(null);
|
|
38
|
+
const [widthBoard, setWidthBoard] = useState(20000);
|
|
39
|
+
const [heightBoard, setHeightBoard] = useState(20000);
|
|
40
|
+
const [panningGroup, setPanningGroup] = useState(false);
|
|
41
|
+
const svgRef = useRef(null);
|
|
42
|
+
const [shadowShape, setShadowShape] = useState([]);
|
|
43
|
+
const [startPoint, setStartPoint] = useState(null);
|
|
44
|
+
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
|
45
|
+
// const [moveComponent, setMoveComponent] = useState(false);
|
|
46
|
+
const [scale, setScale] = useState(1);
|
|
47
|
+
const [boardSize, setBoardSize] = useState({ width: 10000, height: 10000 });
|
|
48
|
+
const [minCoords, setMinCoords] = useState({ x: -100, y: -100 });
|
|
49
|
+
const lastPos = useRef({ x: 0, y: 0 });
|
|
50
|
+
const [hasInitialized, setHasInitialzed] = useState(false);
|
|
51
|
+
const extendBy = 2000; // seberapa banyak canvas ditambah saat pan ke tepi
|
|
52
|
+
const buffer = 100; //
|
|
53
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
54
|
+
const activeTool = useAppSelector((state) => state.tool.active);
|
|
55
|
+
const grid = useAppSelector((state) => state.tool.grid);
|
|
56
|
+
const lockBackground = useAppSelector((state) => state.tool.lockBackground);
|
|
57
|
+
const { components: componentsProps, extraComponents: extraComponentsProps, flagChange, updateBy, } = useAppSelector((state) => state.board);
|
|
58
|
+
const { selectionLines } = useAppSelector((state) => state.panel);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (selectionLines) {
|
|
61
|
+
setSelectedLines(selectionLines);
|
|
62
|
+
const idSelected = dataElementSelectionGroupRef.current.map((item) => item.id);
|
|
63
|
+
const newSelectionData = [
|
|
64
|
+
...componentsState,
|
|
65
|
+
...extraComponentsState,
|
|
66
|
+
].filter((comp) => idSelected.includes(comp.id));
|
|
67
|
+
dataElementSelectionGroupRef.current = newSelectionData;
|
|
68
|
+
}
|
|
69
|
+
}, [selectionLines]);
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
var _a;
|
|
72
|
+
if (activeTool !== ((_a = shadowShape[0]) === null || _a === void 0 ? void 0 : _a.shape)) {
|
|
73
|
+
setShadowShape([]);
|
|
74
|
+
}
|
|
75
|
+
}, [activeTool]);
|
|
76
|
+
const minPosision = useMemo(() => {
|
|
77
|
+
var _a, _b;
|
|
78
|
+
const minX = (_a = context === null || context === void 0 ? void 0 : context.state) === null || _a === void 0 ? void 0 : _a.positionX;
|
|
79
|
+
const minY = (_b = context === null || context === void 0 ? void 0 : context.state) === null || _b === void 0 ? void 0 : _b.positionY;
|
|
80
|
+
return { minX, minY };
|
|
81
|
+
}, [context]);
|
|
82
|
+
// useEffect(() => {
|
|
83
|
+
// if (flagChange) {
|
|
84
|
+
// setComponentsState(componentsProps);
|
|
85
|
+
// setExtraComponentsState(extraComponentsProps);
|
|
86
|
+
// }
|
|
87
|
+
// }, [context]);
|
|
88
|
+
// const [isDragging, setIsDragging] = useState(false);
|
|
89
|
+
const [resizeDirection, setResizeDirection] = useState(null);
|
|
90
|
+
const backgroundColor = useAppSelector((state) => state.board.backgroundColor);
|
|
91
|
+
const selectedComponentProps = useAppSelector((state) => state.panel.selectedComponent);
|
|
92
|
+
const screenCTMRef = useRef(null);
|
|
93
|
+
const dragIndex = useRef(null);
|
|
94
|
+
const [componentsState, setComponentsState] = useState([]);
|
|
95
|
+
const [extraComponentsState, setExtraComponentsState] = useState([]);
|
|
96
|
+
const [selectedComponent, setSelectedComponent] = useState(null);
|
|
97
|
+
const [selectedLines, setSelectedLines] = useState([]);
|
|
98
|
+
const isSyncingFromRedux = useRef(false);
|
|
99
|
+
const startPos = useRef({ x: 0, y: 0 });
|
|
100
|
+
const isDragging = useRef(false);
|
|
101
|
+
const moveComponent = useRef(false);
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
104
|
+
if (hasInitialized)
|
|
105
|
+
return;
|
|
106
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
107
|
+
componentsProps === null || componentsProps === void 0 ? void 0 : componentsProps.forEach((_) => {
|
|
108
|
+
var _a, _b, _c, _d;
|
|
109
|
+
let values = _;
|
|
110
|
+
if ((_a = values === null || values === void 0 ? void 0 : values.shape) === null || _a === void 0 ? void 0 : _a.includes("square")) {
|
|
111
|
+
minX = Math.min(minX, values.x);
|
|
112
|
+
minY = Math.min(minY, values.y);
|
|
113
|
+
maxX = Math.max(maxX, values.x + values.width);
|
|
114
|
+
maxY = Math.max(maxY, values.y + values.height);
|
|
115
|
+
}
|
|
116
|
+
if ((_b = values === null || values === void 0 ? void 0 : values.shape) === null || _b === void 0 ? void 0 : _b.includes("circle")) {
|
|
117
|
+
minX = Math.min(minX, values.x);
|
|
118
|
+
minY = Math.min(minY, values.y);
|
|
119
|
+
maxX = Math.max(maxX, values.x + values.width);
|
|
120
|
+
maxY = Math.max(maxY, values.y + values.height);
|
|
121
|
+
}
|
|
122
|
+
if ((_c = values === null || values === void 0 ? void 0 : values.shape) === null || _c === void 0 ? void 0 : _c.includes("table-seat-circle")) {
|
|
123
|
+
minX = Math.min(minX, values.x);
|
|
124
|
+
minY = Math.min(minY, values.y);
|
|
125
|
+
maxX = Math.max(maxX, values.x + values.width);
|
|
126
|
+
maxY = Math.max(maxY, values.y + values.height);
|
|
127
|
+
}
|
|
128
|
+
if ((_d = values === null || values === void 0 ? void 0 : values.shape) === null || _d === void 0 ? void 0 : _d.includes("image-table")) {
|
|
129
|
+
minX = Math.min(minX, values.x);
|
|
130
|
+
minY = Math.min(minY, values.y);
|
|
131
|
+
maxX = Math.max(maxX, values.x + values.width);
|
|
132
|
+
maxY = Math.max(maxY, values.y + values.height);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
extraComponentsProps === null || extraComponentsProps === void 0 ? void 0 : extraComponentsProps.forEach((values) => {
|
|
136
|
+
var _a, _b;
|
|
137
|
+
if ((_a = values === null || values === void 0 ? void 0 : values.shape) === null || _a === void 0 ? void 0 : _a.includes("background")) {
|
|
138
|
+
minX = Math.min(minX, values.x);
|
|
139
|
+
minY = Math.min(minY, values.y);
|
|
140
|
+
maxX = Math.max(maxX, values.x + values.width);
|
|
141
|
+
maxY = Math.max(maxY, values.y + values.height);
|
|
142
|
+
}
|
|
143
|
+
if ((_b = values === null || values === void 0 ? void 0 : values.shape) === null || _b === void 0 ? void 0 : _b.includes("text")) {
|
|
144
|
+
minX = Math.min(minX, values.x);
|
|
145
|
+
minY = Math.min(minY, values.y);
|
|
146
|
+
maxX = Math.max(maxX, values.x + values.width);
|
|
147
|
+
maxY = Math.max(maxY, values.y + values.height);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
let backgroundHasOne = false;
|
|
151
|
+
if ((extraComponentsProps === null || extraComponentsProps === void 0 ? void 0 : extraComponentsProps.length) === 1 &&
|
|
152
|
+
((_b = (_a = extraComponentsProps === null || extraComponentsProps === void 0 ? void 0 : extraComponentsProps[0]) === null || _a === void 0 ? void 0 : _a.shape) === null || _b === void 0 ? void 0 : _b.includes("background"))) {
|
|
153
|
+
backgroundHasOne = true;
|
|
154
|
+
minX = (_c = extraComponentsProps === null || extraComponentsProps === void 0 ? void 0 : extraComponentsProps[0]) === null || _c === void 0 ? void 0 : _c.x;
|
|
155
|
+
minY = (_d = extraComponentsProps === null || extraComponentsProps === void 0 ? void 0 : extraComponentsProps[0]) === null || _d === void 0 ? void 0 : _d.y;
|
|
156
|
+
maxX = (_e = extraComponentsProps === null || extraComponentsProps === void 0 ? void 0 : extraComponentsProps[0]) === null || _e === void 0 ? void 0 : _e.width;
|
|
157
|
+
maxY = (_f = extraComponentsProps === null || extraComponentsProps === void 0 ? void 0 : extraComponentsProps[0]) === null || _f === void 0 ? void 0 : _f.height;
|
|
158
|
+
}
|
|
159
|
+
if ((extraComponentsProps === null || extraComponentsProps === void 0 ? void 0 : extraComponentsProps.length) < 1 &&
|
|
160
|
+
["background", "text"].includes((_g = componentsProps === null || componentsProps === void 0 ? void 0 : componentsProps[0]) === null || _g === void 0 ? void 0 : _g.shape)) {
|
|
161
|
+
minX = minX - minX * 0.5;
|
|
162
|
+
minY = minY - minY * 0.5;
|
|
163
|
+
}
|
|
164
|
+
if ((minX !== Infinity || minY !== Infinity) && !activeTool) {
|
|
165
|
+
setMinCoords({ x: minX - 200, y: minY - 100 });
|
|
166
|
+
(_h = transformRef.current) === null || _h === void 0 ? void 0 : _h.setTransform(minX - 100, minY - 100, scale);
|
|
167
|
+
setHasInitialzed(true);
|
|
168
|
+
}
|
|
169
|
+
// }
|
|
170
|
+
// if (!backgroundHasOne) {
|
|
171
|
+
// setMinCoords({ x: minX - minX * 0.5, y: minY - minY * 0.5 });
|
|
172
|
+
// }
|
|
173
|
+
// if(maxX > boardSize.width){
|
|
174
|
+
// setBoardSize((prev) => ({ ...prev, width: maxX }));
|
|
175
|
+
// }
|
|
176
|
+
// if(maxY > boardSize.height){
|
|
177
|
+
// setBoardSize((prev) => ({ ...prev, height: maxY }));
|
|
178
|
+
// }
|
|
179
|
+
// return {
|
|
180
|
+
// minX: backgroundHasOne ? minX : minX - minX * 0.5,
|
|
181
|
+
// minY: backgroundHasOne ? minY : minY - minY * 0.5,
|
|
182
|
+
// width: maxX,
|
|
183
|
+
// height: maxY,
|
|
184
|
+
// };
|
|
185
|
+
}, [componentsProps, extraComponentsProps]);
|
|
186
|
+
const debouncedSyncToReduxSelected = useRef(debounce((data) => {
|
|
187
|
+
throttledDispatch(data);
|
|
188
|
+
}, 300)).current;
|
|
189
|
+
const updateQueue = useRef([]);
|
|
190
|
+
const isFlushing = useRef(false);
|
|
191
|
+
const updateFlushToRedux = () => {
|
|
192
|
+
if (isFlushing.current)
|
|
193
|
+
return;
|
|
194
|
+
isFlushing.current = true;
|
|
195
|
+
requestAnimationFrame(() => {
|
|
196
|
+
const batch = updateQueue.current;
|
|
197
|
+
updateQueue.current = [];
|
|
198
|
+
isFlushing.current = false;
|
|
199
|
+
dispatch({
|
|
200
|
+
type: "board/setNewComponents",
|
|
201
|
+
payload: batch === null || batch === void 0 ? void 0 : batch[0],
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
const queueUpdateComponents = (data) => {
|
|
206
|
+
dispatch({
|
|
207
|
+
type: "board/setNewComponents",
|
|
208
|
+
payload: data,
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
// const debouncedSyncComponents = useRef(
|
|
212
|
+
// debounce((data) => {
|
|
213
|
+
// dispatch({
|
|
214
|
+
// type: "board/setNewComponents",
|
|
215
|
+
// payload: data,
|
|
216
|
+
// });
|
|
217
|
+
// }, 0)
|
|
218
|
+
// );
|
|
219
|
+
const debouncedSyncExtraComponents = useRef(debounce((data) => {
|
|
220
|
+
dispatch({
|
|
221
|
+
type: "board/setNewExtraComponents",
|
|
222
|
+
payload: data,
|
|
223
|
+
});
|
|
224
|
+
}, 300));
|
|
225
|
+
// Redux → Local
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
if (flagChange && updateBy === "global") {
|
|
228
|
+
if (!isEqual(componentsProps, componentsState)) {
|
|
229
|
+
isSyncingFromRedux.current = true;
|
|
230
|
+
setComponentsState(componentsProps !== null && componentsProps !== void 0 ? componentsProps : []);
|
|
231
|
+
}
|
|
232
|
+
if (!isEqual(extraComponentsProps, extraComponentsState)) {
|
|
233
|
+
isSyncingFromRedux.current = true;
|
|
234
|
+
setExtraComponentsState(extraComponentsProps !== null && extraComponentsProps !== void 0 ? extraComponentsProps : []);
|
|
235
|
+
}
|
|
236
|
+
if (!isEqual(selectedComponentProps, selectedComponent)) {
|
|
237
|
+
isSyncingFromRedux.current = true;
|
|
238
|
+
setSelectedComponent(selectedComponentProps !== null && selectedComponentProps !== void 0 ? selectedComponentProps : []);
|
|
239
|
+
}
|
|
240
|
+
dispatch({ type: "board/setFlagChange", payload: false });
|
|
241
|
+
}
|
|
242
|
+
}, [
|
|
243
|
+
componentsProps,
|
|
244
|
+
extraComponentsProps,
|
|
245
|
+
// selectedComponentProps,
|
|
246
|
+
// flagChange,
|
|
247
|
+
]);
|
|
248
|
+
// Local → Redux
|
|
249
|
+
// const syncFromLocalToRedux = (
|
|
250
|
+
// newComponentState?: any,
|
|
251
|
+
// newExtraComponentState?: any
|
|
252
|
+
// ) => {
|
|
253
|
+
// console.log("MAU SYNC DARI LOCAL KE REDUX", newComponentState);
|
|
254
|
+
// if (
|
|
255
|
+
// !isEmpty(newComponentState) &&
|
|
256
|
+
// !isEqual(newComponentState, componentsProps)
|
|
257
|
+
// ) {
|
|
258
|
+
// dispatch({ type: "board/setUpdateBy", payload: "local" });
|
|
259
|
+
// queueUpdateComponents(newComponentState);
|
|
260
|
+
// }
|
|
261
|
+
// if (
|
|
262
|
+
// !isEmpty(newExtraComponentState) &&
|
|
263
|
+
// !isEqual(newExtraComponentState, extraComponentsProps)
|
|
264
|
+
// ) {
|
|
265
|
+
// debouncedSyncExtraComponents.current(newExtraComponentState);
|
|
266
|
+
// }
|
|
267
|
+
// if (
|
|
268
|
+
// !isEqual(componentsState, componentsProps) &&
|
|
269
|
+
// !isEqual(componentsState, []) &&
|
|
270
|
+
// isEmpty(newComponentState)
|
|
271
|
+
// ) {
|
|
272
|
+
// dispatch({ type: "board/setUpdateBy", payload: "local" });
|
|
273
|
+
// queueUpdateComponents(componentsState);
|
|
274
|
+
// }
|
|
275
|
+
// if (
|
|
276
|
+
// !isEqual(extraComponentsState, extraComponentsProps) &&
|
|
277
|
+
// !isEqual(extraComponentsState, []) &&
|
|
278
|
+
// isEmpty(newExtraComponentState)
|
|
279
|
+
// ) {
|
|
280
|
+
// debouncedSyncExtraComponents.current(extraComponentsState);
|
|
281
|
+
// }
|
|
282
|
+
// };
|
|
283
|
+
const updateComponentAttribute = (component) => {
|
|
284
|
+
console.log("MAU UPDATE COMPONENT", component);
|
|
285
|
+
if (!component)
|
|
286
|
+
return;
|
|
287
|
+
const previousComponent = componentsState.find((item) => item.id == (component === null || component === void 0 ? void 0 : component.id));
|
|
288
|
+
const previousExtraComponent = extraComponentsState.find((item) => item.id == (component === null || component === void 0 ? void 0 : component.id));
|
|
289
|
+
const isNotEqualPreviousComponent = !isEqual(previousComponent, component);
|
|
290
|
+
const isNotEqualPreviousExtraComponent = !isEqual(previousExtraComponent, component);
|
|
291
|
+
if (previousComponent && isNotEqualPreviousComponent) {
|
|
292
|
+
// update local state
|
|
293
|
+
const newComponent = componentsState.map((item) => {
|
|
294
|
+
if (item.id === (component === null || component === void 0 ? void 0 : component.id)) {
|
|
295
|
+
return Object.assign(Object.assign({}, item), component);
|
|
296
|
+
}
|
|
297
|
+
return item;
|
|
298
|
+
});
|
|
299
|
+
setComponentsState(newComponent);
|
|
300
|
+
// update redux
|
|
301
|
+
dispatch({ type: "board/setUpdateBy", payload: "local" });
|
|
302
|
+
queueUpdateComponents(newComponent);
|
|
303
|
+
}
|
|
304
|
+
if (previousExtraComponent && isNotEqualPreviousExtraComponent) {
|
|
305
|
+
// update local state
|
|
306
|
+
const newExtraComponent = extraComponentsState.map((item) => {
|
|
307
|
+
if (item.id === component.id) {
|
|
308
|
+
return Object.assign(Object.assign({}, item), component);
|
|
309
|
+
}
|
|
310
|
+
return item;
|
|
311
|
+
});
|
|
312
|
+
setExtraComponentsState(newExtraComponent);
|
|
313
|
+
// update redux
|
|
314
|
+
debouncedSyncExtraComponents.current(newExtraComponent);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
const updateComponentsAttribute = (components) => {
|
|
318
|
+
// if(components.length === 0) return
|
|
319
|
+
console.log("MAU SYNC DARI LOCAL KE REDUX MANY", components);
|
|
320
|
+
const newComponents = components.map((item) => {
|
|
321
|
+
const previousComponent = componentsState.find((comp) => comp.id === (item === null || item === void 0 ? void 0 : item.id));
|
|
322
|
+
if (previousComponent) {
|
|
323
|
+
return Object.assign(Object.assign({}, previousComponent), item);
|
|
324
|
+
}
|
|
325
|
+
return item;
|
|
326
|
+
});
|
|
327
|
+
const newComponentState = componentsState.map((item) => {
|
|
328
|
+
const updatedItem = newComponents.find((comp) => comp.id === item.id);
|
|
329
|
+
return updatedItem ? Object.assign(Object.assign({}, item), updatedItem) : item;
|
|
330
|
+
});
|
|
331
|
+
setComponentsState(newComponentState);
|
|
332
|
+
dispatch({ type: "board/setUpdateBy", payload: "local" });
|
|
333
|
+
queueUpdateComponents(newComponentState);
|
|
334
|
+
};
|
|
335
|
+
const addComponents = (components) => {
|
|
336
|
+
const newComponentState = [...extraComponentsState, components];
|
|
337
|
+
// setComponentsState(newComponentState);
|
|
338
|
+
setExtraComponentsState(newComponentState);
|
|
339
|
+
dispatch({ type: "board/setUpdateBy", payload: "local" });
|
|
340
|
+
dispatch({ type: "board/setExtraComponent", payload: components });
|
|
341
|
+
};
|
|
342
|
+
const getSvgCoords = (e) => {
|
|
343
|
+
var _a;
|
|
344
|
+
const svg = svgRef.current;
|
|
345
|
+
const point = svg.createSVGPoint();
|
|
346
|
+
point.x = e.clientX;
|
|
347
|
+
point.y = e.clientY;
|
|
348
|
+
const transformed = point.matrixTransform((_a = svg.getScreenCTM()) === null || _a === void 0 ? void 0 : _a.inverse());
|
|
349
|
+
return { x: transformed.x, y: transformed.y };
|
|
350
|
+
};
|
|
351
|
+
const throttledDispatch = useCallback(throttle((component) => {
|
|
352
|
+
// dispatch({
|
|
353
|
+
// type: "board/updateComponent",
|
|
354
|
+
// payload: component,
|
|
355
|
+
// });
|
|
356
|
+
dispatch({
|
|
357
|
+
type: "panel/updateSelectedComponent",
|
|
358
|
+
payload: component,
|
|
359
|
+
});
|
|
360
|
+
}, 16), // 16ms ≈ 60fps
|
|
361
|
+
[dispatch]);
|
|
362
|
+
const getCursorStyle = () => {
|
|
363
|
+
if (activeTool === "select" && moveComponent.current) {
|
|
364
|
+
return "grabbing";
|
|
365
|
+
}
|
|
366
|
+
else if (activeTool === "select" && resizeDirection) {
|
|
367
|
+
return "grab";
|
|
368
|
+
}
|
|
369
|
+
else if (activeTool === "grab") {
|
|
370
|
+
return "grab";
|
|
371
|
+
}
|
|
372
|
+
else if (activeTool === "ruler") {
|
|
373
|
+
return "crosshair";
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const handelZoomIn = () => {
|
|
377
|
+
var _a;
|
|
378
|
+
if (activeTool !== "grab") {
|
|
379
|
+
dispatch({
|
|
380
|
+
type: "tool/setActiveTool",
|
|
381
|
+
payload: "grab",
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
(_a = transformRef.current) === null || _a === void 0 ? void 0 : _a.zoomIn();
|
|
385
|
+
};
|
|
386
|
+
const handleZoomOut = () => {
|
|
387
|
+
var _a;
|
|
388
|
+
if (activeTool !== "grab") {
|
|
389
|
+
dispatch({
|
|
390
|
+
type: "tool/setActiveTool",
|
|
391
|
+
payload: "grab",
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
(_a = transformRef.current) === null || _a === void 0 ? void 0 : _a.zoomOut();
|
|
395
|
+
};
|
|
396
|
+
const [touch, setTouch] = useState(false);
|
|
397
|
+
function createTableGhost({ x, y, width, height, fill, shape, id = "ghost-element-create", }) {
|
|
398
|
+
// buat group dulu
|
|
399
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
400
|
+
let el = null;
|
|
401
|
+
// tambahkan rectangle
|
|
402
|
+
if (shape === "square" ||
|
|
403
|
+
shape === "table-seat-circle" ||
|
|
404
|
+
shape === "selection-box") {
|
|
405
|
+
el = document.createElementNS(SVG_NS, "rect");
|
|
406
|
+
el.setAttribute("id", id);
|
|
407
|
+
el.setAttribute("x", x);
|
|
408
|
+
el.setAttribute("y", y);
|
|
409
|
+
el.setAttribute("width", width);
|
|
410
|
+
el.setAttribute("height", height);
|
|
411
|
+
el.setAttribute("fill", fill);
|
|
412
|
+
el.setAttribute("stroke", "blue");
|
|
413
|
+
el.setAttribute("data-table", JSON.stringify({
|
|
414
|
+
x,
|
|
415
|
+
y,
|
|
416
|
+
width,
|
|
417
|
+
height,
|
|
418
|
+
shape,
|
|
419
|
+
fill,
|
|
420
|
+
stroke: "blue",
|
|
421
|
+
rotation: 0,
|
|
422
|
+
}));
|
|
423
|
+
}
|
|
424
|
+
else if (shape === "circle") {
|
|
425
|
+
el = document.createElementNS(SVG_NS, "circle");
|
|
426
|
+
el.setAttribute("id", id);
|
|
427
|
+
el.setAttribute("cx", x);
|
|
428
|
+
el.setAttribute("cy", y);
|
|
429
|
+
el.setAttribute("r", Math.min(width, height) / 2);
|
|
430
|
+
el.setAttribute("fill", fill);
|
|
431
|
+
el.setAttribute("stroke", "blue");
|
|
432
|
+
el.setAttribute("data-table", JSON.stringify({
|
|
433
|
+
x,
|
|
434
|
+
y,
|
|
435
|
+
width,
|
|
436
|
+
height,
|
|
437
|
+
shape,
|
|
438
|
+
fill,
|
|
439
|
+
stroke: "blue",
|
|
440
|
+
rotation: 0,
|
|
441
|
+
}));
|
|
442
|
+
}
|
|
443
|
+
return el;
|
|
444
|
+
}
|
|
445
|
+
const handleUnSelectComponent = () => {
|
|
446
|
+
dispatch({ type: "panel/setSelectedComponent", payload: null });
|
|
447
|
+
setSelectedComponent(null);
|
|
448
|
+
setSelectedLines(null);
|
|
449
|
+
dispatch({ type: "panel/setShow", payload: false });
|
|
450
|
+
};
|
|
451
|
+
const startAngleRef = useRef(0);
|
|
452
|
+
const startRotationRef = useRef(0);
|
|
453
|
+
const ghostRotateElement = useRef(0);
|
|
454
|
+
const isResizeRef = useRef(false);
|
|
455
|
+
const isResizeSelectionRef = useRef(false);
|
|
456
|
+
const selectionDataRef = useRef([]);
|
|
457
|
+
// const selectionElementsRef = useRef<any>([]);
|
|
458
|
+
// const selectionElementRawsRef = useRef<any>([]);
|
|
459
|
+
const hadSelectionRef = useRef(false);
|
|
460
|
+
//FLAGE EVERY HANDLE EVENT
|
|
461
|
+
//CREATE
|
|
462
|
+
const isCreateElementRef = useRef(false);
|
|
463
|
+
//RESIZE
|
|
464
|
+
const onResizeSelectionRef = useRef(false);
|
|
465
|
+
//ROTATE
|
|
466
|
+
const isRotatingRef = useRef(false);
|
|
467
|
+
const rotationSelectionRef = useRef(0);
|
|
468
|
+
//DRAG/MOVE
|
|
469
|
+
//SELECT
|
|
470
|
+
const isSelectingRef = useRef(false);
|
|
471
|
+
const activeIdSelectedRef = useRef(null);
|
|
472
|
+
//SELECTION
|
|
473
|
+
const selectionLinesRef = useRef(null);
|
|
474
|
+
const onMakeSelectionRef = useRef(false);
|
|
475
|
+
// const dataElementSelectionGroupRawRef = useRef<Element[]>([]);
|
|
476
|
+
const onMoveSelectionBoxRef = useRef(false);
|
|
477
|
+
const onResizeSelectionBoxRef = useRef(false);
|
|
478
|
+
const dataElementSelectionGroupRef = useRef([]);
|
|
479
|
+
//polygon
|
|
480
|
+
const isPolygonRef = useRef(false);
|
|
481
|
+
const polygonElementRef = useRef([]);
|
|
482
|
+
const isOnMakePolygonRef = useRef(false);
|
|
483
|
+
const startPointPolygonLineRef = useRef({ x: 0, y: 0 });
|
|
484
|
+
// useEffect(() => {
|
|
485
|
+
// if(dataElementSelectionGroupRawRef.current.length > 0){
|
|
486
|
+
// const newElementSelectionGroupWar = []
|
|
487
|
+
// const selectAll = svgRef.current?.querySelectorAll(`[data-table]`)
|
|
488
|
+
// selectAll?.forEach((el: any) => {
|
|
489
|
+
// const data = JSON.parse(el.getAttribute("data-table"))
|
|
490
|
+
// dataElementSelectionGroupRawRef.current.forEach((item: any) => {
|
|
491
|
+
// const dataItem = JSON.parse(item.getAttribute("data-table"))
|
|
492
|
+
// if(dataItem.id === data.id){
|
|
493
|
+
// newElementSelectionGroupWar.push(el)
|
|
494
|
+
// }
|
|
495
|
+
// })
|
|
496
|
+
// })
|
|
497
|
+
// console.log(newElementSelectionGroupWar,dataElementSelectionGroupRawRef.current,selectAll,"masukk")
|
|
498
|
+
// dataElementSelectionGroupRawRef.current = newElementSelectionGroupWar
|
|
499
|
+
// }
|
|
500
|
+
// },[componentsState])
|
|
501
|
+
const handlePointerDown = (e) => {
|
|
502
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
503
|
+
const shiftActive = e.shiftKey;
|
|
504
|
+
console.log("shiftActive", shiftActive);
|
|
505
|
+
const svg = svgRef.current;
|
|
506
|
+
if (!e.isPrimary)
|
|
507
|
+
return;
|
|
508
|
+
if (!svg)
|
|
509
|
+
return;
|
|
510
|
+
isDragging.current = false;
|
|
511
|
+
let hasMoved = false;
|
|
512
|
+
releaseGroupRef.current = false;
|
|
513
|
+
const startX = e.clientX;
|
|
514
|
+
const startY = e.clientY;
|
|
515
|
+
const pt = svg.createSVGPoint();
|
|
516
|
+
pt.x = e.clientX;
|
|
517
|
+
pt.y = e.clientY;
|
|
518
|
+
const { x, y } = getSvgCoords(e);
|
|
519
|
+
//CREATE ELEMENT
|
|
520
|
+
const isInitialCreateElemente = toolElement.includes(activeTool) && !isCreateElementRef.current;
|
|
521
|
+
if (isInitialCreateElemente) {
|
|
522
|
+
const tables = createTableGhost({
|
|
523
|
+
x,
|
|
524
|
+
y,
|
|
525
|
+
width: 1,
|
|
526
|
+
height: 1,
|
|
527
|
+
fill: "red",
|
|
528
|
+
shape: activeTool,
|
|
529
|
+
});
|
|
530
|
+
// setGhostCreateElement(tables);
|
|
531
|
+
(_a = svgRef.current) === null || _a === void 0 ? void 0 : _a.appendChild(tables);
|
|
532
|
+
isCreateElementRef.current = true;
|
|
533
|
+
}
|
|
534
|
+
const targetSelection = e.target.closest("g[id='selection-lines']");
|
|
535
|
+
//ROTATE
|
|
536
|
+
const targetRotate = e.target.closest("circle[data-role]");
|
|
537
|
+
if (targetRotate) {
|
|
538
|
+
isRotatingRef.current = true;
|
|
539
|
+
}
|
|
540
|
+
// RESIZE
|
|
541
|
+
const targetGroup = e.target.closest("g[data-id]");
|
|
542
|
+
const targetPointPolygon = e.target.closest("circle[data-point]");
|
|
543
|
+
let idTarget = JSON.parse((targetGroup === null || targetGroup === void 0 ? void 0 : targetGroup.getAttribute("data-id")) || "{}");
|
|
544
|
+
const selectionTarget = (_c = (_b = targetSelection === null || targetSelection === void 0 ? void 0 : targetSelection.dataset) === null || _b === void 0 ? void 0 : _b.selection) === null || _c === void 0 ? void 0 : _c.replace("selection-", "");
|
|
545
|
+
const activeId = selectionTarget !== null && selectionTarget !== void 0 ? selectionTarget : idTarget;
|
|
546
|
+
const { g, inner } = getAttributeElement(svg, activeId);
|
|
547
|
+
if (targetGroup && shiftActive) {
|
|
548
|
+
const findById = [...componentsState, ...extraComponentsState].find((comp) => comp.id == activeId);
|
|
549
|
+
console.log("findById", findById);
|
|
550
|
+
const dataSelection = [...dataElementSelectionGroupRef.current, findById];
|
|
551
|
+
dataElementSelectionGroupRef.current = dataSelection;
|
|
552
|
+
}
|
|
553
|
+
const box = getGlobalBBox(svg, g);
|
|
554
|
+
const { x: initialXG, y: initialYG } = getTranslate(g);
|
|
555
|
+
const targetDragPosition = e.target.closest("circle[data-position]");
|
|
556
|
+
const { clientX, clientY } = e;
|
|
557
|
+
const allGroups = (_d = svgRef.current) === null || _d === void 0 ? void 0 : _d.querySelectorAll("g[data-id]");
|
|
558
|
+
// cari elemen group dengan data-table
|
|
559
|
+
// ambil elemen yang benar-benar ada di bawah pointer
|
|
560
|
+
const data = JSON.parse(((_e = ghostRef.current) === null || _e === void 0 ? void 0 : _e.getAttribute("data-table")) || "{}");
|
|
561
|
+
const cx = data.x + data.width / 2;
|
|
562
|
+
const cy = data.y + data.height / 2;
|
|
563
|
+
const dx = x - cx;
|
|
564
|
+
const dy = y - cy;
|
|
565
|
+
startAngleRef.current = Math.atan2(dy, dx); // simpan angle awal
|
|
566
|
+
startRotationRef.current = data.rotation || 0; // rotation existing
|
|
567
|
+
const hitPoint = document.elementFromPoint(clientX, clientY);
|
|
568
|
+
// CHECK FOR HIT ON SVG FOR SELECTION BOX
|
|
569
|
+
// MAKE AND UNMAKE SELECTION BOX START ------
|
|
570
|
+
const hadSelectionBox = ((_f = dataElementSelectionGroupRef.current) === null || _f === void 0 ? void 0 : _f.length) > 0;
|
|
571
|
+
console.log(dataElementSelectionGroupRef.current, "dataElementSelectionGroupRef.current");
|
|
572
|
+
const downInSelectionBox = hadSelectionBox && (hitPoint === null || hitPoint === void 0 ? void 0 : hitPoint.nodeName) !== "svg";
|
|
573
|
+
const downOutSelectionBox = hadSelectionBox && (hitPoint === null || hitPoint === void 0 ? void 0 : hitPoint.nodeName) === "svg";
|
|
574
|
+
const downBeforeHasSelectionBox = !downInSelectionBox && (hitPoint === null || hitPoint === void 0 ? void 0 : hitPoint.nodeName) === "svg";
|
|
575
|
+
const isRotateSelectionBox = targetRotate && hadSelectionBox;
|
|
576
|
+
let currentRotation = 0;
|
|
577
|
+
if (isRotateSelectionBox) {
|
|
578
|
+
currentRotation = rotationSelectionRef.current;
|
|
579
|
+
}
|
|
580
|
+
const isMightMove = !hadSelectionBox && activeId && !targetPointPolygon;
|
|
581
|
+
//JIKA PUNYA SELECTION TAPI DOWN DI ELEMENT LAIN
|
|
582
|
+
const downInMatchSelectionBox = downInSelectionBox &&
|
|
583
|
+
(hitPoint === null || hitPoint === void 0 ? void 0 : hitPoint.nodeName) !== "svg" &&
|
|
584
|
+
((_g = dataElementSelectionGroupRef.current) === null || _g === void 0 ? void 0 : _g.some((data) => (data === null || data === void 0 ? void 0 : data.id) === activeId));
|
|
585
|
+
if (downInSelectionBox) {
|
|
586
|
+
hadSelectionRef.current = true;
|
|
587
|
+
}
|
|
588
|
+
if (downOutSelectionBox) {
|
|
589
|
+
hadSelectionRef.current = false;
|
|
590
|
+
onMoveSelectionBoxRef.current = false;
|
|
591
|
+
handleUnSelectComponent();
|
|
592
|
+
}
|
|
593
|
+
if (downBeforeHasSelectionBox) {
|
|
594
|
+
// RELEASE SELECT
|
|
595
|
+
releaseGroupRef.current = true;
|
|
596
|
+
// MAKING SELECTION BOX DOWN
|
|
597
|
+
onMakeSelectionRef.current = true;
|
|
598
|
+
onMoveSelectionBoxRef.current = false;
|
|
599
|
+
handleUnSelectComponent();
|
|
600
|
+
const boxSelection = createTableGhost({
|
|
601
|
+
x,
|
|
602
|
+
y,
|
|
603
|
+
width: 1,
|
|
604
|
+
height: 1,
|
|
605
|
+
fill: "transparent",
|
|
606
|
+
shape: nameShapeSelectionBoxGhost,
|
|
607
|
+
id: idSelectionBoxGhost,
|
|
608
|
+
});
|
|
609
|
+
(_h = svgRef.current) === null || _h === void 0 ? void 0 : _h.appendChild(boxSelection);
|
|
610
|
+
}
|
|
611
|
+
// MAKE AND UNMAKE SELECTION BOX END ------
|
|
612
|
+
// RESIZE SELECTION BOX
|
|
613
|
+
const resizeSide = (targetDragPosition === null || targetDragPosition === void 0 ? void 0 : targetDragPosition.dataset.position) || "{}";
|
|
614
|
+
const downAtResizePosition = resizeSide !== "{}";
|
|
615
|
+
const downOutResizePosition = resizeSide === "{}";
|
|
616
|
+
const downOutResizePositionAndInSelectionBox = downOutResizePosition && hadSelectionBox && !downOutSelectionBox;
|
|
617
|
+
const downAtResizePositionAndHasSelectionBox = downAtResizePosition && hadSelectionBox && !isRotateSelectionBox;
|
|
618
|
+
const isMightResizeElement = !isEmpty(selectedComponent) &&
|
|
619
|
+
downAtResizePosition &&
|
|
620
|
+
downAtResizePosition &&
|
|
621
|
+
!hadSelectionBox;
|
|
622
|
+
if (!downInMatchSelectionBox &&
|
|
623
|
+
!downAtResizePositionAndHasSelectionBox &&
|
|
624
|
+
!targetRotate &&
|
|
625
|
+
!isMightResizeElement &&
|
|
626
|
+
!targetPointPolygon &&
|
|
627
|
+
!isMightMove &&
|
|
628
|
+
!targetSelection) {
|
|
629
|
+
console.log("MAUK UNSELET");
|
|
630
|
+
hadSelectionRef.current = false;
|
|
631
|
+
onMoveSelectionBoxRef.current = false;
|
|
632
|
+
handleUnSelectComponent();
|
|
633
|
+
selectionLinesRef.current = null;
|
|
634
|
+
}
|
|
635
|
+
if (downOutResizePositionAndInSelectionBox) {
|
|
636
|
+
onMoveSelectionBoxRef.current = true;
|
|
637
|
+
}
|
|
638
|
+
if (downAtResizePosition) {
|
|
639
|
+
onResizeSelectionRef.current = true;
|
|
640
|
+
}
|
|
641
|
+
// targetGroup?.removeAttribute("transform");
|
|
642
|
+
const mousePositionInGroup = {
|
|
643
|
+
x: x - (box === null || box === void 0 ? void 0 : box.x),
|
|
644
|
+
y: y - (box === null || box === void 0 ? void 0 : box.y),
|
|
645
|
+
};
|
|
646
|
+
const offset = {
|
|
647
|
+
x: x - initialXG,
|
|
648
|
+
y: y - initialYG,
|
|
649
|
+
};
|
|
650
|
+
// create polygon
|
|
651
|
+
const isInitialPolyGon = activeTool === "polygon" && !isCreateElementRef.current;
|
|
652
|
+
if (isInitialPolyGon && (selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.shape) !== "polygon") {
|
|
653
|
+
const newPolygon = {
|
|
654
|
+
id: `${Date.now()}`,
|
|
655
|
+
shape: "polygon",
|
|
656
|
+
fill: "red",
|
|
657
|
+
points: [{ x, y }],
|
|
658
|
+
rotation: 0,
|
|
659
|
+
};
|
|
660
|
+
polygonElementRef.current = newPolygon;
|
|
661
|
+
// addComponents(newPolygon);
|
|
662
|
+
setSelectedComponent(newPolygon);
|
|
663
|
+
dispatch({
|
|
664
|
+
type: "panel/setSelectedComponent",
|
|
665
|
+
payload: newPolygon,
|
|
666
|
+
});
|
|
667
|
+
isOnMakePolygonRef.current = true;
|
|
668
|
+
}
|
|
669
|
+
const selectionLines = (_j = svgRef.current) === null || _j === void 0 ? void 0 : _j.querySelector("#selection-lines");
|
|
670
|
+
const boxSelection = (_k = svgRef.current) === null || _k === void 0 ? void 0 : _k.querySelector("#rect-box-selection");
|
|
671
|
+
// const allElementInSelection = svgRef.current?.querySelectorAll(
|
|
672
|
+
// "#ghost-element-has-selection"
|
|
673
|
+
// );
|
|
674
|
+
const pointerMoveGhost = (ev) => {
|
|
675
|
+
var _a;
|
|
676
|
+
//SVG POINTER
|
|
677
|
+
const pt = svg.createSVGPoint();
|
|
678
|
+
pt.x = ev.clientX;
|
|
679
|
+
pt.y = ev.clientY;
|
|
680
|
+
const pos = pt.matrixTransform(svg.getScreenCTM().inverse());
|
|
681
|
+
//RESIZE POLYGON
|
|
682
|
+
const onResizePolygon = (pos) => {
|
|
683
|
+
const dx = pos.x - x;
|
|
684
|
+
const dy = pos.y - y;
|
|
685
|
+
const { g, inner, element } = getAttributeElement(svg, activeId);
|
|
686
|
+
const index = Number(targetPointPolygon.getAttribute("id"));
|
|
687
|
+
const points = element === null || element === void 0 ? void 0 : element.getAttribute("points");
|
|
688
|
+
let arrayPoints = pointsStringToArray(points);
|
|
689
|
+
arrayPoints[index] = { x: x + dx, y: y + dy };
|
|
690
|
+
// targetPointPolygon.setAttribute("cx", `${x + dx}`);
|
|
691
|
+
// targetPointPolygon.setAttribute("cy", `${y + dy}`);
|
|
692
|
+
// targetPointPolygon.setAttribute("x", `${x + dx}`);
|
|
693
|
+
// targetPointPolygon.setAttribute("y", `${y + dy}`);
|
|
694
|
+
const newPointsAttr = arrayToSvgPointsAttr(arrayPoints);
|
|
695
|
+
element === null || element === void 0 ? void 0 : element.setAttribute("points", newPointsAttr);
|
|
696
|
+
};
|
|
697
|
+
if (targetPointPolygon && targetGroup) {
|
|
698
|
+
onResizePolygon(pos);
|
|
699
|
+
}
|
|
700
|
+
//ROTATE SELECTION BOX
|
|
701
|
+
const onRotateSelectionBox = (pos) => {
|
|
702
|
+
var _a, _b, _c, _d, _e;
|
|
703
|
+
const selectionLines = (_a = svgRef.current) === null || _a === void 0 ? void 0 : _a.querySelector("#selection-lines");
|
|
704
|
+
const circleHelper = svg.querySelector("#circle-help");
|
|
705
|
+
const lineHelper = svg.querySelector("#line-help");
|
|
706
|
+
const inner = (_b = selectionLines === null || selectionLines === void 0 ? void 0 : selectionLines.children) === null || _b === void 0 ? void 0 : _b[0];
|
|
707
|
+
const rect = svg.querySelector("#selection-lines");
|
|
708
|
+
const x0 = (selectedLines === null || selectedLines === void 0 ? void 0 : selectedLines.x) || 0;
|
|
709
|
+
const y0 = (selectedLines === null || selectedLines === void 0 ? void 0 : selectedLines.y) || 0;
|
|
710
|
+
const w0 = (selectedLines === null || selectedLines === void 0 ? void 0 : selectedLines.width) || 0;
|
|
711
|
+
const h0 = (selectedLines === null || selectedLines === void 0 ? void 0 : selectedLines.height) || 0;
|
|
712
|
+
const bbox = getGlobalBBox(svg, inner);
|
|
713
|
+
const cx = bbox.x + bbox.width / 2;
|
|
714
|
+
const cy = bbox.y + bbox.height / 2;
|
|
715
|
+
//start center
|
|
716
|
+
const dx0 = x - cx;
|
|
717
|
+
const dy0 = y - cy;
|
|
718
|
+
// ✅ ANGLE DARI CENTER
|
|
719
|
+
const dx = pos.x - cx;
|
|
720
|
+
const dy = pos.y - cy;
|
|
721
|
+
//start angle
|
|
722
|
+
const startAngle = Math.atan2(dy0, dx0);
|
|
723
|
+
const currentAngle = Math.atan2(dy, dx);
|
|
724
|
+
const newAngle = currentRotation + ((currentAngle - startAngle) * 180) / Math.PI;
|
|
725
|
+
const angle = (currentRotation * Math.PI) / 180;
|
|
726
|
+
// helper line
|
|
727
|
+
const centerX = w0 / 2;
|
|
728
|
+
const centerY = h0 / 2;
|
|
729
|
+
const cos = Math.cos(angle);
|
|
730
|
+
const sin = Math.sin(angle);
|
|
731
|
+
// GLOBAL → LOCAL
|
|
732
|
+
const localDx = dx * cos + dy * sin;
|
|
733
|
+
const localDy = -dx * sin + dy * cos;
|
|
734
|
+
// helper (LOCAL SPACE)
|
|
735
|
+
const x2 = centerX + localDx;
|
|
736
|
+
const y2 = centerY + localDy;
|
|
737
|
+
lineHelper === null || lineHelper === void 0 ? void 0 : lineHelper.setAttribute("x1", `${centerX}`);
|
|
738
|
+
lineHelper === null || lineHelper === void 0 ? void 0 : lineHelper.setAttribute("y1", `${centerY}`);
|
|
739
|
+
lineHelper === null || lineHelper === void 0 ? void 0 : lineHelper.setAttribute("x2", `${x2}`);
|
|
740
|
+
lineHelper === null || lineHelper === void 0 ? void 0 : lineHelper.setAttribute("y2", `${y2}`);
|
|
741
|
+
circleHelper === null || circleHelper === void 0 ? void 0 : circleHelper.setAttribute("cx", `${x2}`);
|
|
742
|
+
circleHelper === null || circleHelper === void 0 ? void 0 : circleHelper.setAttribute("cy", `${y2}`);
|
|
743
|
+
(_c = selectionLines === null || selectionLines === void 0 ? void 0 : selectionLines.children) === null || _c === void 0 ? void 0 : _c[0].setAttribute("transform", `rotate(${newAngle}, 0, 0)`);
|
|
744
|
+
const { tx, ty } = stabilizeRotation(x0, y0, w0, h0, currentRotation, newAngle);
|
|
745
|
+
selectionLines === null || selectionLines === void 0 ? void 0 : selectionLines.setAttribute("transform", `translate(${tx}, ${ty}) `);
|
|
746
|
+
// ROTATE ALL ELEMENT IN SELECTION
|
|
747
|
+
const allID = (_d = dataElementSelectionGroupRef.current) === null || _d === void 0 ? void 0 : _d.map((el) => el.id);
|
|
748
|
+
const allDataRealSelection = (_e = [
|
|
749
|
+
...componentsState,
|
|
750
|
+
...extraComponentsState,
|
|
751
|
+
]) === null || _e === void 0 ? void 0 : _e.filter((el) => allID.includes(el.id));
|
|
752
|
+
const deltaAngle = newAngle - currentRotation;
|
|
753
|
+
const rad = (deltaAngle * Math.PI) / 180;
|
|
754
|
+
const cosA = Math.cos(rad);
|
|
755
|
+
const sinA = Math.sin(rad);
|
|
756
|
+
const allGroupsAtrribute = getAttributeElements(svg, allID);
|
|
757
|
+
allGroupsAtrribute.forEach(({ g, inner }, i) => {
|
|
758
|
+
var _a;
|
|
759
|
+
const el = (_a = dataElementSelectionGroupRef.current) === null || _a === void 0 ? void 0 : _a[i];
|
|
760
|
+
if (!el)
|
|
761
|
+
return;
|
|
762
|
+
const rotateBefore = el.rotation;
|
|
763
|
+
const ex = el.x;
|
|
764
|
+
const ey = el.y;
|
|
765
|
+
// 🔑 ROTATE AROUND SELECTION CENTER
|
|
766
|
+
const dx = ex - cx;
|
|
767
|
+
const dy = ey - cy;
|
|
768
|
+
// const angle = ((newAngle - currentRotation) * Math.PI) / 180;
|
|
769
|
+
// const cos = Math.cos(angle);
|
|
770
|
+
// const sin = Math.sin(angle);
|
|
771
|
+
const nx = cx + dx * cosA - dy * sinA;
|
|
772
|
+
const ny = cy + dx * sinA + dy * cosA;
|
|
773
|
+
g.setAttribute("data-is-rotating", "1");
|
|
774
|
+
inner.setAttribute("transform", `rotate(${rotateBefore + deltaAngle}, 0, 0)`);
|
|
775
|
+
g.setAttribute("transform", `translate(${nx}, ${ny})`);
|
|
776
|
+
});
|
|
777
|
+
};
|
|
778
|
+
if (isRotateSelectionBox) {
|
|
779
|
+
onRotateSelectionBox(pos);
|
|
780
|
+
}
|
|
781
|
+
//RESIZE SELECTION BOX
|
|
782
|
+
// const onResizeSelectionBox = (ev: MouseEvent) => {
|
|
783
|
+
// // ... (ambil data awal sama seperti kode kamu)
|
|
784
|
+
// console.log("RESIZE SELECTION BOX");
|
|
785
|
+
// const selectionLines = svg.querySelector("#selection-lines");
|
|
786
|
+
// const innerSelection = selectionLines?.children?.[0];
|
|
787
|
+
// const rotationSelection = getRotation((innerSelection as SVGGraphicsElement).transform.baseVal)
|
|
788
|
+
// const angle = (rotationSelection * Math.PI) / 180;
|
|
789
|
+
// const rect = innerSelection?.children?.[0];
|
|
790
|
+
// const dx = pos.x - x;
|
|
791
|
+
// const dy = pos.y - y;
|
|
792
|
+
// // Konversi delta mouse ke Local Space agar sesuai dengan arah kotak miring
|
|
793
|
+
// const deltaLocalX = dx * Math.cos(-angle) - dy * Math.sin(-angle);
|
|
794
|
+
// const deltaLocalY = dx * Math.sin(-angle) + dy * Math.cos(-angle);
|
|
795
|
+
// const gx: number = selectedLines?.x || 0;
|
|
796
|
+
// const gy: number = selectedLines?.y || 0;
|
|
797
|
+
// const gw: number = selectedLines?.width || 0;
|
|
798
|
+
// const gh: number = selectedLines?.height || 0;
|
|
799
|
+
// const oldSel = { x: gx, y: gy, width: gw, height: gh };
|
|
800
|
+
// const rule = RESIZE_RULES[resizeSide];
|
|
801
|
+
// const newWidth = Math.max(0, gw + rule.widthFactor * deltaLocalX);
|
|
802
|
+
// const newHeight = Math.max(0, gh + rule.heightFactor * deltaLocalY);
|
|
803
|
+
// // Ambil perpindahan lokal dari rule
|
|
804
|
+
// const mlX = rule.moveLocalX(deltaLocalX, deltaLocalY);
|
|
805
|
+
// const mlY = rule.moveLocalY(deltaLocalX, deltaLocalY);
|
|
806
|
+
// // Konversi perpindahan lokal ke World Space (mx, my)
|
|
807
|
+
// const mx = mlX * Math.cos(angle) - mlY * Math.sin(angle);
|
|
808
|
+
// const my = mlX * Math.sin(angle) + mlY * Math.cos(angle);
|
|
809
|
+
// // Objek Selection Baru (Penting: x dan y harus sinkron dengan mx, my)
|
|
810
|
+
// const newSelection = {
|
|
811
|
+
// x: gx + mx,
|
|
812
|
+
// y: gy + my,
|
|
813
|
+
// width: newWidth,
|
|
814
|
+
// height: newHeight,
|
|
815
|
+
// };
|
|
816
|
+
// // Update visual selection box
|
|
817
|
+
// applyResizeWithRotation({
|
|
818
|
+
// element: rect, group: selectionLines, resizeSide,
|
|
819
|
+
// widthOriginal: gw, heightOriginal: gh,
|
|
820
|
+
// deltaLocalX, deltaLocalY, xOriginal: gx, yOriginal: gy, angle,
|
|
821
|
+
// });
|
|
822
|
+
// const allID = dataElementSelectionGroupRef.current?.map(
|
|
823
|
+
// (el: any) => el.id
|
|
824
|
+
// );
|
|
825
|
+
// const allDataRealSelection = [
|
|
826
|
+
// ...componentsState,
|
|
827
|
+
// ...extraComponentsState,
|
|
828
|
+
// ]?.filter((el: any) => allID.includes(el.id));
|
|
829
|
+
// const allGroupsAttribute = getAttributeElements(svg, allID);
|
|
830
|
+
// // Update elemen-elemen di dalamnya
|
|
831
|
+
// allGroupsAttribute.forEach(({ g, element, inner }, i) => {
|
|
832
|
+
// const updated = mapElementToResize(
|
|
833
|
+
// allDataRealSelection[i],
|
|
834
|
+
// oldSel, // selection box lama
|
|
835
|
+
// newSelection, // selection box baru
|
|
836
|
+
// angle
|
|
837
|
+
// );
|
|
838
|
+
// applyResizeToSvgElement(element, g, updated, inner);
|
|
839
|
+
// });
|
|
840
|
+
// };
|
|
841
|
+
const onResizeSelectionBox = (ev) => {
|
|
842
|
+
var _a, _b, _c, _d;
|
|
843
|
+
console.log("RESIZE SELECTION BOX");
|
|
844
|
+
const selectionLines = svg.querySelector("#selection-lines");
|
|
845
|
+
const innerSelection = (_a = selectionLines === null || selectionLines === void 0 ? void 0 : selectionLines.children) === null || _a === void 0 ? void 0 : _a[0];
|
|
846
|
+
const rect = (_b = innerSelection === null || innerSelection === void 0 ? void 0 : innerSelection.children) === null || _b === void 0 ? void 0 : _b[0];
|
|
847
|
+
const rotationSelection = getRotation(innerSelection.transform.baseVal);
|
|
848
|
+
isResizeSelectionRef.current = true;
|
|
849
|
+
const gx = (selectedLines === null || selectedLines === void 0 ? void 0 : selectedLines.x) || 0;
|
|
850
|
+
const gy = (selectedLines === null || selectedLines === void 0 ? void 0 : selectedLines.y) || 0;
|
|
851
|
+
const gw = (selectedLines === null || selectedLines === void 0 ? void 0 : selectedLines.width) || 0;
|
|
852
|
+
const gh = (selectedLines === null || selectedLines === void 0 ? void 0 : selectedLines.height) || 0;
|
|
853
|
+
const oldSel = { x: gx, y: gy, width: gw, height: gh };
|
|
854
|
+
//Selection Resize
|
|
855
|
+
//angle
|
|
856
|
+
const angle = (rotationSelection * Math.PI) / 180;
|
|
857
|
+
//delta mouse di screen
|
|
858
|
+
const dx = pos.x - x;
|
|
859
|
+
const dy = pos.y - y;
|
|
860
|
+
//conversi ke local space (rotate balik)
|
|
861
|
+
const consA = Math.cos(-angle);
|
|
862
|
+
const sinA = Math.sin(-angle);
|
|
863
|
+
const deltaLocalX = dx * consA - dy * sinA;
|
|
864
|
+
const deltaLocalY = dx * sinA + dy * consA;
|
|
865
|
+
applyResizeWithRotation({
|
|
866
|
+
element: rect,
|
|
867
|
+
group: selectionLines,
|
|
868
|
+
resizeSide,
|
|
869
|
+
widthOriginal: gw,
|
|
870
|
+
heightOriginal: gh,
|
|
871
|
+
deltaLocalX,
|
|
872
|
+
deltaLocalY,
|
|
873
|
+
xOriginal: gx,
|
|
874
|
+
yOriginal: gy,
|
|
875
|
+
angle,
|
|
876
|
+
});
|
|
877
|
+
//end Resize selection
|
|
878
|
+
const allID = (_c = dataElementSelectionGroupRef.current) === null || _c === void 0 ? void 0 : _c.map((el) => el.id);
|
|
879
|
+
const allDataRealSelection = (_d = [
|
|
880
|
+
...componentsState,
|
|
881
|
+
...extraComponentsState,
|
|
882
|
+
]) === null || _d === void 0 ? void 0 : _d.filter((el) => allID.includes(el.id));
|
|
883
|
+
const allGroupsAttribute = getAttributeElements(svg, allID);
|
|
884
|
+
const isValidResizeSide = Boolean(resizeSide);
|
|
885
|
+
if (!isValidResizeSide)
|
|
886
|
+
return;
|
|
887
|
+
const newSelection = calculateNewSelection(oldSel, pos, { x, y }, resizeSide, angle);
|
|
888
|
+
allGroupsAttribute.forEach(({ g, element, inner }, i) => {
|
|
889
|
+
const updated = mapElementToResize(allDataRealSelection[i], oldSel, newSelection, angle);
|
|
890
|
+
applyResizeToSvgElement(element, g, updated, inner);
|
|
891
|
+
});
|
|
892
|
+
};
|
|
893
|
+
if (downAtResizePositionAndHasSelectionBox)
|
|
894
|
+
onResizeSelectionBox(ev);
|
|
895
|
+
//SELECTION BOX MOVE
|
|
896
|
+
const onMoveSelectionBox = () => {
|
|
897
|
+
var _a, _b;
|
|
898
|
+
const selectionLines = svg.querySelector("#selection-lines");
|
|
899
|
+
const allID = (_a = dataElementSelectionGroupRef.current) === null || _a === void 0 ? void 0 : _a.map((el) => el.id);
|
|
900
|
+
const allDataRealSelection = (_b = [
|
|
901
|
+
...componentsState,
|
|
902
|
+
...extraComponentsState,
|
|
903
|
+
]) === null || _b === void 0 ? void 0 : _b.filter((el) => allID.includes(el.id));
|
|
904
|
+
allDataRealSelection.forEach((item) => {
|
|
905
|
+
const { g } = getAttributeElement(svg, item.id);
|
|
906
|
+
const newX = pos.x - x;
|
|
907
|
+
const newY = pos.y - y;
|
|
908
|
+
g.setAttribute("transform", `translate(${item.x + newX}, ${item.y + newY})`);
|
|
909
|
+
});
|
|
910
|
+
const newX = pos.x - x;
|
|
911
|
+
const newY = pos.y - y;
|
|
912
|
+
selectionLines === null || selectionLines === void 0 ? void 0 : selectionLines.setAttribute("transform", `translate(${(selectedLines === null || selectedLines === void 0 ? void 0 : selectedLines.x) + newX}, ${(selectedLines === null || selectedLines === void 0 ? void 0 : selectedLines.y) + newY})`);
|
|
913
|
+
};
|
|
914
|
+
if (downOutResizePositionAndInSelectionBox && !isRotateSelectionBox)
|
|
915
|
+
onMoveSelectionBox();
|
|
916
|
+
// MAKING SELECTION BOX MOVE
|
|
917
|
+
const onMakeSelectionBox = (ev) => {
|
|
918
|
+
var _a, _b;
|
|
919
|
+
const svgSize = svg.getBoundingClientRect();
|
|
920
|
+
let selectionBoxGhost = svg.querySelector("#selection-box-ghost");
|
|
921
|
+
function rectIntersect(a, b) {
|
|
922
|
+
return !(a.x + a.width < b.x ||
|
|
923
|
+
a.x > b.x + b.width ||
|
|
924
|
+
a.y + a.height < b.y ||
|
|
925
|
+
a.y > b.y + b.height);
|
|
926
|
+
}
|
|
927
|
+
const selX = Math.min(pos.x, x);
|
|
928
|
+
const selY = Math.min(pos.y, y);
|
|
929
|
+
const selW = Math.abs(pos.x - x);
|
|
930
|
+
const selH = Math.abs(pos.y - y);
|
|
931
|
+
selectionBoxGhost === null || selectionBoxGhost === void 0 ? void 0 : selectionBoxGhost.setAttribute("x", String(selX));
|
|
932
|
+
selectionBoxGhost === null || selectionBoxGhost === void 0 ? void 0 : selectionBoxGhost.setAttribute("y", String(selY));
|
|
933
|
+
selectionBoxGhost === null || selectionBoxGhost === void 0 ? void 0 : selectionBoxGhost.setAttribute("width", String(selW));
|
|
934
|
+
selectionBoxGhost === null || selectionBoxGhost === void 0 ? void 0 : selectionBoxGhost.setAttribute("height", String(selH));
|
|
935
|
+
// FIND ALL COMPONENTS INSIDE SELECTION BOX
|
|
936
|
+
const allGroups = (_a = svgRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll("g[data-id]");
|
|
937
|
+
const selLeft = selX - svgSize.left;
|
|
938
|
+
const selTop = selY - svgSize.top;
|
|
939
|
+
const selectedIds = [...allGroups]
|
|
940
|
+
.map((g) => JSON === null || JSON === void 0 ? void 0 : JSON.parse((g === null || g === void 0 ? void 0 : g.getAttribute("data-id")) || "{}"))
|
|
941
|
+
.filter((d) => {
|
|
942
|
+
const { element } = getAttributeElement(svg, d);
|
|
943
|
+
const box = getGlobalBBox(svg, element);
|
|
944
|
+
return (box.x < selLeft + selW &&
|
|
945
|
+
box.x + box.width > selLeft &&
|
|
946
|
+
box.y < selTop + selH &&
|
|
947
|
+
box.y + box.height > selTop);
|
|
948
|
+
})
|
|
949
|
+
.map((d) => `${d}`);
|
|
950
|
+
if (selectedIds.length === 0)
|
|
951
|
+
return;
|
|
952
|
+
const selectedComps = (_b = [...componentsState, ...extraComponentsState]
|
|
953
|
+
.filter((c) => selectedIds.map(String).includes(String(c.id)))) === null || _b === void 0 ? void 0 : _b.map((item) => {
|
|
954
|
+
const { element } = getAttributeElement(svg, item.id);
|
|
955
|
+
const { x, y } = getTranslate(g);
|
|
956
|
+
const box = getGlobalBBox(svg, element);
|
|
957
|
+
return Object.assign(Object.assign({}, item), { x: box.x, y: box.y, width: box.width, height: box.height });
|
|
958
|
+
});
|
|
959
|
+
dataElementSelectionGroupRef.current = selectedComps;
|
|
960
|
+
};
|
|
961
|
+
if (downBeforeHasSelectionBox)
|
|
962
|
+
onMakeSelectionBox(ev);
|
|
963
|
+
// CREATE GHOST ELEMENT ---
|
|
964
|
+
const onDrawNewElements = toolElement.includes(activeTool) && isCreateElementRef.current;
|
|
965
|
+
if (onDrawNewElements) {
|
|
966
|
+
const ghost = (_a = svgRef.current) === null || _a === void 0 ? void 0 : _a.querySelector("#ghost-element-create");
|
|
967
|
+
if (!ghost)
|
|
968
|
+
return;
|
|
969
|
+
const data = JSON.parse(ghost.getAttribute("data-table") || "{}");
|
|
970
|
+
const { x: ox, y: oy } = data;
|
|
971
|
+
// ---- HITUNG RECT BARU ----
|
|
972
|
+
const x1 = Math.min(ox, pos.x);
|
|
973
|
+
const y1 = Math.min(oy, pos.y);
|
|
974
|
+
const x2 = Math.max(ox, pos.x);
|
|
975
|
+
const y2 = Math.max(oy, pos.y);
|
|
976
|
+
const newX = x1;
|
|
977
|
+
const newY = y1;
|
|
978
|
+
const newW = x2 - x1;
|
|
979
|
+
const newH = y2 - y1;
|
|
980
|
+
// ---- RECT ----
|
|
981
|
+
if (["square", "table-seat-circle"].includes(activeTool)) {
|
|
982
|
+
ghost.setAttribute("x", String(newX));
|
|
983
|
+
ghost.setAttribute("y", String(newY));
|
|
984
|
+
ghost.setAttribute("width", String(newW));
|
|
985
|
+
ghost.setAttribute("height", String(newH));
|
|
986
|
+
}
|
|
987
|
+
// ---- CIRCLE (ambil dari bounding box) ----
|
|
988
|
+
if (activeTool === "circle") {
|
|
989
|
+
const cx = newX + newW / 2;
|
|
990
|
+
const cy = newY + newH / 2;
|
|
991
|
+
const r = Math.min(newW, newH) / 2;
|
|
992
|
+
ghost.setAttribute("cx", String(cx));
|
|
993
|
+
ghost.setAttribute("cy", String(cy));
|
|
994
|
+
ghost.setAttribute("r", String(r));
|
|
995
|
+
}
|
|
996
|
+
// ---- UPDATE DATA TABLE ----
|
|
997
|
+
ghost.setAttribute("data-table", JSON.stringify(Object.assign(Object.assign({}, data), { x: newX, y: newY, width: newW, height: newH })));
|
|
998
|
+
}
|
|
999
|
+
// RESIZE GHOST SINGLE ELEMENT ---
|
|
1000
|
+
function globalToLocal(px, py, tx, ty, angle) {
|
|
1001
|
+
const rad = (angle * Math.PI) / 180;
|
|
1002
|
+
// buang translate group A
|
|
1003
|
+
let x = px - tx;
|
|
1004
|
+
let y = py - ty;
|
|
1005
|
+
// balik rotasi group B
|
|
1006
|
+
const cos = Math.cos(-rad);
|
|
1007
|
+
const sin = Math.sin(-rad);
|
|
1008
|
+
return {
|
|
1009
|
+
x: x * cos - y * sin,
|
|
1010
|
+
y: x * sin + y * cos,
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
const onResize = (ev) => {
|
|
1014
|
+
const activeId = selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.id;
|
|
1015
|
+
const svg = svgRef.current;
|
|
1016
|
+
const { g, element, inner, seats } = getAttributeElement(svg, activeId);
|
|
1017
|
+
// ORIGINAL size sebelum resize
|
|
1018
|
+
const rotate = getRotation(inner.transform.baseVal);
|
|
1019
|
+
const widthOriginal = selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.width;
|
|
1020
|
+
const heightOriginal = selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.height;
|
|
1021
|
+
const xOriginal = selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.x;
|
|
1022
|
+
const yOriginal = selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.y;
|
|
1023
|
+
// Hitung pointer → local SVG coordinate
|
|
1024
|
+
const co = svg.createSVGPoint();
|
|
1025
|
+
co.x = ev.clientX;
|
|
1026
|
+
co.y = ev.clientY;
|
|
1027
|
+
const pos = co.matrixTransform(svg.getScreenCTM().inverse());
|
|
1028
|
+
// 1. angle rotasi elemen
|
|
1029
|
+
const angle = (rotate * Math.PI) / 180;
|
|
1030
|
+
// 2. delta mouse di screen
|
|
1031
|
+
const dx = pos.x - x;
|
|
1032
|
+
const dy = pos.y - y;
|
|
1033
|
+
// 3. konversi ke local space (rotate balik)
|
|
1034
|
+
const cosA = Math.cos(-angle);
|
|
1035
|
+
const sinA = Math.sin(-angle);
|
|
1036
|
+
const deltaLocalX = dx * cosA - dy * sinA;
|
|
1037
|
+
const deltaLocalY = dx * sinA + dy * cosA;
|
|
1038
|
+
applyResizeWithRotation({
|
|
1039
|
+
element,
|
|
1040
|
+
group: g,
|
|
1041
|
+
resizeSide,
|
|
1042
|
+
widthOriginal,
|
|
1043
|
+
heightOriginal,
|
|
1044
|
+
deltaLocalX,
|
|
1045
|
+
deltaLocalY,
|
|
1046
|
+
xOriginal,
|
|
1047
|
+
yOriginal,
|
|
1048
|
+
angle,
|
|
1049
|
+
seats,
|
|
1050
|
+
});
|
|
1051
|
+
isResizeRef.current = true;
|
|
1052
|
+
};
|
|
1053
|
+
// const hasSelectedOneElement =
|
|
1054
|
+
// !isEmpty(selectedComponent) &&
|
|
1055
|
+
// onResizeSelectionRef.current &&
|
|
1056
|
+
// downAtResizePosition;
|
|
1057
|
+
if (!isEmpty(selectedComponent) && isMightResizeElement)
|
|
1058
|
+
onResize(ev);
|
|
1059
|
+
//ROTATE
|
|
1060
|
+
const onRotate = (ev) => {
|
|
1061
|
+
var _a;
|
|
1062
|
+
const svg = svgRef.current;
|
|
1063
|
+
if (!svg || !selectedComponent)
|
|
1064
|
+
return;
|
|
1065
|
+
const lineHelper = svg.querySelector("#line-help");
|
|
1066
|
+
const circleHelper = svg.querySelector("#circle-help");
|
|
1067
|
+
const rect = svg.querySelector("#selection-lines");
|
|
1068
|
+
const { inner, g } = getAttributeElement(svg, selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.id);
|
|
1069
|
+
if (!inner || !g)
|
|
1070
|
+
return;
|
|
1071
|
+
// mouse → SVG space
|
|
1072
|
+
const w0 = selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.width;
|
|
1073
|
+
const h0 = selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.height;
|
|
1074
|
+
// 🔴 GLOBAL BBOX (INI KUNCI)
|
|
1075
|
+
const bbox = getGlobalBBox(svg, inner);
|
|
1076
|
+
if (!bbox)
|
|
1077
|
+
return;
|
|
1078
|
+
const cx = bbox.x + bbox.width / 2;
|
|
1079
|
+
const cy = bbox.y + bbox.height / 2;
|
|
1080
|
+
// vector awal & sekarang (GLOBAL)
|
|
1081
|
+
const dx0 = x - cx;
|
|
1082
|
+
const dy0 = y - cy;
|
|
1083
|
+
const dx = pos.x - cx;
|
|
1084
|
+
const dy = pos.y - cy;
|
|
1085
|
+
const startAngle = Math.atan2(dy0, dx0);
|
|
1086
|
+
const currentAngle = Math.atan2(dy, dx);
|
|
1087
|
+
const newAngle = (selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.rotation) +
|
|
1088
|
+
((currentAngle - startAngle) * 180) / Math.PI;
|
|
1089
|
+
// helper line
|
|
1090
|
+
const centerX = w0 / 2;
|
|
1091
|
+
const centerY = h0 / 2;
|
|
1092
|
+
const angle = (selectedComponent.rotation * Math.PI) / 180;
|
|
1093
|
+
const cos = Math.cos(angle);
|
|
1094
|
+
const sin = Math.sin(angle);
|
|
1095
|
+
// GLOBAL → LOCAL
|
|
1096
|
+
const localDx = dx * cos + dy * sin;
|
|
1097
|
+
const localDy = -dx * sin + dy * cos;
|
|
1098
|
+
// helper (LOCAL SPACE)
|
|
1099
|
+
const x2 = centerX + localDx;
|
|
1100
|
+
const y2 = centerY + localDy;
|
|
1101
|
+
lineHelper === null || lineHelper === void 0 ? void 0 : lineHelper.setAttribute("x1", `${centerX}`);
|
|
1102
|
+
lineHelper === null || lineHelper === void 0 ? void 0 : lineHelper.setAttribute("y1", `${centerY}`);
|
|
1103
|
+
lineHelper === null || lineHelper === void 0 ? void 0 : lineHelper.setAttribute("x2", `${x2}`);
|
|
1104
|
+
lineHelper === null || lineHelper === void 0 ? void 0 : lineHelper.setAttribute("y2", `${y2}`);
|
|
1105
|
+
circleHelper === null || circleHelper === void 0 ? void 0 : circleHelper.setAttribute("cx", `${x2}`);
|
|
1106
|
+
circleHelper === null || circleHelper === void 0 ? void 0 : circleHelper.setAttribute("cy", `${y2}`);
|
|
1107
|
+
// rotate di local space
|
|
1108
|
+
inner.setAttribute("transform", `rotate(${newAngle}, 0, 0)`);
|
|
1109
|
+
// stabilize translate
|
|
1110
|
+
const { tx, ty } = selectedComponent.shape === "polygon"
|
|
1111
|
+
? stabilizeTranslateOnRotate({
|
|
1112
|
+
points: selectedComponentProps === null || selectedComponentProps === void 0 ? void 0 : selectedComponentProps.points,
|
|
1113
|
+
oldAngle: selectedComponent.rotation,
|
|
1114
|
+
newAngle,
|
|
1115
|
+
tx: selectedComponent.x,
|
|
1116
|
+
ty: selectedComponent.y,
|
|
1117
|
+
})
|
|
1118
|
+
: stabilizeRotation(selectedComponent.x, selectedComponent.y, selectedComponent.width, selectedComponent.height, selectedComponent.rotation, newAngle);
|
|
1119
|
+
g.setAttribute("transform", `translate(${tx}, ${ty})`);
|
|
1120
|
+
ghostRotateElement.current = newAngle;
|
|
1121
|
+
rect === null || rect === void 0 ? void 0 : rect.setAttribute("transform", `translate(${tx}, ${ty})`);
|
|
1122
|
+
(_a = rect === null || rect === void 0 ? void 0 : rect.children) === null || _a === void 0 ? void 0 : _a[0].setAttribute("transform", `rotate(${newAngle}, 0, 0)`);
|
|
1123
|
+
// helpGroup?.setAttribute("transform", `rotate(${-newAngle}, 0, 0)`);
|
|
1124
|
+
// rect?.setAttribute("visibility", "hidden");
|
|
1125
|
+
};
|
|
1126
|
+
if (targetRotate && !isRotateSelectionBox)
|
|
1127
|
+
onRotate(ev);
|
|
1128
|
+
const onMove = (ev) => {
|
|
1129
|
+
var _a, _b;
|
|
1130
|
+
// const selectionLines = svg.querySelector("#selection-lines");
|
|
1131
|
+
const dx = ev.clientX - startX;
|
|
1132
|
+
const dy = ev.clientY - startY;
|
|
1133
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
1134
|
+
// onPanning(ev);
|
|
1135
|
+
if (!hasMoved && distance > 0) {
|
|
1136
|
+
// only move ghost if the mouse has moved more than 5 pixels
|
|
1137
|
+
hasMoved = true;
|
|
1138
|
+
}
|
|
1139
|
+
const newX = pos.x - offset.x;
|
|
1140
|
+
const newY = pos.y - offset.y;
|
|
1141
|
+
let activeId = JSON.parse((targetGroup === null || targetGroup === void 0 ? void 0 : targetGroup.getAttribute("data-id")) || "{}");
|
|
1142
|
+
const selection = (_b = (_a = targetSelection === null || targetSelection === void 0 ? void 0 : targetSelection.dataset) === null || _a === void 0 ? void 0 : _a.selection) === null || _b === void 0 ? void 0 : _b.replace("selection-", "");
|
|
1143
|
+
const { g } = getAttributeElement(svg, selection !== null && selection !== void 0 ? selection : activeId);
|
|
1144
|
+
g === null || g === void 0 ? void 0 : g.setAttribute("transform", `translate(${newX}, ${newY})`);
|
|
1145
|
+
// selectionLines?.setAttribute("transform", `translate(${newX}, ${newY})`);
|
|
1146
|
+
};
|
|
1147
|
+
if (isMightMove) {
|
|
1148
|
+
onMove(ev);
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
const pointerHandleUp = (e) => {
|
|
1152
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
|
|
1153
|
+
//POLYGON RESIZE
|
|
1154
|
+
if (targetSelection) {
|
|
1155
|
+
console.log("KLIK UP SELECTION");
|
|
1156
|
+
//show
|
|
1157
|
+
dispatch({ type: "panel/setShow", payload: true });
|
|
1158
|
+
}
|
|
1159
|
+
if (targetPointPolygon && targetGroup) {
|
|
1160
|
+
const { element } = getAttributeElement(svg, targetGroup.dataset.id);
|
|
1161
|
+
const points = element === null || element === void 0 ? void 0 : element.getAttribute("points");
|
|
1162
|
+
const pointsArray = pointsStringToArray(points);
|
|
1163
|
+
const findById = [...componentsState, ...extraComponentsState].find((c) => c.id === targetGroup.dataset.id);
|
|
1164
|
+
const newDataComponent = Object.assign(Object.assign({}, findById), { points: pointsArray });
|
|
1165
|
+
updateComponentAttribute(newDataComponent);
|
|
1166
|
+
setSelectedComponent(newDataComponent);
|
|
1167
|
+
dispatch({
|
|
1168
|
+
type: "panel/setSelectedComponent",
|
|
1169
|
+
payload: newDataComponent,
|
|
1170
|
+
});
|
|
1171
|
+
selectionLinesRef.current = newDataComponent;
|
|
1172
|
+
}
|
|
1173
|
+
//POLYGON
|
|
1174
|
+
const isInitialPolyGon = activeTool === "polygon" && (selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.shape) === "polygon";
|
|
1175
|
+
if (isInitialPolyGon) {
|
|
1176
|
+
const closing = isClosingPolygon(x, y, selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.points);
|
|
1177
|
+
const newCoord = closing ? selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.points[0] : { x, y };
|
|
1178
|
+
const newPoints = Object.assign(Object.assign({}, selectedComponent), { points: [...selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.points, newCoord] });
|
|
1179
|
+
if (closing && ((_a = selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.points) === null || _a === void 0 ? void 0 : _a.length) > 2) {
|
|
1180
|
+
const { g, inner } = getAttributeElement(svg, selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.id);
|
|
1181
|
+
const { height, width } = getGlobalBBox(svg, inner);
|
|
1182
|
+
isOnMakePolygonRef.current = false;
|
|
1183
|
+
setSelectedComponent(null);
|
|
1184
|
+
dispatch({
|
|
1185
|
+
type: "panel/setSelectedComponent",
|
|
1186
|
+
payload: null,
|
|
1187
|
+
});
|
|
1188
|
+
polygonElementRef.current = [];
|
|
1189
|
+
addComponents(Object.assign(Object.assign({}, newPoints), { x: 0, y: 0, width, height }));
|
|
1190
|
+
(_b = svg.querySelector("#selection-box-ghost")) === null || _b === void 0 ? void 0 : _b.remove();
|
|
1191
|
+
(_c = svg.querySelector("#polyline-helper")) === null || _c === void 0 ? void 0 : _c.remove();
|
|
1192
|
+
}
|
|
1193
|
+
else {
|
|
1194
|
+
setSelectedComponent(newPoints);
|
|
1195
|
+
dispatch({
|
|
1196
|
+
type: "panel/setSelectedComponent",
|
|
1197
|
+
payload: newPoints,
|
|
1198
|
+
});
|
|
1199
|
+
polygonElementRef.current = newPoints;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
// ROTATE
|
|
1203
|
+
if (targetRotate && !isRotateSelectionBox) {
|
|
1204
|
+
const activeId = selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.id;
|
|
1205
|
+
const { g, inner, element } = getAttributeElement(svg, activeId);
|
|
1206
|
+
const boxGroup = getGlobalBBox(svg, inner);
|
|
1207
|
+
const findById = [...componentsState, ...extraComponentsState].find((c) => c.id === activeId);
|
|
1208
|
+
const { width, height } = getSvgElementSize(element);
|
|
1209
|
+
const { x, y } = getTranslate(g);
|
|
1210
|
+
const newDataComponent = Object.assign(Object.assign({}, findById), { x,
|
|
1211
|
+
y, rotation: ghostRotateElement.current });
|
|
1212
|
+
const boxSelection = {
|
|
1213
|
+
x,
|
|
1214
|
+
y,
|
|
1215
|
+
width,
|
|
1216
|
+
height,
|
|
1217
|
+
rotation: ghostRotateElement.current,
|
|
1218
|
+
shape: "selection-box",
|
|
1219
|
+
id: `${Date.now()}`,
|
|
1220
|
+
};
|
|
1221
|
+
if (boxGroup)
|
|
1222
|
+
setSelectedLines(boxSelection);
|
|
1223
|
+
updateComponentAttribute(newDataComponent);
|
|
1224
|
+
setSelectedComponent(newDataComponent);
|
|
1225
|
+
dispatch({
|
|
1226
|
+
type: "panel/setSelectedComponent",
|
|
1227
|
+
payload: newDataComponent,
|
|
1228
|
+
});
|
|
1229
|
+
//LOGIC FOR SET SHOW
|
|
1230
|
+
dispatch({ type: "panel/setShow", payload: false });
|
|
1231
|
+
}
|
|
1232
|
+
//CREATE NEW ELEMENT
|
|
1233
|
+
if (toolElement.includes(activeTool) && isCreateElementRef.current) {
|
|
1234
|
+
const ghostElementNew = (_d = svgRef.current) === null || _d === void 0 ? void 0 : _d.querySelector("#ghost-element-create");
|
|
1235
|
+
if (!ghostElementNew)
|
|
1236
|
+
return;
|
|
1237
|
+
const dataOriginal = JSON.parse(ghostElementNew.getAttribute("data-table") || "{}");
|
|
1238
|
+
const newComponent = Object.assign(Object.assign({ id: `${Date.now()}` }, dataOriginal), { seatCount: 4 });
|
|
1239
|
+
const newExtraComponent = [...extraComponentsState, newComponent];
|
|
1240
|
+
//ADD TO REDUX
|
|
1241
|
+
setExtraComponentsState(newExtraComponent);
|
|
1242
|
+
// setComponentsState(newComponentState);
|
|
1243
|
+
addComponents(newComponent);
|
|
1244
|
+
// syncFromLocalToRedux(newComponentState);
|
|
1245
|
+
//REMOVE GHOST ELEMENT
|
|
1246
|
+
(_e = svgRef.current) === null || _e === void 0 ? void 0 : _e.querySelectorAll("#ghost-element-create").forEach((el) => el.remove());
|
|
1247
|
+
isCreateElementRef.current = false;
|
|
1248
|
+
}
|
|
1249
|
+
// CREATE POLYGON
|
|
1250
|
+
const hasSelectedOneElement = !isEmpty(selectedComponent) &&
|
|
1251
|
+
onResizeSelectionRef.current &&
|
|
1252
|
+
downAtResizePosition;
|
|
1253
|
+
const idDataset = targetGroup === null || targetGroup === void 0 ? void 0 : targetGroup.dataset.id;
|
|
1254
|
+
const isSelectElement = !hasMoved &&
|
|
1255
|
+
!releaseGroupRef.current &&
|
|
1256
|
+
!targetRotate &&
|
|
1257
|
+
!hasSelectedOneElement &&
|
|
1258
|
+
idDataset &&
|
|
1259
|
+
activeTool === "select";
|
|
1260
|
+
// &&
|
|
1261
|
+
// !hadSelectionBox;
|
|
1262
|
+
console.log({ isSelectElement }, !hasMoved, !releaseGroupRef.current, !targetRotate, !hasSelectedOneElement, idDataset, activeTool, hadSelectionBox);
|
|
1263
|
+
if (isSelectElement) {
|
|
1264
|
+
//KLIK UP
|
|
1265
|
+
console.log("KLIK UP");
|
|
1266
|
+
const findById = [...componentsState, ...extraComponentsState].find((component) => component.id === idDataset);
|
|
1267
|
+
if (!findById) {
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
const { g, inner, element } = getAttributeElement(svg, idDataset);
|
|
1271
|
+
if (shiftActive) {
|
|
1272
|
+
const allElementSelectionGroup = dataElementSelectionGroupRef.current;
|
|
1273
|
+
// HITUNG BOUNDING BOX
|
|
1274
|
+
const xs = allElementSelectionGroup.map((c) => c.x);
|
|
1275
|
+
const ys = allElementSelectionGroup.map((c) => c.y);
|
|
1276
|
+
const x2 = allElementSelectionGroup.map((c) => c.x + c.width);
|
|
1277
|
+
const y2 = allElementSelectionGroup.map((c) => c.y + c.height);
|
|
1278
|
+
const selectionBox = {
|
|
1279
|
+
x: Math.min(...xs),
|
|
1280
|
+
y: Math.min(...ys),
|
|
1281
|
+
width: Math.max(...x2) - Math.min(...xs),
|
|
1282
|
+
height: Math.max(...y2) - Math.min(...ys),
|
|
1283
|
+
stroke: "red",
|
|
1284
|
+
shape: "selection-box",
|
|
1285
|
+
fill: "transparent",
|
|
1286
|
+
id: `${Date.now()}`,
|
|
1287
|
+
rotation: 0,
|
|
1288
|
+
};
|
|
1289
|
+
selectionLinesRef.current = selectionBox;
|
|
1290
|
+
setSelectedLines(selectionBox);
|
|
1291
|
+
dispatch({
|
|
1292
|
+
type: "panel/setSelectedGroup",
|
|
1293
|
+
payload: allElementSelectionGroup,
|
|
1294
|
+
});
|
|
1295
|
+
setSelectedComponent(null);
|
|
1296
|
+
dispatch({
|
|
1297
|
+
type: "panel/setSelectedComponent",
|
|
1298
|
+
payload: null,
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
else {
|
|
1302
|
+
const boxGroup = getGlobalBBox(svg, inner);
|
|
1303
|
+
// const { x, y } = getTranslate(g);
|
|
1304
|
+
const targets = ["table-seat-square", "table-seat-circle"];
|
|
1305
|
+
const isNotRealSize = targets.includes(findById === null || findById === void 0 ? void 0 : findById.shape);
|
|
1306
|
+
let boxSelection = Object.assign(Object.assign({}, findById), {
|
|
1307
|
+
// ...boxGroup,
|
|
1308
|
+
// x,
|
|
1309
|
+
// y,
|
|
1310
|
+
// width,
|
|
1311
|
+
// height: height ,
|
|
1312
|
+
// width,
|
|
1313
|
+
// height,
|
|
1314
|
+
// x:boxGroup.x,
|
|
1315
|
+
// y:boxGroup.y,
|
|
1316
|
+
shape: "selection-box" });
|
|
1317
|
+
if ((findById === null || findById === void 0 ? void 0 : findById.shape) === "polygon") {
|
|
1318
|
+
const boxGroup = getGlobalBBox(svg, g);
|
|
1319
|
+
boxSelection = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, boxSelection), (boxGroup ? { x: boxGroup.x } : {})), (boxGroup ? { y: boxGroup.y } : {})), (boxGroup ? { width: boxGroup.width } : {})), (boxGroup ? { height: boxGroup.height } : {})), { shape: "selection-box" });
|
|
1320
|
+
}
|
|
1321
|
+
if ((boxSelection === null || boxSelection === void 0 ? void 0 : boxSelection.x) &&
|
|
1322
|
+
(boxSelection === null || boxSelection === void 0 ? void 0 : boxSelection.y) &&
|
|
1323
|
+
(boxSelection === null || boxSelection === void 0 ? void 0 : boxSelection.width) &&
|
|
1324
|
+
(boxSelection === null || boxSelection === void 0 ? void 0 : boxSelection.height)) {
|
|
1325
|
+
setSelectedLines(boxSelection);
|
|
1326
|
+
setSelectedComponent(findById);
|
|
1327
|
+
dispatch({
|
|
1328
|
+
type: "panel/setSelectedComponent",
|
|
1329
|
+
payload: findById,
|
|
1330
|
+
});
|
|
1331
|
+
selectionLinesRef.current = boxSelection;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
//LOGIC FOR SET SHOW
|
|
1335
|
+
dispatch({ type: "panel/setShow", payload: true });
|
|
1336
|
+
}
|
|
1337
|
+
//UPDATE DATASET TO STATE IF RESIZE
|
|
1338
|
+
if (isMightResizeElement) {
|
|
1339
|
+
const activeId = selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.id;
|
|
1340
|
+
const svg = svgRef.current;
|
|
1341
|
+
const { g, element } = getAttributeElement(svg, activeId);
|
|
1342
|
+
const { width, height } = getSvgElementSize(element);
|
|
1343
|
+
const { x, y } = getTranslate(g);
|
|
1344
|
+
const newSelectedComponent = Object.assign(Object.assign({}, selectedComponent), { x: x, y: y, width: width, height: height });
|
|
1345
|
+
const newSelection = Object.assign(Object.assign({}, selectedComponent), { x: x, y: y, width: width, height: height, shape: "selection-box", id: `${Date.now()}` });
|
|
1346
|
+
setSelectedLines(newSelection);
|
|
1347
|
+
// dragGhostRef.current = false;
|
|
1348
|
+
setSelectedComponent(newSelectedComponent);
|
|
1349
|
+
dispatch({
|
|
1350
|
+
type: "panel/setSelectedComponent",
|
|
1351
|
+
payload: newSelectedComponent,
|
|
1352
|
+
});
|
|
1353
|
+
updateComponentAttribute(newSelectedComponent);
|
|
1354
|
+
//LOGIC FOR SET SHOW
|
|
1355
|
+
dispatch({ type: "panel/setShow", payload: false });
|
|
1356
|
+
// isResizeRef.current = false;
|
|
1357
|
+
}
|
|
1358
|
+
//MAKING SELECTION BOX UP
|
|
1359
|
+
const isMakingSelectionBoxUp = downBeforeHasSelectionBox && activeTool === "select";
|
|
1360
|
+
if (isMakingSelectionBoxUp
|
|
1361
|
+
// &&
|
|
1362
|
+
// !hadSelectionRef.current &&
|
|
1363
|
+
// !isResizeSelectionRef.current
|
|
1364
|
+
) {
|
|
1365
|
+
console.log("MAKING SELECTION BOX UP");
|
|
1366
|
+
const allElementSelectionGroup = dataElementSelectionGroupRef.current;
|
|
1367
|
+
// if(allElementSelectionGroup.length > 1){
|
|
1368
|
+
const findAllPolygonGroup = allElementSelectionGroup.filter((c) => c.shape === "polygon");
|
|
1369
|
+
// HITUNG BOUNDING BOX
|
|
1370
|
+
let xs = allElementSelectionGroup.map((c) => c.x);
|
|
1371
|
+
let ys = allElementSelectionGroup.map((c) => c.y);
|
|
1372
|
+
let x2 = allElementSelectionGroup.map((c) => c.x + c.width);
|
|
1373
|
+
let y2 = allElementSelectionGroup.map((c) => c.y + c.height);
|
|
1374
|
+
findAllPolygonGroup.forEach((polygon) => {
|
|
1375
|
+
const { points } = polygon;
|
|
1376
|
+
for (let i = 0; i < points.numberOfItems; i++) {
|
|
1377
|
+
const point = points.getItem(i);
|
|
1378
|
+
xs.push(point.x);
|
|
1379
|
+
ys.push(point.y);
|
|
1380
|
+
x2.push(point.x);
|
|
1381
|
+
y2.push(point.y);
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
const selectionBox = {
|
|
1385
|
+
x: Math.min(...xs),
|
|
1386
|
+
y: Math.min(...ys),
|
|
1387
|
+
width: Math.max(...x2) - Math.min(...xs),
|
|
1388
|
+
height: Math.max(...y2) - Math.min(...ys),
|
|
1389
|
+
stroke: "red",
|
|
1390
|
+
shape: "selection-box",
|
|
1391
|
+
fill: "transparent",
|
|
1392
|
+
id: `${Date.now()}`,
|
|
1393
|
+
rotation: 0,
|
|
1394
|
+
};
|
|
1395
|
+
function hasInvalidNumber(obj) {
|
|
1396
|
+
return Object.values(obj).some((v) => typeof v === "number" && !Number.isFinite(v));
|
|
1397
|
+
}
|
|
1398
|
+
if (!hasInvalidNumber(selectionBox)) {
|
|
1399
|
+
selectionLinesRef.current = selectionBox;
|
|
1400
|
+
setSelectedLines(selectionBox);
|
|
1401
|
+
dispatch({
|
|
1402
|
+
type: "panel/setSelectedGroup",
|
|
1403
|
+
payload: allElementSelectionGroup,
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
setSelectedComponent(null);
|
|
1407
|
+
dispatch({
|
|
1408
|
+
type: "panel/setSelectedComponent",
|
|
1409
|
+
payload: null,
|
|
1410
|
+
});
|
|
1411
|
+
rotationSelectionRef.current = 0;
|
|
1412
|
+
(_g = (_f = svgRef.current) === null || _f === void 0 ? void 0 : _f.querySelectorAll("#selection-box-ghost")) === null || _g === void 0 ? void 0 : _g.forEach((el) => el.remove());
|
|
1413
|
+
}
|
|
1414
|
+
//RESIZE SELECTION BOX UP
|
|
1415
|
+
if (downAtResizePositionAndHasSelectionBox || isRotateSelectionBox) {
|
|
1416
|
+
const selectionLines = (_h = svgRef.current) === null || _h === void 0 ? void 0 : _h.querySelector("#selection-lines");
|
|
1417
|
+
isResizeSelectionRef.current = false;
|
|
1418
|
+
const results = (_j = dataElementSelectionGroupRef.current) === null || _j === void 0 ? void 0 : _j.map((item) => {
|
|
1419
|
+
const { g, element, inner } = getAttributeElement(svg, item.id);
|
|
1420
|
+
const { x, y } = getTranslate(g);
|
|
1421
|
+
const { width, height } = getSvgElementSize(element);
|
|
1422
|
+
const rotate = getRotation(inner.transform.baseVal);
|
|
1423
|
+
const getBBox = getGlobalBBox(svg, element);
|
|
1424
|
+
const newDataComponent = Object.assign(Object.assign({}, item), { x,
|
|
1425
|
+
y,
|
|
1426
|
+
width,
|
|
1427
|
+
height, rotation: rotate });
|
|
1428
|
+
const newDataSelection = Object.assign(Object.assign({}, newDataComponent), { x: getBBox.x, y: getBBox.y, width: getBBox.width, height: getBBox.height, shape: "selection-box", id: `${Date.now()}` });
|
|
1429
|
+
return {
|
|
1430
|
+
newDataComponent,
|
|
1431
|
+
newDataSelection,
|
|
1432
|
+
};
|
|
1433
|
+
});
|
|
1434
|
+
const newDataComponent = results.map((r) => r.newDataComponent);
|
|
1435
|
+
// const dataSelection = results.map((r) => r.newDataSelection);
|
|
1436
|
+
const linesSelection = getTranslate(selectionLines);
|
|
1437
|
+
const currentRotation = getRotation((_l = (_k = selectionLines === null || selectionLines === void 0 ? void 0 : selectionLines.children[0]) === null || _k === void 0 ? void 0 : _k.transform) === null || _l === void 0 ? void 0 : _l.baseVal);
|
|
1438
|
+
rotationSelectionRef.current = currentRotation;
|
|
1439
|
+
const sizeSelection = getSvgElementSize((_m = selectionLines === null || selectionLines === void 0 ? void 0 : selectionLines.children[0]) === null || _m === void 0 ? void 0 : _m.children[0]);
|
|
1440
|
+
updateComponentsAttribute(newDataComponent);
|
|
1441
|
+
const newDataSelection = Object.assign(Object.assign(Object.assign({}, linesSelection), sizeSelection), { shape: "selection-box", id: `${Date.now()}`, rotation: currentRotation });
|
|
1442
|
+
dispatch({
|
|
1443
|
+
type: "panel/setSelectedGroup",
|
|
1444
|
+
payload: newDataComponent,
|
|
1445
|
+
});
|
|
1446
|
+
setSelectedComponent(null);
|
|
1447
|
+
dispatch({
|
|
1448
|
+
type: "panel/setSelectedComponent",
|
|
1449
|
+
payload: null,
|
|
1450
|
+
});
|
|
1451
|
+
setSelectedLines(newDataSelection);
|
|
1452
|
+
dataElementSelectionGroupRef.current = newDataComponent;
|
|
1453
|
+
// // remove ghost element
|
|
1454
|
+
// svgRef.current
|
|
1455
|
+
// ?.querySelectorAll("#ghost-element-has-selection")
|
|
1456
|
+
// .forEach((el) => el.remove());
|
|
1457
|
+
hadSelectionRef.current = false;
|
|
1458
|
+
}
|
|
1459
|
+
//MOVE SELECTION BOX UP
|
|
1460
|
+
if (downOutResizePositionAndInSelectionBox &&
|
|
1461
|
+
!shiftActive &&
|
|
1462
|
+
!isRotateSelectionBox) {
|
|
1463
|
+
console.log("MOVE SELECTION BOX UP");
|
|
1464
|
+
const selectionLines = (_o = svgRef.current) === null || _o === void 0 ? void 0 : _o.querySelector("#selection-lines");
|
|
1465
|
+
isResizeSelectionRef.current = false;
|
|
1466
|
+
const results = (_p = dataElementSelectionGroupRef.current) === null || _p === void 0 ? void 0 : _p.map((item) => {
|
|
1467
|
+
const { g, element, inner } = getAttributeElement(svg, item.id);
|
|
1468
|
+
const { x, y } = getTranslate(g);
|
|
1469
|
+
const getBBox = getGlobalBBox(svg, element);
|
|
1470
|
+
const newDataComponent = Object.assign(Object.assign({}, item), { x,
|
|
1471
|
+
y });
|
|
1472
|
+
return {
|
|
1473
|
+
newDataComponent,
|
|
1474
|
+
};
|
|
1475
|
+
});
|
|
1476
|
+
const newDataComponent = results.map((r) => r.newDataComponent);
|
|
1477
|
+
// const dataSelection = results.map((r) => r.newDataSelection);
|
|
1478
|
+
const linesSelection = getTranslate(selectionLines);
|
|
1479
|
+
const currentRotation = getRotation((_r = (_q = selectionLines === null || selectionLines === void 0 ? void 0 : selectionLines.children[0]) === null || _q === void 0 ? void 0 : _q.transform) === null || _r === void 0 ? void 0 : _r.baseVal);
|
|
1480
|
+
rotationSelectionRef.current = currentRotation;
|
|
1481
|
+
const sizeSelection = getSvgElementSize((_s = selectionLines === null || selectionLines === void 0 ? void 0 : selectionLines.children[0]) === null || _s === void 0 ? void 0 : _s.children[0]);
|
|
1482
|
+
updateComponentsAttribute(newDataComponent);
|
|
1483
|
+
const newDataSelection = Object.assign(Object.assign(Object.assign({}, linesSelection), sizeSelection), { shape: "selection-box", id: `${Date.now()}-from-move`, rotation: currentRotation });
|
|
1484
|
+
dispatch({
|
|
1485
|
+
type: "panel/setSelectedGroup",
|
|
1486
|
+
payload: newDataComponent,
|
|
1487
|
+
});
|
|
1488
|
+
setSelectedComponent(null);
|
|
1489
|
+
dispatch({
|
|
1490
|
+
type: "panel/setSelectedComponent",
|
|
1491
|
+
payload: null,
|
|
1492
|
+
});
|
|
1493
|
+
if ((_t = newDataSelection === null || newDataSelection === void 0 ? void 0 : newDataSelection.height) === null || _t === void 0 ? void 0 : _t.id) {
|
|
1494
|
+
newDataSelection.height = newDataSelection.height.id;
|
|
1495
|
+
}
|
|
1496
|
+
if (newDataSelection === null || newDataSelection === void 0 ? void 0 : newDataSelection.x) {
|
|
1497
|
+
setSelectedLines(newDataSelection);
|
|
1498
|
+
}
|
|
1499
|
+
dataElementSelectionGroupRef.current = newDataComponent;
|
|
1500
|
+
hadSelectionRef.current = false;
|
|
1501
|
+
}
|
|
1502
|
+
//DELETE GHOST ELEMENT BISA MEMBU
|
|
1503
|
+
(_u = svgRef.current) === null || _u === void 0 ? void 0 : _u.querySelectorAll("#ghost-element").forEach((el) => el.remove());
|
|
1504
|
+
dataSetGhost.current = null;
|
|
1505
|
+
//UPDATE DATASET TO STATE IF MOVE
|
|
1506
|
+
// if (releaseGroupRef.current) return;
|
|
1507
|
+
if (isMightMove &&
|
|
1508
|
+
!isMakingSelectionBoxUp &&
|
|
1509
|
+
!isSelectElement &&
|
|
1510
|
+
!isMightResizeElement) {
|
|
1511
|
+
console.log("MOVE ONE ELEMENT");
|
|
1512
|
+
// console.log({ idActive, datasetUp });
|
|
1513
|
+
let activeId = JSON.parse((targetGroup === null || targetGroup === void 0 ? void 0 : targetGroup.getAttribute("data-id")) || "{}");
|
|
1514
|
+
const selection = (_w = (_v = targetSelection === null || targetSelection === void 0 ? void 0 : targetSelection.dataset) === null || _v === void 0 ? void 0 : _v.selection) === null || _w === void 0 ? void 0 : _w.replace("selection-", "");
|
|
1515
|
+
const activeIdSelection = selection !== null && selection !== void 0 ? selection : activeId;
|
|
1516
|
+
const findData = [...componentsState, ...extraComponentsState].find((item) => item.id == activeIdSelection);
|
|
1517
|
+
const { g } = getAttributeElement(svg, activeIdSelection);
|
|
1518
|
+
const bbox = getTranslate(g);
|
|
1519
|
+
const updateComponent = Object.assign(Object.assign({}, findData), { x: bbox === null || bbox === void 0 ? void 0 : bbox.x, y: bbox === null || bbox === void 0 ? void 0 : bbox.y });
|
|
1520
|
+
const selectionLines = Object.assign(Object.assign({}, selectedLines), { x: bbox === null || bbox === void 0 ? void 0 : bbox.x, y: bbox === null || bbox === void 0 ? void 0 : bbox.y, shape: "selection-box" });
|
|
1521
|
+
if (findData) {
|
|
1522
|
+
setSelectedLines(selectionLines);
|
|
1523
|
+
setSelectedComponent(updateComponent);
|
|
1524
|
+
dispatch({
|
|
1525
|
+
type: "panel/updateSelectedComponent",
|
|
1526
|
+
payload: updateComponent,
|
|
1527
|
+
});
|
|
1528
|
+
updateComponentAttribute(updateComponent);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
window.removeEventListener("pointermove", pointerMoveGhost);
|
|
1532
|
+
window.removeEventListener("pointerup", pointerHandleUp);
|
|
1533
|
+
};
|
|
1534
|
+
window.addEventListener("pointermove", pointerMoveGhost);
|
|
1535
|
+
window.addEventListener("pointerup", pointerHandleUp);
|
|
1536
|
+
};
|
|
1537
|
+
const handlePointerMove = (e) => {
|
|
1538
|
+
var _a;
|
|
1539
|
+
const svg = svgRef.current;
|
|
1540
|
+
const pt = svg.createSVGPoint();
|
|
1541
|
+
pt.x = e.clientX;
|
|
1542
|
+
pt.y = e.clientY;
|
|
1543
|
+
const pos = pt.matrixTransform(svg.getScreenCTM().inverse());
|
|
1544
|
+
if (isOnMakePolygonRef === null || isOnMakePolygonRef === void 0 ? void 0 : isOnMakePolygonRef.current) {
|
|
1545
|
+
const polylineHelper = svg === null || svg === void 0 ? void 0 : svg.querySelector("#polyline-helper");
|
|
1546
|
+
const startPoint = selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.points[((_a = selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.points) === null || _a === void 0 ? void 0 : _a.length) - 1];
|
|
1547
|
+
;
|
|
1548
|
+
const newPoints = [...selectedComponent === null || selectedComponent === void 0 ? void 0 : selectedComponent.points, { x: pos.x, y: pos.y }];
|
|
1549
|
+
const points = newPoints.map((item) => `${item.x},${item.y}`).join(" ");
|
|
1550
|
+
polylineHelper === null || polylineHelper === void 0 ? void 0 : polylineHelper.setAttribute("points", points);
|
|
1551
|
+
//lin helper from start to move
|
|
1552
|
+
}
|
|
1553
|
+
};
|
|
1554
|
+
return (_jsxs(_Fragment, { children: [_jsx(ModalPreview, { children: _jsx(LayerView, { statusKey: "status" }) }), touch && _jsx(Button, { onClick: () => setTouch(false), children: "Stop Touch" }), _jsxs("div", { className: "relative w-full h-screen flex-1 overflow-hidden", ref: containerRef, children: [_jsx("div", { className: "absolute bottom-5 left-1/2 transform -translate-x-1/2 z-10", children: _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { icon: _jsx(ZoomIn, {}), onClick: handelZoomIn }), _jsx(Button, { icon: _jsx(ZoomOut, {}), onClick: handleZoomOut })] }) }), _jsxs(TransformWrapper, { ref: transformRef, panning: {
|
|
1555
|
+
disabled: [
|
|
1556
|
+
"node",
|
|
1557
|
+
"select",
|
|
1558
|
+
"square",
|
|
1559
|
+
"circle",
|
|
1560
|
+
"table-seat-circle",
|
|
1561
|
+
].includes(activeTool),
|
|
1562
|
+
}, centerZoomedOut: true,
|
|
1563
|
+
// onTransformed={handleTransformed}
|
|
1564
|
+
minScale: 0.1, maxScale: 1000, initialScale: scale, pinch: { step: 1 }, wheel: { disabled: true }, smooth: true, doubleClick: { step: 1, disabled: activeTool === "select" }, disablePadding: true, centerOnInit: true, children: [_jsx("div", { className: "absolute bottom-[60px] left-1/2 transform -translate-x-1/2 z-10" }), _jsxs(TransformComponent, { wrapperStyle: {
|
|
1565
|
+
width: "100%",
|
|
1566
|
+
height: "100%",
|
|
1567
|
+
}, contentStyle: { width: boardSize.width, height: boardSize.height }, children: [isLoading && (_jsx("div", { className: "absolute z-20 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2", children: "Loading..." })), _jsxs("svg", { id: "workspace", ref: svgRef, width: boardSize.width, height: boardSize.height, viewBox: `${minCoords.x} ${minCoords.y} ${boardSize.width} ${boardSize.height}`,
|
|
1568
|
+
// viewBox={`0 0 ${widthBoard} ${heightBoard}`}
|
|
1569
|
+
// className="h-screen"
|
|
1570
|
+
// onMouseUp={handleMouseUp}
|
|
1571
|
+
// onPointerMove={handleMouseMove}
|
|
1572
|
+
onPointerDown: handlePointerDown, onPointerMove: handlePointerMove,
|
|
1573
|
+
// onMouseEnter={handleMouseEnter}
|
|
1574
|
+
// onMouseUp={handleEnd}
|
|
1575
|
+
// onTouchMove={handleMouseMove}
|
|
1576
|
+
// onTouchEnd={handleMouseUp}
|
|
1577
|
+
// onClick={(e) => {
|
|
1578
|
+
// e.stopPropagation();
|
|
1579
|
+
// handleMouseClick(e);
|
|
1580
|
+
// }}
|
|
1581
|
+
// onMouseLeave={handleMouseLeave}
|
|
1582
|
+
xmlns: "http://www.w3.org/2000/svg", preserveAspectRatio: "xMidYMid meet", style: {
|
|
1583
|
+
// background: backgroundColor,
|
|
1584
|
+
background: "black",
|
|
1585
|
+
display: "block",
|
|
1586
|
+
cursor: activeTool === "ruler" ? "crosshair" : "auto",
|
|
1587
|
+
touchAction: "none",
|
|
1588
|
+
pointerEvents: "all",
|
|
1589
|
+
userSelect: "none",
|
|
1590
|
+
}, children: [_jsx(Layers, { shadowShape: shadowShape, components: [
|
|
1591
|
+
...extraComponentsState,
|
|
1592
|
+
...componentsState,
|
|
1593
|
+
polygonElementRef === null || polygonElementRef === void 0 ? void 0 : polygonElementRef.current,
|
|
1594
|
+
], style: {
|
|
1595
|
+
cursor: getCursorStyle(),
|
|
1596
|
+
},
|
|
1597
|
+
// onClick={handleSelectComponent}
|
|
1598
|
+
// onMouseDown={handleMouseDown}
|
|
1599
|
+
// onMouseUp={handleMouseUp}
|
|
1600
|
+
// onBlur={handleUnSelectComponent}
|
|
1601
|
+
selectedComponent: selectedComponent, selectionLines: selectedLines, activeTool: activeTool }), activeTool === "polygon" && (_jsx("polyline", { id: "polyline-helper", stroke: "white", fill: "transparent", strokeWidth: 2 })), activeTool === "ruler" && (_jsxs(_Fragment, { children: [_jsxs("g", { id: "horizontal-ruler", children: [_jsx("rect", { x: "0", y: "0", width: window.innerWidth, height: "30", fill: "#e0e0e0" }), _jsx("g", { stroke: "#888", "font-size": "10", "text-anchor": "middle", children: Array.from({ length: window.innerWidth / 50 }, (_, i) => (_jsxs("g", { children: [_jsx("line", { x1: i * 50, y1: "0", x2: i * 50, y2: "10" }), _jsx("text", { x: i * 50, y: "15", children: i * 50 })] }, i))) })] }), _jsxs("g", { id: "vertical-ruler", children: [_jsx("rect", { x: "0", y: "0", width: "30", height: window.innerHeight, fill: "#e0e0e0" }), _jsx("g", { stroke: "#888", "font-size": "10", "text-anchor": "middle", children: Array.from({ length: window.innerHeight / 10 }, (_, i) => (_jsxs("g", { children: [_jsx("line", { x1: "0", y1: i * 50, x2: "10", y2: i * 50 }), _jsx("text", { x: "15", y: i * 50, children: i * 50 })] }, i))) })] })] })), grid && (_jsxs("g", { stroke: "#ddd", strokeWidth: 0.5, children: [Array.from({ length: widthBoard / (10 * scale) }, (_, i) => (_jsx("line", { x1: i * 10 * scale, y1: 0, x2: i * 10 * scale, y2: heightBoard }, `v-${i}`))), Array.from({ length: heightBoard / (10 * scale) }, (_, i) => (_jsx("line", { x1: 0, y1: i * 10 * scale, x2: widthBoard, y2: i * 10 * scale }, `h-${i}`)))] }))] })] })] })] })] }));
|
|
1602
|
+
};
|
|
1603
|
+
export default BoardTemplate;
|
|
1604
|
+
function getGlobalBBox(svg, el) {
|
|
1605
|
+
var _a;
|
|
1606
|
+
const bbox = el === null || el === void 0 ? void 0 : el.getBBox();
|
|
1607
|
+
// matrix dari element ke screen pixel
|
|
1608
|
+
const m = el === null || el === void 0 ? void 0 : el.getScreenCTM();
|
|
1609
|
+
if (!m)
|
|
1610
|
+
return null;
|
|
1611
|
+
// matrix dari screen pixel ke svg user coords
|
|
1612
|
+
const screenToSvg = (_a = svg.getScreenCTM()) === null || _a === void 0 ? void 0 : _a.inverse();
|
|
1613
|
+
if (!screenToSvg)
|
|
1614
|
+
return null;
|
|
1615
|
+
function transformPoint(x, y) {
|
|
1616
|
+
const p = new DOMPoint(x, y)
|
|
1617
|
+
.matrixTransform(m)
|
|
1618
|
+
.matrixTransform(screenToSvg);
|
|
1619
|
+
return { x: p.x, y: p.y };
|
|
1620
|
+
}
|
|
1621
|
+
const p1 = transformPoint(bbox.x, bbox.y);
|
|
1622
|
+
const p2 = transformPoint(bbox.x + bbox.width, bbox.y);
|
|
1623
|
+
const p3 = transformPoint(bbox.x + bbox.width, bbox.y + bbox.height);
|
|
1624
|
+
const p4 = transformPoint(bbox.x, bbox.y + bbox.height);
|
|
1625
|
+
const xs = [p1.x, p2.x, p3.x, p4.x];
|
|
1626
|
+
const ys = [p1.y, p2.y, p3.y, p4.y];
|
|
1627
|
+
// const rotation = Math.atan2(p2.y - p1.y, p2.x - p1.x) * (180 / Math.PI);
|
|
1628
|
+
return {
|
|
1629
|
+
x: Math.min(...xs),
|
|
1630
|
+
y: Math.min(...ys),
|
|
1631
|
+
width: Math.max(...xs) - Math.min(...xs),
|
|
1632
|
+
height: Math.max(...ys) - Math.min(...ys),
|
|
1633
|
+
// rotation
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function localToWorld(dx, dy, angle) {
|
|
1637
|
+
const cosR = Math.cos(angle);
|
|
1638
|
+
const sinR = Math.sin(angle);
|
|
1639
|
+
return {
|
|
1640
|
+
x: dx * cosR - dy * sinR,
|
|
1641
|
+
y: dx * sinR + dy * cosR,
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
export function getTranslate(el) {
|
|
1645
|
+
if (!el)
|
|
1646
|
+
return {
|
|
1647
|
+
x: null,
|
|
1648
|
+
y: null,
|
|
1649
|
+
};
|
|
1650
|
+
const t = el.getAttribute("transform") || "";
|
|
1651
|
+
// cari pola translate( x , y )
|
|
1652
|
+
const match = t.match(/translate\(\s*([-\d.]+)[ ,]+([-\d.]+)\s*\)/);
|
|
1653
|
+
if (!match)
|
|
1654
|
+
return { x: 0, y: 0 };
|
|
1655
|
+
return {
|
|
1656
|
+
x: parseFloat(match[1]),
|
|
1657
|
+
y: parseFloat(match[2]),
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
export function getSize(el) {
|
|
1661
|
+
const bbox = el.getBBox();
|
|
1662
|
+
return {
|
|
1663
|
+
width: bbox.width,
|
|
1664
|
+
height: bbox.height,
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
function stabilizeRotation(tx, ty, W, H, oldAngle, newAngle) {
|
|
1668
|
+
const cx = W / 2;
|
|
1669
|
+
const cy = H / 2;
|
|
1670
|
+
const oldA = (oldAngle * Math.PI) / 180;
|
|
1671
|
+
const newA = (newAngle * Math.PI) / 180;
|
|
1672
|
+
// old center global
|
|
1673
|
+
const oldGx = tx + (cx * Math.cos(oldA) - cy * Math.sin(oldA));
|
|
1674
|
+
const oldGy = ty + (cx * Math.sin(oldA) + cy * Math.cos(oldA));
|
|
1675
|
+
// new center global
|
|
1676
|
+
const newGx = tx + (cx * Math.cos(newA) - cy * Math.sin(newA));
|
|
1677
|
+
const newGy = ty + (cx * Math.sin(newA) + cy * Math.cos(newA));
|
|
1678
|
+
// delta shift
|
|
1679
|
+
const dx = oldGx - newGx;
|
|
1680
|
+
const dy = oldGy - newGy;
|
|
1681
|
+
return { tx: tx + dx, ty: ty + dy };
|
|
1682
|
+
}
|
|
1683
|
+
const SNAP_RADIUS = 10;
|
|
1684
|
+
function isClosingPolygon(mouseX, mouseY, points) {
|
|
1685
|
+
const p0 = points[0]; // titik awal
|
|
1686
|
+
const dx = mouseX - p0.x;
|
|
1687
|
+
const dy = mouseY - p0.y;
|
|
1688
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1689
|
+
return dist < SNAP_RADIUS;
|
|
1690
|
+
}
|
|
1691
|
+
function calculateNewSelection(oldSel, pos, // Mouse Position saat ini (World)
|
|
1692
|
+
startPos, // Mouse Position saat MouseDown (World)
|
|
1693
|
+
side, angleRad // Sudut rotasi selection saat ini
|
|
1694
|
+
) {
|
|
1695
|
+
// 1. Hitung selisih mouse di layar (World Delta)
|
|
1696
|
+
const dx = pos.x - startPos.x;
|
|
1697
|
+
const dy = pos.y - startPos.y;
|
|
1698
|
+
// 2. Proyeksikan ke sumbu kotak (Local Delta)
|
|
1699
|
+
// Ini kunci agar narik miring tetap dihitung sebagai lebar/tinggi
|
|
1700
|
+
const localDX = dx * Math.cos(-angleRad) - dy * Math.sin(-angleRad);
|
|
1701
|
+
const localDY = dx * Math.sin(-angleRad) + dy * Math.cos(-angleRad);
|
|
1702
|
+
const rule = RESIZE_RULES[side];
|
|
1703
|
+
// 3. Hitung dimensi baru (selalu positif)
|
|
1704
|
+
const width = Math.max(1, oldSel.width + (rule.widthFactor * localDX));
|
|
1705
|
+
const height = Math.max(1, oldSel.height + (rule.heightFactor * localDY));
|
|
1706
|
+
// 4. Hitung perpindahan Anchor (Local Space)
|
|
1707
|
+
// Berapa banyak (x,y) harus bergeser agar sisi seberang diam
|
|
1708
|
+
const mlX = rule.moveLocalX(localDX, localDY);
|
|
1709
|
+
const mlY = rule.moveLocalY(localDX, localDY);
|
|
1710
|
+
// 5. Putar balik perpindahan ke World Space
|
|
1711
|
+
const worldMX = mlX * Math.cos(angleRad) - mlY * Math.sin(angleRad);
|
|
1712
|
+
const worldMY = mlX * Math.sin(angleRad) + mlY * Math.cos(angleRad);
|
|
1713
|
+
return {
|
|
1714
|
+
x: oldSel.x + worldMX,
|
|
1715
|
+
y: oldSel.y + worldMY,
|
|
1716
|
+
width,
|
|
1717
|
+
height
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
// function mapElementToResize(el: any, oldSel: any, newSel: any): any {
|
|
1721
|
+
// const rx = (el.x - oldSel.x) / oldSel.width;
|
|
1722
|
+
// const ry = (el.y - oldSel.y) / oldSel.height;
|
|
1723
|
+
// const rw = el.width / oldSel.width;
|
|
1724
|
+
// const rh = el.height / oldSel.height;
|
|
1725
|
+
// return {
|
|
1726
|
+
// ...el,
|
|
1727
|
+
// x: newSel.x + rx * newSel.width,
|
|
1728
|
+
// y: newSel.y + ry * newSel.height,
|
|
1729
|
+
// width: rw * newSel.width,
|
|
1730
|
+
// height: rh * newSel.height,
|
|
1731
|
+
// rotation: el.rotation,
|
|
1732
|
+
// };
|
|
1733
|
+
// }
|
|
1734
|
+
// function mapElementToResize(
|
|
1735
|
+
// el: any,
|
|
1736
|
+
// oldSel: any, // x, y, width, height original
|
|
1737
|
+
// newSel: any, // x, y, width, height hasil resize
|
|
1738
|
+
// angleRad: number
|
|
1739
|
+
// ) {
|
|
1740
|
+
// // 1. Hitung jarak elemen dari pojok kiri atas (Top-Left) original di World Space
|
|
1741
|
+
// const dx = el.x - oldSel.x;
|
|
1742
|
+
// const dy = el.y - oldSel.y;
|
|
1743
|
+
// // 2. UN-ROTATE: Ubah jarak World ke jarak Local (seolah-olah kotak tidak miring)
|
|
1744
|
+
// const cosA = Math.cos(-angleRad);
|
|
1745
|
+
// const sinA = Math.sin(-angleRad);
|
|
1746
|
+
// const localX = dx * cosA - dy * sinA;
|
|
1747
|
+
// const localY = dx * sinA + dy * cosA;
|
|
1748
|
+
// // 3. SCALE: Hitung rasio perubahan ukuran
|
|
1749
|
+
// const scaleX = newSel.width / oldSel.width || 1;
|
|
1750
|
+
// const scaleY = newSel.height / oldSel.height || 1;
|
|
1751
|
+
// // 4. Hitung posisi baru di Local Space
|
|
1752
|
+
// const newLocalX = localX * scaleX;
|
|
1753
|
+
// const newLocalY = localY * scaleY;
|
|
1754
|
+
// // 5. RE-ROTATE: Kembalikan posisi lokal ke World Space sesuai rotasi saat ini
|
|
1755
|
+
// const cosA2 = Math.cos(angleRad);
|
|
1756
|
+
// const sinA2 = Math.sin(angleRad);
|
|
1757
|
+
// const newDx = newLocalX * cosA2 - newLocalY * sinA2;
|
|
1758
|
+
// const newDy = newLocalX * sinA2 + newLocalY * cosA2;
|
|
1759
|
+
// return {
|
|
1760
|
+
// ...el,
|
|
1761
|
+
// x: newSel.x + newDx, // Posisi baru = Origin Baru + Jarak Baru yang sudah diputar
|
|
1762
|
+
// y: newSel.y + newDy,
|
|
1763
|
+
// width: el.width * scaleX,
|
|
1764
|
+
// height: el.height * scaleY,
|
|
1765
|
+
// };
|
|
1766
|
+
// }
|
|
1767
|
+
// function mapElementToResize(
|
|
1768
|
+
// el: any,
|
|
1769
|
+
// oldSel: any,
|
|
1770
|
+
// newSel: any,
|
|
1771
|
+
// angleRad: number // Sudut dari Selection Box
|
|
1772
|
+
// ) {
|
|
1773
|
+
// // 1. Hitung Rasio Skala Selection Box
|
|
1774
|
+
// const scaleX_Sel = newSel.width / oldSel.width || 1;
|
|
1775
|
+
// const scaleY_Sel = newSel.height / oldSel.height || 1;
|
|
1776
|
+
// // 2. Hitung Titik Pusat (Penting untuk rotasi)
|
|
1777
|
+
// const cxOld = oldSel.x + oldSel.width / 2;
|
|
1778
|
+
// const cyOld = oldSel.y + oldSel.height / 2;
|
|
1779
|
+
// const cxNew = newSel.x + newSel.width / 2;
|
|
1780
|
+
// const cyNew = newSel.y + newSel.height / 2;
|
|
1781
|
+
// // 3. POSISI: Konversi Global -> Local -> Scale -> Global
|
|
1782
|
+
// // Menggunakan fungsi toLocal dan toGlobal bawaan Anda
|
|
1783
|
+
// const local = toLocal(el.x, el.y, cxOld, cyOld, angleRad);
|
|
1784
|
+
// const newLocalX = local.x * scaleX_Sel;
|
|
1785
|
+
// const newLocalY = local.y * scaleY_Sel;
|
|
1786
|
+
// const global = toGlobal(newLocalX, newLocalY, cxNew, cyNew, angleRad);
|
|
1787
|
+
// // 4. UKURAN: Proyeksi Skala berdasarkan perbedaan rotasi
|
|
1788
|
+
// // Kita hitung seberapa besar pengaruh scaleX/Y seleksi terhadap lebar/tinggi elemen
|
|
1789
|
+
// const elAngleRad = (el.rotation * Math.PI) / 180;
|
|
1790
|
+
// const relativeAngle = elAngleRad - angleRad;
|
|
1791
|
+
// const cosR = Math.abs(Math.cos(relativeAngle));
|
|
1792
|
+
// const sinR = Math.abs(Math.sin(relativeAngle));
|
|
1793
|
+
// // Menentukan skala elemen berdasarkan orientasinya terhadap selection box
|
|
1794
|
+
// // Jika beda 90 derajat, scaleX elemen akan memakai scaleY seleksi
|
|
1795
|
+
// const finalScaleX = (scaleX_Sel * cosR) + (scaleY_Sel * sinR);
|
|
1796
|
+
// const finalScaleY = (scaleX_Sel * sinR) + (scaleY_Sel * cosR);
|
|
1797
|
+
// const newWidth = el.width * finalScaleX;
|
|
1798
|
+
// const newHeight = el.height * finalScaleY;
|
|
1799
|
+
// return {
|
|
1800
|
+
// ...el,
|
|
1801
|
+
// x: global.x,
|
|
1802
|
+
// y: global.y,
|
|
1803
|
+
// width: newWidth,
|
|
1804
|
+
// height: newHeight,
|
|
1805
|
+
// // Update center point properti jika diperlukan
|
|
1806
|
+
// cx: global.x + newWidth / 2,
|
|
1807
|
+
// cy: global.y + newHeight / 2,
|
|
1808
|
+
// rotation: el.rotation,
|
|
1809
|
+
// };
|
|
1810
|
+
// }
|
|
1811
|
+
function mapElementToResize(el, oldSel, newSel, angleRad) {
|
|
1812
|
+
// 1. Skala selection
|
|
1813
|
+
const scaleX = newSel.width / oldSel.width || 1;
|
|
1814
|
+
const scaleY = newSel.height / oldSel.height || 1;
|
|
1815
|
+
// 2. Center selection
|
|
1816
|
+
const cxOld = oldSel.x + oldSel.width / 2;
|
|
1817
|
+
const cyOld = oldSel.y + oldSel.height / 2;
|
|
1818
|
+
const cxNew = newSel.x + newSel.width / 2;
|
|
1819
|
+
const cyNew = newSel.y + newSel.height / 2;
|
|
1820
|
+
// 3. POSISI (rotate → scale → rotate back)
|
|
1821
|
+
const local = toLocal(el.x, el.y, cxOld, cyOld, angleRad);
|
|
1822
|
+
const scaledLocal = {
|
|
1823
|
+
x: local.x * scaleX,
|
|
1824
|
+
y: local.y * scaleY,
|
|
1825
|
+
};
|
|
1826
|
+
const global = toGlobal(scaledLocal.x, scaledLocal.y, cxNew, cyNew, angleRad);
|
|
1827
|
+
// 4. UKURAN (TANPA trig / rotasi)
|
|
1828
|
+
const newWidth = el.width * scaleX;
|
|
1829
|
+
const newHeight = el.height * scaleY;
|
|
1830
|
+
return Object.assign(Object.assign({}, el), { x: global.x, y: global.y, width: newWidth, height: newHeight, cx: global.x + newWidth / 2, cy: global.y + newHeight / 2, rotation: el.rotation });
|
|
1831
|
+
}
|
|
1832
|
+
export function getPolygonCenter(points) {
|
|
1833
|
+
if (!points.length) {
|
|
1834
|
+
return { cx: 0, cy: 0 };
|
|
1835
|
+
}
|
|
1836
|
+
let minX = Infinity;
|
|
1837
|
+
let minY = Infinity;
|
|
1838
|
+
let maxX = -Infinity;
|
|
1839
|
+
let maxY = -Infinity;
|
|
1840
|
+
for (const p of points) {
|
|
1841
|
+
if (p.x < minX)
|
|
1842
|
+
minX = p.x;
|
|
1843
|
+
if (p.y < minY)
|
|
1844
|
+
minY = p.y;
|
|
1845
|
+
if (p.x > maxX)
|
|
1846
|
+
maxX = p.x;
|
|
1847
|
+
if (p.y > maxY)
|
|
1848
|
+
maxY = p.y;
|
|
1849
|
+
}
|
|
1850
|
+
return {
|
|
1851
|
+
cx: (minX + maxX) / 2,
|
|
1852
|
+
cy: (minY + maxY) / 2,
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
function stabilizeTranslateOnRotate({ points, oldAngle, newAngle, tx, ty, }) {
|
|
1856
|
+
// center polygon (bbox center)
|
|
1857
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
1858
|
+
for (const p of points) {
|
|
1859
|
+
minX = Math.min(minX, p.x);
|
|
1860
|
+
minY = Math.min(minY, p.y);
|
|
1861
|
+
maxX = Math.max(maxX, p.x);
|
|
1862
|
+
maxY = Math.max(maxY, p.y);
|
|
1863
|
+
}
|
|
1864
|
+
const cx = (minX + maxX) / 2;
|
|
1865
|
+
const cy = (minY + maxY) / 2;
|
|
1866
|
+
const r0 = (oldAngle * Math.PI) / 180;
|
|
1867
|
+
const r1 = (newAngle * Math.PI) / 180;
|
|
1868
|
+
const x0 = cx * Math.cos(r0) - cy * Math.sin(r0);
|
|
1869
|
+
const y0 = cx * Math.sin(r0) + cy * Math.cos(r0);
|
|
1870
|
+
const x1 = cx * Math.cos(r1) - cy * Math.sin(r1);
|
|
1871
|
+
const y1 = cx * Math.sin(r1) + cy * Math.cos(r1);
|
|
1872
|
+
return {
|
|
1873
|
+
tx: tx + (x0 - x1),
|
|
1874
|
+
ty: ty + (y0 - y1),
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
function pointsStringToArray(pointsStr) {
|
|
1878
|
+
return pointsStr
|
|
1879
|
+
.trim()
|
|
1880
|
+
.split(/\s+/) // pisah antar titik
|
|
1881
|
+
.map((pair) => {
|
|
1882
|
+
const [x, y] = pair.split(",").map(Number);
|
|
1883
|
+
return { x, y };
|
|
1884
|
+
})
|
|
1885
|
+
.filter((p) => !Number.isNaN(p.x) && !Number.isNaN(p.y));
|
|
1886
|
+
}
|
|
1887
|
+
function arrayToSvgPointsAttr(points) {
|
|
1888
|
+
return points.map((p) => `${p.x},${p.y}`).join(" ");
|
|
1889
|
+
}
|
|
1890
|
+
function getSvgElementSize(el) {
|
|
1891
|
+
if (el instanceof SVGRectElement) {
|
|
1892
|
+
return {
|
|
1893
|
+
width: el.width.baseVal.value,
|
|
1894
|
+
height: el.height.baseVal.value,
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
if (el instanceof SVGCircleElement) {
|
|
1898
|
+
const r = el.r.baseVal.value;
|
|
1899
|
+
return {
|
|
1900
|
+
width: r * 2,
|
|
1901
|
+
height: r * 2,
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
if (el instanceof SVGEllipseElement) {
|
|
1905
|
+
return {
|
|
1906
|
+
width: el.rx.baseVal.value * 2,
|
|
1907
|
+
height: el.ry.baseVal.value * 2,
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
return {
|
|
1911
|
+
width: 0,
|
|
1912
|
+
height: 0,
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
export function applyResizeToSvgElement(element, group, resize, inner) {
|
|
1916
|
+
const tagName = element.tagName.toLowerCase();
|
|
1917
|
+
console.log({ resize });
|
|
1918
|
+
switch (tagName) {
|
|
1919
|
+
case "rect":
|
|
1920
|
+
case "image": {
|
|
1921
|
+
element.setAttribute("width", String(resize.width));
|
|
1922
|
+
element.setAttribute("height", String(resize.height));
|
|
1923
|
+
// group.setAttribute("transform", `translate(${resize.x}, ${resize.y})`);
|
|
1924
|
+
// 🔑 translate by CENTER, not top-left
|
|
1925
|
+
group.setAttribute("transform", `translate(${resize.cx - resize.width / 2}, ${resize.cy - resize.height / 2})`);
|
|
1926
|
+
break;
|
|
1927
|
+
}
|
|
1928
|
+
case "circle": {
|
|
1929
|
+
const r = Math.min(resize.width, resize.height) / 2;
|
|
1930
|
+
element.setAttribute("r", String(r));
|
|
1931
|
+
element.setAttribute("cx", String(resize.width / 2));
|
|
1932
|
+
element.setAttribute("cy", String(resize.height / 2));
|
|
1933
|
+
group.setAttribute("transform", `translate(${resize.x}, ${resize.y})`);
|
|
1934
|
+
// inner
|
|
1935
|
+
break;
|
|
1936
|
+
}
|
|
1937
|
+
case "ellipse": {
|
|
1938
|
+
element.setAttribute("rx", String(resize.width / 2));
|
|
1939
|
+
element.setAttribute("ry", String(resize.height / 2));
|
|
1940
|
+
break;
|
|
1941
|
+
}
|
|
1942
|
+
default: {
|
|
1943
|
+
console.warn(`[applyResizeToSvgElement] Unsupported SVG tag: ${tagName}`);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
// translate selalu di group
|
|
1947
|
+
}
|
|
1948
|
+
function applySelectionToSvg(selectionLines, boxSelection, sel) {
|
|
1949
|
+
selectionLines === null || selectionLines === void 0 ? void 0 : selectionLines.setAttribute("transform", `translate(${sel.x}, ${sel.y})`);
|
|
1950
|
+
boxSelection === null || boxSelection === void 0 ? void 0 : boxSelection.setAttribute("width", String(sel.width));
|
|
1951
|
+
boxSelection === null || boxSelection === void 0 ? void 0 : boxSelection.setAttribute("height", String(sel.height));
|
|
1952
|
+
}
|
|
1953
|
+
function applySizeToSvgElement(element, width, height) {
|
|
1954
|
+
const tag = element.tagName.toLowerCase();
|
|
1955
|
+
if (tag === "rect" || tag === "image") {
|
|
1956
|
+
console.log({ width, height });
|
|
1957
|
+
element.setAttribute("width", String(width));
|
|
1958
|
+
element.setAttribute("height", String(height));
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
if (tag === "circle") {
|
|
1962
|
+
const r = Math.max(0, Math.min(width, height) / 2);
|
|
1963
|
+
element.setAttribute("r", String(r));
|
|
1964
|
+
element.setAttribute("cx", String(width / 2));
|
|
1965
|
+
element.setAttribute("cy", String(height / 2));
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
if (tag === "ellipse") {
|
|
1969
|
+
element.setAttribute("rx", String(width / 2));
|
|
1970
|
+
element.setAttribute("ry", String(height / 2));
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
const RESIZE_RULES = {
|
|
1975
|
+
"bottom-right": {
|
|
1976
|
+
widthFactor: 1,
|
|
1977
|
+
heightFactor: 1,
|
|
1978
|
+
moveLocalX: () => 0,
|
|
1979
|
+
moveLocalY: () => 0,
|
|
1980
|
+
},
|
|
1981
|
+
"bottom-left": {
|
|
1982
|
+
widthFactor: -1,
|
|
1983
|
+
heightFactor: 1,
|
|
1984
|
+
moveLocalX: (dx) => dx,
|
|
1985
|
+
moveLocalY: () => 0,
|
|
1986
|
+
},
|
|
1987
|
+
"top-left": {
|
|
1988
|
+
widthFactor: -1,
|
|
1989
|
+
heightFactor: -1,
|
|
1990
|
+
moveLocalX: (dx) => dx,
|
|
1991
|
+
moveLocalY: (_, dy) => dy,
|
|
1992
|
+
},
|
|
1993
|
+
"top-right": {
|
|
1994
|
+
widthFactor: 1,
|
|
1995
|
+
heightFactor: -1,
|
|
1996
|
+
moveLocalX: () => 0,
|
|
1997
|
+
moveLocalY: (_, dy) => dy,
|
|
1998
|
+
},
|
|
1999
|
+
left: {
|
|
2000
|
+
widthFactor: -1,
|
|
2001
|
+
heightFactor: 0,
|
|
2002
|
+
moveLocalX: (dx) => dx,
|
|
2003
|
+
moveLocalY: () => 0,
|
|
2004
|
+
},
|
|
2005
|
+
right: {
|
|
2006
|
+
widthFactor: 1,
|
|
2007
|
+
heightFactor: 0,
|
|
2008
|
+
moveLocalX: () => 0,
|
|
2009
|
+
moveLocalY: () => 0,
|
|
2010
|
+
},
|
|
2011
|
+
top: {
|
|
2012
|
+
widthFactor: 0,
|
|
2013
|
+
heightFactor: -1,
|
|
2014
|
+
moveLocalX: () => 0,
|
|
2015
|
+
moveLocalY: (_, dy) => dy,
|
|
2016
|
+
},
|
|
2017
|
+
bottom: {
|
|
2018
|
+
widthFactor: 0,
|
|
2019
|
+
heightFactor: 1,
|
|
2020
|
+
moveLocalX: () => 0,
|
|
2021
|
+
moveLocalY: () => 0,
|
|
2022
|
+
},
|
|
2023
|
+
};
|
|
2024
|
+
export function applyResizeWithRotation({ element, group, resizeSide, widthOriginal, heightOriginal, deltaLocalX, deltaLocalY, xOriginal, yOriginal, angle, seats, }) {
|
|
2025
|
+
const rule = RESIZE_RULES[resizeSide];
|
|
2026
|
+
let newWidth = widthOriginal;
|
|
2027
|
+
let newHeight = heightOriginal;
|
|
2028
|
+
if (rule.widthFactor !== 0) {
|
|
2029
|
+
newWidth = widthOriginal + rule.widthFactor * deltaLocalX;
|
|
2030
|
+
}
|
|
2031
|
+
if (rule.heightFactor !== 0) {
|
|
2032
|
+
newHeight = heightOriginal + rule.heightFactor * deltaLocalY;
|
|
2033
|
+
}
|
|
2034
|
+
// clamp
|
|
2035
|
+
newWidth = Math.max(0, newWidth);
|
|
2036
|
+
newHeight = Math.max(0, newHeight);
|
|
2037
|
+
// 🔹 apply size (rect / circle / ellipse)
|
|
2038
|
+
applySizeToSvgElement(element, newWidth, newHeight);
|
|
2039
|
+
// 🔹 move anchor (kalau perlu)
|
|
2040
|
+
const moveLocalX = rule.moveLocalX(deltaLocalX, deltaLocalY);
|
|
2041
|
+
const moveLocalY = rule.moveLocalY(deltaLocalX, deltaLocalY);
|
|
2042
|
+
if (moveLocalX !== 0 || moveLocalY !== 0) {
|
|
2043
|
+
const { x: mx, y: my } = localToWorld(moveLocalX, moveLocalY, angle);
|
|
2044
|
+
group.setAttribute("transform", `translate(${xOriginal + mx}, ${yOriginal + my})`);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
function getRotation(transformList) {
|
|
2048
|
+
for (let i = 0; i < (transformList === null || transformList === void 0 ? void 0 : transformList.numberOfItems); i++) {
|
|
2049
|
+
const t = transformList.getItem(i);
|
|
2050
|
+
if (t.type === SVGTransform.SVG_TRANSFORM_ROTATE) {
|
|
2051
|
+
return t.angle;
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
return 0;
|
|
2055
|
+
}
|
|
2056
|
+
function toLocal(x, y, cx, cy, angle) {
|
|
2057
|
+
const dx = x - cx;
|
|
2058
|
+
const dy = y - cy;
|
|
2059
|
+
const cos = Math.cos(-angle);
|
|
2060
|
+
const sin = Math.sin(-angle);
|
|
2061
|
+
return {
|
|
2062
|
+
x: dx * cos - dy * sin,
|
|
2063
|
+
y: dx * sin + dy * cos,
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
function toGlobal(lx, ly, cx, cy, angle) {
|
|
2067
|
+
const cos = Math.cos(angle);
|
|
2068
|
+
const sin = Math.sin(angle);
|
|
2069
|
+
return {
|
|
2070
|
+
x: cx + lx * cos - ly * sin,
|
|
2071
|
+
y: cy + lx * sin + ly * cos,
|
|
2072
|
+
};
|
|
2073
|
+
}
|