Handle undo for collab

This commit is contained in:
1ilit 2024-01-12 04:10:49 +02:00
parent f870c213ae
commit 9256c75621
2 changed files with 242 additions and 47 deletions

View File

@ -62,6 +62,7 @@ import { areaSchema, noteSchema, tableSchema } from "../data/schemas";
import { Editor } from "@monaco-editor/react"; import { Editor } from "@monaco-editor/react";
import { db } from "../data/db"; import { db } from "../data/db";
import { useLiveQuery } from "dexie-react-hooks"; import { useLiveQuery } from "dexie-react-hooks";
import { socket } from "../data/socket";
export default function ControlPanel({ export default function ControlPanel({
diagramId, diagramId,
@ -125,7 +126,7 @@ export default function ControlPanel({
useContext(NoteContext); useContext(NoteContext);
const { areas, setAreas, updateArea, addArea, deleteArea } = const { areas, setAreas, updateArea, addArea, deleteArea } =
useContext(AreaContext); useContext(AreaContext);
const { undoStack, redoStack, setUndoStack, setRedoStack } = const { undoStack, redoStack, setUndoStack, setRedoStack, setHistoryCount } =
useContext(UndoRedoContext); useContext(UndoRedoContext);
const { selectedElement, setSelectedElement } = useContext(SelectContext); const { selectedElement, setSelectedElement } = useContext(SelectContext);
const { tab, setTab } = useContext(TabContext); const { tab, setTab } = useContext(TabContext);
@ -151,7 +152,11 @@ export default function ControlPanel({
const undo = () => { const undo = () => {
if (undoStack.length === 0) return; if (undoStack.length === 0) return;
const a = undoStack.pop(); setHistoryCount(undoStack.length)
const a = undoStack[undoStack.length - 1];
if (socket && a)
socket.emit("send-reversed-changes", a);
setUndoStack(prev => prev.filter((e, i) => i !== prev.length - 1));
if (a.action === Action.ADD) { if (a.action === Action.ADD) {
if (a.element === ObjectType.TABLE) { if (a.element === ObjectType.TABLE) {
deleteTable(tables[tables.length - 1].id, false); deleteTable(tables[tables.length - 1].id, false);
@ -323,7 +328,8 @@ export default function ControlPanel({
const redo = () => { const redo = () => {
if (redoStack.length === 0) return; if (redoStack.length === 0) return;
const a = redoStack.pop(); const a = redoStack[redoStack.length - 1];
setRedoStack(prev => prev.filter((e, i) => i !== prev.length - 1));
if (a.action === Action.ADD) { if (a.action === Action.ADD) {
if (a.element === ObjectType.TABLE) { if (a.element === ObjectType.TABLE) {
addTable(false); addTable(false);
@ -499,6 +505,7 @@ export default function ControlPanel({
})); }));
setUndoStack((prev) => [...prev, a]); setUndoStack((prev) => [...prev, a]);
} }
setHistoryCount(undoStack.length)
}; };
const fileImport = () => setVisible(MODAL.IMPORT); const fileImport = () => setVisible(MODAL.IMPORT);
@ -1394,8 +1401,7 @@ export default function ControlPanel({
<div className="h-[360px] grid grid-cols-3 gap-2 overflow-auto px-1"> <div className="h-[360px] grid grid-cols-3 gap-2 overflow-auto px-1">
<div> <div>
<div <div
className={`h-[180px] w-full bg-blue-400 bg-opacity-30 flex justify-center items-center rounded hover:bg-opacity-40 hover:border-2 hover:border-dashed ${ className={`h-[180px] w-full bg-blue-400 bg-opacity-30 flex justify-center items-center rounded hover:bg-opacity-40 hover:border-2 hover:border-dashed ${settings.mode === "light"
settings.mode === "light"
? "hover:border-blue-500" ? "hover:border-blue-500"
: "hover:border-white" : "hover:border-white"
} ${selectedTemplateId === 0 && "border-2 border-blue-500"}`} } ${selectedTemplateId === 0 && "border-2 border-blue-500"}`}
@ -1408,8 +1414,7 @@ export default function ControlPanel({
{[1, 2, 3, 4, 5, 6, 7, 8].map((i) => ( {[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
<div key={i}> <div key={i}>
<div <div
className={`h-[180px] w-full bg-blue-400 bg-opacity-30 flex justify-center items-center rounded hover:bg-opacity-40 hover:border-2 hover:border-dashed ${ className={`h-[180px] w-full bg-blue-400 bg-opacity-30 flex justify-center items-center rounded hover:bg-opacity-40 hover:border-2 hover:border-dashed ${settings.mode === "light"
settings.mode === "light"
? "hover:border-blue-500" ? "hover:border-blue-500"
: "hover:border-white" : "hover:border-white"
} ${selectedTemplateId === i && "border-2 border-blue-500"}`} } ${selectedTemplateId === i && "border-2 border-blue-500"}`}
@ -1471,8 +1476,7 @@ export default function ControlPanel({
return ( return (
<tr <tr
key={d.id} key={d.id}
className={`${ className={`${selectedDiagramId === d.id
selectedDiagramId === d.id
? "bg-blue-300 bg-opacity-30" ? "bg-blue-300 bg-opacity-30"
: "hover-1" : "hover-1"
}`} }`}

View File

@ -14,6 +14,7 @@ import {
} from "../data/data"; } from "../data/data";
import { socket } from "../data/socket"; import { socket } from "../data/socket";
import { db } from "../data/db"; import { db } from "../data/db";
// import { flipAction } from "../utils";
// import { uniqueNamesGenerator, colors, animals } from "unique-names-generator"; // import { uniqueNamesGenerator, colors, animals } from "unique-names-generator";
export const LayoutContext = createContext(); export const LayoutContext = createContext();
@ -72,6 +73,7 @@ export default function Editor() {
]); ]);
const [undoStack, setUndoStack] = useState([]); const [undoStack, setUndoStack] = useState([]);
const [redoStack, setRedoStack] = useState([]); const [redoStack, setRedoStack] = useState([]);
const [historyCount, setHistoryCount] = useState(0);
const [selectedElement, setSelectedElement] = useState({ const [selectedElement, setSelectedElement] = useState({
element: ObjectType.NONE, element: ObjectType.NONE,
id: -1, id: -1,
@ -482,6 +484,7 @@ export default function Editor() {
areas.length, areas.length,
notes.length, notes.length,
types.length, types.length,
relationships.length
]); ]);
useEffect(() => { useEffect(() => {
@ -553,13 +556,13 @@ export default function Editor() {
}, [tables, relationships, notes, areas, types, title, id, state]); }, [tables, relationships, notes, areas, types, title, id, state]);
useEffect(() => { useEffect(() => {
if (socket && undoStack[undoStack.length - 1]) if (socket) {
if (historyCount < undoStack.length) {
socket.emit("send-changes", undoStack[undoStack.length - 1]); socket.emit("send-changes", undoStack[undoStack.length - 1]);
}, [undoStack]); }
useEffect(() => { }
if (socket && redoStack[redoStack.length - 1]) }, [undoStack, historyCount]);
socket.emit("send-reversed-changes", redoStack[redoStack.length - 1]);
}, [redoStack]);
useEffect(() => { useEffect(() => {
document.title = "Editor | drawDB"; document.title = "Editor | drawDB";
@ -705,6 +708,8 @@ export default function Editor() {
// socket.on("user-disconnected", onUserDisconnected); // socket.on("user-disconnected", onUserDisconnected);
const applyChange = (delta) => { const applyChange = (delta) => {
// console.log("apply: ", delta)
if (!delta) return;
if (delta.action === Action.ADD) { if (delta.action === Action.ADD) {
switch (delta.element) { switch (delta.element) {
case ObjectType.TABLE: case ObjectType.TABLE:
@ -942,8 +947,193 @@ export default function Editor() {
}; };
const reverseChange = (delta) => { const reverseChange = (delta) => {
console.log(delta); // console.log("reverse: ", delta);
if (delta.action === Action.ADD) {
switch (delta.element) {
case ObjectType.TABLE:
setTables(prev => prev.filter((e, i) => i !== prev.length - 1));
return;
case ObjectType.RELATIONSHIP:
setRelationships(prev => prev.filter((e, i) => i !== prev.length - 1));
return;
case ObjectType.AREA:
setAreas(prev => prev.filter((e, i) => i !== prev.length - 1));
return;
case ObjectType.NOTE:
setNotes(prev => prev.filter((e, i) => i !== prev.length - 1));
return;
case ObjectType.TYPE:
setTypes(prev => prev.filter((e, i) => i !== prev.length - 1));
return;
}
} else if (delta.action === Action.MOVE) {
switch (delta.element) {
case ObjectType.TABLE:
updateTable(delta.id, { x: delta.x, y: delta.y }, true);
return;
case ObjectType.AREA:
updateArea(delta.id, { x: delta.x, y: delta.y });
return;
case ObjectType.NOTE:
updateNote(delta.id, { x: delta.x, y: delta.y });
return;
}
} else if (delta.action === Action.DELETE) {
switch (delta.element) {
case ObjectType.TABLE:
addTable(false, delta.data);
return;
case ObjectType.AREA:
addArea(false, delta.data);
return;
case ObjectType.NOTE:
addNote(false, delta.data);
return;
case ObjectType.RELATIONSHIP:
addRelationship(false, delta.data);
return;
case ObjectType.TYPE:
addType(false, delta.data);
return;
}
} else if (delta.action === Action.EDIT) {
switch (delta.element) {
case ObjectType.AREA:
updateArea(delta.aid, delta.undo);
return;
case ObjectType.RELATIONSHIP:
setRelationships((prev) =>
prev.map((e, idx) =>
idx === delta.rid ? { ...e, ...delta.undo } : e
)
);
return;
case ObjectType.NOTE:
updateNote(delta.nid, delta.undo);
return;
case ObjectType.TABLE:
if (delta.component === "field") {
updateField(delta.tid, delta.fid, delta.undo);
} else if (delta.component === "field_delete") {
setTables((prev) =>
prev.map((t) => {
if (t.id === delta.tid) {
const temp = t.fields.slice();
temp.splice(delta.data.id, 0, delta.data);
return { ...t, fields: temp.map((t, i) => ({ ...t, id: i })) };
}
return t;
})
);
} else if (delta.component === "field_add") {
setTables((prev) =>
prev.map((t) => {
if (t.id === delta.tid) {
return {
...t,
fields: t.fields.filter((f) => f.id !== t.fields.length - 1).map((f, i) => ({ ...f, id: i })),
}; };
}
return t;
})
);
} else if (delta.component === "index_add") {
setTables((prev) =>
prev.map((t) => {
if (t.id === delta.tid) {
return {
...t,
indices: t.indices.filter((f) => f.id !== t.indices.length - 1).map((f, i) => ({ ...f, id: i })),
};
}
return t;
})
);
} else if (delta.component === "index") {
setTables((prev) =>
prev.map((t) => {
if (t.id === delta.tid) {
return {
...t,
indices: t.indices.map((index) =>
index.id === delta.iid
? {
...index,
...delta.undo,
}
: index
),
};
}
return t;
})
);
} else if (delta.component === "index_delete") {
setTables((prev) =>
prev.map((table) => {
if (table.id === delta.tid) {
const temp = table.indices.slice();
temp.splice(delta.data.id, 0, delta.data);
return {
...table,
indices: temp.map((t, i) => ({ ...t, id: i })),
};
}
return table;
})
);
} else if (delta.component === "self") {
updateTable(delta.tid, delta.undo, false);
}
return;
case ObjectType.TYPE:
if (delta.component === "field") {
setTypes((prev) =>
prev.map((t, i) => {
if (i === delta.tid) {
return {
...t,
fields: t.fields.map((e, j) =>
j === delta.fid ? { ...e, ...delta.undo } : e
),
};
}
return t;
})
);
} else if (delta.component === "field_add") {
setTypes((prev) =>
prev.map((t, i) => {
if (i === delta.tid) {
return {
...t,
fields: t.fields.filter(
(e, i) => i !== t.fields.length - 1
),
};
}
return t;
})
);
} else if (delta.component === "field_delete") {
setTypes((prev) =>
prev.map((t, i) => {
if (i === delta.tid) {
const temp = t.fields.slice();
temp.splice(delta.fid, 0, delta.data);
return { ...t, fields: temp };
}
return t;
})
);
} else if (delta.component === "self") {
updateType(delta.tid, delta.undo);
}
return;
}
}
};
socket.on("recieve-changes", (delta) => applyChange(delta)); socket.on("recieve-changes", (delta) => applyChange(delta));
socket.on("recieve-reversed-changes", (delta) => reverseChange(delta)); socket.on("recieve-reversed-changes", (delta) => reverseChange(delta));
return () => { return () => {
@ -951,6 +1141,7 @@ export default function Editor() {
// socket.off("recieve-message", onRecieve); // socket.off("recieve-message", onRecieve);
// socket.off("user-connected", onUserConnected); // socket.off("user-connected", onUserConnected);
// socket.off("user-disconnected", onUserDisconnected); // socket.off("user-disconnected", onUserDisconnected);
socket.off("recieve-reversed-changes", reverseChange);
socket.off("recieve-changes", applyChange); socket.off("recieve-changes", applyChange);
socket.disconnect(); socket.disconnect();
}; };
@ -982,7 +1173,7 @@ export default function Editor() {
<TabContext.Provider value={{ tab, setTab }}> <TabContext.Provider value={{ tab, setTab }}>
<SettingsContext.Provider value={{ settings, setSettings }}> <SettingsContext.Provider value={{ settings, setSettings }}>
<UndoRedoContext.Provider <UndoRedoContext.Provider
value={{ undoStack, redoStack, setUndoStack, setRedoStack }} value={{ undoStack, redoStack, setUndoStack, setRedoStack, setHistoryCount }}
> >
<SelectContext.Provider <SelectContext.Provider
value={{ selectedElement, setSelectedElement }} value={{ selectedElement, setSelectedElement }}