Browse Source

Fix bug when deleting node in EditableGraph.

Joeri Exelmans 3 years ago
parent
commit
e8cbf23f73
2 changed files with 48 additions and 36 deletions
  1. 1 14
      src/frontend/app.tsx
  2. 47 22
      src/frontend/editable_graph.tsx

+ 1 - 14
src/frontend/app.tsx

@@ -17,19 +17,6 @@ const emptyGraph = {
   links: [],
 };
 
-// const graph2 = {
-//   nodes: [
-//     {id: "0", group: 0},
-//     {id: "1", group: 0},
-//     {id: "2", group: 0},
-//   ],
-//   links: [
-//     {source: "0", target: "1", value: 1},
-//     {source: "1", target: "2", value: 1},
-//     {source: "2", target: "0", value: 1},
-//   ],
-// };
-
 const initialHistoryGraph = {
   nodes: [versionToNode(initialVersion)],
   links: [],
@@ -125,7 +112,7 @@ export function App() {
           </Group>
           <EditableGraph graph={_.cloneDeep(emptyGraph)} forces={editableGraphForces} getUuid={getUuid} newVersionHandler={newVersionHandler} newDeltaHandler={newDeltaHandler} />
           <Text>Left mouse button: Drag node around.</Text>
-          <Text>Middle mouse button: Delete node.</Text>
+          <Text>Middle mouse button: Delete node (+ incoming/outgoing edges).</Text>
           <Text>Right mouse button: Create node or edge.</Text>
           <Text>Mouse wheel: Zoom.</Text>
         </Grid.Col>

+ 47 - 22
src/frontend/editable_graph.tsx

@@ -21,14 +21,18 @@ import {
   VersionRegistry,
 } from "../onion/version";
 
+// The stuff that we store in each (visible) node:
 interface NodeObjType {
-  nodeCreation: NodeCreation;
-  outgoing: Map<string, [EdgeCreation|EdgeUpdate, NodeObjType]>;
+  nodeCreation: NodeCreation; // The delta that created the node
+  outgoing: Map<string, [EdgeCreation|EdgeUpdate, NodeObjType]>; // Outgoing edges: Mapping from edge label to the most recent EdgeCreation or EdgeUpdate.
+
   incoming: Array<[EdgeCreation|EdgeUpdate|NodeDeletion, NodeObjType]>;
 }
 
+type EdgeObjType = null; // We don't need to store anything special in edges.
+
 interface EditableGraphProps {
-  graph: d3Types.d3Graph<NodeObjType,EdgeCreation|EdgeUpdate>;
+  graph: d3Types.d3Graph<NodeObjType,EdgeObjType>;
   forces: Forces;
   getUuid: () => UUID;
   newVersionHandler: (Version) => void;
@@ -39,7 +43,7 @@ interface EditableGraphState {
 }
 
 export class EditableGraph extends React.Component<EditableGraphProps, EditableGraphState> {
-  graphRef: React.RefObject<Graph<NodeObjType,EdgeCreation|EdgeUpdate>> = React.createRef<Graph<NodeObjType,EdgeCreation|EdgeUpdate>>();
+  graphRef: React.RefObject<Graph<NodeObjType,EdgeObjType>> = React.createRef<Graph<NodeObjType,EdgeObjType>>();
   mouseDownNode: d3Types.d3Node<NodeObjType> | null = null; // only while mouse button is down, does this record the d3Node that was under the cursor when the mouse went down.
 
   nextId: number = 0;
@@ -81,21 +85,25 @@ export class EditableGraph extends React.Component<EditableGraphProps, EditableG
                 // an edge with the same source and label already exists:
                 const edgeUpdate = new EdgeUpdate(prevUpdate, target.nodeCreation);
                 this.graphRef.current.deleteLink({source: this.mouseDownNode, label});
-                // remove item from array:
-                target.incoming.splice(target.incoming.findIndex(([delta, _])=>delta===prevUpdate), 1);
+                // replace item in array:
+                console.log("replace item in array:");
+                prevTargetObj.incoming.splice(target.incoming.findIndex(([delta, _])=>delta===prevUpdate), 1, [edgeUpdate, prevTargetObj]);
+                console.log("prevTargetObj:", prevTargetObj);
                 return edgeUpdate;
-              } else {
+              }
+              else {
                 const edgeCreation = new EdgeCreation(source.nodeCreation, label, target.nodeCreation);
                 return edgeCreation;
               }
             })();
             source.outgoing.set(label, [edgeCreationOrUpdate, target]);
             target.incoming.push([edgeCreationOrUpdate, source]);
+            console.log("target:", target);
 
-            // console.log("target.incoming:", target.incoming);
+            // console.log("target.incoming:", target.incoming); 
             // console.log("edgeCreationOrUpdate.conflicts:", edgeCreationOrUpdate.getConflicts());
 
-            this.graphRef.current.createLink({source: this.mouseDownNode, label, target: node, obj: edgeCreationOrUpdate});
+            this.graphRef.current.createLink({source: this.mouseDownNode, label, target: node, obj: null});
 
             const tx = this.compositeLvl.createComposite([edgeCreationOrUpdate]);
             this.currentVersion = this.versionRegistry.createVersion(this.currentVersion, tx);
@@ -122,30 +130,47 @@ export class EditableGraph extends React.Component<EditableGraphProps, EditableG
       else if (event.button === 1) { // middle mouse button
         if (node) {
           // Delete node (and its incoming + outgoing edges):
-          console.log("incoming:", node.obj.incoming);
-          console.log("outgoing:", node.obj.outgoing);
-          const incomingEdgeUnsettings = node.obj.incoming.map(([incomingEdge,sourceObj]) => {
+          // console.log("incoming:", node.obj.incoming);
+          // console.log("outgoing:", node.obj.outgoing);
+
+          const deleteDependencies: any[] = [];
+          const compositeDeltas: any[] = [];
+
+          for (const [incomingEdge,sourceObj] of node.obj.incoming) {
+            // We also depend on incoming edges that were deleted (because their source node was deleted):
             if (incomingEdge instanceof NodeDeletion) {
-              return incomingEdge;
+              console.log("edge was already deleted");
+              deleteDependencies.push(incomingEdge);
             }
             else {
-              const edgeUnset = new EdgeUpdate(incomingEdge, null);
-              sourceObj.outgoing.set(incomingEdge.getCreation().label, [edgeUnset,node.obj]);
-              return edgeUnset;
+              if (incomingEdge.target.target === node.obj.nodeCreation) {
+                console.log("unsetting incoming edge...");
+                // Must set the value of every incoming edge to 'null' (with an EdgeUpdate):
+                const edgeUnset = new EdgeUpdate(incomingEdge, null);
+                sourceObj.outgoing.set(incomingEdge.getCreation().label, [edgeUnset,node.obj]);
+                deleteDependencies.push(edgeUnset);
+                compositeDeltas.push(edgeUnset);
+              }
+              else {
+                console.log("edge already pointing somewhere else");
+                // Edge is already pointing somewhere else: just include the operation as a dependency for the deletion.
+                deleteDependencies.push(incomingEdge);
+              }
             }
-          });
+          }
           const targetIncomings: Array<[EdgeCreation|EdgeUpdate|NodeDeletion, NodeObjType]>[] = [];
-          const deletedOutgoingEdges = [...node.obj.outgoing.values()].map(([outgoingEdge,targetObj]) => {
+          for (const [outgoingEdge, targetObj] of node.obj.outgoing.values()) {
             targetObj.incoming.splice(targetObj.incoming.findIndex(([d,_])=>d===outgoingEdge), 1);
             targetIncomings.push(targetObj.incoming);
-            return outgoingEdge;
-          });
-          const nodeDeletion = new NodeDeletion(node.obj.nodeCreation, [...incomingEdgeUnsettings, ...deletedOutgoingEdges]);
+            deleteDependencies.push(outgoingEdge);
+          }
+          const nodeDeletion = new NodeDeletion(node.obj.nodeCreation, deleteDependencies);
+          compositeDeltas.push(nodeDeletion);
           for (const targetIncoming of targetIncomings) {
             targetIncoming.push([nodeDeletion, node.obj]);
           }
           this.graphRef.current.deleteNode(node.id);
-          const tx = this.compositeLvl.createComposite([nodeDeletion, ...incomingEdgeUnsettings.filter(d=>!(d instanceof NodeDeletion))]);
+          const tx = this.compositeLvl.createComposite(compositeDeltas);
           this.currentVersion = this.versionRegistry.createVersion(this.currentVersion, tx);
           this.props.newDeltaHandler(tx);
           this.props.newVersionHandler(this.currentVersion);