Change to square grid which moves when panning, add snap to grid functionality (#492)

* change to square grid which moves when panning, add snap to grid functionality

* change grid back to circles, minor improvments

* add new constants
This commit is contained in:
Karen Mkrtumyan
2025-06-14 00:55:29 +04:00
committed by GitHub
parent 6ef3d76228
commit b974a7d854
6 changed files with 98 additions and 72 deletions

View File

@@ -7,6 +7,8 @@ import {
ObjectType, ObjectType,
tableFieldHeight, tableFieldHeight,
tableHeaderHeight, tableHeaderHeight,
gridSize,
gridCircleRadius,
} from "../../data/constants"; } from "../../data/constants";
import { Toast } from "@douyinfe/semi-ui"; import { Toast } from "@douyinfe/semi-ui";
import Table from "./Table"; import Table from "./Table";
@@ -272,6 +274,23 @@ export default function Canvas() {
if (!e.isPrimary) return; if (!e.isPrimary) return;
const isDragging =
dragging.element !== ObjectType.NONE && dragging.id !== null;
const currentX = pointer.spaces.diagram.x + (isDragging ? grabOffset.x : 0);
const currentY = pointer.spaces.diagram.y + (isDragging ? grabOffset.y : 0);
let finalX = currentX;
let finalY = currentY;
if (settings.snapToGrid) {
finalX = Math.round(currentX / gridSize) * gridSize;
finalY = Math.round(currentY / gridSize) * gridSize;
}
const deltaX = finalX - dragging.prevX;
const deltaY = finalY - dragging.prevY;
if (linking) { if (linking) {
setLinkingLine({ setLinkingLine({
...linkingLine, ...linkingLine,
@@ -283,11 +302,6 @@ export default function Canvas() {
dragging.id !== null && dragging.id !== null &&
bulkSelectedElements.length bulkSelectedElements.length
) { ) {
const currentX = pointer.spaces.diagram.x + grabOffset.x;
const currentY = pointer.spaces.diagram.y + grabOffset.y;
const deltaX = currentX - dragging.prevX;
const deltaY = currentY - dragging.prevY;
for (const element of bulkSelectedElements) { for (const element of bulkSelectedElements) {
if (element.type === ObjectType.TABLE) { if (element.type === ObjectType.TABLE) {
const { x, y } = tables.find((e) => e.id === element.id); const { x, y } = tables.find((e) => e.id === element.id);
@@ -312,8 +326,8 @@ export default function Canvas() {
setDragging((prev) => ({ setDragging((prev) => ({
...prev, ...prev,
prevX: currentX, prevX: finalX,
prevY: currentY, prevY: finalY,
})); }));
} else if ( } else if (
panning.isPanning && panning.isPanning &&
@@ -339,8 +353,8 @@ export default function Canvas() {
if (table.locked) return; if (table.locked) return;
updateTable(dragging.id, { updateTable(dragging.id, {
x: pointer.spaces.diagram.x + grabOffset.x, x: finalX,
y: pointer.spaces.diagram.y + grabOffset.y, y: finalY,
}); });
} else if ( } else if (
dragging.element === ObjectType.AREA && dragging.element === ObjectType.AREA &&
@@ -351,16 +365,16 @@ export default function Canvas() {
if (area.locked) return; if (area.locked) return;
updateArea(dragging.id, { updateArea(dragging.id, {
x: pointer.spaces.diagram.x + grabOffset.x, x: finalX,
y: pointer.spaces.diagram.y + grabOffset.y, y: finalY,
}); });
} else if (dragging.element === ObjectType.NOTE && dragging.id !== null) { } else if (dragging.element === ObjectType.NOTE && dragging.id !== null) {
const note = notes.find((t) => t.id === dragging.id); const note = notes.find((t) => t.id === dragging.id);
if (note.locked) return; if (note.locked) return;
updateNote(dragging.id, { updateNote(dragging.id, {
x: pointer.spaces.diagram.x + grabOffset.x, x: finalX,
y: pointer.spaces.diagram.y + grabOffset.y, y: finalY,
}); });
} else if (areaResize.id !== -1) { } else if (areaResize.id !== -1) {
if (areaResize.dir === "none") return; if (areaResize.dir === "none") return;
@@ -371,28 +385,24 @@ export default function Canvas() {
switch (areaResize.dir) { switch (areaResize.dir) {
case "br": case "br":
newDims.width = pointer.spaces.diagram.x - initCoords.x; newDims.width = finalX - initCoords.x;
newDims.height = pointer.spaces.diagram.y - initCoords.y; newDims.height = finalY - initCoords.y;
break; break;
case "tl": case "tl":
newDims.x = pointer.spaces.diagram.x; newDims.x = finalX;
newDims.y = pointer.spaces.diagram.y; newDims.y = finalY;
newDims.width = newDims.width = initCoords.width - (finalX - initCoords.x);
initCoords.x + initCoords.width - pointer.spaces.diagram.x; newDims.height = initCoords.height - (finalY - initCoords.y);
newDims.height =
initCoords.y + initCoords.height - pointer.spaces.diagram.y;
break; break;
case "tr": case "tr":
newDims.y = pointer.spaces.diagram.y; newDims.y = finalY;
newDims.width = pointer.spaces.diagram.x - initCoords.x; newDims.width = finalX - initCoords.x;
newDims.height = newDims.height = initCoords.height - (finalY - initCoords.y);
initCoords.y + initCoords.height - pointer.spaces.diagram.y;
break; break;
case "bl": case "bl":
newDims.x = pointer.spaces.diagram.x; newDims.x = finalX;
newDims.width = newDims.width = initCoords.width - (finalX - initCoords.x);
initCoords.x + initCoords.width - pointer.spaces.diagram.x; newDims.height = finalY - initCoords.y;
newDims.height = pointer.spaces.diagram.y - initCoords.y;
break; break;
} }
@@ -400,8 +410,8 @@ export default function Canvas() {
} else if (bulkSelectRectPts.show) { } else if (bulkSelectRectPts.show) {
setBulkSelectRectPts((prev) => ({ setBulkSelectRectPts((prev) => ({
...prev, ...prev,
x2: pointer.spaces.diagram.x, x2: finalX,
y2: pointer.spaces.diagram.y, y2: finalY,
})); }));
} }
}; };
@@ -709,36 +719,6 @@ export default function Canvas() {
backgroundColor: theme === "dark" ? darkBgTheme : "white", backgroundColor: theme === "dark" ? darkBgTheme : "white",
}} }}
> >
{settings.showGrid && (
<svg className="absolute w-full h-full">
<defs>
<pattern
id="pattern-circles"
x="0"
y="0"
width="24"
height="24"
patternUnits="userSpaceOnUse"
patternContentUnits="userSpaceOnUse"
>
<circle
id="pattern-circle"
cx="4"
cy="4"
r="0.85"
fill="rgb(99, 152, 191)"
/>
</pattern>
</defs>
<rect
x="0"
y="0"
width="100%"
height="100%"
fill="url(#pattern-circles)"
/>
</svg>
)}
<svg <svg
id="diagram" id="diagram"
ref={canvasRef} ref={canvasRef}
@@ -748,6 +728,36 @@ export default function Canvas() {
className="absolute w-full h-full touch-none" className="absolute w-full h-full touch-none"
viewBox={`${viewBox.left} ${viewBox.top} ${viewBox.width} ${viewBox.height}`} viewBox={`${viewBox.left} ${viewBox.top} ${viewBox.width} ${viewBox.height}`}
> >
{settings.showGrid && (
<>
<defs>
<pattern
id="pattern-grid"
x={-gridCircleRadius}
y={-gridCircleRadius}
width={gridSize}
height={gridSize}
patternUnits="userSpaceOnUse"
patternContentUnits="userSpaceOnUse"
>
<circle
cx={gridCircleRadius}
cy={gridCircleRadius}
r={gridCircleRadius}
fill="rgb(99, 152, 191)"
opacity="1"
/>
</pattern>
</defs>
<rect
x={viewBox.left}
y={viewBox.top}
width={viewBox.width}
height={viewBox.height}
fill="url(#pattern-grid)"
/>
</>
)}
{areas.map((a) => ( {areas.map((a) => (
<Area <Area
key={a.id} key={a.id}

View File

@@ -477,6 +477,8 @@ export default function ControlPanel({
const fileImport = () => setModal(MODAL.IMPORT); const fileImport = () => setModal(MODAL.IMPORT);
const viewGrid = () => const viewGrid = () =>
setSettings((prev) => ({ ...prev, showGrid: !prev.showGrid })); setSettings((prev) => ({ ...prev, showGrid: !prev.showGrid }));
const snapToGrid = () =>
setSettings((prev) => ({ ...prev, snapToGrid: !prev.snapToGrid }));
const zoomIn = () => const zoomIn = () =>
setTransform((prev) => ({ ...prev, zoom: prev.zoom * 1.2 })); setTransform((prev) => ({ ...prev, zoom: prev.zoom * 1.2 }));
const zoomOut = () => const zoomOut = () =>
@@ -1307,6 +1309,14 @@ export default function ControlPanel({
function: viewGrid, function: viewGrid,
shortcut: "Ctrl+Shift+G", shortcut: "Ctrl+Shift+G",
}, },
snap_to_grid: {
state: settings.snapToGrid ? (
<i className="bi bi-toggle-on" />
) : (
<i className="bi bi-toggle-off" />
),
function: snapToGrid,
},
show_cardinality: { show_cardinality: {
state: settings.showCardinality ? ( state: settings.showCardinality ? (
<i className="bi bi-toggle-on" /> <i className="bi bi-toggle-on" />

View File

@@ -4,6 +4,8 @@ import {
noteWidth, noteWidth,
noteRadius, noteRadius,
noteFold, noteFold,
gridSize,
gridCircleRadius,
} from "../data/constants"; } from "../data/constants";
export default function Thumbnail({ diagram, i, zoom, theme }) { export default function Thumbnail({ diagram, i, zoom, theme }) {
@@ -15,21 +17,21 @@ export default function Thumbnail({ diagram, i, zoom, theme }) {
> >
<defs> <defs>
<pattern <pattern
id={"pattern-circles-" + i} id={"pattern-grid-" + i}
x="0" x={-gridCircleRadius}
y="0" y={-gridCircleRadius}
width="10" width={gridSize * zoom}
height="10" height={gridSize * zoom}
patternUnits="userSpaceOnUse" patternUnits="userSpaceOnUse"
patternContentUnits="userSpaceOnUse" patternContentUnits="userSpaceOnUse"
> >
<circle <circle
id={"pattern-circle-" + i} cx={gridCircleRadius * zoom}
cx="2" cy={gridCircleRadius * zoom}
cy="2" r={gridCircleRadius * zoom}
r="0.4"
fill="rgb(99, 152, 191)" fill="rgb(99, 152, 191)"
></circle> opacity="1"
/>
</pattern> </pattern>
</defs> </defs>
<rect <rect
@@ -37,7 +39,7 @@ export default function Thumbnail({ diagram, i, zoom, theme }) {
y="0" y="0"
width="100%" width="100%"
height="100%" height="100%"
fill={"url(#pattern-circles-" + i + ")"} fill={"url(#pattern-grid-" + i + ")"}
></rect> ></rect>
<g <g
style={{ style={{

View File

@@ -5,6 +5,7 @@ const defaultSettings = {
strictMode: false, strictMode: false,
showFieldSummary: true, showFieldSummary: true,
showGrid: true, showGrid: true,
snapToGrid: false,
showDataTypes: true, showDataTypes: true,
mode: "light", mode: "light",
autosave: true, autosave: true,

View File

@@ -18,6 +18,8 @@ export const otherColor = "text-zinc-500";
export const dateColor = "text-cyan-500"; export const dateColor = "text-cyan-500";
export const tableHeaderHeight = 50; export const tableHeaderHeight = 50;
export const tableWidth = 220; export const tableWidth = 220;
export const gridSize = 24;
export const gridCircleRadius = 0.85;
export const tableFieldHeight = 36; export const tableFieldHeight = 36;
export const tableColorStripHeight = 7; export const tableColorStripHeight = 7;

View File

@@ -49,6 +49,7 @@ const en = {
field_details: "Field details", field_details: "Field details",
reset_view: "Reset view", reset_view: "Reset view",
show_grid: "Show grid", show_grid: "Show grid",
snap_to_grid: "Snap to grid",
show_datatype: "Show datatype", show_datatype: "Show datatype",
show_cardinality: "Show cardinality", show_cardinality: "Show cardinality",
theme: "Theme", theme: "Theme",