drawDB/src/components/canvas.jsx

326 lines
8.8 KiB
React
Raw Normal View History

2023-09-19 20:47:06 +08:00
import React, { useRef, useState } from "react";
import { useDrop } from "react-dnd";
2023-09-19 20:47:43 +08:00
import Table from "./table";
2023-09-19 20:48:01 +08:00
import { defaultTableTheme, Cardinality, Constraint } from "../data/data";
2023-09-19 20:48:04 +08:00
import Area from "./area";
2023-09-19 20:47:34 +08:00
2023-09-19 20:47:06 +08:00
export default function Canvas(props) {
2023-09-19 20:48:04 +08:00
const ObjectType = {
NONE: 0,
TABLE: 1,
AREA: 2,
};
const [dragging, setDragging] = useState([ObjectType.NONE, -1]);
2023-09-19 20:47:35 +08:00
const [linking, setLinking] = useState(false);
const [line, setLine] = useState({
2023-09-19 20:47:48 +08:00
startTableId: -1,
startFieldId: -1,
endTableId: -1,
endFieldId: -1,
2023-09-19 20:47:35 +08:00
startX: 0,
startY: 0,
endX: 0,
endY: 0,
name: "",
2023-09-19 20:48:01 +08:00
cardinality: Cardinality.ONE_TO_MANY,
updateConstraint: Constraint.none,
deleteConstraint: Constraint.none,
mandatory: false,
2023-09-19 20:47:35 +08:00
});
2023-09-19 20:47:06 +08:00
const [offset, setOffset] = useState({ x: 0, y: 0 });
2023-09-19 20:47:45 +08:00
const [onRect, setOnRect] = useState({
tableId: -1,
field: -2,
});
2023-09-19 20:47:13 +08:00
const [panning, setPanning] = useState(false);
const [panOffset, setPanOffset] = useState({ x: 0, y: 0 });
const [cursor, setCursor] = useState("default");
2023-09-19 20:46:43 +08:00
const canvas = useRef(null);
2023-09-19 20:48:04 +08:00
const handleMouseDownRect = (e, id, type) => {
2023-09-19 20:47:13 +08:00
const { clientX, clientY } = e;
2023-09-19 20:48:04 +08:00
if (type === ObjectType.TABLE) {
const table = props.tables.find((t) => t.id === id);
setOffset({
x: clientX - table.x,
y: clientY - table.y,
});
setDragging([ObjectType.TABLE, id]);
} else if (type === ObjectType.AREA) {
const area = props.areas.find((t) => t.id === id);
setOffset({
x: clientX - area.x,
y: clientY - area.y,
});
setDragging([ObjectType.AREA, id]);
}
2023-09-19 20:47:06 +08:00
};
2023-09-19 20:47:13 +08:00
const handleMouseMove = (e) => {
2023-09-19 20:47:35 +08:00
if (linking) {
const rect = canvas.current.getBoundingClientRect();
const offsetX = rect.left;
const offsetY = rect.top;
setLine({
...line,
endX: e.clientX - offsetX,
endY: e.clientY - offsetY,
});
2023-09-19 20:48:05 +08:00
} else if (dragging[0] === ObjectType.NONE && panning) {
2023-09-19 20:47:13 +08:00
const dx = e.clientX - panOffset.x;
const dy = e.clientY - panOffset.y;
setPanOffset({ x: e.clientX, y: e.clientY });
2023-09-19 20:47:47 +08:00
props.setTables(
props.tables.map((t) => ({
...t,
x: t.x + dx,
y: t.y + dy,
}))
);
props.setRelationships(
props.relationships.map((r) => ({
2023-09-19 20:47:48 +08:00
...r,
2023-09-19 20:47:47 +08:00
startX: r.startX + dx,
startY: r.startY + dy,
endX: r.endX + dx,
endY: r.endY + dy,
}))
);
2023-09-19 20:48:04 +08:00
props.setAreas(
props.areas.map((t) => ({
...t,
x: t.x + dx,
y: t.y + dy,
}))
);
} else if (dragging[0] === ObjectType.TABLE && dragging[1] >= 0) {
const updatedTables = props.tables.map((t) => {
2023-09-19 20:48:04 +08:00
if (t.id === dragging[1]) {
const updatedTable = {
...t,
x: e.clientX - offset.x,
y: e.clientY - offset.y,
2023-09-19 20:47:13 +08:00
};
return updatedTable;
2023-09-19 20:47:13 +08:00
}
return t;
2023-09-19 20:47:13 +08:00
});
props.setTables(updatedTables);
const updatedRelationShips = props.relationships.map((r) => {
2023-09-19 20:48:04 +08:00
if (r.startTableId === dragging[1]) {
2023-09-19 20:47:48 +08:00
return {
...r,
startX: props.tables[r.startTableId].x + 15,
startY:
props.tables[r.startTableId].y + r.startFieldId * 36 + 40 + 19,
};
2023-09-19 20:48:04 +08:00
} else if (r.endTableId === dragging[1]) {
2023-09-19 20:47:48 +08:00
return {
...r,
endX: props.tables[r.endTableId].x + 15,
endY: props.tables[r.endTableId].y + r.endFieldId * 36 + 40 + 19,
};
}
return r;
});
props.setRelationships(updatedRelationShips);
2023-09-19 20:48:04 +08:00
} else if (dragging[0] === ObjectType.AREA && dragging[1] >= 0) {
const updatedAreas = props.areas.map((t) => {
if (t.id === dragging[1]) {
const updatedArea = {
...t,
x: e.clientX - offset.x,
y: e.clientY - offset.y,
};
return updatedArea;
}
return t;
});
props.setAreas(updatedAreas);
2023-09-19 20:47:13 +08:00
}
};
const handleMouseDown = (e) => {
2023-09-19 20:48:05 +08:00
setPanning(true);
setPanOffset({ x: e.clientX, y: e.clientY });
setCursor("grabbing");
2023-09-19 20:47:06 +08:00
};
2023-09-19 20:46:43 +08:00
2023-09-19 20:47:06 +08:00
const handleMouseUp = () => {
2023-09-19 20:48:04 +08:00
setDragging([ObjectType.NONE, -1]);
2023-09-19 20:47:13 +08:00
setPanning(false);
setCursor("default");
2023-09-19 20:47:45 +08:00
if (linking) handleLinking();
2023-09-19 20:47:35 +08:00
setLinking(false);
2023-09-19 20:47:06 +08:00
};
2023-09-19 20:47:35 +08:00
const handleGripField = (id) => {
setPanning(false);
2023-09-19 20:48:04 +08:00
setDragging([ObjectType.NONE, -1]);
2023-09-19 20:47:35 +08:00
setLinking(true);
};
2023-09-19 20:47:45 +08:00
const handleLinking = () => {
if (onRect.tableId < 0) return;
if (onRect.field < 0) return;
if (
line.startTableId === onRect.tableId &&
line.startFieldId === onRect.field
)
return;
props.setRelationships((prev) => [
2023-09-19 20:47:45 +08:00
...prev,
{
2023-09-19 20:47:48 +08:00
...line,
endTableId: onRect.tableId,
endFieldId: onRect.field,
endX: props.tables[onRect.tableId].x + 15,
endY: props.tables[onRect.tableId].y + onRect.field * 36 + 40 + 19,
name: `${props.tables[line.startTableId].name}_to_${
props.tables[onRect.tableId].name
}`,
2023-09-19 20:47:45 +08:00
},
]);
};
2023-09-19 20:47:06 +08:00
const [, drop] = useDrop(
() => ({
accept: "CARD",
drop: (item, monitor) => {
const offset = monitor.getClientOffset();
const canvasRect = canvas.current.getBoundingClientRect();
const x = offset.x - canvasRect.left - 100 * 0.5;
const y = offset.y - canvasRect.top - 100 * 0.5;
const newTable = {
2023-09-19 20:47:54 +08:00
id: props.tables.length,
name: `table_${props.tables.length}`,
x: x,
y: y,
fields: [
{
name: "id",
type: "UUID",
2023-09-19 20:47:56 +08:00
check: "",
default: "",
primary: true,
unique: true,
notNull: true,
increment: true,
2023-09-19 20:47:52 +08:00
comment: "",
},
],
2023-09-19 20:47:51 +08:00
comment: "",
indices: [],
2023-09-19 20:47:54 +08:00
color: defaultTableTheme,
2023-09-19 20:47:06 +08:00
};
props.setTables((prev) => [...prev, newTable]);
2023-09-19 20:47:10 +08:00
props.setCode((prev) =>
prev === ""
? `CREATE TABLE \`${newTable.name}\`;`
: `${prev}\n\nCREATE TABLE \`${newTable.name}\`;`
2023-09-19 20:47:10 +08:00
);
2023-09-19 20:46:43 +08:00
},
2023-09-19 20:47:06 +08:00
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}),
[props.tables]
2023-09-19 20:47:06 +08:00
);
return (
2023-09-19 20:47:06 +08:00
<div ref={drop} className="flex-grow" id="canvas">
<div ref={canvas} className="w-full h-screen">
<svg
onMouseMove={handleMouseMove}
2023-09-19 20:47:13 +08:00
onMouseDown={handleMouseDown}
2023-09-19 20:47:06 +08:00
onMouseUp={handleMouseUp}
2023-09-19 20:47:13 +08:00
style={{ width: "100%", height: "100%", cursor: cursor }}
2023-09-19 20:47:06 +08:00
>
2023-09-19 20:47:14 +08:00
<defs>
<pattern
id="smallGrid"
width="10"
height="10"
patternUnits="userSpaceOnUse"
>
<path
d="M 10 0 L 0 0 0 10"
fill="none"
stroke="lightblue"
strokeWidth="0.5"
/>
</pattern>
<pattern
id="grid"
width="100"
height="100"
patternUnits="userSpaceOnUse"
>
<rect width="100" height="100" fill="url(#smallGrid)" />
<path
d="M 100 0 L 0 0 0 100"
fill="none"
stroke="lightblue"
strokeWidth="1"
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
2023-09-19 20:48:04 +08:00
{props.areas.map((a) => (
<Area
key={a.id}
areaData={a}
onMouseDown={(e) => handleMouseDownRect(e, a.id, ObjectType.AREA)}
2023-09-19 20:48:06 +08:00
setPanning={setPanning}
setAreas={props.setAreas}
2023-09-19 20:48:04 +08:00
></Area>
))}
{props.tables.map((table, i) => (
2023-09-19 20:47:43 +08:00
<Table
key={table.id}
2023-09-19 20:47:54 +08:00
id={table.id}
tableData={table}
tables={props.tables}
setTables={props.setTables}
2023-09-19 20:47:13 +08:00
setOnRect={setOnRect}
2023-09-19 20:47:35 +08:00
handleGripField={handleGripField}
setLine={setLine}
2023-09-19 20:48:04 +08:00
onMouseDown={(e) =>
handleMouseDownRect(e, table.id, ObjectType.TABLE)
}
2023-09-19 20:47:06 +08:00
/>
))}
2023-09-19 20:47:35 +08:00
{linking && (
<line
x1={line.startX}
y1={line.startY}
x2={line.endX}
y2={line.endY}
stroke="red"
strokeDasharray="5,5"
/>
)}
{props.relationships.map((e, i) => (
2023-09-19 20:47:45 +08:00
<line
2023-09-19 20:47:48 +08:00
key={i}
2023-09-19 20:47:45 +08:00
x1={e.startX}
y1={e.startY}
x2={e.endX}
y2={e.endY}
stroke="gray"
strokeWidth={2.5}
onClick={() => {}}
2023-09-19 20:47:45 +08:00
/>
))}
2023-09-19 20:47:06 +08:00
</svg>
</div>
</div>
);
2023-09-19 20:46:43 +08:00
}