This commit is contained in:
1ilit 2023-09-19 15:49:57 +03:00
parent 39a8dbb315
commit be66cf6e84
9 changed files with 414 additions and 463 deletions

View File

@ -16,8 +16,8 @@ export default function Area(props) {
y: props.areaData.y, y: props.areaData.y,
width: props.areaData.width, width: props.areaData.width,
height: props.areaData.height, height: props.areaData.height,
mouseX: e.clientX/props.zoom, mouseX: e.clientX / props.zoom,
mouseY: e.clientY/props.zoom, mouseY: e.clientY / props.zoom,
}); });
}; };

View File

@ -23,7 +23,7 @@ import { defaultTableTheme, tableThemes } from "../data/data";
import { AreaContext } from "../pages/editor"; import { AreaContext } from "../pages/editor";
export default function AreaOverview(props) { export default function AreaOverview(props) {
const { areas, setAreas } = useContext(AreaContext); const { areas, setAreas, addArea, deleteArea } = useContext(AreaContext);
const [value, setValue] = useState(""); const [value, setValue] = useState("");
const [filteredResult, setFilteredResult] = useState( const [filteredResult, setFilteredResult] = useState(
@ -79,22 +79,7 @@ export default function AreaOverview(props) {
/> />
</Col> </Col>
<Col span={8}> <Col span={8}>
<Button <Button icon={<IconPlus />} block onClick={() => addArea()}>
icon={<IconPlus />}
block
onClick={() => {
const newArea = {
id: areas.length,
name: `area_${areas.length}`,
x: 0,
y: 0,
width: 200,
height: 200,
color: defaultTableTheme,
};
setAreas((prev) => [...prev, newArea]);
}}
>
Add area Add area
</Button> </Button>
</Col> </Col>
@ -208,13 +193,8 @@ export default function AreaOverview(props) {
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
Toast.success(`Area deleted!`); Toast.success(`Area deleted!`);
setAreas((prev) => deleteArea(i, true);
prev
.filter((e) => e.id !== i)
.map((e, idx) => ({ ...e, id: idx }))
);
}} }}
// className="delete-button"
></Button> ></Button>
</Col> </Col>
</Row> </Row>

View File

@ -13,10 +13,10 @@ import {
import Note from "./note"; import Note from "./note";
export default function Canvas(props) { export default function Canvas(props) {
const { tables, setTables, relationships, setRelationships } = const { tables, moveTable, relationships, addRelationship } =
useContext(TableContext); useContext(TableContext);
const { areas, setAreas } = useContext(AreaContext); const { areas, setAreas, moveArea } = useContext(AreaContext);
const { notes, setNotes } = useContext(NoteContext); const { notes, moveNote } = useContext(NoteContext);
const { settings, setSettings } = useContext(SettingsContext); const { settings, setSettings } = useContext(SettingsContext);
const { setUndoStack, setRedoStack } = useContext(UndoRedoContext); const { setUndoStack, setRedoStack } = useContext(UndoRedoContext);
const [dragging, setDragging] = useState({ const [dragging, setDragging] = useState({
@ -126,66 +126,21 @@ export default function Canvas(props) {
})); }));
setPanOffset({ x: e.clientX, y: e.clientY }); setPanOffset({ x: e.clientX, y: e.clientY });
} else if (dragging.element === ObjectType.TABLE && dragging.id >= 0) { } else if (dragging.element === ObjectType.TABLE && dragging.id >= 0) {
const updatedTables = tables.map((t) => { const dx = e.clientX / settings.zoom - offset.x;
if (t.id === dragging.id) { const dy = e.clientY / settings.zoom - offset.y;
return { moveTable(dragging.id, dx, dy);
...t,
x: e.clientX / settings.zoom - offset.x,
y: e.clientY / settings.zoom - offset.y,
};
}
return t;
});
setTables(updatedTables);
setRelationships((prev) =>
prev.map((r) => {
if (r.startTableId === dragging.id) {
return {
...r,
startX: tables[r.startTableId].x + 15,
startY: tables[r.startTableId].y + r.startFieldId * 36 + 50 + 19,
};
} else if (r.endTableId === dragging.id) {
return {
...r,
endX: tables[r.endTableId].x + 15,
endY: tables[r.endTableId].y + r.endFieldId * 36 + 50 + 19,
};
}
return r;
})
);
} else if ( } else if (
dragging.element === ObjectType.AREA && dragging.element === ObjectType.AREA &&
dragging.id >= 0 && dragging.id >= 0 &&
areaResize.id === -1 areaResize.id === -1
) { ) {
setAreas((prev) => const dx = e.clientX / settings.zoom - offset.x;
prev.map((t) => { const dy = e.clientY / settings.zoom - offset.y;
if (t.id === dragging.id) { moveArea(dragging.id, dx, dy);
const updatedArea = {
...t,
x: e.clientX / settings.zoom - offset.x,
y: e.clientY / settings.zoom - offset.y,
};
return updatedArea;
}
return t;
})
);
} else if (dragging.element === ObjectType.NOTE && dragging.id >= 0) { } else if (dragging.element === ObjectType.NOTE && dragging.id >= 0) {
setNotes((prev) => const dx = e.clientX / settings.zoom - offset.x;
prev.map((t) => { const dy = e.clientY / settings.zoom - offset.y;
if (t.id === dragging.id) { moveNote(dragging.id, dx, dy);
return {
...t,
x: e.clientX / settings.zoom - offset.x,
y: e.clientY / settings.zoom - offset.y,
};
}
return t;
})
);
} else if (areaResize.id !== -1) { } else if (areaResize.id !== -1) {
if (areaResize.dir === "none") return; if (areaResize.dir === "none") return;
@ -237,15 +192,30 @@ export default function Canvas(props) {
setCursor("grabbing"); setCursor("grabbing");
}; };
const coordsDidUpdate = () => { const coordsDidUpdate = (element) => {
return !( switch (element) {
dragging.prevX === tables[dragging.id].x && case ObjectType.TABLE:
dragging.prevY === tables[dragging.id].y return !(
); dragging.prevX === tables[dragging.id].x &&
dragging.prevY === tables[dragging.id].y
);
case ObjectType.AREA:
return !(
dragging.prevX === areas[dragging.id].x &&
dragging.prevY === areas[dragging.id].y
);
case ObjectType.NOTE:
return !(
dragging.prevX === notes[dragging.id].x &&
dragging.prevY === notes[dragging.id].y
);
default:
return false;
}
}; };
const handleMouseUp = (e) => { const handleMouseUp = (e) => {
if (dragging.element !== ObjectType.NONE && coordsDidUpdate()) { if (coordsDidUpdate(dragging.element)) {
setUndoStack((prev) => [ setUndoStack((prev) => [
...prev, ...prev,
{ {
@ -289,38 +259,25 @@ export default function Canvas(props) {
) )
return; return;
setRelationships((prev) => { addRelationship(true, {
const newRelationship = { ...line,
...line, endTableId: onRect.tableId,
endTableId: onRect.tableId, endFieldId: onRect.field,
endFieldId: onRect.field, endX: tables[onRect.tableId].x + 15,
endX: tables[onRect.tableId].x + 15, endY: tables[onRect.tableId].y + onRect.field * 36 + 69,
endY: tables[onRect.tableId].y + onRect.field * 36 + 50 + 19, name: `${tables[line.startTableId].name}_to_${
name: `${tables[line.startTableId].name}_to_${ tables[onRect.tableId].name
tables[onRect.tableId].name }`,
}`, id: relationships.length,
id: prev.length,
};
setUndoStack((prevUndo) => [
...prevUndo,
{
action: Action.ADD,
element: ObjectType.RELATIONSHIP,
data: newRelationship,
},
]);
setRedoStack([]);
return [...prev, newRelationship];
}); });
}; };
const handleMouseWheel = (e) => { const handleMouseWheel = (e) => {
e.preventDefault(); e.preventDefault();
if (e.deltaY <= 0) { setSettings((prev) => ({
setSettings((prev) => ({ ...prev, zoom: prev.zoom * 1.05 })); ...prev,
} else { zoom: e.deltaY <= 0 ? prev.zoom * 1.05 : prev.zoom / 1.05,
setSettings((prev) => ({ ...prev, zoom: prev.zoom / 1.05 })); }));
}
}; };
useEffect(() => { useEffect(() => {

View File

@ -45,12 +45,7 @@ import {
UndoRedoContext, UndoRedoContext,
} from "../pages/editor"; } from "../pages/editor";
import { IconAddTable, IconAddArea, IconAddNote } from "./custom_icons"; import { IconAddTable, IconAddArea, IconAddNote } from "./custom_icons";
import { import { ObjectType, Action } from "../data/data";
defaultTableTheme,
defaultNoteTheme,
ObjectType,
Action,
} from "../data/data";
import CodeMirror from "@uiw/react-codemirror"; import CodeMirror from "@uiw/react-codemirror";
import { json } from "@codemirror/lang-json"; import { json } from "@codemirror/lang-json";
import jsPDF from "jspdf"; import jsPDF from "jspdf";
@ -62,7 +57,7 @@ export default function ControlPanel(props) {
CODE: 2, CODE: 2,
IMPORT: 3, IMPORT: 3,
}; };
const ERROR = { const STATUS = {
NONE: 0, NONE: 0,
WARNING: 1, WARNING: 1,
ERROR: 2, ERROR: 2,
@ -75,16 +70,27 @@ export default function ControlPanel(props) {
extension: "", extension: "",
}); });
const [error, setError] = useState({ const [error, setError] = useState({
type: ERROR.NONE, type: STATUS.NONE,
message: "", message: "",
}); });
const [data, setData] = useState(null); const [data, setData] = useState(null);
const { layout, setLayout } = useContext(LayoutContext); const { layout, setLayout } = useContext(LayoutContext);
const { settings, setSettings } = useContext(SettingsContext); const { settings, setSettings } = useContext(SettingsContext);
const { relationships, tables, setTables, setRelationships } = const {
useContext(TableContext); relationships,
const { notes, setNotes } = useContext(NoteContext); tables,
const { areas, setAreas } = useContext(AreaContext); setTables,
addTable,
moveTable,
deleteTable,
setRelationships,
addRelationship,
deleteRelationship,
} = useContext(TableContext);
const { notes, setNotes, moveNote, addNote, deleteNote } =
useContext(NoteContext);
const { areas, setAreas, moveArea, addArea, deleteArea } =
useContext(AreaContext);
const { undoStack, redoStack, setUndoStack, setRedoStack } = const { undoStack, redoStack, setUndoStack, setRedoStack } =
useContext(UndoRedoContext); useContext(UndoRedoContext);
@ -107,155 +113,18 @@ export default function ControlPanel(props) {
setNotes(data.notes); setNotes(data.notes);
}; };
const addTable = () => {
setTables((prev) => [
...prev,
{
id: prev.length,
name: `table_${prev.length}`,
x: -settings.pan.x,
y: -settings.pan.y,
fields: [
{
name: "id",
type: "UUID",
default: "",
check: "",
primary: true,
unique: true,
notNull: true,
increment: true,
comment: "",
},
],
comment: "",
indices: [],
color: defaultTableTheme,
},
]);
};
const addArea = () => {
setAreas((prev) => [
...prev,
{
id: prev.length,
name: `area_${prev.length}`,
x: -settings.pan.x,
y: -settings.pan.y,
width: 200,
height: 200,
color: defaultTableTheme,
},
]);
};
const addNote = () => {
setNotes((prev) => [
...prev,
{
id: prev.length,
x: -settings.pan.x,
y: -settings.pan.y,
title: `note_${prev.length}`,
content: "",
color: defaultNoteTheme,
height: 88,
},
]);
};
const moveTable = (id, x, y) => {
setTables((prev) =>
prev.map((t) => {
if (t.id === id) {
setRelationships((prev) =>
prev.map((r) => {
if (r.startTableId === id) {
return {
...r,
startX: x + 15,
startY: y + r.startFieldId * 36 + 69,
};
} else if (r.endTableId === id) {
return {
...r,
endX: x + 15,
endY: y + r.endFieldId * 36 + 69,
};
}
return r;
})
);
return {
...t,
x: x,
y: y,
};
}
return t;
})
);
};
const moveArea = (id, x, y) => {
setAreas((prev) =>
prev.map((t) => {
if (t.id === id) {
return {
...t,
x: x,
y: y,
};
}
return t;
})
);
};
const moveNote = (id, x, y) => {
setNotes((prev) =>
prev.map((t) => {
if (t.id === id) {
return {
...t,
x: x,
y: y,
};
}
return t;
})
);
};
const undo = () => { const undo = () => {
if (undoStack.length === 0) return; if (undoStack.length === 0) return;
const a = undoStack.pop(); const a = undoStack.pop();
if (a.action === Action.ADD) { if (a.action === Action.ADD) {
if (a.element === ObjectType.TABLE) { if (a.element === ObjectType.TABLE) {
setTables((prev) => deleteTable(tables[tables.length - 1].id, false);
prev
.filter((e) => e.id !== prev.length - 1)
.map((e, i) => ({ ...e, id: i }))
);
} else if (a.element === ObjectType.AREA) { } else if (a.element === ObjectType.AREA) {
setAreas((prev) => deleteArea(areas[areas.length - 1].id, false);
prev
.filter((e) => e.id !== prev.length - 1)
.map((e, i) => ({ ...e, id: i }))
);
} else if (a.element === ObjectType.NOTE) { } else if (a.element === ObjectType.NOTE) {
setNotes((prev) => deleteNote(notes[notes.length - 1].id, false);
prev
.filter((e) => e.id !== prev.length - 1)
.map((e, i) => ({ ...e, id: i }))
);
} else if (a.element === ObjectType.RELATIONSHIP) { } else if (a.element === ObjectType.RELATIONSHIP) {
setRelationships((prev) => deleteRelationship(a.data.id, false);
prev
.filter((e) => e.id !== a.data.id)
.map((e, idx) => ({ ...e, id: idx }))
);
} }
setRedoStack((prev) => [...prev, a]); setRedoStack((prev) => [...prev, a]);
} else if (a.action === Action.MOVE) { } else if (a.action === Action.MOVE) {
@ -280,17 +149,13 @@ export default function ControlPanel(props) {
} }
} else if (a.action === Action.DELETE) { } else if (a.action === Action.DELETE) {
if (a.element === ObjectType.TABLE) { if (a.element === ObjectType.TABLE) {
setTables((prev) => { addTable(false, a.data);
const temp = prev.slice();
temp.splice(a.data.id, 0, a.data);
return temp.map((t, i) => ({ ...t, id: i }));
});
} else if (a.element === ObjectType.RELATIONSHIP) { } else if (a.element === ObjectType.RELATIONSHIP) {
setRelationships((prev) => { addRelationship(false, a.data);
const temp = prev.slice(); } else if (a.element === ObjectType.NOTE) {
temp.splice(a.data.id, 0, a.data); addNote(false, a.data);
return temp.map((t, i) => ({ ...t, id: i })); } else if (a.element === ObjectType.AREA) {
}); addArea(false, a.data);
} }
setRedoStack((prev) => [...prev, a]); setRedoStack((prev) => [...prev, a]);
} }
@ -301,17 +166,13 @@ export default function ControlPanel(props) {
const a = redoStack.pop(); const a = redoStack.pop();
if (a.action === Action.ADD) { if (a.action === Action.ADD) {
if (a.element === ObjectType.TABLE) { if (a.element === ObjectType.TABLE) {
addTable(); addTable(false);
} else if (a.element === ObjectType.AREA) { } else if (a.element === ObjectType.AREA) {
addArea(); addArea(false);
} else if (a.element === ObjectType.NOTE) { } else if (a.element === ObjectType.NOTE) {
addNote(); addNote(false);
} else if (a.element === ObjectType.RELATIONSHIP) { } else if (a.element === ObjectType.RELATIONSHIP) {
setRelationships((prev) => { addRelationship(false, a.data);
const temp = prev.slice();
temp.splice(a.data.id, 0, a.data);
return temp.map((t, i) => ({ ...t, id: i }));
});
} }
setUndoStack((prev) => [...prev, a]); setUndoStack((prev) => [...prev, a]);
} else if (a.action === Action.MOVE) { } else if (a.action === Action.MOVE) {
@ -336,17 +197,13 @@ export default function ControlPanel(props) {
} }
} else if (a.action === Action.DELETE) { } else if (a.action === Action.DELETE) {
if (a.element === ObjectType.TABLE) { if (a.element === ObjectType.TABLE) {
setTables((prev) => deleteTable(a.data.id, false);
prev
.filter((t) => t.id !== a.data.id)
.map((t, i) => ({ ...t, id: i }))
);
} else if (a.element === ObjectType.RELATIONSHIP) { } else if (a.element === ObjectType.RELATIONSHIP) {
setRelationships((prev) => deleteRelationship(a.data.id, false);
prev } else if (a.element === ObjectType.NOTE) {
.filter((t) => t.id !== a.data.id) deleteNote(a.data.id, false);
.map((t, i) => ({ ...t, id: i })) } else if (a.element === ObjectType.AREA) {
); deleteArea(a.data.id, false);
} }
setUndoStack((prev) => [...prev, a]); setUndoStack((prev) => [...prev, a]);
} }
@ -772,51 +629,21 @@ export default function ControlPanel(props) {
<button <button
className="flex items-center py-1 px-2 hover:bg-slate-200 rounded" className="flex items-center py-1 px-2 hover:bg-slate-200 rounded"
title="Add new table" title="Add new table"
onClick={() => { onClick={() => addTable()}
addTable();
setUndoStack((prev) => [
...prev,
{
action: Action.ADD,
element: ObjectType.TABLE,
},
]);
setRedoStack([]);
}}
> >
<IconAddTable /> <IconAddTable />
</button> </button>
<button <button
className="py-1 px-2 hover:bg-slate-200 rounded flex items-center" className="py-1 px-2 hover:bg-slate-200 rounded flex items-center"
title="Add subject area" title="Add subject area"
onClick={() => { onClick={() => addArea()}
addArea();
setUndoStack((prev) => [
...prev,
{
action: Action.ADD,
element: ObjectType.AREA,
},
]);
setRedoStack([]);
}}
> >
<IconAddArea /> <IconAddArea />
</button> </button>
<button <button
className="py-1 px-2 hover:bg-slate-200 rounded flex items-center" className="py-1 px-2 hover:bg-slate-200 rounded flex items-center"
title="Add new note" title="Add new note"
onClick={() => { onClick={() => addNote()}
addNote();
setUndoStack((prev) => [
...prev,
{
action: Action.ADD,
element: ObjectType.NOTE,
},
]);
setRedoStack([]);
}}
> >
<IconAddNote /> <IconAddNote />
</button> </button>
@ -856,7 +683,7 @@ export default function ControlPanel(props) {
}); });
saveAs(blob, `${exportData.filename}.${exportData.extension}`); saveAs(blob, `${exportData.filename}.${exportData.extension}`);
} else if (visible === MODAL.IMPORT) { } else if (visible === MODAL.IMPORT) {
if (error.type !== ERROR.ERROR) { if (error.type !== STATUS.ERROR) {
setSettings((prev) => ({ ...prev, pan: { x: 0, y: 0 } })); setSettings((prev) => ({ ...prev, pan: { x: 0, y: 0 } }));
overwriteDiagram(); overwriteDiagram();
setData(null); setData(null);
@ -873,7 +700,7 @@ export default function ControlPanel(props) {
filename: `diagram_${new Date().toISOString()}`, filename: `diagram_${new Date().toISOString()}`,
})); }));
setError({ setError({
type: ERROR.NONE, type: STATUS.NONE,
message: "", message: "",
}); });
setData(null); setData(null);
@ -885,7 +712,7 @@ export default function ControlPanel(props) {
okButtonProps={{ okButtonProps={{
disabled: disabled:
(visible === MODAL.IMPORT && (visible === MODAL.IMPORT &&
(error.type === ERROR.ERROR || !data)) || (error.type === STATUS.ERROR || !data)) ||
((visible === MODAL.IMG || visible === MODAL.CODE) && ((visible === MODAL.IMG || visible === MODAL.CODE) &&
!exportData.data), !exportData.data),
}} }}
@ -909,7 +736,7 @@ export default function ControlPanel(props) {
jsonObject = JSON.parse(event.target.result); jsonObject = JSON.parse(event.target.result);
} catch (error) { } catch (error) {
setError({ setError({
type: ERROR.ERROR, type: STATUS.ERROR,
message: "The file contains an error.", message: "The file contains an error.",
}); });
return; return;
@ -917,7 +744,7 @@ export default function ControlPanel(props) {
if (f.type === "application/json") { if (f.type === "application/json") {
if (!jsonDiagramIsValid(jsonObject)) { if (!jsonDiagramIsValid(jsonObject)) {
setError({ setError({
type: ERROR.ERROR, type: STATUS.ERROR,
message: message:
"The file is missing necessary properties for a diagram.", "The file is missing necessary properties for a diagram.",
}); });
@ -926,7 +753,7 @@ export default function ControlPanel(props) {
} else if (f.name.split(".").pop() === "ddb") { } else if (f.name.split(".").pop() === "ddb") {
if (!ddbDiagramIsValid(jsonObject)) { if (!ddbDiagramIsValid(jsonObject)) {
setError({ setError({
type: ERROR.ERROR, type: STATUS.ERROR,
message: message:
"The file is missing necessary properties for a diagram.", "The file is missing necessary properties for a diagram.",
}); });
@ -936,12 +763,12 @@ export default function ControlPanel(props) {
setData(jsonObject); setData(jsonObject);
if (diagramIsEmpty()) { if (diagramIsEmpty()) {
setError({ setError({
type: ERROR.OK, type: STATUS.OK,
message: "Everything looks good. You can now import.", message: "Everything looks good. You can now import.",
}); });
} else { } else {
setError({ setError({
type: ERROR.WARNING, type: STATUS.WARNING,
message: message:
"The current diagram is not empty. Importing a new diagram will overwrite the current changes.", "The current diagram is not empty. Importing a new diagram will overwrite the current changes.",
}); });
@ -962,19 +789,19 @@ export default function ControlPanel(props) {
accept="application/json,.ddb" accept="application/json,.ddb"
onRemove={() => onRemove={() =>
setError({ setError({
type: ERROR.NONE, type: STATUS.NONE,
message: "", message: "",
}) })
} }
onFileChange={() => onFileChange={() =>
setError({ setError({
type: ERROR.NONE, type: STATUS.NONE,
message: "", message: "",
}) })
} }
limit={1} limit={1}
></Upload> ></Upload>
{error.type === ERROR.ERROR ? ( {error.type === STATUS.ERROR ? (
<Banner <Banner
type="danger" type="danger"
fullMode={false} fullMode={false}
@ -982,14 +809,14 @@ export default function ControlPanel(props) {
<div className="text-red-800">{error.message}</div> <div className="text-red-800">{error.message}</div>
} }
/> />
) : error.type === ERROR.OK ? ( ) : error.type === STATUS.OK ? (
<Banner <Banner
type="info" type="info"
fullMode={false} fullMode={false}
description={<div>{error.message}</div>} description={<div>{error.message}</div>}
/> />
) : ( ) : (
error.type === ERROR.WARNING && ( error.type === STATUS.WARNING && (
<Banner <Banner
type="warning" type="warning"
fullMode={false} fullMode={false}

View File

@ -24,7 +24,7 @@ import { NoteContext } from "../pages/editor";
import { defaultNoteTheme, noteThemes } from "../data/data"; import { defaultNoteTheme, noteThemes } from "../data/data";
export default function NotesOverview(props) { export default function NotesOverview(props) {
const { notes, setNotes } = useContext(NoteContext); const { notes, setNotes, addNote, deleteNote } = useContext(NoteContext);
const [value, setValue] = useState(""); const [value, setValue] = useState("");
const [activeKey, setActiveKey] = useState(""); const [activeKey, setActiveKey] = useState("");
const [filteredResult, setFilteredResult] = useState( const [filteredResult, setFilteredResult] = useState(
@ -78,22 +78,7 @@ export default function NotesOverview(props) {
/> />
</Col> </Col>
<Col span={8}> <Col span={8}>
<Button <Button icon={<IconPlus />} block onClick={() => addNote()}>
icon={<IconPlus />}
block
onClick={() => {
const newNote = {
id: notes.length,
x: 0,
y: 0,
title: `note_${notes.length}`,
content: "",
color: defaultNoteTheme,
height: 88,
};
setNotes((prev) => [...prev, newNote]);
}}
>
Add note Add note
</Button> </Button>
</Col> </Col>
@ -189,11 +174,7 @@ export default function NotesOverview(props) {
type="danger" type="danger"
onClick={() => { onClick={() => {
Toast.success(`Note deleted!`); Toast.success(`Note deleted!`);
setNotes((prev) => deleteNote(i, true);
prev
.filter((e) => e.id !== i)
.map((e, idx) => ({ ...e, id: idx }))
);
}} }}
></Button> ></Button>
</div> </div>

View File

@ -22,8 +22,8 @@ import {
IllustrationNoContent, IllustrationNoContent,
IllustrationNoContentDark, IllustrationNoContentDark,
} from "@douyinfe/semi-illustrations"; } from "@douyinfe/semi-illustrations";
import { Action, Cardinality, Constraint, ObjectType } from "../data/data"; import { Cardinality, Constraint } from "../data/data";
import { TableContext, UndoRedoContext } from "../pages/editor"; import { TableContext } from "../pages/editor";
export default function ReferenceOverview(props) { export default function ReferenceOverview(props) {
const columns = [ const columns = [
@ -36,8 +36,8 @@ export default function ReferenceOverview(props) {
dataIndex: "foreign", dataIndex: "foreign",
}, },
]; ];
const { tables, relationships, setRelationships } = useContext(TableContext); const { tables, relationships, setRelationships, deleteRelationship } =
const { setUndoStack, setRedoStack } = useContext(UndoRedoContext); useContext(TableContext);
const [refActiveIndex, setRefActiveIndex] = useState(""); const [refActiveIndex, setRefActiveIndex] = useState("");
const [value, setValue] = useState(""); const [value, setValue] = useState("");
const [filteredResult, setFilteredResult] = useState( const [filteredResult, setFilteredResult] = useState(
@ -233,22 +233,7 @@ export default function ReferenceOverview(props) {
icon={<IconDeleteStroked />} icon={<IconDeleteStroked />}
block block
type="danger" type="danger"
onClick={() => { onClick={() => deleteRelationship(r.id, true)}
setUndoStack((prev) => [
...prev,
{
action: Action.DELETE,
element: ObjectType.RELATIONSHIP,
data: relationships[i],
},
]);
setRelationships((prev) =>
prev
.filter((e) => e.id !== i)
.map((e, idx) => ({ ...e, id: idx }))
);
setRedoStack([]);
}}
> >
Delete Delete
</Button> </Button>

View File

@ -40,7 +40,7 @@ export default function Table(props) {
const [hoveredField, setHoveredField] = useState(-1); const [hoveredField, setHoveredField] = useState(-1);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const { layout } = useContext(LayoutContext); const { layout } = useContext(LayoutContext);
const { setTables } = useContext(TableContext); const { setTables, deleteTable } = useContext(TableContext);
const { tab, setTab } = useContext(TabContext); const { tab, setTab } = useContext(TabContext);
const { settings } = useContext(SettingsContext); const { settings } = useContext(SettingsContext);
@ -180,11 +180,7 @@ export default function Table(props) {
style={{ marginTop: "8px" }} style={{ marginTop: "8px" }}
onClick={() => { onClick={() => {
Toast.success(`Table deleted!`); Toast.success(`Table deleted!`);
setTables((prev) => deleteTable(props.tableData.id);
prev
.filter((e) => e.id !== props.tableData.id)
.map((e, idx) => ({ ...e, id: idx }))
);
props.setSelectedTable(""); props.setSelectedTable("");
}} }}
> >
@ -683,11 +679,7 @@ export default function Table(props) {
type="danger" type="danger"
onClick={() => { onClick={() => {
Toast.success(`Table deleted!`); Toast.success(`Table deleted!`);
setTables((prev) => deleteTable(props.tableData.id);
prev
.filter((e) => e.id !== props.tableData.id)
.map((e, idx) => ({ ...e, id: idx }))
);
props.setSelectedTable(""); props.setSelectedTable("");
setVisible(false); setVisible(false);
}} }}

View File

@ -1,11 +1,5 @@
import { React, useContext, useState } from "react"; import { React, useContext, useState } from "react";
import { import { defaultTableTheme, sqlDataTypes, tableThemes } from "../data/data";
defaultTableTheme,
sqlDataTypes,
tableThemes,
Action,
ObjectType,
} from "../data/data";
import { import {
Collapse, Collapse,
Row, Row,
@ -32,18 +26,12 @@ import {
IllustrationNoContent, IllustrationNoContent,
IllustrationNoContentDark, IllustrationNoContentDark,
} from "@douyinfe/semi-illustrations"; } from "@douyinfe/semi-illustrations";
import { import { TableContext } from "../pages/editor";
SettingsContext,
TableContext,
UndoRedoContext,
} from "../pages/editor";
export default function TableOverview(props) { export default function TableOverview(props) {
const [indexActiveKey, setIndexActiveKey] = useState(""); const [indexActiveKey, setIndexActiveKey] = useState("");
const [value, setValue] = useState(""); const [value, setValue] = useState("");
const { tables, setTables } = useContext(TableContext); const { tables, setTables, addTable, deleteTable } = useContext(TableContext);
const { settings } = useContext(SettingsContext);
const { setUndoStack, setRedoStack } = useContext(UndoRedoContext);
const [filteredResult, setFilteredResult] = useState( const [filteredResult, setFilteredResult] = useState(
tables.map((t) => { tables.map((t) => {
return t.name; return t.name;
@ -118,39 +106,7 @@ export default function TableOverview(props) {
icon={<IconPlus />} icon={<IconPlus />}
block block
onClick={() => { onClick={() => {
setTables((prev) => [ addTable(true);
...prev,
{
id: prev.length,
name: `table_${prev.length}`,
x: -settings.pan.x,
y: -settings.pan.y,
fields: [
{
name: "id",
type: "UUID",
default: "",
check: "",
primary: true,
unique: true,
notNull: true,
increment: true,
comment: "",
},
],
comment: "",
indices: [],
color: defaultTableTheme,
},
]);
setUndoStack((prev) => [
...prev,
{
action: Action.ADD,
element: ObjectType.TABLE,
},
]);
setRedoStack([]);
}} }}
> >
Add table Add table
@ -570,20 +526,7 @@ export default function TableOverview(props) {
type="danger" type="danger"
onClick={() => { onClick={() => {
Toast.success(`Table deleted!`); Toast.success(`Table deleted!`);
setUndoStack((prev) => [ deleteTable(i);
...prev,
{
action: Action.DELETE,
element: ObjectType.TABLE,
data: tables[i],
},
]);
setRedoStack([]);
setTables((prev) =>
prev
.filter((e) => e.id !== i)
.map((e, idx) => ({ ...e, id: idx }))
);
props.setSelectedTable(""); props.setSelectedTable("");
}} }}
></Button> ></Button>

View File

@ -5,7 +5,13 @@ import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend"; import { HTML5Backend } from "react-dnd-html5-backend";
import Canvas from "../components/canvas"; import Canvas from "../components/canvas";
import EditorPanel from "../components/editor_panel"; import EditorPanel from "../components/editor_panel";
import { Tab } from "../data/data"; import {
Tab,
defaultTableTheme,
defaultNoteTheme,
Action,
ObjectType,
} from "../data/data";
export const LayoutContext = createContext(); export const LayoutContext = createContext();
export const TableContext = createContext(); export const TableContext = createContext();
@ -41,7 +47,7 @@ export default function Editor(props) {
strictMode: false, strictMode: false,
showFieldSummary: true, showFieldSummary: true,
zoom: 1, zoom: 1,
pan: {x: 0, y: 0}, pan: { x: 0, y: 0 },
showGrid: true, showGrid: true,
}); });
const [undoStack, setUndoStack] = useState([]); const [undoStack, setUndoStack] = useState([]);
@ -53,17 +59,297 @@ export default function Editor(props) {
if (w > 340) setWidth(w); if (w > 340) setWidth(w);
}; };
const addTable = (addToHistory = true, data) => {
if (data) {
setTables((prev) => {
const temp = prev.slice();
temp.splice(data.id, 0, data);
return temp.map((t, i) => ({ ...t, id: i }));
});
} else {
setTables((prev) => [
...prev,
{
id: prev.length,
name: `table_${prev.length}`,
x: -settings.pan.x,
y: -settings.pan.y,
fields: [
{
name: "id",
type: "UUID",
default: "",
check: "",
primary: true,
unique: true,
notNull: true,
increment: true,
comment: "",
},
],
comment: "",
indices: [],
color: defaultTableTheme,
},
]);
}
if (addToHistory) {
setUndoStack((prev) => [
...prev,
{
action: Action.ADD,
element: ObjectType.TABLE,
},
]);
setRedoStack([]);
}
};
const addArea = (addToHistory = true, data) => {
if (data) {
setAreas((prev) => {
const temp = prev.slice();
temp.splice(data.id, 0, data);
return temp.map((t, i) => ({ ...t, id: i }));
});
} else {
setAreas((prev) => [
...prev,
{
id: prev.length,
name: `area_${prev.length}`,
x: -settings.pan.x,
y: -settings.pan.y,
width: 200,
height: 200,
color: defaultTableTheme,
},
]);
}
if (addToHistory) {
setUndoStack((prev) => [
...prev,
{
action: Action.ADD,
element: ObjectType.AREA,
},
]);
setRedoStack([]);
}
};
const addNote = (addToHistory = true, data) => {
if (data) {
setNotes((prev) => {
const temp = prev.slice();
temp.splice(data.id, 0, data);
return temp.map((t, i) => ({ ...t, id: i }));
});
} else {
setNotes((prev) => [
...prev,
{
id: prev.length,
x: -settings.pan.x,
y: -settings.pan.y,
title: `note_${prev.length}`,
content: "",
color: defaultNoteTheme,
height: 88,
},
]);
}
if (addToHistory) {
setUndoStack((prev) => [
...prev,
{
action: Action.ADD,
element: ObjectType.NOTE,
},
]);
setRedoStack([]);
}
};
const addRelationship = (addToHistory = true, data) => {
if (addToHistory) {
setRelationships((prev) => {
setUndoStack((prevUndo) => [
...prevUndo,
{
action: Action.ADD,
element: ObjectType.RELATIONSHIP,
data: data,
},
]);
setRedoStack([]);
return [...prev, data];
});
} else {
setRelationships((prev) => {
const temp = prev.slice();
temp.splice(data.id, 0, data);
return temp.map((t, i) => ({ ...t, id: i }));
});
}
};
const moveTable = (id, x, y) => {
setTables((prev) =>
prev.map((t) => {
if (t.id === id) {
setRelationships((prev) =>
prev.map((r) => {
if (r.startTableId === id) {
return {
...r,
startX: x + 15,
startY: y + r.startFieldId * 36 + 69,
};
} else if (r.endTableId === id) {
return {
...r,
endX: x + 15,
endY: y + r.endFieldId * 36 + 69,
};
}
return r;
})
);
return {
...t,
x: x,
y: y,
};
}
return t;
})
);
};
const moveArea = (id, x, y) => {
setAreas((prev) =>
prev.map((t) => {
if (t.id === id) {
return {
...t,
x: x,
y: y,
};
}
return t;
})
);
};
const moveNote = (id, x, y) => {
setNotes((prev) =>
prev.map((t) => {
if (t.id === id) {
return {
...t,
x: x,
y: y,
};
}
return t;
})
);
};
const deleteTable = (id, addToHistory = true) => {
if (addToHistory) {
setUndoStack((prev) => [
...prev,
{
action: Action.DELETE,
element: ObjectType.TABLE,
data: tables[id],
},
]);
setRedoStack([]);
}
setTables((prev) =>
prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i }))
);
};
const deleteArea = (id, addToHistory = true) => {
if (addToHistory) {
setUndoStack((prev) => [
...prev,
{
action: Action.DELETE,
element: ObjectType.AREA,
data: areas[id],
},
]);
setRedoStack([]);
}
setAreas((prev) =>
prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i }))
);
};
const deleteNote = (id, addToHistory = true) => {
if (addToHistory) {
setUndoStack((prev) => [
...prev,
{
action: Action.DELETE,
element: ObjectType.NOTE,
data: notes[id],
},
]);
setRedoStack([]);
}
setNotes((prev) =>
prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i }))
);
};
const deleteRelationship = (id, addToHistory = true) => {
if (addToHistory) {
setUndoStack((prev) => [
...prev,
{
action: Action.DELETE,
element: ObjectType.RELATIONSHIP,
data: relationships[id],
},
]);
setRedoStack([]);
}
setRelationships((prev) =>
prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i }))
);
};
useEffect(() => { useEffect(() => {
document.title = "Editor"; document.title = "Editor - drawDB";
}, []); }, []);
return ( return (
<LayoutContext.Provider value={{ layout, setLayout }}> <LayoutContext.Provider value={{ layout, setLayout }}>
<TableContext.Provider <TableContext.Provider
value={{ tables, setTables, relationships, setRelationships }} value={{
tables,
setTables,
addTable,
moveTable,
deleteTable,
relationships,
setRelationships,
addRelationship,
deleteRelationship,
}}
> >
<AreaContext.Provider value={{ areas, setAreas }}> <AreaContext.Provider
<NoteContext.Provider value={{ notes, setNotes }}> value={{ areas, setAreas, moveArea, addArea, deleteArea }}
>
<NoteContext.Provider
value={{ notes, setNotes, moveNote, addNote, deleteNote }}
>
<TabContext.Provider value={{ tab, setTab }}> <TabContext.Provider value={{ tab, setTab }}>
<SettingsContext.Provider value={{ settings, setSettings }}> <SettingsContext.Provider value={{ settings, setSettings }}>
<UndoRedoContext.Provider <UndoRedoContext.Provider