Handle undo for collab
This commit is contained in:
parent
f870c213ae
commit
9256c75621
@ -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);
|
||||||
@ -789,7 +796,7 @@ export default function ControlPanel({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Share: {
|
Share: {
|
||||||
function: () => {},
|
function: () => { },
|
||||||
},
|
},
|
||||||
Rename: {
|
Rename: {
|
||||||
function: () => {
|
function: () => {
|
||||||
@ -926,7 +933,7 @@ export default function ControlPanel({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
function: () => {},
|
function: () => { },
|
||||||
},
|
},
|
||||||
"Export source": {
|
"Export source": {
|
||||||
children: [
|
children: [
|
||||||
@ -960,9 +967,9 @@ export default function ControlPanel({
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ DBML: () => {} },
|
{ DBML: () => { } },
|
||||||
],
|
],
|
||||||
function: () => {},
|
function: () => { },
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
children: [
|
children: [
|
||||||
@ -997,7 +1004,7 @@ export default function ControlPanel({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
Exit: {
|
Exit: {
|
||||||
function: () => {},
|
function: () => { },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Edit: {
|
Edit: {
|
||||||
@ -1111,7 +1118,7 @@ export default function ControlPanel({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
function: () => {},
|
function: () => { },
|
||||||
},
|
},
|
||||||
"Zoom in": {
|
"Zoom in": {
|
||||||
function: zoomIn,
|
function: zoomIn,
|
||||||
@ -1127,13 +1134,13 @@ export default function ControlPanel({
|
|||||||
},
|
},
|
||||||
Logs: {
|
Logs: {
|
||||||
"Open logs": {
|
"Open logs": {
|
||||||
function: () => {},
|
function: () => { },
|
||||||
},
|
},
|
||||||
"Commit changes": {
|
"Commit changes": {
|
||||||
function: () => {},
|
function: () => { },
|
||||||
},
|
},
|
||||||
"Revert changes": {
|
"Revert changes": {
|
||||||
function: () => {},
|
function: () => { },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Help: {
|
Help: {
|
||||||
@ -1142,10 +1149,10 @@ export default function ControlPanel({
|
|||||||
shortcut: "Ctrl+H",
|
shortcut: "Ctrl+H",
|
||||||
},
|
},
|
||||||
"Ask us on discord": {
|
"Ask us on discord": {
|
||||||
function: () => {},
|
function: () => { },
|
||||||
},
|
},
|
||||||
"Tweet us": {
|
"Tweet us": {
|
||||||
function: () => {},
|
function: () => { },
|
||||||
},
|
},
|
||||||
"Report a bug": {
|
"Report a bug": {
|
||||||
function: () => window.open("/bug_report", "_blank"),
|
function: () => window.open("/bug_report", "_blank"),
|
||||||
@ -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"
|
||||||
}`}
|
}`}
|
||||||
|
@ -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 }}
|
||||||
|
Loading…
Reference in New Issue
Block a user