Procházet zdrojové kódy

Pulled up state: contents of Graph react component. WIP: clicking on a Version (in History Graph) shows that version in Graph State view.

Joeri Exelmans před 2 roky
rodič
revize
04e44844aa

+ 117 - 41
src/frontend/app.tsx

@@ -4,8 +4,9 @@ import * as _ from "lodash";
 import {Grid, Text, Title, Group, Stack} from "@mantine/core";
 
 import {d3Types, Graph} from "./graph"
-import {EditableGraph} from "./editable_graph";
+import {EditableGraph, GraphType, NodeType, LinkType, nodeCreationToD3} from "./editable_graph";
 
+import {UUID} from "../onion/types";
 import {mockUuid} from "../onion/test_helpers";
 import {Version, initialVersion} from "../onion/version";
 import {Delta} from "../onion/delta";
@@ -71,30 +72,64 @@ function getDeltaColor(delta: Delta) {
   return color;
 }
 
-export function App() {
-  const getUuid = mockUuid();
-  const refHistoryGraph = React.useRef<Graph<Version,Delta>>(null);
-  const refDependencyGraphL1 = React.useRef<Graph<Delta,null>>(null);
-  const refDependencyGraphL0 = React.useRef<Graph<Delta,null>>(null);
+interface BranchProps {
+  getUuid: () => UUID;
+}
+
+export function Branch(props: BranchProps) {
+  const [version, setVersion] = React.useState<Version>(initialVersion); // the version currently being displayed in the Graph State (on the left)
+  const [graph, setGraph] = React.useState<GraphType>(_.cloneDeep(emptyGraph));
+  const [historyGraph, setHistoryGraph] = React.useState<d3Types.d3Graph<Version,Delta>>(_.cloneDeep(initialHistoryGraph));
+  const [dependencyGraph, setDependencyGraph] = React.useState<d3Types.d3Graph<Delta,null>>(_.cloneDeep(emptyGraph));
 
+  // Callbacks for our EditableGraph component:
+  const createNode = (node: NodeType) => {
+    setGraph((prevState: GraphType) => ({
+      nodes: [...prevState.nodes, node],
+      links: prevState.links,
+    }));
+  }
+  const deleteNode = (id: string) => {
+    setGraph((prevState: GraphType) => {
+      const newLinks = prevState.links.filter(l => l.source.id !== id && l.target.id !== id);
+      return {
+        nodes: prevState.nodes.filter(n => n.id !== id),
+        links: newLinks,
+      };
+    });
+  };
+  const createLink = ({source, label, target, obj}: LinkType) => {
+    setGraph((prevState: GraphType) => ({
+      nodes: prevState.nodes,
+      links: [...prevState.links, {source, target, label, obj}],
+    }));
+  };
+  const deleteLink = (source: NodeType, label: string) => {
+    setGraph((prevState: GraphType) => {
+      const newLinks = prevState.links.filter(l => l.source !== source || l.label !== label)
+      return {
+        nodes: prevState.nodes,
+        links: newLinks,
+      };
+    });
+  };
   const newVersionHandler = (version: Version) => {
-    if (refHistoryGraph.current !== null) {
-      refHistoryGraph.current.createNode(versionToNode(version));
-      for (const parent of version.parents) {
-        const [parentVersion, delta] = parent;
-        refHistoryGraph.current.createLink({source:fullVersionId(version), label:"", target:fullVersionId(parentVersion), obj: delta});
-      }
-    }
+    setHistoryGraph(prevHistoryGraph => {
+      return {
+        nodes: prevHistoryGraph.nodes.concat(versionToNode(version)),
+        links: prevHistoryGraph.links.concat(...version.parents.map(([parentVersion,delta]) => ({source:fullVersionId(version), label:"", target:fullVersionId(parentVersion), obj: delta}))),
+      };
+    });
+    setVersion(version);
   };
-
   const newDeltaHandler = (delta: Delta) => {
-    if (refDependencyGraphL1.current !== null) {
+    setDependencyGraph(prevDepGraph => {
       const color = getDeltaColor(delta);
-      refDependencyGraphL1.current.createNode({id: fullDeltaId(delta), label: delta.getDescription(), color, obj: delta});
-      for (const [dep,depType] of delta.getTypedDependencies()) {
-        refDependencyGraphL1.current.createLink({source: fullDeltaId(delta), label: depType, target: fullDeltaId(dep), obj: null});
-      }
-    }
+      return {
+        nodes: prevDepGraph.nodes.concat({id: fullDeltaId(delta), label: delta.getDescription(), color, obj: delta}),
+        links: prevDepGraph.links.concat(...delta.getTypedDependencies().map(([dep,depType]) => ({source: fullDeltaId(delta), label: depType, target: fullDeltaId(dep), obj: null}))),
+      };
+    });
   }
 
   // "physics" stuff (for graph layout)
@@ -102,30 +137,71 @@ export function App() {
   const historyGraphForces = {charge: -100, center:0.1, link:2};
   const depGraphForces = {charge: -200, center:0.1, link:0.2};
 
+
+
+  const historyMouseUpHandler = (event, {x,y}, node: d3Types.d3Node<Version> | undefined) => {
+    if (node !== undefined) {
+      // the user clicked on a version
+      const versionClicked = node.obj;
+      const graphState = _.cloneDeep(emptyGraph);
+      const deltas = [...versionClicked].reverse(); // all deltas of versionClicked, from early to late.
+      function execDelta(delta) {
+        // just update graphState, in-place.
+        if (delta instanceof NodeCreation) {
+          graphState.nodes.push(nodeCreationToD3(delta, 0, 0));
+        }
+        else if (delta instanceof NodeDeletion) {
+          graphState.nodes.splice(graphState.nodes.findIndex((node: NodeType) => node.id === delta.creation.id.toString()), 1);
+        }
+        else if (delta instanceof EdgeCreation) {
+          // graphState.links.push()
+          // ?
+        }
+        else if (delta instanceof CompositeDelta) {
+          delta.deltas.forEach(execDelta);
+        }
+      }
+      deltas.forEach(execDelta);
+      console.log(graphState);
+      setGraph(graphState);
+      setVersion(versionClicked);
+    }
+  }
+
+  return (
+    <Grid grow>
+      <Grid.Col span={1}>
+        <Group>
+          <Title order={4}>Graph state</Title>
+        </Group>
+        <EditableGraph graph={graph} forces={editableGraphForces} getUuid={props.getUuid} newVersionHandler={newVersionHandler} newDeltaHandler={newDeltaHandler}
+          version={version}
+          createNode={createNode} deleteNode={deleteNode} createLink={createLink} deleteLink={deleteLink} />
+        <Text>Left mouse button: Drag node around.</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>
+      <Grid.Col span={1}>
+        <Title order={4}>History Graph</Title>
+        <Graph graph={historyGraph} forces={historyGraphForces} mouseDownHandler={()=>{}} mouseUpHandler={historyMouseUpHandler} />
+        <Text>All links are parent links.</Text>
+      </Grid.Col>
+      <Grid.Col span={1}>
+        <Title order={4}>Dependency Graph (L1)</Title>
+        <Graph graph={dependencyGraph} forces={depGraphForces} mouseDownHandler={()=>{}} mouseUpHandler={()=>{}} />
+      </Grid.Col>
+    </Grid>
+  );
+}
+
+export function App() {
+  const getUuid = mockUuid();
+
   return (
     <Stack>
       <Title order={2}>Onion VCS Demo</Title>
-      <Grid grow>
-        <Grid.Col span={1}>
-          <Group>
-            <Title order={4}>Graph state</Title>
-          </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 (+ incoming/outgoing edges).</Text>
-          <Text>Right mouse button: Create node or edge.</Text>
-          <Text>Mouse wheel: Zoom.</Text>
-        </Grid.Col>
-        <Grid.Col span={1}>
-          <Title order={4}>History Graph</Title>
-          <Graph ref={refHistoryGraph} graph={_.cloneDeep(initialHistoryGraph)} forces={historyGraphForces} mouseDownHandler={()=>{}} mouseUpHandler={()=>{}} />
-          <Text>All links are parent links.</Text>
-        </Grid.Col>
-        <Grid.Col span={1}>
-          <Title order={4}>Dependency Graph (L1)</Title>
-          <Graph ref={refDependencyGraphL1} graph={_.cloneDeep(emptyGraph)} forces={depGraphForces} mouseDownHandler={()=>{}} mouseUpHandler={()=>{}} />
-        </Grid.Col>
-      </Grid>
+      <Branch getUuid={getUuid} />
     </Stack>
   );
 }

+ 40 - 21
src/frontend/editable_graph.tsx

@@ -35,32 +35,53 @@ interface NodeObjType {
 
 type EdgeObjType = null; // We don't need to store anything special in edges.
 
+export type NodeType = d3Types.d3Node<NodeObjType>;
+export type LinkType = d3Types.d3Link<EdgeObjType>;
+
+export type GraphType = d3Types.d3Graph<NodeObjType,EdgeObjType>;
+
 interface EditableGraphProps {
-  graph: d3Types.d3Graph<NodeObjType,EdgeObjType>;
+  graph: GraphType;
   forces: Forces;
+  version: Version;
   getUuid: () => UUID;
   newVersionHandler: (Version) => void;
   newDeltaHandler: (Delta) => void;
+
+  createNode: (NodeType) => void;
+  deleteNode: (string) => void;
+  createLink: (LinkType) => void;
+  deleteLink: (source: NodeType, label:string) => void;
 }
 
 interface EditableGraphState {
 }
 
+export function nodeCreationToD3(nodeCreation: NodeCreation, x: number, y: number): NodeType {
+  return {
+    id: nodeCreation.id.value.toString(),
+    label: nodeCreation.id.value.toString(),
+    x, y,
+    color: "darkturquoise",
+    obj: {nodeCreation, outgoing: new Map(), incoming: []},
+  };
+}
+
 export class EditableGraph extends React.Component<EditableGraphProps, EditableGraphState> {
-  graphRef: React.RefObject<Graph<NodeObjType,EdgeObjType>> = React.createRef<Graph<NodeObjType,EdgeObjType>>();
+  graphRef: React.RefObject<Graph<NodeObjType,null>> = React.createRef<Graph<NodeObjType,null>>();
   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;
 
   readonly compositeLvl: CompositeLevel = new CompositeLevel();
   readonly versionRegistry: VersionRegistry = new VersionRegistry();
-  currentVersion: Version;
+  // currentVersion: Version;
+
+  // constructor(props) {
+  //   super(props);
+  //   // this.currentVersion = initialVersion;
+  // }
 
-  constructor(props) {
-    super(props);
-    this.state = {};
-    this.currentVersion = initialVersion;
-  }
 
   mouseDownHandler = (event, {x,y}, node) => {
     event.stopPropagation();
@@ -88,7 +109,7 @@ export class EditableGraph extends React.Component<EditableGraphProps, EditableG
                 const [prevUpdate, prevTargetObj] = previousEdgeUpdate;
                 // 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});
+                this.props.deleteLink(this.mouseDownNode, label);
                 // replace item in array:
                 console.log("replace item in array:");
                 prevTargetObj.incoming.splice(target.incoming.findIndex(([delta, _])=>delta===prevUpdate), 1, [edgeUpdate, prevTargetObj]);
@@ -107,12 +128,12 @@ export class EditableGraph extends React.Component<EditableGraphProps, EditableG
             // console.log("target.incoming:", target.incoming); 
             // console.log("edgeCreationOrUpdate.conflicts:", edgeCreationOrUpdate.getConflicts());
 
-            this.graphRef.current.createLink({source: this.mouseDownNode, label, target: node, obj: null});
+            this.props.createLink({source: this.mouseDownNode, label, target: node, obj: null});
 
             const tx = this.compositeLvl.createComposite([edgeCreationOrUpdate]);
-            this.currentVersion = this.versionRegistry.createVersion(this.currentVersion, tx);
+            const version = this.versionRegistry.createVersion(this.props.version, tx);
             this.props.newDeltaHandler(tx);
-            this.props.newVersionHandler(this.currentVersion);
+            this.props.newVersionHandler(version);
           }
         }
         else { // right mouse button
@@ -120,15 +141,13 @@ export class EditableGraph extends React.Component<EditableGraphProps, EditableG
           const uuid = this.props.getUuid();
           const nodeCreation = new NodeCreation(uuid);
 
-          this.graphRef.current.createNode({
-            id: uuid.value.toString(), label: uuid.value.toString(), x, y, color: "darkturquoise",
-            obj: {nodeCreation, outgoing: new Map(), incoming: []},
-          });
+          console.log("createNode??")
+          this.props.createNode(nodeCreationToD3(nodeCreation, x, y));
 
           const tx = this.compositeLvl.createComposite([nodeCreation]);
-          this.currentVersion = this.versionRegistry.createVersion(this.currentVersion, tx);
+          const version = this.versionRegistry.createVersion(this.props.version, tx);
           this.props.newDeltaHandler(tx);
-          this.props.newVersionHandler(this.currentVersion);
+          this.props.newVersionHandler(version);
         }
       }
       else if (event.button === 1) { // middle mouse button
@@ -174,13 +193,13 @@ export class EditableGraph extends React.Component<EditableGraphProps, EditableG
           for (const targetIncoming of targetIncomings) {
             targetIncoming.push([nodeDeletion, node.obj]);
           }
-          this.graphRef.current.deleteNode(node.id);
+          this.props.deleteNode(node.id);
           // console.log("deleteDependencies:", deleteDependencies);
           // console.log("compositeDeltas:", compositeDeltas);
           const tx = this.compositeLvl.createComposite(compositeDeltas);
-          this.currentVersion = this.versionRegistry.createVersion(this.currentVersion, tx);
+          const version = this.versionRegistry.createVersion(this.props.version, tx);
           this.props.newDeltaHandler(tx);
-          this.props.newVersionHandler(this.currentVersion);
+          this.props.newVersionHandler(version);
         }
       }
     }

