Skip to content

Commit f65591e

Browse files
committed
fix: rewrite coordinate management
After some initial smaller fixes, it turned out that I had broken the red line used when linking fields. Fixing this was not trivial as I found myself battling a lot of small bugs relating to scale and translation in the existing code. This was made extra difficult as a lot of coordinates were calculated when necessary in Canvas.jsx. This commit attempts to simplify the coordinate management in a few different ways: * There are now two distinct coordinate systems in use, typically referred to as "spaces". Screen space and diagram space. * Diagram space is no longer measured in pixels (though the dimension-less measure used instead still maps to pixels at 100% zoom). * The canvas now exposes helper methods for transforming between spaces. * Zoom and translation is now managed via the svg viewBox property. * This makes moving items in diagram space much easier as the coordinates remain constant regardless of zoom level. * The canvas now wraps the current mouse position in a context object, making mouse movement much easier to work with. * The transform.pan property now refers to the center of the screen. A new feature in this commit is that scroll wheel zoom is now based on the current cursor location, making the diagram more convenient to move around in. I have tried to focus on Canvas.jsx and avoid changes that might be desctructive on existing save files. I also believe more refactors and abstractions could be introduced based on these changes to make the diagram even easier to work with. However, I deem that out of scope for now.
1 parent 92d13c2 commit f65591e

11 files changed

+440
-203
lines changed

package-lock.json

+22-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@uiw/react-codemirror": "^4.21.25",
2020
"@vercel/analytics": "^1.2.2",
2121
"axios": "^1.6.2",
22+
"classnames": "^2.5.1",
2223
"dexie": "^3.2.4",
2324
"dexie-react-hooks": "^1.1.7",
2425
"file-saver": "^2.0.5",
@@ -35,7 +36,8 @@
3536
"react-hotkeys-hook": "^4.4.1",
3637
"react-i18next": "^14.1.1",
3738
"react-router-dom": "^6.21.0",
38-
"url": "^0.11.1"
39+
"url": "^0.11.1",
40+
"usehooks-ts": "^3.1.0"
3941
},
4042
"devDependencies": {
4143
"@types/react": "^18.2.43",

src/components/EditorCanvas/Area.jsx

+22-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from "react";
1+
import { useContext, useRef, useState } from "react";
22
import { Button, Popover, Input } from "@douyinfe/semi-ui";
33
import { IconEdit, IconDeleteStroked } from "@douyinfe/semi-icons";
44
import {
@@ -15,16 +15,27 @@ import {
1515
useSelect,
1616
useAreas,
1717
useSaveState,
18-
useTransform,
1918
} from "../../hooks";
2019
import ColorPalette from "../ColorPicker";
2120
import { useTranslation } from "react-i18next";
21+
import { useHover } from "usehooks-ts";
22+
import { CanvasContext } from "../../context/CanvasContext";
2223

23-
export default function Area({ data, onPointerDown, setResize, setInitCoords }) {
24-
const [hovered, setHovered] = useState(false);
24+
export default function Area({
25+
data,
26+
onPointerDown,
27+
setResize,
28+
setInitCoords,
29+
}) {
30+
const ref = useRef(null);
31+
const isHovered = useHover(ref);
32+
const {
33+
pointer: {
34+
spaces: { diagram: pointer },
35+
},
36+
} = useContext(CanvasContext);
2537
const { layout } = useLayout();
2638
const { settings } = useSettings();
27-
const { transform } = useTransform();
2839
const { setSaveState } = useSaveState();
2940
const { selectedElement, setSelectedElement } = useSelect();
3041

@@ -35,8 +46,8 @@ export default function Area({ data, onPointerDown, setResize, setInitCoords })
3546
y: data.y,
3647
width: data.width,
3748
height: data.height,
38-
pointerX: e.clientX / transform.zoom,
39-
pointerY: e.clientY / transform.zoom,
49+
pointerX: pointer.x,
50+
pointerY: pointer.y,
4051
});
4152
};
4253

@@ -84,10 +95,7 @@ export default function Area({ data, onPointerDown, setResize, setInitCoords })
8495
selectedElement.open;
8596

8697
return (
87-
<g
88-
onPointerEnter={(e) => e.isPrimary && setHovered(true)}
89-
onPointerLeave={(e) => e.isPrimary &&setHovered(false)}
90-
>
98+
<g ref={ref}>
9199
<foreignObject
92100
key={data.id}
93101
x={data.x}
@@ -98,7 +106,7 @@ export default function Area({ data, onPointerDown, setResize, setInitCoords })
98106
>
99107
<div
100108
className={`border-2 ${
101-
hovered
109+
isHovered
102110
? "border-dashed border-blue-500"
103111
: selectedElement.element === ObjectType.AREA &&
104112
selectedElement.id === data.id
@@ -114,7 +122,7 @@ export default function Area({ data, onPointerDown, setResize, setInitCoords })
114122
<div className="text-color select-none overflow-hidden text-ellipsis">
115123
{data.name}
116124
</div>
117-
{(hovered || (areaIsSelected() && !layout.sidebar)) && (
125+
{(isHovered || (areaIsSelected() && !layout.sidebar)) && (
118126
<Popover
119127
visible={areaIsSelected() && !layout.sidebar}
120128
onClickOutSide={onClickOutSide}
@@ -139,7 +147,7 @@ export default function Area({ data, onPointerDown, setResize, setInitCoords })
139147
</div>
140148
</div>
141149
</foreignObject>
142-
{hovered && (
150+
{isHovered && (
143151
<>
144152
<circle
145153
cx={data.x}

0 commit comments

Comments
 (0)