drawDB/src/pages/editor.jsx

803 lines
22 KiB
React
Raw Normal View History

2023-12-16 11:39:13 +08:00
import { useState, createContext, useEffect } from "react";
import Sidebar from "../components/Sidebar";
import ControlPanel from "../components/ControlPanel";
import Canvas from "../components/Canvas";
import SidePanel from "../components/SidePanel";
2023-09-19 20:49:57 +08:00
import {
Tab,
defaultTableTheme,
defaultNoteTheme,
2023-09-19 20:51:00 +08:00
avatarThemes,
2023-09-19 20:49:57 +08:00
Action,
ObjectType,
2023-11-25 00:28:39 +08:00
State,
2023-09-19 20:49:57 +08:00
} from "../data/data";
2023-09-19 20:50:57 +08:00
import { socket } from "../data/socket";
2023-10-28 02:10:17 +08:00
import { db } from "../data/db";
2023-09-19 20:51:00 +08:00
import { uniqueNamesGenerator, colors, animals } from "unique-names-generator";
2023-09-19 20:48:46 +08:00
export const LayoutContext = createContext();
2023-09-19 20:48:48 +08:00
export const TableContext = createContext();
2023-09-19 20:48:49 +08:00
export const AreaContext = createContext();
2023-09-19 20:48:57 +08:00
export const TabContext = createContext();
2023-09-19 20:49:09 +08:00
export const NoteContext = createContext();
2023-09-19 20:49:14 +08:00
export const SettingsContext = createContext();
2023-09-19 20:49:41 +08:00
export const UndoRedoContext = createContext();
2023-09-19 20:50:28 +08:00
export const SelectContext = createContext();
2023-09-19 20:50:53 +08:00
export const TaskContext = createContext();
2023-09-19 20:50:57 +08:00
export const MessageContext = createContext();
2023-10-21 01:37:06 +08:00
export const BotMessageContext = createContext();
2023-09-19 20:51:28 +08:00
export const TypeContext = createContext();
2023-09-19 20:48:46 +08:00
2023-12-16 11:39:13 +08:00
export default function Editor() {
2023-10-28 02:10:17 +08:00
const [id, setId] = useState(0);
const [title, setTitle] = useState("Untitled Diagram");
2023-11-25 00:28:39 +08:00
const [state, setState] = useState(State.NONE);
const [lastSaved, setLastSaved] = useState("");
const [tables, setTables] = useState([]);
const [relationships, setRelationships] = useState([]);
2023-09-19 20:48:04 +08:00
const [areas, setAreas] = useState([]);
2023-09-19 20:49:09 +08:00
const [notes, setNotes] = useState([]);
2023-09-19 20:51:28 +08:00
const [types, setTypes] = useState([]);
2023-09-19 20:48:30 +08:00
const [resize, setResize] = useState(false);
2023-09-19 20:49:39 +08:00
const [width, setWidth] = useState(340);
2023-09-19 20:48:57 +08:00
const [tab, setTab] = useState(Tab.tables);
2023-09-19 20:48:34 +08:00
const [layout, setLayout] = useState({
header: true,
sidebar: true,
services: true,
tables: true,
2023-09-19 20:48:55 +08:00
areas: true,
2023-09-19 20:48:34 +08:00
relationships: true,
issues: true,
editor: true,
2023-09-19 20:49:09 +08:00
notes: true,
2023-09-19 20:48:35 +08:00
fullscreen: false,
2023-09-19 20:48:34 +08:00
});
2023-09-19 20:49:14 +08:00
const [settings, setSettings] = useState({
strictMode: false,
2023-09-19 20:49:16 +08:00
showFieldSummary: true,
2023-09-19 20:49:36 +08:00
zoom: 1,
2023-09-19 20:49:57 +08:00
pan: { x: 0, y: 0 },
2023-09-19 20:49:37 +08:00
showGrid: true,
2023-09-19 20:51:08 +08:00
mode: "light",
2023-11-24 07:32:26 +08:00
autosave: true,
panning: true,
2023-09-19 20:49:14 +08:00
});
2023-09-19 20:50:53 +08:00
const [tasks, setTasks] = useState([]);
2023-09-19 20:50:57 +08:00
const [messages, setMessages] = useState([]);
2023-10-21 01:37:06 +08:00
const [botMessages, setBotMessages] = useState([
{ sender: "bot", message: "Hey there! How can I help you?" },
]);
2023-09-19 20:49:41 +08:00
const [undoStack, setUndoStack] = useState([]);
const [redoStack, setRedoStack] = useState([]);
2023-09-19 20:50:28 +08:00
const [selectedElement, setSelectedElement] = useState({
element: ObjectType.NONE,
id: -1,
openDialogue: false,
openCollapse: false,
});
2023-09-19 20:48:31 +08:00
const dragHandler = (e) => {
if (!resize) return;
const w = e.clientX;
2023-09-19 20:49:39 +08:00
if (w > 340) setWidth(w);
2023-09-19 20:48:31 +08:00
};
2023-09-19 20:46:56 +08:00
2023-09-19 20:49:57 +08:00
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",
2023-09-19 20:50:39 +08:00
type: "INT",
2023-09-19 20:49:57 +08:00
default: "",
check: "",
primary: true,
unique: true,
notNull: true,
increment: true,
comment: "",
2023-09-19 20:50:04 +08:00
id: 0,
2023-09-19 20:49:57 +08:00
},
],
comment: "",
indices: [],
color: defaultTableTheme,
},
]);
}
if (addToHistory) {
setUndoStack((prev) => [
...prev,
{
action: Action.ADD,
element: ObjectType.TABLE,
2023-09-19 20:50:52 +08:00
message: `Add new table`,
2023-09-19 20:49:57 +08:00
},
]);
setRedoStack([]);
}
};
2023-09-19 20:51:28 +08:00
const addType = (addToHistory = true, data) => {
if (data) {
setTypes((prev) => {
const temp = prev.slice();
temp.splice(data.id, 0, data);
return temp;
});
} else {
setTypes((prev) => [
...prev,
{
name: `type_${prev.length}`,
fields: [],
comment: "",
},
]);
}
if (addToHistory) {
setUndoStack((prev) => [
...prev,
{
action: Action.ADD,
element: ObjectType.TYPE,
message: `Add new type`,
},
]);
setRedoStack([]);
}
};
const deleteType = (id, addToHistory = true) => {
if (addToHistory) {
setUndoStack((prev) => [
...prev,
{
action: Action.DELETE,
element: ObjectType.TYPE,
id: id,
data: types[id],
message: `Delete type`,
},
]);
setRedoStack([]);
}
setTypes((prev) => prev.filter((e, i) => i !== id));
};
const updateType = (id, values) => {
setTypes((prev) =>
prev.map((e, i) => (i === id ? { ...e, ...values } : e))
);
};
2023-09-19 20:50:04 +08:00
const updateField = (tid, fid, updatedValues) => {
setTables((prev) =>
prev.map((table, i) => {
if (tid === i) {
return {
...table,
fields: table.fields.map((field, j) =>
fid === j ? { ...field, ...updatedValues } : field
),
};
}
return table;
})
);
};
2023-09-19 20:49:57 +08:00
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,
2023-09-19 20:50:52 +08:00
message: `Add new subject area`,
2023-09-19 20:49:57 +08:00
},
]);
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,
2023-09-19 20:50:52 +08:00
message: `Add new note`,
2023-09-19 20:49:57 +08:00
},
]);
setRedoStack([]);
}
};
const addRelationship = (addToHistory = true, data) => {
if (addToHistory) {
setRelationships((prev) => {
setUndoStack((prevUndo) => [
...prevUndo,
{
action: Action.ADD,
element: ObjectType.RELATIONSHIP,
data: data,
2023-09-19 20:50:52 +08:00
message: `Add new relationship`,
2023-09-19 20:49:57 +08:00
},
]);
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 deleteTable = (id, addToHistory = true) => {
if (addToHistory) {
setUndoStack((prev) => [
...prev,
{
action: Action.DELETE,
element: ObjectType.TABLE,
data: tables[id],
2023-09-19 20:50:52 +08:00
message: `Delete table`,
2023-09-19 20:49:57 +08:00
},
]);
setRedoStack([]);
}
setTables((prev) =>
prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i }))
);
2023-09-19 20:50:49 +08:00
setRelationships((prev) =>
prev
.filter((e) => e.startTableId !== id && e.endTableId !== id)
.map((e, i) => ({ ...e, id: i }))
);
2023-09-19 20:50:28 +08:00
if (id === selectedElement.id) {
setSelectedElement({
element: ObjectType.NONE,
id: -1,
openDialogue: false,
openCollapse: false,
});
}
2023-09-19 20:49:57 +08:00
};
const deleteArea = (id, addToHistory = true) => {
if (addToHistory) {
setUndoStack((prev) => [
...prev,
{
action: Action.DELETE,
element: ObjectType.AREA,
data: areas[id],
2023-09-19 20:50:52 +08:00
message: `Delete subject area`,
2023-09-19 20:49:57 +08:00
},
]);
setRedoStack([]);
}
setAreas((prev) =>
prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i }))
);
2023-09-19 20:50:35 +08:00
if (id === selectedElement.id) {
setSelectedElement({
element: ObjectType.NONE,
id: -1,
openDialogue: false,
openCollapse: false,
});
}
2023-09-19 20:49:57 +08:00
};
const deleteNote = (id, addToHistory = true) => {
if (addToHistory) {
setUndoStack((prev) => [
...prev,
{
action: Action.DELETE,
element: ObjectType.NOTE,
data: notes[id],
2023-09-19 20:50:52 +08:00
message: `Delete note`,
2023-09-19 20:49:57 +08:00
},
]);
setRedoStack([]);
}
setNotes((prev) =>
prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i }))
);
2023-09-19 20:50:35 +08:00
if (id === selectedElement.id) {
setSelectedElement({
element: ObjectType.NONE,
id: -1,
openDialogue: false,
openCollapse: false,
});
}
2023-09-19 20:49:57 +08:00
};
const deleteRelationship = (id, addToHistory = true) => {
if (addToHistory) {
setUndoStack((prev) => [
...prev,
{
action: Action.DELETE,
element: ObjectType.RELATIONSHIP,
data: relationships[id],
2023-09-19 20:50:52 +08:00
message: `Delete relationship`,
2023-09-19 20:49:57 +08:00
},
]);
setRedoStack([]);
}
setRelationships((prev) =>
prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i }))
);
};
2023-09-19 20:50:15 +08:00
const updateArea = (id, values) => {
setAreas((prev) =>
prev.map((t) => {
if (t.id === id) {
return {
...t,
...values,
};
}
return t;
})
);
};
2023-12-25 05:26:14 +08:00
const updateNote = (id, values) => {
2023-09-19 20:50:00 +08:00
setNotes((prev) =>
prev.map((t) => {
if (t.id === id) {
return {
...t,
...values,
};
}
return t;
})
);
2023-09-19 20:50:04 +08:00
};
2023-09-19 20:50:00 +08:00
2023-09-19 20:50:15 +08:00
const updateTable = (id, updatedValues, updateRelationships = false) => {
setTables((prev) =>
prev.map((table) => {
if (table.id === id) {
if (updateRelationships) {
setRelationships((prev) =>
prev.map((r) => {
if (r.startTableId === id) {
return {
...r,
startX: updatedValues.x + 15,
startY: updatedValues.y + r.startFieldId * 36 + 69,
2023-09-19 20:50:47 +08:00
endX: tables[r.endTableId].x + 15,
endY: tables[r.endTableId].y + r.endFieldId * 36 + 69,
2023-09-19 20:50:15 +08:00
};
} else if (r.endTableId === id) {
return {
...r,
2023-09-19 20:50:47 +08:00
startX: tables[r.startTableId].x + 15,
startY: tables[r.startTableId].y + r.startFieldId * 36 + 69,
2023-09-19 20:50:15 +08:00
endX: updatedValues.x + 15,
endY: updatedValues.y + r.endFieldId * 36 + 69,
};
}
return r;
})
);
}
return {
...table,
...updatedValues,
};
}
return table;
})
);
};
2023-09-19 20:50:53 +08:00
const updateTask = (id, values) =>
setTasks((prev) =>
prev.map((task, i) => (id === i ? { ...task, ...values } : task))
);
2023-11-25 00:28:39 +08:00
useEffect(() => {
if (
tables.length === 0 &&
areas.length === 0 &&
notes.length === 0 &&
types.length === 0
)
return;
if (settings.autosave) {
setState(State.SAVING);
}
}, [
undoStack,
redoStack,
settings.autosave,
tables.length,
areas.length,
notes.length,
types.length,
]);
useEffect(() => {
2023-12-25 05:26:14 +08:00
const save = async (diagram = true) => {
2023-11-25 00:28:39 +08:00
if (state !== State.SAVING) {
return;
}
2023-12-25 05:26:14 +08:00
if (diagram) {
if (id === 0 && window.name === "") {
db.diagrams
.add({
name: title,
lastModified: new Date(),
tables: tables,
references: relationships,
types: types,
notes: notes,
areas: areas,
})
.then((id) => {
setId(id);
window.name = `d ${id}`;
setState(State.SAVED);
setLastSaved(new Date().toLocaleString());
});
} else {
db.diagrams
.update(id, {
name: title,
lastModified: new Date(),
tables: tables,
references: relationships,
types: types,
notes: notes,
areas: areas,
})
.then(() => {
setState(State.SAVED);
setLastSaved(new Date().toLocaleString());
});
}
2023-11-25 00:28:39 +08:00
} else {
2023-12-25 05:26:14 +08:00
db.templates
2023-11-25 00:28:39 +08:00
.update(id, {
2023-12-25 05:26:14 +08:00
title: title,
2023-11-25 00:28:39 +08:00
tables: tables,
2023-12-25 05:26:14 +08:00
relationships: relationships,
2023-11-25 00:28:39 +08:00
types: types,
notes: notes,
2023-12-25 05:26:14 +08:00
subjectAreas: areas,
2023-11-25 00:28:39 +08:00
})
.then(() => {
setState(State.SAVED);
setLastSaved(new Date().toLocaleString());
2023-12-25 05:26:14 +08:00
})
.catch(() => {
setState(State.ERROR);
2023-11-25 00:28:39 +08:00
});
}
};
2023-12-25 05:26:14 +08:00
const name = window.name.split(" ");
const op = name[0];
const diagram = window.name === "" || op === "d";
save(diagram);
2023-11-25 00:28:39 +08:00
}, [tables, relationships, notes, areas, types, title, id, state]);
2023-09-19 20:48:57 +08:00
useEffect(() => {
2023-09-25 00:38:39 +08:00
document.title = "Editor | drawDB";
2023-09-19 20:50:57 +08:00
2023-10-28 02:10:17 +08:00
const loadLatestDiagram = async () => {
await db.diagrams
.orderBy("lastModified")
.last()
.then((d) => {
if (d) {
setId(d.id);
setTables(d.tables);
setRelationships(d.references);
setNotes(d.notes);
setAreas(d.areas);
setTypes(d.types);
2023-11-25 00:28:39 +08:00
window.name = `d ${d.id}`;
} else {
window.name = "";
}
})
.catch((error) => {
console.log(error);
});
};
const loadDiagram = async (id) => {
await db.diagrams
.get(id)
.then((diagram) => {
if (diagram) {
setId(diagram.id);
setTitle(diagram.name);
setTables(diagram.tables);
setTypes(diagram.types);
setRelationships(diagram.references);
setAreas(diagram.areas);
setNotes(diagram.notes);
setUndoStack([]);
setRedoStack([]);
window.name = `d ${diagram.id}`;
2023-11-25 00:28:39 +08:00
} else {
window.name = "";
2023-10-28 02:10:17 +08:00
}
})
.catch((error) => {
console.log(error);
});
};
2023-12-25 05:26:14 +08:00
const loadTemplate = async (id) => {
await db.templates
.get(id)
.then((diagram) => {
if (diagram) {
setId(diagram.id);
setTitle(diagram.title);
setTables(diagram.tables);
setTypes(diagram.types);
setRelationships(diagram.relationships);
setAreas(diagram.subjectAreas);
setNotes(diagram.notes);
setUndoStack([]);
setRedoStack([]);
}
})
.catch((error) => {
console.log(error);
});
};
2023-12-24 09:06:49 +08:00
if (window.name == "") {
console.log("Loading the latest diagram");
loadLatestDiagram();
2023-11-02 19:31:26 +08:00
} else {
2023-12-24 09:06:49 +08:00
const name = window.name.split(" ");
const op = name[0];
const did = parseInt(name[1]);
switch (op) {
case "d": {
loadDiagram(did);
break;
}
case "lt": {
console.log("Loading template with id", did);
break;
}
2023-12-25 05:26:14 +08:00
case "t": {
loadTemplate(did);
break;
}
2023-12-24 09:06:49 +08:00
default:
break;
}
2023-11-02 19:31:26 +08:00
}
2023-12-25 05:26:14 +08:00
2023-09-19 20:50:57 +08:00
socket.connect();
2023-09-19 20:51:00 +08:00
const onConnect = () => {
const name = uniqueNamesGenerator({
dictionaries: [colors, animals],
separator: " ",
style: "capital",
});
const color =
avatarThemes[Math.floor(Math.random() * avatarThemes.length)];
socket.emit("new-user", name, color);
setMessages((prev) => [
...prev,
{ message: `You joined as ${name}`, type: "note", action: "join" },
]);
};
const onRecieve = (value) =>
setMessages((prev) => [{ ...value, type: "message" }, ...prev]);
const onUserConnected = (name) =>
setMessages((prev) => [
{ message: `${name} just joined`, type: "note", action: "join" },
...prev,
]);
const onUserDisconnected = (name) =>
setMessages((prev) => [
{ message: `${name} left`, type: "note", action: "leave" },
...prev,
]);
2023-09-19 20:50:57 +08:00
socket.on("connect", onConnect);
socket.on("recieve-message", onRecieve);
2023-09-19 20:51:00 +08:00
socket.on("user-connected", onUserConnected);
socket.on("user-disconnected", onUserDisconnected);
2023-09-19 20:50:57 +08:00
2023-09-19 20:51:43 +08:00
const theme = localStorage.getItem("theme");
if (theme === "dark") {
setSettings((prev) => ({ ...prev, mode: "dark" }));
const body = document.body;
if (body.hasAttribute("theme-mode")) {
body.setAttribute("theme-mode", "dark");
}
} else {
setSettings((prev) => ({ ...prev, mode: "light" }));
const body = document.body;
if (body.hasAttribute("theme-mode")) {
body.setAttribute("theme-mode", "light");
}
}
2023-09-19 20:50:57 +08:00
return () => {
socket.off("connect", onConnect);
socket.off("recieve-message", onRecieve);
2023-09-19 20:51:00 +08:00
socket.off("user-connected", onUserConnected);
socket.off("user-disconnected", onUserDisconnected);
socket.disconnect();
2023-09-19 20:50:57 +08:00
};
2023-09-19 20:48:57 +08:00
}, []);
2023-09-19 20:48:51 +08:00
2023-09-19 20:46:46 +08:00
return (
2023-09-19 20:48:49 +08:00
<LayoutContext.Provider value={{ layout, setLayout }}>
<TableContext.Provider
2023-09-19 20:49:57 +08:00
value={{
tables,
setTables,
addTable,
2023-09-19 20:50:15 +08:00
updateTable,
2023-09-19 20:50:04 +08:00
updateField,
2023-09-19 20:49:57 +08:00
deleteTable,
relationships,
setRelationships,
addRelationship,
deleteRelationship,
}}
2023-09-19 20:48:49 +08:00
>
2023-09-19 20:49:57 +08:00
<AreaContext.Provider
2023-09-19 20:50:15 +08:00
value={{ areas, setAreas, updateArea, addArea, deleteArea }}
2023-09-19 20:49:57 +08:00
>
<NoteContext.Provider
2023-09-19 20:50:15 +08:00
value={{ notes, setNotes, updateNote, addNote, deleteNote }}
2023-09-19 20:49:57 +08:00
>
2023-09-19 20:49:09 +08:00
<TabContext.Provider value={{ tab, setTab }}>
2023-09-19 20:49:39 +08:00
<SettingsContext.Provider value={{ settings, setSettings }}>
2023-09-19 20:49:41 +08:00
<UndoRedoContext.Provider
value={{ undoStack, redoStack, setUndoStack, setRedoStack }}
>
2023-09-19 20:50:28 +08:00
<SelectContext.Provider
value={{ selectedElement, setSelectedElement }}
>
2023-09-19 20:50:53 +08:00
<TaskContext.Provider
value={{ tasks, setTasks, updateTask }}
>
2023-09-19 20:51:28 +08:00
<TypeContext.Provider
value={{
types,
setTypes,
addType,
updateType,
deleteType,
}}
>
<div className="h-[100vh] overflow-hidden theme">
<ControlPanel
diagramId={id}
setDiagramId={setId}
title={title}
setTitle={setTitle}
2023-11-25 00:28:39 +08:00
state={state}
setState={setState}
lastSaved={lastSaved}
setLastSaved={setLastSaved}
/>
2023-09-19 20:51:28 +08:00
<div
className={
layout.header
? `flex h-[calc(100vh-120px)]`
: `flex h-[calc(100vh-52px)]`
}
onMouseUp={() => setResize(false)}
onMouseMove={dragHandler}
>
{layout.sidebar && (
<SidePanel
2023-09-19 20:51:28 +08:00
resize={resize}
setResize={setResize}
width={width}
/>
)}
2023-11-25 00:28:39 +08:00
<Canvas state={state} setState={setState} />
2023-09-19 20:51:28 +08:00
{layout.services && (
<MessageContext.Provider
value={{ messages, setMessages }}
>
2023-10-21 01:37:06 +08:00
<BotMessageContext.Provider
value={{ botMessages, setBotMessages }}
>
<Sidebar />
</BotMessageContext.Provider>
2023-09-19 20:51:28 +08:00
</MessageContext.Provider>
)}
</div>
2023-09-19 20:50:53 +08:00
</div>
2023-09-19 20:51:28 +08:00
</TypeContext.Provider>
2023-09-19 20:50:53 +08:00
</TaskContext.Provider>
2023-09-19 20:50:28 +08:00
</SelectContext.Provider>
2023-09-19 20:49:41 +08:00
</UndoRedoContext.Provider>
2023-09-19 20:49:14 +08:00
</SettingsContext.Provider>
2023-09-19 20:49:09 +08:00
</TabContext.Provider>
</NoteContext.Provider>
2023-09-19 20:48:49 +08:00
</AreaContext.Provider>
2023-09-19 20:48:48 +08:00
</TableContext.Provider>
2023-09-19 20:48:46 +08:00
</LayoutContext.Provider>
2023-09-19 20:46:46 +08:00
);
}