+ 5 - 40
src/frontend/graph.tsx

@@ -240,8 +240,6 @@ export interface GraphProps<NodeType,LinkType> {
 }
 
 interface GraphState<NodeType,LinkType> {
-  nodes: d3Types.d3Node<NodeType>[],
-  links: d3Types.d3Link<LinkType>[],
   zoom: number;
 }
 
@@ -255,8 +253,6 @@ export class Graph<NodeType,LinkType> extends React.Component<GraphProps<NodeTyp
   constructor(props) {
     super(props);
     this.state = {
-      nodes: props.graph.nodes,
-      links: props.graph.links,
       zoom: 1.0,
     };
     this.simulation = d3.forceSimulation()
@@ -266,37 +262,6 @@ export class Graph<NodeType,LinkType> extends React.Component<GraphProps<NodeTyp
       .force("y", d3.forceY().strength(props.forces.center))
   }
 
-  createNode = (node: d3Types.d3Node<NodeType>) => {
-    this.setState((prevState: GraphState<NodeType,LinkType>) => ({
-      nodes: [...prevState.nodes, node],
-    }));
-  }
-
-  deleteNode = (id) => {
-    this.setState((prevState: GraphState<NodeType,LinkType>) => {
-      const newLinks = prevState.links.filter(l => l.source.id !== id && l.target.id !== id);
-      return {
-        nodes: prevState.nodes.filter(n => n.id !== id),
-        links: newLinks,
-      };
-    });
-  }
-
-  createLink = ({source, label, target, obj}) => {
-    this.setState((prevState: GraphState<NodeType,LinkType>) => ({
-      links: [...prevState.links, {source, target, label, obj}],
-    }));
-  }
-
-  deleteLink = ({source, label}) => {
-    this.setState((prevState: GraphState<NodeType,LinkType>) => {
-      const newLinks = prevState.links.filter(l => l.source !== source || l.label !== label)
-      return {
-        links: newLinks,
-      };
-    });
-  } 
-
   render() {
     const { graph } = this.props;
 
@@ -333,9 +298,9 @@ export class Graph<NodeType,LinkType> extends React.Component<GraphProps<NodeTyp
           </marker>
         </defs>
 
-        <Links ref={this.refLinks} links={this.state.links} />
-        <Labels ref={this.refLabels} nodes={this.state.nodes} />
-        <Nodes ref={this.refNodes} nodes={this.state.nodes} simulation={this.simulation}
+        <Links ref={this.refLinks} links={graph.links} />
+        <Labels ref={this.refLabels} nodes={graph.nodes} />
+        <Nodes ref={this.refNodes} nodes={graph.nodes} simulation={this.simulation}
           mouseDownHandler={(e, node) => clientToSvgCoords(e, (e,coords) => this.props.mouseDownHandler(e,coords,node))}
           mouseUpHandler={(e, node) => clientToSvgCoords(e, (e,coords) => this.props.mouseUpHandler(e,coords,node))}
         />
@@ -345,8 +310,8 @@ export class Graph<NodeType,LinkType> extends React.Component<GraphProps<NodeTyp
   }
 
   update = () => {
-    this.simulation.nodes(this.state.nodes);
-    this.simulation.force("link").links(this.state.links);
+    this.simulation.nodes(this.props.graph.nodes);
+    this.simulation.force("link").links(this.props.graph.links);
   }
 
   ticked = () => {

+ 1 - 1
src/onion/composite_delta.ts

@@ -86,7 +86,7 @@ export class CompositeLevel {
     for (const delta of deltas) {
       hash.update(delta.getHash());
     }
-    const description = "["+deltas.map(d=>d.getDescription()).join(",")+"]";
+    const description = deltas.map(d=>d.getDescription()).join(",");
     const composite = new CompositeDelta(deltas, dependencies, typedDependencies, conflicts, hash.digest(), description);
 
     for (const delta of deltas) {

+ 2 - 2
src/onion/primitive_delta.ts

@@ -362,9 +362,9 @@ export class EdgeCreation implements Delta {
 
   getTypedDependencies(): Array<[NodeCreation, string]> {
     if (this.target.target === null) {
-      return [[this.source, "REQ(S)"]];
+      return [[this.source, "SRC"]];
     } else {
-      return [[this.source, "REQ(S)"], [this.target.target, "REQ(T)"]];
+      return [[this.source, "SRC"], [this.target.target, "TGT"]];
     }
   }