Skip to content

Commit 60ae7ce

Browse files
committed
Feat: added workspace auto centering
1 parent c277dd8 commit 60ae7ce

File tree

5 files changed

+464
-30
lines changed

5 files changed

+464
-30
lines changed

src/components/workspace/index.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { useCallback, useLayoutEffect } from "react";
22
import { useSelector } from "react-redux";
33
import { twMerge } from "tailwind-merge";
4-
import { ids } from "@/constants";
4+
import { ids, selectors } from "@/constants";
55
import { store } from "@/store";
66
import { initializeElements, sync } from "@/store/reducers/editor";
77
import { type ISTKProps } from "@/types";
8+
import { d3Extended } from "@/utils";
89
import { Tool, tools } from "../toolbar/data";
910
import { default as Crosshairs } from "./crosshairs";
1011
import { default as Element, ElementType } from "./elements";
1112
import { default as Grid } from "./grid";
12-
import { default as Zoom } from "./zoom";
13+
import { default as Zoom, zoomAndPan } from "./zoom";
1314

1415
export { default as Cursor } from "./cursor";
1516

@@ -29,6 +30,23 @@ export const Workspace: React.FC<ISTKProps> = (props) => {
2930
useLayoutEffect(() => {
3031
if (props.data) {
3132
store.dispatch(sync(props.data));
33+
setTimeout(() => {
34+
const { height: workspaceheight, width: workspaceWidth } = d3Extended.selectionBounds(
35+
d3Extended.selectById(ids.workspace)
36+
);
37+
const {
38+
left: wgOffsetLeft,
39+
top: wgOffsetTop,
40+
height: workspaceGroupHeight,
41+
width: workspaceGroupWidth
42+
} = d3Extended.selectionBounds(d3Extended.select(selectors.workspaceGroup));
43+
const scaleFactor = 1.05;
44+
zoomAndPan({
45+
k: scaleFactor,
46+
y: (workspaceheight - (wgOffsetTop * scaleFactor * 2 + workspaceGroupHeight * scaleFactor)) / 2 - 5,
47+
x: (workspaceWidth - (wgOffsetLeft * scaleFactor * 2 + workspaceGroupWidth * scaleFactor)) / 2
48+
});
49+
}, 0);
3250
} else {
3351
store.dispatch(initializeElements());
3452
}

src/components/workspace/zoom.jsx renamed to src/components/workspace/zoom.tsx

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,63 @@
11
import { memo, useLayoutEffect } from "react";
22
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Minus, Plus } from "lucide-react";
33
import { useSelector } from "react-redux";
4-
import * as d3 from "d3";
54
import { twMerge } from "tailwind-merge";
65
import { ids, selectors } from "@/constants";
76
import { useSkipFirstRender } from "@/hooks";
7+
import { d3Extended } from "@/utils";
88
import { Tool } from "../toolbar/data";
99

1010
function handleZoom(e) {
11-
const workspace = d3.select(selectors.workspaceGroup);
11+
const workspace = d3Extended.select(selectors.workspaceGroup);
1212
workspace.attr("transform", e.transform);
1313
}
1414

15-
const zoom = d3.zoom().on("zoom", handleZoom);
15+
const zoom = d3Extended.zoom().on("zoom", handleZoom);
1616

1717
const zoomIn = () => {
18-
d3.select(`#${ids.workspace}`).transition().call(zoom.scaleBy, 1.1);
18+
d3Extended.selectById(ids.workspace).transition().call(zoom.scaleBy, 1.1);
1919
};
2020

2121
const zoomOut = () => {
22-
d3.select(`#${ids.workspace}`).transition().call(zoom.scaleBy, 0.9);
22+
d3Extended.selectById(ids.workspace).transition().call(zoom.scaleBy, 0.9);
2323
};
2424

2525
const panLeft = () => {
26-
d3.select(`#${ids.workspace}`).transition().call(zoom.translateBy, 50, 0);
26+
d3Extended.selectById(ids.workspace).transition().call(zoom.translateBy, 50, 0);
2727
};
2828

2929
const panRight = () => {
30-
d3.select(`#${ids.workspace}`).transition().call(zoom.translateBy, -50, 0);
30+
d3Extended.selectById(ids.workspace).transition().call(zoom.translateBy, -50, 0);
3131
};
3232

3333
const panUp = () => {
34-
d3.select(`#${ids.workspace}`).transition().call(zoom.translateBy, 0, 50);
34+
d3Extended.selectById(ids.workspace).transition().call(zoom.translateBy, 0, 50);
3535
};
3636

3737
const panDown = () => {
38-
d3.select(`#${ids.workspace}`).transition().call(zoom.translateBy, 0, -50);
38+
d3Extended.selectById(ids.workspace).transition().call(zoom.translateBy, 0, -50);
39+
};
40+
41+
export const zoomAndPan = ({ k, x, y }) => {
42+
d3Extended
43+
.selectById(ids.workspace)
44+
.transition()
45+
.duration(1000)
46+
.call(zoom.transform, d3Extended.zoomIdentity.translate(x, y).scale(k));
3947
};
4048

4149
const panHandleClasses =
4250
"absolute z-10 text-black/40 cursor-pointer hover:text-black/80 transition-all duration-medium";
4351

4452
const Zoom = () => {
45-
const selectedTool = useSelector((state) => state.toolbar.selectedTool);
46-
const showControls = useSelector((state) => state.editor.showControls);
53+
const selectedTool = useSelector((state: any) => state.toolbar.selectedTool);
54+
const showControls = useSelector((state: any) => state.editor.showControls);
4755

4856
useLayoutEffect(() => {
49-
const selection = d3.select(`#${ids.workspace}`);
57+
const selection = d3Extended.selectById(ids.workspace);
5058
selection.on("zoom", null);
5159
if (selectedTool == Tool.Pan) {
52-
d3.select(`#${ids.workspace}`).call(zoom);
60+
selection.call(zoom);
5361
} else {
5462
selection
5563
.call(zoom)
@@ -62,7 +70,7 @@ const Zoom = () => {
6270
const currentZoom = selection.property("__zoom").k || 1;
6371
if (e.ctrlKey) {
6472
const nextZoom = currentZoom * Math.pow(2, -e.deltaY * 0.01);
65-
zoom.scaleTo(selection, nextZoom, d3.pointer(e));
73+
zoom.scaleTo(selection, nextZoom, d3Extended.pointer(e));
6674
} else {
6775
zoom.translateBy(selection, -(e.deltaX / currentZoom), -(e.deltaY / currentZoom));
6876
}
@@ -71,7 +79,7 @@ const Zoom = () => {
7179
}, [selectedTool]);
7280

7381
useSkipFirstRender(() => {
74-
const workspace = d3.select(`#${ids.workspace}`);
82+
const workspace = d3Extended.selectById(ids.workspace);
7583
const controlTransformActive = workspace.attr("control-transform-active");
7684
if (showControls) {
7785
if (!controlTransformActive) {

0 commit comments

Comments
 (0)