From 9256c75621a5b910676ee6ff8baf86eaadaa0f83 Mon Sep 17 00:00:00 2001 From: 1ilit Date: Fri, 12 Jan 2024 04:10:49 +0200 Subject: [PATCH] Handle undo for collab --- src/components/ControlPanel.jsx | 74 +++++------ src/pages/editor.jsx | 215 ++++++++++++++++++++++++++++++-- 2 files changed, 242 insertions(+), 47 deletions(-) diff --git a/src/components/ControlPanel.jsx b/src/components/ControlPanel.jsx index e01cbce..02000f7 100644 --- a/src/components/ControlPanel.jsx +++ b/src/components/ControlPanel.jsx @@ -62,6 +62,7 @@ import { areaSchema, noteSchema, tableSchema } from "../data/schemas"; import { Editor } from "@monaco-editor/react"; import { db } from "../data/db"; import { useLiveQuery } from "dexie-react-hooks"; +import { socket } from "../data/socket"; export default function ControlPanel({ diagramId, @@ -125,7 +126,7 @@ export default function ControlPanel({ useContext(NoteContext); const { areas, setAreas, updateArea, addArea, deleteArea } = useContext(AreaContext); - const { undoStack, redoStack, setUndoStack, setRedoStack } = + const { undoStack, redoStack, setUndoStack, setRedoStack, setHistoryCount } = useContext(UndoRedoContext); const { selectedElement, setSelectedElement } = useContext(SelectContext); const { tab, setTab } = useContext(TabContext); @@ -151,7 +152,11 @@ export default function ControlPanel({ const undo = () => { 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.element === ObjectType.TABLE) { deleteTable(tables[tables.length - 1].id, false); @@ -255,9 +260,9 @@ export default function ControlPanel({ indices: tables[a.tid].indices.map((index) => index.id === a.iid ? { - ...index, - ...a.undo, - } + ...index, + ...a.undo, + } : index ), }); @@ -323,7 +328,8 @@ export default function ControlPanel({ const redo = () => { 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.element === ObjectType.TABLE) { addTable(false); @@ -447,9 +453,9 @@ export default function ControlPanel({ indices: tables[a.tid].indices.map((index) => index.id === a.iid ? { - ...index, - ...a.redo, - } + ...index, + ...a.redo, + } : index ), }); @@ -499,6 +505,7 @@ export default function ControlPanel({ })); setUndoStack((prev) => [...prev, a]); } + setHistoryCount(undoStack.length) }; const fileImport = () => setVisible(MODAL.IMPORT); @@ -789,7 +796,7 @@ export default function ControlPanel({ }, }, Share: { - function: () => {}, + function: () => { }, }, Rename: { function: () => { @@ -926,7 +933,7 @@ export default function ControlPanel({ }, }, ], - function: () => {}, + function: () => { }, }, "Export source": { children: [ @@ -960,9 +967,9 @@ export default function ControlPanel({ })); }, }, - { DBML: () => {} }, + { DBML: () => { } }, ], - function: () => {}, + function: () => { }, }, Settings: { children: [ @@ -997,7 +1004,7 @@ export default function ControlPanel({ ], }, Exit: { - function: () => {}, + function: () => { }, }, }, Edit: { @@ -1111,7 +1118,7 @@ export default function ControlPanel({ }, }, ], - function: () => {}, + function: () => { }, }, "Zoom in": { function: zoomIn, @@ -1127,13 +1134,13 @@ export default function ControlPanel({ }, Logs: { "Open logs": { - function: () => {}, + function: () => { }, }, "Commit changes": { - function: () => {}, + function: () => { }, }, "Revert changes": { - function: () => {}, + function: () => { }, }, }, Help: { @@ -1142,10 +1149,10 @@ export default function ControlPanel({ shortcut: "Ctrl+H", }, "Ask us on discord": { - function: () => {}, + function: () => { }, }, "Tweet us": { - function: () => {}, + function: () => { }, }, "Report a bug": { function: () => window.open("/bug_report", "_blank"), @@ -1394,11 +1401,10 @@ export default function ControlPanel({
setSelectedTemplateId(0)} > @@ -1408,11 +1414,10 @@ export default function ControlPanel({ {[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
setSelectedTemplateId(i)} > + @@ -1471,11 +1476,10 @@ export default function ControlPanel({ return ( { setSelectedDiagramId(d.id); }} diff --git a/src/pages/editor.jsx b/src/pages/editor.jsx index d170d80..4c2e423 100644 --- a/src/pages/editor.jsx +++ b/src/pages/editor.jsx @@ -14,6 +14,7 @@ import { } from "../data/data"; import { socket } from "../data/socket"; import { db } from "../data/db"; +// import { flipAction } from "../utils"; // import { uniqueNamesGenerator, colors, animals } from "unique-names-generator"; export const LayoutContext = createContext(); @@ -72,6 +73,7 @@ export default function Editor() { ]); const [undoStack, setUndoStack] = useState([]); const [redoStack, setRedoStack] = useState([]); + const [historyCount, setHistoryCount] = useState(0); const [selectedElement, setSelectedElement] = useState({ element: ObjectType.NONE, id: -1, @@ -482,6 +484,7 @@ export default function Editor() { areas.length, notes.length, types.length, + relationships.length ]); useEffect(() => { @@ -553,13 +556,13 @@ export default function Editor() { }, [tables, relationships, notes, areas, types, title, id, state]); useEffect(() => { - if (socket && undoStack[undoStack.length - 1]) - socket.emit("send-changes", undoStack[undoStack.length - 1]); - }, [undoStack]); - useEffect(() => { - if (socket && redoStack[redoStack.length - 1]) - socket.emit("send-reversed-changes", redoStack[redoStack.length - 1]); - }, [redoStack]); + if (socket) { + if (historyCount < undoStack.length) { + socket.emit("send-changes", undoStack[undoStack.length - 1]); + } + } + }, [undoStack, historyCount]); + useEffect(() => { document.title = "Editor | drawDB"; @@ -705,6 +708,8 @@ export default function Editor() { // socket.on("user-disconnected", onUserDisconnected); const applyChange = (delta) => { + // console.log("apply: ", delta) + if (!delta) return; if (delta.action === Action.ADD) { switch (delta.element) { case ObjectType.TABLE: @@ -866,9 +871,9 @@ export default function Editor() { indices: t.indices.map((index) => index.id === delta.iid ? { - ...index, - ...delta.redo, - } + ...index, + ...delta.redo, + } : index ), }; @@ -942,8 +947,193 @@ export default function Editor() { }; 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-reversed-changes", (delta) => reverseChange(delta)); return () => { @@ -951,6 +1141,7 @@ export default function Editor() { // socket.off("recieve-message", onRecieve); // socket.off("user-connected", onUserConnected); // socket.off("user-disconnected", onUserDisconnected); + socket.off("recieve-reversed-changes", reverseChange); socket.off("recieve-changes", applyChange); socket.disconnect(); }; @@ -982,7 +1173,7 @@ export default function Editor() {