drawDB/src/pages/Editor.jsx

417 lines
11 KiB
React
Raw Normal View History

2024-02-26 08:42:46 +08:00
import { useState, createContext, useEffect, useCallback } from "react";
import ControlPanel from "../components/ControlPanel";
import Canvas from "../components/Canvas";
import SidePanel from "../components/SidePanel";
2024-03-13 05:36:49 +08:00
import { Tab, Action, ObjectType, State } from "../data/data";
2023-10-28 02:10:17 +08:00
import { db } from "../data/db";
2024-03-10 01:42:09 +08:00
import useLayout from "../hooks/useLayout";
import LayoutContextProvider from "../context/LayoutContext";
2024-03-10 04:39:46 +08:00
import useSettings from "../hooks/useSettings";
2024-03-11 01:24:19 +08:00
import TransformContextProvider from "../context/TransformContext";
import useTransform from "../hooks/useTransform";
import useTables from "../hooks/useTables";
import TablesContextProvider from "../context/TablesContext";
import useUndoRedo from "../hooks/useUndoRedo";
import UndoRedoContextProvider from "../context/UndoRedoContext";
import SelectContextProvider from "../context/SelectContext";
2024-03-12 05:59:04 +08:00
import AreasContextProvider from "../context/AreasContext";
2024-03-11 07:50:34 +08:00
import Controls from "../components/Controls";
2024-03-12 05:59:04 +08:00
import useAreas from "../hooks/useAreas";
2024-03-13 05:36:49 +08:00
import useNotes from "../hooks/useNotes";
import NotesContextProvider from "../context/NotesContext";
2024-03-10 01:42:09 +08:00
export const StateContext = createContext();
2023-09-19 20:48:57 +08:00
export const TabContext = createContext();
2023-09-19 20:50:53 +08:00
export const TaskContext = 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() {
2024-03-10 01:42:09 +08:00
return (
<LayoutContextProvider>
2024-03-11 01:24:19 +08:00
<TransformContextProvider>
<UndoRedoContextProvider>
<SelectContextProvider>
2024-03-12 05:59:04 +08:00
<AreasContextProvider>
2024-03-13 05:36:49 +08:00
<NotesContextProvider>
<TablesContextProvider>
<WorkSpace />
</TablesContextProvider>
</NotesContextProvider>
2024-03-12 05:59:04 +08:00
</AreasContextProvider>
</SelectContextProvider>
</UndoRedoContextProvider>
2024-03-11 01:24:19 +08:00
</TransformContextProvider>
2024-03-10 01:42:09 +08:00
</LayoutContextProvider>
);
}
function WorkSpace() {
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("");
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);
2024-03-13 05:36:49 +08:00
const [tasks, setTasks] = useState([]);
2024-03-11 07:50:34 +08:00
const { layout } = useLayout();
2024-03-13 05:36:49 +08:00
const { areas, setAreas } = useAreas();
2024-03-10 04:39:46 +08:00
const { settings, setSettings } = useSettings();
2024-03-13 05:36:49 +08:00
const { notes, setNotes } = useNotes();
2024-03-11 01:24:19 +08:00
const { transform, setTransform } = useTransform();
2024-03-13 05:36:49 +08:00
const { tables, relationships, setTables, setRelationships } = useTables();
const { undoStack, redoStack, setUndoStack, setRedoStack } = useUndoRedo();
2023-09-19 20:48:31 +08:00
2024-03-13 06:58:03 +08:00
const handleResize = (e) => {
2023-09-19 20:48:31 +08:00
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: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: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 &&
tasks?.length === 0
2023-11-25 00:28:39 +08:00
)
return;
if (settings.autosave) {
setState(State.SAVING);
}
}, [
undoStack,
redoStack,
settings.autosave,
tables?.length,
areas?.length,
notes?.length,
types?.length,
relationships?.length,
2024-02-20 17:32:19 +08:00
tasks?.length,
2024-03-10 02:35:04 +08:00
transform.zoom,
2024-02-26 08:48:03 +08:00
title,
2023-11-25 00:28:39 +08:00
]);
2024-02-26 08:48:03 +08:00
2024-02-26 08:42:46 +08:00
const save = useCallback(
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) {
2023-12-26 22:40:47 +08:00
if (
(id === 0 && window.name === "") ||
window.name.split(" ")[0] === "lt"
) {
2023-12-25 05:26:14 +08:00
db.diagrams
.add({
name: title,
lastModified: new Date(),
tables: tables,
references: relationships,
types: types,
notes: notes,
areas: areas,
2024-02-20 15:03:08 +08:00
todos: tasks,
2024-03-10 02:35:04 +08:00
pan: transform.pan,
zoom: transform.zoom,
2023-12-25 05:26:14 +08:00
})
.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,
2024-02-20 15:03:08 +08:00
todos: tasks,
2024-03-10 02:35:04 +08:00
pan: transform.pan,
zoom: transform.zoom,
2023-12-25 05:26:14 +08:00
})
.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,
2024-02-20 15:03:08 +08:00
todos: tasks,
2024-03-10 02:35:04 +08:00
pan: transform.pan,
zoom: transform.zoom,
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
});
}
2024-02-26 08:42:46 +08:00
},
[
tables,
relationships,
notes,
areas,
types,
title,
id,
state,
tasks,
2024-03-10 02:35:04 +08:00
transform.zoom,
transform.pan,
2024-02-26 08:42:46 +08:00
]
);
useEffect(() => {
2023-12-25 05:26:14 +08:00
const name = window.name.split(" ");
const op = name[0];
2023-12-26 22:40:47 +08:00
const diagram = window.name === "" || op === "d" || op === "lt";
2023-12-25 05:26:14 +08:00
save(diagram);
2024-02-26 08:48:03 +08:00
}, [id, state, save]);
2023-11-25 00:28:39 +08:00
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
2024-02-26 19:17:20 +08:00
const loadLatestDiagram = async () => {
2024-01-10 11:16:06 +08:00
db.diagrams
2023-10-28 02:10:17 +08:00
.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);
2024-02-20 15:03:08 +08:00
setTasks(d.todos);
2024-03-10 02:35:04 +08:00
setTransform({ pan: d.pan, zoom: d.zoom });
2023-11-25 00:28:39 +08:00
window.name = `d ${d.id}`;
} else {
window.name = "";
}
})
.catch((error) => {
console.log(error);
});
};
2024-02-26 19:17:20 +08:00
const loadDiagram = async (id) => {
2024-01-10 11:16:06 +08:00
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);
2024-02-20 15:03:08 +08:00
setTasks(diagram.todos);
2024-03-10 02:35:04 +08:00
setTransform({
2024-02-26 08:42:46 +08:00
pan: diagram.pan,
zoom: diagram.zoom,
2024-03-10 02:35:04 +08:00
});
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);
});
};
2024-02-26 19:17:20 +08:00
const loadTemplate = async (id) => {
2024-01-10 11:16:06 +08:00
db.templates
2023-12-25 05:26:14 +08:00
.get(id)
.then((diagram) => {
if (diagram) {
setId(diagram.id);
setTitle(diagram.title);
setTables(diagram.tables);
setTypes(diagram.types);
setRelationships(diagram.relationships);
setAreas(diagram.subjectAreas);
2024-02-20 15:03:08 +08:00
setTasks(diagram.tasks);
2023-12-25 05:26:14 +08:00
setNotes(diagram.notes);
2024-03-10 02:35:04 +08:00
setTransform({
2024-02-29 04:47:07 +08:00
zoom: 1,
2024-03-10 02:35:04 +08:00
pan: { x: 0, y: 0 },
});
2023-12-25 05:26:14 +08:00
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": {
2023-12-26 22:40:47 +08:00
loadTemplate(did);
2023-12-24 09:06:49 +08:00
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
}
}, [
setSettings,
setTransform,
setRedoStack,
setUndoStack,
setRelationships,
setTables,
2024-03-12 05:59:04 +08:00
setAreas,
2024-03-13 05:36:49 +08:00
setNotes,
]);
2023-09-19 20:48:51 +08:00
2023-09-19 20:46:46 +08:00
return (
2024-03-10 01:42:09 +08:00
<StateContext.Provider value={{ state, setState }}>
2024-03-13 05:36:49 +08:00
<TabContext.Provider value={{ tab, setTab }}>
<TypeContext.Provider
value={{
types,
setTypes,
addType,
updateType,
deleteType,
}}
>
<div className="h-[100vh] flex flex-col overflow-hidden theme">
<TaskContext.Provider value={{ tasks, setTasks, updateTask }}>
<ControlPanel
diagramId={id}
setDiagramId={setId}
title={title}
setTitle={setTitle}
lastSaved={lastSaved}
setLastSaved={setLastSaved}
/>
</TaskContext.Provider>
<div
className="flex h-full overflow-y-auto"
onMouseUp={() => setResize(false)}
2024-03-13 06:58:03 +08:00
onMouseLeave={() => setResize(false)}
onMouseMove={handleResize}
2024-03-13 05:36:49 +08:00
>
{layout.sidebar && (
<SidePanel
resize={resize}
setResize={setResize}
width={width}
2024-03-12 05:59:04 +08:00
/>
2024-03-13 05:36:49 +08:00
)}
<div className="relative w-full h-full overflow-hidden">
<Canvas state={state} setState={setState} />
{!(layout.sidebar || layout.toolbar || layout.header) && (
<div className="fixed right-5 bottom-4">
<Controls />
</div>
2024-03-12 05:59:04 +08:00
)}
</div>
2024-03-12 05:59:04 +08:00
</div>
2024-03-13 05:36:49 +08:00
</div>
</TypeContext.Provider>
</TabContext.Provider>
2024-03-10 01:42:09 +08:00
</StateContext.Provider>
2023-09-19 20:46:46 +08:00
);
}