Skip to content

Commit 6dc7185

Browse files
committed
Feat: added multi element drag
1 parent 7b2648f commit 6dc7185

File tree

2 files changed

+153
-129
lines changed

2 files changed

+153
-129
lines changed

src/components/workspace/elements/utils.ts

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { drag, select } from "d3";
22
import { dataAttributes } from "@/constants";
33
import { resizeCursors } from "@/hooks/interactions";
4+
import store from "@/store";
45
import { IPopulatedSeat } from "@/types";
56
import { d3Extended } from "@/utils";
67
import Booth from "./booth";
@@ -28,6 +29,53 @@ export const elements = {
2829
[ElementType.Image]: Image
2930
};
3031

32+
const repositionSeat = (seat, dx, dy) => {
33+
const x = +seat.attr("cx") + dx;
34+
const y = +seat.attr("cy") + dy;
35+
36+
seat.attr("cx", x);
37+
seat.attr("cy", y);
38+
39+
const label = d3Extended.selectById(`${seat.attr("id")}-label`);
40+
label.attr("x", +label.attr("x") + dx);
41+
label.attr("y", +label.attr("y") + dy);
42+
};
43+
44+
const repositionText = (text, dx, dy) => {
45+
text.attr("x", +text.attr("x") + dx);
46+
text.attr("y", +text.attr("y") + dy);
47+
};
48+
49+
const repositionShape = (shape, dx, dy) => {
50+
if (resizeCursors.includes(shape.style("cursor"))) return;
51+
const x = +shape.attr("x") + dx;
52+
const y = +shape.attr("y") + dy;
53+
shape.attr("x", x);
54+
shape.attr("y", y);
55+
};
56+
57+
const repositionPolyline = (polyline, dx, dy) => {
58+
const points = polyline
59+
.attr("points")
60+
.split(" ")
61+
.map((point) => {
62+
const [x, y] = point.split(",");
63+
return `${+x + dx},${+y + dy}`;
64+
})
65+
.join(" ");
66+
polyline.attr("points", points);
67+
};
68+
69+
const repositionElements = (currentElem, repositionFn, elementType: string, dx: number, dy: number) => {
70+
repositionFn(currentElem, dx, dy);
71+
store.getState().editor.selectedElementIds.forEach((id: string) => {
72+
if (currentElem.attr("id") !== id) {
73+
const element = d3Extended.selectById(id);
74+
if (element.attr(dataAttributes.elementType) === elementType) repositionFn(element, dx, dy);
75+
}
76+
});
77+
};
78+
3179
export const handleDrag = drag().on("drag", function (event) {
3280
const me = select(this);
3381
const controls = d3Extended.selectById(`${me.attr("id")}-controls`);
@@ -41,49 +89,19 @@ export const handleDrag = drag().on("drag", function (event) {
4189
});
4290

4391
export const handleSeatDrag = drag().on("drag", function (event) {
44-
const me = select(this);
45-
46-
const x = +me.attr("cx") + event.dx;
47-
const y = +me.attr("cy") + event.dy;
48-
49-
me.attr("cx", x);
50-
me.attr("cy", y);
51-
52-
const controls = d3Extended.selectById(`${me.attr("id")}-controls`);
53-
controls.attr("cx", x);
54-
controls.attr("cy", y);
55-
56-
const label = d3Extended.selectById(`${me.attr("id")}-label`);
57-
label.attr("x", +label.attr("x") + event.dx);
58-
label.attr("y", +label.attr("y") + event.dy);
92+
repositionElements(select(this), repositionSeat, ElementType.Seat, event.dx, event.dy);
5993
});
6094

6195
export const handleTextDrag = drag().on("drag", function (event) {
62-
const me = select(this);
63-
me.attr("x", +me.attr("x") + event.dx);
64-
me.attr("y", +me.attr("y") + event.dy);
96+
repositionElements(select(this), repositionText, ElementType.Text, event.dx, event.dy);
6597
});
6698

6799
export const handleShapeDrag = drag().on("drag", function (event) {
68-
const me = select(this);
69-
if (resizeCursors.includes(me.style("cursor"))) return;
70-
const x = +me.attr("x") + event.dx;
71-
const y = +me.attr("y") + event.dy;
72-
me.attr("x", x);
73-
me.attr("y", y);
100+
repositionElements(select(this), repositionShape, ElementType.Shape, event.dx, event.dy);
74101
});
75102

76103
export const handlePolylineDrag = drag().on("drag", function (event) {
77-
const me = select(this);
78-
const points = me
79-
.attr("points")
80-
.split(" ")
81-
.map((point) => {
82-
const [x, y] = point.split(",");
83-
return `${+x + event.dx},${+y + event.dy}`;
84-
})
85-
.join(" ");
86-
me.attr("points", points);
104+
repositionElements(select(this), repositionPolyline, ElementType.Polyline, event.dx, event.dy);
87105
});
88106

89107
export const showSeat = (seat: d3.Selection<Element, {}, HTMLElement, any>) => {

src/hooks/events/selection.ts

Lines changed: 101 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,120 @@
11
import { useLayoutEffect } from "react";
2+
import { useSelector } from "react-redux";
3+
import { Tool } from "@/components/toolbar/data";
24
import { ElementType } from "@/components/workspace/elements";
35
import { dataAttributes, ids, selectors } from "@/constants";
46
import { default as store } from "@/store";
57
import { clearAndSelectElements } from "@/store/reducers/editor";
68
import { coordsWithTransform, d3Extended } from "@/utils";
79

810
const useSelection = () => {
11+
const selectedTool = useSelector((state: any) => state.toolbar.selectedTool);
912
useLayoutEffect(() => {
1013
const svg = d3Extended.selectById(ids.workspace);
1114
if (svg.node()) {
12-
const { top: workspaceTop, left: workspaceLeft } = d3Extended.selectionBounds(
13-
d3Extended.selectById(ids.workspace)
14-
);
15-
const selectionRect = {
16-
element: null,
17-
currentY: 0,
18-
currentX: 0,
19-
originX: 0,
20-
originY: 0,
21-
setElement: function (ele) {
22-
this.element = ele;
23-
},
24-
getNewAttributes: function () {
25-
const x = this.currentX < this.originX ? this.currentX : this.originX;
26-
const y = this.currentY < this.originY ? this.currentY : this.originY;
27-
const width = Math.abs(this.currentX - this.originX);
28-
const height = Math.abs(this.currentY - this.originY);
29-
return {
30-
x: x,
31-
y: y,
32-
width: width,
33-
height: height
34-
};
35-
},
36-
getCurrentAttributes: function () {
37-
const transform = d3Extended.zoomTransform(document.querySelector(selectors.workspaceGroup));
38-
const { x, y } = coordsWithTransform({ x: +this.element.attr("x"), y: +this.element.attr("y") }, transform);
39-
return {
40-
x1: x,
41-
y1: y,
42-
x2: x + Number(this.element.attr("width")) / transform.k,
43-
y2: y + Number(this.element.attr("height")) / transform.k
44-
};
45-
},
46-
init: function (newX, newY) {
47-
const rectElement = svg
48-
.append("rect")
49-
.attr("id", ids.workspaceSelection)
50-
.attr("rx", 4)
51-
.attr("ry", 4)
52-
.attr("x", 0)
53-
.attr("y", 0)
54-
.attr("width", 0)
55-
.attr("height", 0)
56-
.classed("workspace-selection", true);
57-
this.setElement(rectElement);
58-
this.originX = newX;
59-
this.originY = newY;
60-
this.update(newX, newY);
61-
},
62-
update: function (newX: number, newY: number) {
63-
this.currentX = newX - (+this.element?.attr("width") || this.currentX === this.originX ? workspaceLeft : 0);
64-
this.currentY = newY - (+this.element?.attr("height") || this.currentY === this.originY ? workspaceTop : 0);
65-
const attributes = this.getNewAttributes();
66-
Object.keys(attributes).forEach((key) => {
67-
this.element.attr(key, attributes[key]);
68-
});
69-
},
70-
remove: function () {
71-
this.element.remove();
72-
this.element = null;
73-
}
74-
};
15+
if (selectedTool === Tool.Select) {
16+
const { top: workspaceTop, left: workspaceLeft } = d3Extended.selectionBounds(
17+
d3Extended.selectById(ids.workspace)
18+
);
19+
const selectionRect = {
20+
element: null,
21+
currentY: 0,
22+
currentX: 0,
23+
originX: 0,
24+
originY: 0,
25+
setElement: function (ele) {
26+
this.element = ele;
27+
},
28+
getNewAttributes: function () {
29+
const x = this.currentX < this.originX ? this.currentX : this.originX;
30+
const y = this.currentY < this.originY ? this.currentY : this.originY;
31+
const width = Math.abs(this.currentX - this.originX);
32+
const height = Math.abs(this.currentY - this.originY);
33+
return {
34+
x: x,
35+
y: y,
36+
width: width,
37+
height: height
38+
};
39+
},
40+
getCurrentAttributes: function () {
41+
const transform = d3Extended.zoomTransform(document.querySelector(selectors.workspaceGroup));
42+
const { x, y } = coordsWithTransform({ x: +this.element.attr("x"), y: +this.element.attr("y") }, transform);
43+
return {
44+
x1: x,
45+
y1: y,
46+
x2: x + Number(this.element.attr("width")) / transform.k,
47+
y2: y + Number(this.element.attr("height")) / transform.k
48+
};
49+
},
50+
init: function (newX, newY) {
51+
const rectElement = svg
52+
.append("rect")
53+
.attr("id", ids.workspaceSelection)
54+
.attr("rx", 4)
55+
.attr("ry", 4)
56+
.attr("x", 0)
57+
.attr("y", 0)
58+
.attr("width", 0)
59+
.attr("height", 0)
60+
.classed("workspace-selection", true);
61+
this.setElement(rectElement);
62+
this.originX = newX;
63+
this.originY = newY;
64+
this.update(newX, newY);
65+
},
66+
update: function (newX: number, newY: number) {
67+
this.currentX = newX - (+this.element?.attr("width") || this.currentX === this.originX ? workspaceLeft : 0);
68+
this.currentY = newY - (+this.element?.attr("height") || this.currentY === this.originY ? workspaceTop : 0);
69+
const attributes = this.getNewAttributes();
70+
Object.keys(attributes).forEach((key) => {
71+
this.element.attr(key, attributes[key]);
72+
});
73+
},
74+
remove: function () {
75+
this.element.remove();
76+
this.element = null;
77+
}
78+
};
7579

76-
const dragStart = (e) => {
77-
const p = d3Extended.pointer(e);
78-
selectionRect.init(p[0], p[1]);
79-
};
80+
const dragStart = (e) => {
81+
const p = d3Extended.pointer(e);
82+
selectionRect.init(p[0], p[1]);
83+
};
8084

81-
const dragMove = (e) => {
82-
const p = d3Extended.pointer(e);
83-
selectionRect.update(p[0], p[1]);
84-
};
85+
const dragMove = (e) => {
86+
const p = d3Extended.pointer(e);
87+
selectionRect.update(p[0], p[1]);
88+
};
8589

86-
const dragEnd = () => {
87-
const finalAttributes = selectionRect.getCurrentAttributes();
88-
selectionRect.remove();
89-
const elements = d3Extended.selectAll(`[${dataAttributes.element}]`);
90-
const idsToSelect = [];
90+
const dragEnd = () => {
91+
const finalAttributes = selectionRect.getCurrentAttributes();
92+
selectionRect.remove();
93+
const elements = d3Extended.selectAll(`[${dataAttributes.element}]`);
94+
const idsToSelect = [];
9195

92-
elements.forEach((element) => {
93-
const isSeat = element.attr(dataAttributes.elementType) === ElementType.Seat;
94-
const x = isSeat ? +element.attr("cx") : +element.attr("x");
95-
const y = isSeat ? +element.attr("cy") : +element.attr("y");
96-
if (
97-
x >= finalAttributes.x1 &&
98-
x <= finalAttributes.x2 &&
99-
y >= finalAttributes.y1 &&
100-
y <= finalAttributes.y2
101-
) {
102-
const id = element.attr("id");
103-
if (!id?.includes("-label")) idsToSelect.push(id);
104-
}
105-
});
106-
store.dispatch(clearAndSelectElements(idsToSelect));
107-
};
108-
109-
svg.call(d3Extended.drag().on("drag", dragMove).on("start", dragStart).on("end", dragEnd));
96+
elements.forEach((element) => {
97+
const isSeat = element.attr(dataAttributes.elementType) === ElementType.Seat;
98+
const x = isSeat ? +element.attr("cx") : +element.attr("x");
99+
const y = isSeat ? +element.attr("cy") : +element.attr("y");
100+
if (
101+
x >= finalAttributes.x1 &&
102+
x <= finalAttributes.x2 &&
103+
y >= finalAttributes.y1 &&
104+
y <= finalAttributes.y2
105+
) {
106+
const id = element.attr("id");
107+
if (!id?.includes("-label")) idsToSelect.push(id);
108+
}
109+
});
110+
store.dispatch(clearAndSelectElements(idsToSelect));
111+
};
112+
svg.call(d3Extended.drag().on("drag", dragMove).on("start", dragStart).on("end", dragEnd));
113+
} else {
114+
svg.call(d3Extended.drag().on("drag", null).on("start", null).on("end", null));
115+
}
110116
}
111-
}, []);
117+
}, [selectedTool]);
112118
};
113119

114120
export default useSelection;

0 commit comments

Comments
 (0)