فهرست منبع

WIP: Frontend uses TrivialParser to parse CS -> Corr + AS

Joeri Exelmans 2 سال پیش
والد
کامیت
6836dbcbc9

+ 277 - 182
src/frontend/app.tsx

@@ -16,7 +16,7 @@ import {Delta} from "../onion/delta";
 import {TrivialParser} from "../parser/trivial_parser";
 
 import {d3Types, Graph} from "./graph"
-import {EditableGraph, GraphType, NodeType, LinkType} from "./editable_graph";
+import {EditableGraph, UserEditCallback, SetNodePositionCallback, GraphType, NodeType, LinkType} from "./editable_graph";
 import {D3StateManipulator} from "./d3_state_manipulator";
 import {RountangleEditor} from "./rountangleEditor/RountangleEditor";
 import {
@@ -58,53 +58,32 @@ interface VersionedModelState {
   historyGraph: HistoryGraphType; // the state of what is displayed in the middle panel
   dependencyGraphL1: DependencyGraphType; // the state of what is displayed in the rightmost panel
   dependencyGraphL0: DependencyGraphType; // the state of what is displayed in the rightmost panel
+  manipulator: D3StateManipulator;
+  graphDeltaExecutor: GraphDeltaExecutor;
+  versionRegistry: VersionRegistry;
+  compositeLevel: CompositeLevel;
 }
 
-const initialModel: VersionedModelState = {
-  version: initialVersion,
-  graph: emptyGraph,
-  historyGraph: initialHistoryGraph,
-  dependencyGraphL1: emptyGraph,
-  dependencyGraphL0: emptyGraph,
-};
-
 export interface VersionedModelProps {
   title: string;
   generateUUID: () => UUID;
+  // compositeLevel: CompositeLevel;
 
   readonly?: boolean;
 
+  onUserEdit?: UserEditCallback;
+  setNextNodePosition: SetNodePositionCallback;
+
   state: VersionedModelState;
   setState: (callback: (VersionedModelState) => VersionedModelState) => void;
 }
 
 class VersionedModel extends React.Component<VersionedModelProps, {}> {
-  readonly manipulator: D3StateManipulator;
-  readonly graphDeltaExecutor: GraphDeltaExecutor;
-  readonly compositeLevel: CompositeLevel;
-  readonly versionRegistry: VersionRegistry;
-
   readonly textarearef: React.RefObject<HTMLTextAreaElement>;
 
   constructor(props) {
     super(props);
-
-    console.log(props);
-
-    // 'Glue' callback
-    const setGraph = (callback: (GraphType) => GraphType) => {
-      this.props.setState(({graph, ...rest}) => ({
-        graph: callback(graph),
-        ...rest,
-      }));
-    };
-
     this.textarearef = React.createRef();
-
-    this.manipulator = new D3StateManipulator(setGraph);
-    this.graphDeltaExecutor = new GraphDeltaExecutor(this.manipulator);
-    this.compositeLevel = new CompositeLevel();
-    this.versionRegistry = new VersionRegistry();
   }
 
   render() {
@@ -122,8 +101,9 @@ class VersionedModel extends React.Component<VersionedModelProps, {}> {
             throw new Error("Could not find path to clicked version!");
           }
 
-          this.manipulator.x = 0;
-          this.manipulator.y = 0;
+          // this.manipulator.x = 0;
+          // this.manipulator.y = 0;
+          this.props.setNextNodePosition(0,0);
 
           for (const [linkType, delta] of path) {
             if (linkType === 'p') {
@@ -144,92 +124,90 @@ class VersionedModel extends React.Component<VersionedModelProps, {}> {
     }
 
     const undo = deltaToUndo => {
-      this.manipulator.x = 0;
-      this.manipulator.y = 0;
-      this.graphDeltaExecutor.unexec(deltaToUndo);
-      this.props.setState(({dependencyGraphL0: prevDepGraphL0, dependencyGraphL1: prevDepGraphL1, ...rest}) => ({
-        dependencyGraphL1: setDeltaInactive(prevDepGraphL1, deltaToUndo),
-        dependencyGraphL0: deltaToUndo.deltas.reduce((prevDepGraphL0, delta) => setDeltaInactive(prevDepGraphL0, delta), prevDepGraphL0),
-        ...rest,
-      }));
+      // this.props.setNextNodePosition(0,0);
+      // this.graphDeltaExecutor.unexec(deltaToUndo);
+      // this.props.setState(({dependencyGraphL0: prevDepGraphL0, dependencyGraphL1: prevDepGraphL1, ...rest}) => ({
+      //   dependencyGraphL1: setDeltaInactive(prevDepGraphL1, deltaToUndo),
+      //   dependencyGraphL0: deltaToUndo.deltas.reduce((prevDepGraphL0, delta) => setDeltaInactive(prevDepGraphL0, delta), prevDepGraphL0),
+      //   ...rest,
+      // }));
     };
     const redo = deltaToRedo => {
-      this.manipulator.x = 0;
-      this.manipulator.y = 0;
-      this.graphDeltaExecutor.exec(deltaToRedo);
-      this.props.setState(({dependencyGraphL0: prevDepGraphL0, dependencyGraphL1: prevDepGraphL1, ...rest}) => ({
-        dependencyGraphL1: setDeltaActive(prevDepGraphL1, deltaToRedo),
-        dependencyGraphL0: deltaToRedo.deltas.reduce((prevDepGraphL0, delta) => setDeltaActive(prevDepGraphL0, delta), prevDepGraphL0),
-        ...rest,
-      }));
+      // this.props.setNextNodePosition(0,0);
+      // this.graphDeltaExecutor.exec(deltaToRedo);
+      // this.props.setState(({dependencyGraphL0: prevDepGraphL0, dependencyGraphL1: prevDepGraphL1, ...rest}) => ({
+      //   dependencyGraphL1: setDeltaActive(prevDepGraphL1, deltaToRedo),
+      //   dependencyGraphL0: deltaToRedo.deltas.reduce((prevDepGraphL0, delta) => setDeltaActive(prevDepGraphL0, delta), prevDepGraphL0),
+      //   ...rest,
+      // }));
     };
 
     const onUndoClicked = (parentVersion, deltaToUndo) => {
-      undo(deltaToUndo);
-      this.props.setState(({historyGraph: prevHistoryGraph, version: prevVersion, ...rest}) => ({
-        version: parentVersion,
-        historyGraph: setCurrentVersion(prevHistoryGraph, prevVersion, parentVersion),
-        ...rest,
-      }));
+      // undo(deltaToUndo);
+      // this.props.setState(({historyGraph: prevHistoryGraph, version: prevVersion, ...rest}) => ({
+      //   version: parentVersion,
+      //   historyGraph: setCurrentVersion(prevHistoryGraph, prevVersion, parentVersion),
+      //   ...rest,
+      // }));
     };
     const onRedoClicked = (childVersion, deltaToRedo) => {
-      redo(deltaToRedo);
-      this.props.setState(({historyGraph: prevHistoryGraph, version: prevVersion, ...rest}) => ({
-        version: childVersion,
-        historyGraph: setCurrentVersion(prevHistoryGraph, prevVersion, childVersion),
-        ...rest,
-      }));
+      // redo(deltaToRedo);
+      // this.props.setState(({historyGraph: prevHistoryGraph, version: prevVersion, ...rest}) => ({
+      //   version: childVersion,
+      //   historyGraph: setCurrentVersion(prevHistoryGraph, prevVersion, childVersion),
+      //   ...rest,
+      // }));
     };
 
 
     const onMergeClicked = () => {
-      const text = this.textarearef.current?.value;
-      if (text) {
-        const lines = text.split('\n');
-
-        const versionIds: Buffer[] = lines.map(s => {
-          try {
-            return Buffer.from(s, 'base64');
-          } catch (e) {
-            return Buffer.alloc(0);
-          }
-        }).filter(buf => buf.length !== 0);
-
-        let versionsToMerge;
-        try {
-          versionsToMerge = versionIds.map((buf: Buffer) => {
-            return this.versionRegistry.lookup(buf);
-          });
-        } catch (e) {
-          alert("Input error:" + e.toString());
-          return;
-        }
-
-        const mergedVersions = this.versionRegistry.merge(versionsToMerge);
-
-        console.log("mergedVersions:", mergedVersions);
-
-        const add = v => {
-          this.props.setState(({historyGraph: prevHistoryGraph, ...rest}) => {
-            return {
-              historyGraph: appendToHistoryGraph(prevHistoryGraph, v),
-              ...rest,
-            };
-          });
-        };
-
-        // add all mergedVersions (and their parents) to history graph:
-        // this may be overkill (and not so scalable), but it does the job :)
-        mergedVersions.forEach(v => {
-          const addParents = v => {
-            v.parents.forEach(([v]) => {
-              addParents(v);
-            });
-            add(v);
-          }
-          addParents(v);
-        });
-      }
+      // const text = this.textarearef.current?.value;
+      // if (text) {
+      //   const lines = text.split('\n');
+
+      //   const versionIds: Buffer[] = lines.map(s => {
+      //     try {
+      //       return Buffer.from(s, 'base64');
+      //     } catch (e) {
+      //       return Buffer.alloc(0);
+      //     }
+      //   }).filter(buf => buf.length !== 0);
+
+      //   let versionsToMerge;
+      //   try {
+      //     versionsToMerge = versionIds.map((buf: Buffer) => {
+      //       return this.props.versionRegistry.lookup(buf);
+      //     });
+      //   } catch (e) {
+      //     alert("Input error:" + e.toString());
+      //     return;
+      //   }
+
+      //   const mergedVersions = this.props.versionRegistry.merge(versionsToMerge);
+
+      //   console.log("mergedVersions:", mergedVersions);
+
+      //   const add = v => {
+      //     this.props.setState(({historyGraph: prevHistoryGraph, ...rest}) => {
+      //       return {
+      //         historyGraph: appendToHistoryGraph(prevHistoryGraph, v),
+      //         ...rest,
+      //       };
+      //     });
+      //   };
+
+      //   // add all mergedVersions (and their parents) to history graph:
+      //   // this may be overkill (and not so scalable), but it does the job :)
+      //   mergedVersions.forEach(v => {
+      //     const addParents = v => {
+      //       v.parents.forEach(([v]) => {
+      //         addParents(v);
+      //       });
+      //       add(v);
+      //     }
+      //     addParents(v);
+      //   });
+      // }
     }
 
     function makeInfoHoverCardIcon(contents) {
@@ -247,46 +225,22 @@ class VersionedModel extends React.Component<VersionedModelProps, {}> {
       );
     }
 
-    const onUserEdit = (deltas: PrimitiveDelta[], description: string) => {
-      const composite = this.compositeLevel.createCompositeWithCustomDescription(deltas, description);
-      const newVersion = this.versionRegistry.createVersion(this.props.state.version, composite);
-
-      this.props.setState(({historyGraph: prevHistoryGraph, version: prevVersion, dependencyGraphL0: prevDepGraphL0, dependencyGraphL1: prevDepGraphL1, ...rest}) => {
-        return {
-          version: newVersion,
-
-          // add new version to history graph + highlight the new version as the current version:
-          historyGraph: setCurrentVersion(appendToHistoryGraph(prevHistoryGraph, newVersion), prevVersion, newVersion),
-
-          // add the composite delta to the L1-graph + highlight it as 'active':
-          dependencyGraphL1: addDeltaAndActivate(prevDepGraphL1, composite),
-
-          // add the primitive L0-deltas to the L0-graph + highlight them as 'active':
-          dependencyGraphL0: composite.deltas.reduce((prevDepGraphL0, delta) => addDeltaAndActivate(prevDepGraphL0, delta), prevDepGraphL0),
-
-          ...rest,
-        };
-      });
-
-      this.graphDeltaExecutor.exec(composite);
-    };
-
     const makeConcreteSyntaxTabs = defaultTab => (
       <Tabs defaultValue={defaultTab}>
         {this.props.readonly?(
           <Tabs.List>
-            <Tabs.Tab value="state">Graph State</Tabs.Tab>
-            <Tabs.Tab value="history">History Graph</Tabs.Tab>
-            <Tabs.Tab value="dependencyL1">Delta Graph (L1)</Tabs.Tab>
-            <Tabs.Tab value="dependencyL0">Delta Graph (L0)</Tabs.Tab>
+            <Tabs.Tab value="state">State</Tabs.Tab>
+            <Tabs.Tab value="history">History</Tabs.Tab>
+            <Tabs.Tab value="dependencyL1">Deltas (L1)</Tabs.Tab>
+            <Tabs.Tab value="dependencyL0">Deltas (L0)</Tabs.Tab>
           </Tabs.List>
         ):(
           <Tabs.List>
             <Tabs.Tab value="editor">Editor</Tabs.Tab>
-            <Tabs.Tab value="state">Graph State</Tabs.Tab>
-            <Tabs.Tab value="history">History Graph</Tabs.Tab>
-            <Tabs.Tab value="dependencyL1">Delta Graph (L1)</Tabs.Tab>
-            <Tabs.Tab value="dependencyL0">Delta Graph (L0)</Tabs.Tab>
+            <Tabs.Tab value="state">State</Tabs.Tab>
+            <Tabs.Tab value="history">History</Tabs.Tab>
+            <Tabs.Tab value="dependencyL1">Deltas (L1)</Tabs.Tab>
+            <Tabs.Tab value="dependencyL0">Deltas (L0)</Tabs.Tab>
           </Tabs.List>
         )}
 
@@ -302,11 +256,11 @@ class VersionedModel extends React.Component<VersionedModelProps, {}> {
           ):(
             <EditableGraph
               graph={this.props.state.graph}
-              graphDeltaExecutor={this.graphDeltaExecutor}
+              graphDeltaExecutor={this.props.state.graphDeltaExecutor}
               forces={historyGraphForces}
               generateUUID={this.props.generateUUID}
-              setNextNodePosition={(x,y)=>{this.manipulator.x = x; this.manipulator.y = y;}}
-              onUserEdit={onUserEdit} />
+              setNextNodePosition={this.props.setNextNodePosition}
+              onUserEdit={this.props.onUserEdit} />
           )}
         </Tabs.Panel>
 
@@ -317,8 +271,8 @@ class VersionedModel extends React.Component<VersionedModelProps, {}> {
           <RountangleEditor
             graph={this.props.state.graph}
             generateUUID={this.props.generateUUID}
-            onUserEdit={onUserEdit}
-            graphDeltaExecutor={this.graphDeltaExecutor} />
+            onUserEdit={this.props.onUserEdit}
+            graphDeltaExecutor={this.props.state.graphDeltaExecutor} />
         </Tabs.Panel>
 
         <Tabs.Panel value="dependencyL1">
@@ -349,16 +303,18 @@ class VersionedModel extends React.Component<VersionedModelProps, {}> {
       <>
         <Title order={4}>{this.props.title}</Title>
         <Stack>
-          {makeConcreteSyntaxTabs(this.props.readonly?"state":"editor")}
-          {makeConcreteSyntaxTabs("history")}
+          {/*{makeConcreteSyntaxTabs(this.props.readonly?"state":"editor")}*/}
+          {/*{makeConcreteSyntaxTabs("history")}*/}
+          {makeConcreteSyntaxTabs("state")}
+          {makeConcreteSyntaxTabs("dependencyL0")}
         </Stack>
 
-        <Textarea size="xs" label="Versions to merge:" ref={this.textarearef}
+{/*        <Textarea size="xs" label="Versions to merge:" ref={this.textarearef}
           autosize placeholder="Right-click on version in History Graph to add it to this textbox"></Textarea>
         <Button onClick={() => {if (this.textarearef.current) this.textarearef.current.value = "";}}>CLEAR</Button>
         &nbsp;
         <Button onClick={onMergeClicked}>MERGE</Button>
-      </>
+*/}      </>
     );
   }
 }
@@ -369,49 +325,133 @@ interface AppState {
   as: VersionedModelState;
 }
 
-export function makeApp() {
-  const generateUUID = mockUuid();
+// const initialModel: VersionedModelState = 
+
+
+export class App extends React.Component<{}, AppState> {
+  generateUUID = mockUuid();
+  // csLvl = new CompositeLevel();
+  // corrLvl = new CompositeLevel();
+  // asLvl = new CompositeLevel();
+  // versionRegistry = new VersionRegistry();
+  parser: TrivialParser;
+
+  setCsState(callback) {
+    return this.setState(({cs, ...rest}) => ({cs: callback(cs), ...rest}));
+  }
+  setCorrState(callback) {
+    return this.setState(({corr, ...rest}) => ({corr: callback(corr), ...rest}));
+  }
+  setAsState(callback) {
+    return this.setState(({as, ...rest}) => ({as: callback(as), ...rest}));
+  }
+
+  constructor(props) {
+    super(props);
+
+    const makeModelState = (setState) => {
+      // 'Glue' callback
+      const setGraph = (callback: (GraphType) => GraphType) => {
+        setState(({graph, ...rest}) => ({
+          graph: callback(graph),
+          ...rest,
+        }));
+      };
+      const manipulator = new D3StateManipulator(setGraph);
+      return {
+        version: initialVersion,
+        graph: emptyGraph,
+        historyGraph: initialHistoryGraph,
+        dependencyGraphL1: emptyGraph,
+        dependencyGraphL0: emptyGraph,
+        compositeLevel: new CompositeLevel(),
+        versionRegistry: new VersionRegistry(),
+        manipulator,
+        graphDeltaExecutor: new GraphDeltaExecutor(manipulator),
+      };
+    }
+
+    this.state = {
+      cs: makeModelState(this.setCsState.bind(this)),
+      corr: makeModelState(this.setCorrState.bind(this)),
+      as: makeModelState(this.setAsState.bind(this)),
+    };
+
+    this.parser = new TrivialParser(this.generateUUID, this.state.corr.versionRegistry,
+      {
+        csLvl: this.state.cs.compositeLevel,
+        asLvl: this.state.as.compositeLevel,
+        corrLvl: this.state.corr.compositeLevel,
+      });
+  }
+
+  render() {
+    // const execChanges = ({version, historyGraph, dependencyGraphL0, dependencyGraphL1, ...rest}: VersionedModelState, newVersion: Version, composite: CompositeDelta) => {
+    const execChanges = ({version, historyGraph, dependencyGraphL1, dependencyGraphL0, ...rest}: VersionedModelState, newVersion: Version, composite: CompositeDelta) => {
+      return {
+        version: newVersion,
 
-  // functional component:
-  return  function App() {
-    const [cs, setCs] = React.useState<VersionedModelState>(initialModel);
-    const [corr, setCorr] = React.useState<VersionedModelState>(initialModel);
-    const [as, setAs] = React.useState<VersionedModelState>(initialModel);
+        // // add new version to history graph + highlight the new version as the current version:
+        historyGraph: setCurrentVersion(appendToHistoryGraph(historyGraph, newVersion), version, newVersion),
 
-    const [corrVersion, setCorrVersion] = React.useState<Version>(initialVersion);
+        // // add the composite delta to the L1-graph + highlight it as 'active':
+        dependencyGraphL1: addDeltaAndActivate(dependencyGraphL1, composite),
 
-    const handleNewCsVersion = (cs: Version) => {
-      
+        // // add the primitive L0-deltas to the L0-graph + highlight them as 'active':
+        dependencyGraphL0: composite.deltas.reduce(
+          (graph, delta) => {
+            return addDeltaAndActivate(graph, delta);
+          }, dependencyGraphL0),
+
+        ...rest,
+      };
     };
 
-    // const undoButtons = corrVersion.parents.map(([parentVersion,deltaToUndo]) => {
-    //   return (
-    //     <div key={fullVersionId(parentVersion)}>
-    //       <Button fullWidth={true} compact={true} leftIcon={<IconPlayerTrackPrev size={18}/>} onClick={onUndoClicked.bind(null, parentVersion,deltaToUndo)}>
-    //         UNDO {deltaToUndo.getDescription()}
-    //       </Button>
-    //       <Space h="xs"/>
-    //     </div>
-    //   );
-    // });
-    // const redoButtons = corrVersion.children.map(([childVersion,deltaToRedo]) => {
-    //   return (
-    //     <div key={fullVersionId(childVersion)}>
-    //       <Button style={{width: "100%"}} compact={true} rightIcon={<IconPlayerTrackNext size={18}/>} onClick={onRedoClicked.bind(null, childVersion,deltaToRedo)}>
-    //         REDO {deltaToRedo.getDescription()}
-    //       </Button>
-    //       <Space h="xs"/>
-    //     </div>
-    //   );
-    // });
+    const onCsEdit = (deltas: PrimitiveDelta[], description: string) => {
+      const csComposite = this.state.cs.compositeLevel.createComposite(deltas, description);
+      const newCsVersion = this.state.cs.versionRegistry.createVersion(this.state.cs.version, csComposite);
+
+      const newCorrVersion = this.parser.parse(newCsVersion, this.state.corr.version);
+      const corrComposite = newCorrVersion.parents.find(([parentVersion, d]) => parentVersion === this.state.corr.version)?.[1] as CompositeDelta;
+      const newAsVersion = newCorrVersion.getEmbedded("as")?.embedded as Version;
+      const asComposite = newAsVersion!.parents.find(([parentVersion, d]) => parentVersion === this.state.as.version)?.[1] as CompositeDelta;
+
+      this.state.cs.graphDeltaExecutor.exec(csComposite);
+      this.state.corr.graphDeltaExecutor.exec(corrComposite);
+      this.state.as.graphDeltaExecutor.exec(asComposite);
+
+      // Update state:
+      this.setState(({cs, corr, as}) => {
+        const result = {
+          cs: execChanges(cs, newCsVersion, csComposite),
+          corr: execChanges(corr, newCorrVersion, corrComposite),
+          as: execChanges(as, newAsVersion, asComposite),
+        };
+        return result;
+      });
+    };
 
     return (<>
       <Grid grow>
         <Grid.Col span={1}>
-          <VersionedModel title="Concrete Syntax" generateUUID={generateUUID} state={cs} setState={setCs} />
+          <VersionedModel title="Concrete Syntax"
+            generateUUID={this.generateUUID}
+            // compositeLevel={this.state.cs.compositeLevel}
+            // versionRegistry={this.state.cs.versionRegistry}
+            state={this.state.cs}
+            setState={this.setCsState.bind(this)}
+            setNextNodePosition={(x,y)=>{this.state.cs.manipulator.x = x; this.state.cs.manipulator.y = y;}}
+            // onNewVersion={handleNewCsVersion}
+            onUserEdit={onCsEdit} />
         </Grid.Col>
         <Grid.Col span={1}>
-          <VersionedModel title="Correspondence" generateUUID={generateUUID} state={corr} setState={setCorr} readonly />
+          <VersionedModel title="Correspondence" readonly
+            generateUUID={this.generateUUID}
+            // compositeLevel={this.state.corr.compositeLevel}
+            // versionRegistry={this.state.corr.versionRegistry}
+            setNextNodePosition={(x,y)=>{this.state.corr.manipulator.x = x; this.state.corr.manipulator.y = y;}}
+            state={this.state.corr}
+            setState={this.setCorrState.bind(this)} />
 
   {/*        <SimpleGrid cols={2}>
             <div>
@@ -424,10 +464,65 @@ export function makeApp() {
   */}
         </Grid.Col>
         <Grid.Col span={1}>
-          <VersionedModel title="Abstract Syntax" generateUUID={generateUUID} state={as} setState={setAs} />
+          <VersionedModel title="Abstract Syntax"
+            generateUUID={this.generateUUID}
+            // compositeLevel={this.state.as.compositeLevel}
+            // versionRegistry={this.state.as.versionRegistry}
+            setNextNodePosition={(x,y)=>{this.state.as.manipulator.x = x; this.state.as.manipulator.y = y;}}
+            state={this.state.as}
+            setState={this.setAsState.bind(this)}
+          />
         </Grid.Col>
       </Grid>
     </>);
-  };
+  }
 }
 
+// export function makeApp() {
+//   const generateUUID = mockUuid();
+
+//   function createModelState() {
+//     const [state, setState] = React.useState
+//     return {
+
+//       lvl: new CompositeLevel(),
+//       registry: new VersionRegistry(),
+//     }
+//   }
+
+
+
+
+//   // functional component:
+//   return  function App() {
+//     const [cs, setCs] = React.useState<VersionedModelState>(initialModel);
+//     const [corr, setCorr] = React.useState<VersionedModelState>(initialModel);
+//     const [as, setAs] = React.useState<VersionedModelState>(initialModel);
+
+//     const [corrVersion, setCorrVersion] = React.useState<Version>(initialVersion);
+
+
+//     // const undoButtons = corrVersion.parents.map(([parentVersion,deltaToUndo]) => {
+//     //   return (
+//     //     <div key={fullVersionId(parentVersion)}>
+//     //       <Button fullWidth={true} compact={true} leftIcon={<IconPlayerTrackPrev size={18}/>} onClick={onUndoClicked.bind(null, parentVersion,deltaToUndo)}>
+//     //         UNDO {deltaToUndo.getDescription()}
+//     //       </Button>
+//     //       <Space h="xs"/>
+//     //     </div>
+//     //   );
+//     // });
+//     // const redoButtons = corrVersion.children.map(([childVersion,deltaToRedo]) => {
+//     //   return (
+//     //     <div key={fullVersionId(childVersion)}>
+//     //       <Button style={{width: "100%"}} compact={true} rightIcon={<IconPlayerTrackNext size={18}/>} onClick={onRedoClicked.bind(null, childVersion,deltaToRedo)}>
+//     //         REDO {deltaToRedo.getDescription()}
+//     //       </Button>
+//     //       <Space h="xs"/>
+//     //     </div>
+//     //   );
+//     // });
+
+//   };
+// }
+

+ 1 - 1
src/frontend/app_state.ts

@@ -162,7 +162,7 @@ export function addDeltaAndActivate(prevDepGraph: DependencyGraphType, delta: De
     // for every dependency and conflict, add a link:
     links: prevDepGraph.links.concat(
         ...delta.getTypedDependencies().map(([dep,depSummary]) => dependencyToDepGraphLink(delta,dep,depSummary)),
-        ...delta.getConflicts().map(conflictingDelta => conflictToDepGraphLink(delta,conflictingDelta)),
+        ...delta.getConflicts().filter(conflictingDelta => prevDepGraph.nodes.some(n => n.id === fullDeltaId(conflictingDelta))).map(conflictingDelta => conflictToDepGraphLink(delta,conflictingDelta)),
       ),
   };
 }

+ 6 - 3
src/frontend/editable_graph.tsx

@@ -24,13 +24,16 @@ export type NodeType = d3Types.d3Node<NodeState|ValueState>;
 export type LinkType = d3Types.d3Link<null>;
 export type GraphType = d3Types.d3Graph<NodeState|ValueState,null>;
 
+export type UserEditCallback = (deltas: PrimitiveDelta[], description: string) => void;
+export type SetNodePositionCallback = (x:number, y:number) => void;
+
 export interface EditableGraphProps {
   graph: GraphType;
   graphDeltaExecutor: GraphDeltaExecutor;
   forces: Forces;
   generateUUID: () => UUID;
-  setNextNodePosition: (x:number, y:number) => void;
-  onUserEdit: (deltas: PrimitiveDelta[], description: string) => void;
+  setNextNodePosition: SetNodePositionCallback;
+  onUserEdit?: UserEditCallback;
 }
 
 interface EditableGraphState {
@@ -115,7 +118,7 @@ export class EditableGraph extends React.Component<EditableGraphProps, EditableG
       // const composite = this.props.compositeLvl.createComposite(deltas);
       // const version = this.props.versionRegistry.createVersionUnsafe(this.props.version, composite);
 
-      this.props.onUserEdit(deltas, deltas.map(d => d.getDescription()).join(","));
+      this.props.onUserEdit?.(deltas, deltas.map(d => d.getDescription()).join(","));
     }
 
     this.mouseDownNode = null;

+ 2 - 2
src/frontend/graph.tsx

@@ -323,8 +323,8 @@ export class Graph<NodeType,LinkType> extends React.Component<GraphProps<NodeTyp
         ref={this.refSVG}
         style={{width: "100%", height}}
         viewBox={`${-height/2/this.state.zoom} ${-height/2/this.state.zoom} ${height/this.state.zoom} ${height/this.state.zoom}`}
-        onMouseDown={e => clientToSvgCoords(e, this.props.mouseDownHandler)}
-        onMouseUp={e => clientToSvgCoords(e, this.props.mouseUpHandler)}
+        onMouseDown={e => this.props.mouseDownHandler ? clientToSvgCoords(e, this.props.mouseDownHandler) : null}
+        onMouseUp={e => this.props.mouseUpHandler ? clientToSvgCoords(e, this.props.mouseUpHandler) : null}
         onContextMenu={e => e.preventDefault()}
         onWheel={e => {
           // cannot use preventDefault here :( see workaround in componentDidMount/componentWillUnmount.

+ 1 - 3
src/frontend/index.tsx

@@ -4,13 +4,11 @@ import './rountangleEditor/RountangleEditor.css';
 import './graph.css';
 import './app.css';
 
-import {makeApp} from "./app";
+import {App} from "./app";
 
 const container = document.getElementById('root');
 const root = createRoot(container!);
 
-const App = makeApp();
-
 root.render(
   <React.StrictMode>
     <App/>

+ 2 - 2
src/frontend/rountangleEditor/RountangleEditor.tsx

@@ -83,7 +83,7 @@ export interface RountangleEditorProps {
   graph: GraphType;
   graphDeltaExecutor: GraphDeltaExecutor;
   generateUUID: () => UUID;
-  onUserEdit: (deltas: PrimitiveDelta[], description: string) => void;
+  onUserEdit?: (deltas: PrimitiveDelta[], description: string) => void;
 }
 
 export class RountangleEditor extends React.Component<RountangleEditorProps, {}> {
@@ -166,7 +166,7 @@ export class RountangleEditor extends React.Component<RountangleEditorProps, {}>
             // AR: WARNING: "createVersionUnsafe" is probably not safe?!
             // JE: differenc between safe and unsafe: unsafe does not check some assertions, but it is ok in this case, by JE does not know why anymore.
             // const version = this.props.versionRegistry.createVersionUnsafe(this.props.version, composite);
-            this.props.onUserEdit(deltas, action.tag);
+            this.props.onUserEdit?.(deltas, action.tag);
         }
 
         this.onDispatchListeners.forEach(listener => listener(action));

+ 2 - 7
src/onion/composite_delta.ts

@@ -58,12 +58,7 @@ export class CompositeDelta implements Delta {
 export class CompositeLevel {
   containedBy: Map<Delta, CompositeDelta> = new Map();
 
-  createComposite(deltas: Array<Delta>): CompositeDelta {
-    const description = deltas.map(d=>d.getDescription()).join(",");
-    return this.createCompositeWithCustomDescription(deltas, description);
-  }
-
-  createCompositeWithCustomDescription(deltas: Array<Delta>, description: string): CompositeDelta {
+  createComposite(deltas: Array<Delta>, description: string = deltas.map(d=>d.getDescription()).join(",")): CompositeDelta {
     const dependencies: Array<CompositeDelta> = [];
     const typedDependencies: Array<[CompositeDelta,string]> = [];
     const conflicts: Array<CompositeDelta> = [];
@@ -77,7 +72,7 @@ export class CompositeLevel {
           // We got ourselves an inter-composite dependency.
           const compositeDependency = this.containedBy.get(dependency);
           if (compositeDependency === undefined) {
-            throw new Error("Assertion failed: cannot find composite of " + dependency.getDescription());
+            throw new Error("Assertion failed: delta " + delta.getDescription() + " depends on " + dependency.getDescription() + " but this dependency could not be found in a composite.");
           }
           const existingDependency = typedDependencies.find(([dep,_]) => dep === compositeDependency);
           if (existingDependency !== undefined) {

+ 0 - 1
src/onion/delta_executor.ts

@@ -120,7 +120,6 @@ export class NodeState extends NodeOrValueState {
   readonly creation: NodeCreation;
 
   readonly outgoing: Map<string, EdgeCreation|EdgeUpdate> = new Map();
-  // readonly incoming: Array<EdgeCreation|EdgeUpdate|NodeDeletion> = [];
 
   constructor(creation: NodeCreation) {
     super();

+ 12 - 4
src/onion/primitive_delta.ts

@@ -13,6 +13,7 @@ export interface PrimitiveDelta extends Delta {
 export class NodeCreation implements PrimitiveDelta {
   readonly id: UUID;
   readonly hash: Buffer;
+  readonly description: string;
 
   // Inverse dependency: Deletions of this node.
   deletions: Array<NodeDeletion> = []; // append-only
@@ -28,6 +29,7 @@ export class NodeCreation implements PrimitiveDelta {
     this.hash = createHash('sha256')
       .update(JSON.stringify(this.id.value)) // prevent collisions between 'true' (actual boolean) and '"true"' (string "true"), or 42 (number) and "42" (string)
       .digest();
+    this.description = "NEW("+this.id.value.toString().slice(0,8)+")";
   }
 
   getDependencies(): [] {
@@ -47,7 +49,7 @@ export class NodeCreation implements PrimitiveDelta {
   }
 
   getDescription(): string {
-    return "NEW("+this.id.value.toString().slice(0,8)+")";
+    return this.description;
   }
 
   // pretty print to console under NodeJS
@@ -73,6 +75,7 @@ export class NodeCreation implements PrimitiveDelta {
 
 export class NodeDeletion implements PrimitiveDelta {
   readonly hash: Buffer;
+  readonly description: string;
 
   // Dependency: The node being deleted.
   readonly creation: NodeCreation;
@@ -155,6 +158,7 @@ export class NodeDeletion implements PrimitiveDelta {
       union = bufferXOR(union, afterIncoming.getHash());
     }
     this.hash = hash.update(union).digest();
+    this.description = "DEL("+this.creation.id.value.toString().slice(0,8)+")"
 
     // Detect conflicts
 
@@ -249,7 +253,7 @@ export class NodeDeletion implements PrimitiveDelta {
   }
 
   getDescription(): string {
-    return "DEL("+this.creation.id.value.toString().slice(0,8)+")";
+    return this.description;
   }
 
   // pretty print to console under NodeJS
@@ -402,6 +406,7 @@ export class EdgeCreation implements PrimitiveDelta {
   readonly target: SetsTarget;
 
   readonly hash: Buffer;
+  readonly description: string;
 
   // Inverse dependency
   // NodeDeletion if source of edge is deleted.
@@ -423,6 +428,7 @@ export class EdgeCreation implements PrimitiveDelta {
       .update('create').update(label)
       .update('target').update(this.target.getHash())
       .digest();
+    this.description = "EDG("+this.label+")";
 
     // Detect conflicts
 
@@ -476,7 +482,7 @@ export class EdgeCreation implements PrimitiveDelta {
   }
 
   getDescription(): string {
-    return "EDG("+this.label+")";
+    return this.description;
   }
 
   // pretty print to console under NodeJS
@@ -508,6 +514,7 @@ export class EdgeUpdate implements PrimitiveDelta {
   readonly target: SetsTarget;
 
   readonly hash: Buffer;
+  readonly description: string;
 
   // Inverse dependency
   // NodeDeletion if source of edge is deleted.
@@ -524,6 +531,7 @@ export class EdgeUpdate implements PrimitiveDelta {
       .update(overwrites.hash)
       .update('target=').update(newTarget instanceof NodeCreation ? newTarget.hash : JSON.stringify(newTarget))
       .digest();
+    this.description = "EDG";
 
     // Detect conflicts
 
@@ -563,7 +571,7 @@ export class EdgeUpdate implements PrimitiveDelta {
   }
 
   getDescription(): string {
-    return "EDG";
+    return this.description;
   }
 
   // pretty print to console under NodeJS

+ 14 - 12
src/onion/version.ts

@@ -195,7 +195,7 @@ export class VersionRegistry {
     // Check pre-condition 2:
     const conflictsWith = iterConflicts(delta, parent).next().value;
     if (conflictsWith !== undefined) {
-      throw new Error("Delta " + delta.toString() + " conflicts with " + conflictsWith.toString());
+      throw new Error("Delta " + delta.getDescription() + " conflicts with " + conflictsWith.getDescription());
     }
 
     const primitives = [...delta.iterPrimitiveDeltas()];
@@ -212,17 +212,19 @@ export class VersionRegistry {
       if (found === undefined) {
         throw new Error("Attempted to create illegal version that embeds a guest, whose parent is not embedded by the parent of the to-be-created version.");
       }
-      // Check pre-condition 5:
-      // console.log("primitives:", primitives);
-      const [parentOfEmbedded, embeddedDelta] = found;
-      for (const embeddedPrimitive of embeddedDelta.iterPrimitiveDeltas()) {
-        // console.log("embeddedPrimitive:", embeddedPrimitive);
-        const haveOriginal = primitives.includes(embeddedPrimitive);
-        const overridden = overriddenDeltas.get(embeddedPrimitive);
-        const haveOverridden = ((overridden !== undefined) && primitives.includes(overridden));
-        // console.log(haveOriginal, haveOverridden)
-        if (!haveOriginal && !haveOverridden || (haveOriginal && haveOverridden)) { // logical XOR
-          throw new Error("Attempt to create illegal version: must contain all deltas (some possibly overridden) of all embedded versions.");
+      else {
+        // Check pre-condition 5:
+        // console.log("primitives:", primitives);
+        const [parentOfEmbedded, embeddedDelta] = found;
+        for (const embeddedPrimitive of embeddedDelta.iterPrimitiveDeltas()) {
+          // console.log("embeddedPrimitive:", embeddedPrimitive);
+          const haveOriginal = primitives.includes(embeddedPrimitive);
+          const overridden = overriddenDeltas.get(embeddedPrimitive);
+          const haveOverridden = ((overridden !== undefined) && primitives.includes(overridden));
+          // console.log(haveOriginal, haveOverridden)
+          if (!haveOriginal && !haveOverridden || (haveOriginal && haveOverridden)) { // logical XOR
+            throw new Error("Attempt to create illegal version: must contain all deltas (some possibly overridden) of all embedded versions.");
+          }
         }
       }
     }

+ 1 - 1
src/parser/trivial_parser.test.ts

@@ -23,7 +23,7 @@ describe("Trivial Parser", () => {
     const asLvl = new CompositeLevel(); // L1
     const corrLvl = new CompositeLevel(); // L1
     const getUuid = mockUuid();
-    const parser = new TrivialParser(getUuid, registry, csLvl, asLvl, corrLvl);
+    const parser = new TrivialParser(getUuid, registry, {csLvl, asLvl, corrLvl});
 
     const csInitial = initialVersion;
     const corrInitial = initialVersion;

+ 29 - 15
src/parser/trivial_parser.ts

@@ -44,21 +44,24 @@ export class TrivialParser implements Parser {
   readonly asLvl: CompositeLevel;
   readonly corrLvl: CompositeLevel;
 
-  constructor(getUuid: () => UUID, registry: VersionRegistry, csLvl: CompositeLevel, asLvl: CompositeLevel, corrLvl: CompositeLevel) {
+  constructor(getUuid: () => UUID, registry: VersionRegistry,
+    compositeLvls: {csLvl: CompositeLevel, asLvl: CompositeLevel, corrLvl: CompositeLevel}) {
     this.getUuid = getUuid;
     this.registry = registry;
 
-    this.csLvl = csLvl;
-    this.asLvl = asLvl;
-    this.corrLvl = corrLvl;
+    this.csLvl = compositeLvls.csLvl;
+    this.asLvl = compositeLvls.asLvl;
+    this.corrLvl = compositeLvls.corrLvl;
   }
 
   parse(cs: Version, parentCorr: Version): Version {
-    const [csParent, lvl1CsDelta] = getParentLink(cs, parentCorr);
+    const override = (map) => (d => map.get(d) || d);
+
+    const [csParent, csComposite] = getParentLink(cs, parentCorr);
     const parentAs = parentCorr.getEmbedded("as");
 
-    if (!(lvl1CsDelta instanceof CompositeDelta)) {
-      throw new Error("Assertion failed: lvl1CsDelta must be CompositeDelta")
+    if (!(csComposite instanceof CompositeDelta)) {
+      throw new Error("Assertion failed: csComposite must be CompositeDelta")
     }
 
     if (parentAs === undefined) {
@@ -66,7 +69,7 @@ export class TrivialParser implements Parser {
     }
 
     // these are the new deltas in 'cs'
-    const csDeltas = [...lvl1CsDelta.iterPrimitiveDeltas()];
+    const csDeltas = [...csComposite.iterPrimitiveDeltas()];
     const parentCorrDeltas = new Set(parentCorr.iterPrimitiveDeltas());
 
     const asDeltas: Delta[] = [];
@@ -112,7 +115,8 @@ export class TrivialParser implements Parser {
         const asDeletion1 = new NodeDeletion(asCreation, [], [corrDeletion]);
 
         // We already have the deletion in the CS model, so we only need to create another one to be used in the CORR model:
-        const csDeletion1 = new NodeDeletion(csCreation, [], [corrDeletion]);
+        const csDeletion1 = new NodeDeletion(csCreation, csDelta.deletedOutgoingEdges, csDelta.afterIncomingEdges.concat(corrDeletion));
+        // const csDeletion1 = new NodeDeletion(csCreation, [], [corrDeletion]);
 
         csOverrides.set(csDelta, csDeletion1);
         asOverrides.set(asDeletion, asDeletion1);
@@ -122,20 +126,30 @@ export class TrivialParser implements Parser {
       }
     }
 
-    // New AS-version:
-    const asComposite = this.asLvl.createComposite(asDeltas);
-    const as = this.registry.createVersion(parentAs.embedded, asComposite);
+    const as = (() => {
+      // Check this later: Maybe not create a new AS version if asDeltas.length === 0 ?
+
+      // if (asDeltas.length > 0) {
+        // Create new AS-version:
+        const asComposite = this.asLvl.createComposite(asDeltas, "parse:"+csComposite.getDescription());
+        return this.registry.createVersion(parentAs.embedded, asComposite);
+      // } else {
+      //   // no new AS version:
+      //   return parentAs.embedded;
+      // }
+    })();
+
 
     // New CORR-version:
-    const csDeltas1 = csDeltas.map(d => csOverrides.get(d) || d);
-    const asDeltas1 = asDeltas.map(d => asOverrides.get(d) || d);
+    const csDeltas1 = csDeltas.map(override(csOverrides));
+    const asDeltas1 = asDeltas.map(override(asOverrides));
     // the order in which corr-deltas are put in corrComposite matters - deltas must occur after their dependencies:
     const orderedByDependency: Delta[] = [];
     visitPartialOrdering(
       [...csDeltas1, ...asDeltas1, ...corrDeltas],
       (d: Delta) => d.getDependencies(),
       (d: Delta) => orderedByDependency.push(d));
-    const corrComposite = this.corrLvl.createComposite(orderedByDependency);
+    const corrComposite = this.corrLvl.createComposite(orderedByDependency, "corr-parse:"+csComposite.getDescription());
     return this.registry.createVersion(parentCorr, corrComposite, embed(["cs", cs, csOverrides], ["as", as, asOverrides]));
   }
 }