Browse Source

Refactored frontend (great reduction in duplicated code!)

Joeri Exelmans 2 years ago
parent
commit
50f18b9963

+ 14 - 9
src/frontend/app.tsx

@@ -1,9 +1,9 @@
 import * as React from "react";
 
-import {DemoPD} from "./demo_pd";
-import {DemoCorr} from "./demo_corr";
+import {getDemoPD} from "./demo_pd";
+import {getDemoCorr} from "./demo_corr";
 
-import {Stack, Group, Text, Tabs, createStyles} from "@mantine/core";
+import {Stack, Group, Text, Title, Tabs, createStyles} from "@mantine/core";
 
 // const useStyles = createStyles(theme => ({
 //   root: {
@@ -16,16 +16,21 @@ import {Stack, Group, Text, Tabs, createStyles} from "@mantine/core";
 //   }
 // }));
 
+const DemoPD = getDemoPD();
+const DemoCorr = getDemoCorr();
+
 export function App(props) {
   // const {classes} = useStyles();
 
   return <>
-    <Text>Select a demo:</Text>
-    <Tabs orientation="vertical" defaultValue="pd">
-      <Tabs.List>
-        <Tabs.Tab value="pd">Primitive Delta</Tabs.Tab>
-        <Tabs.Tab value="corr">Correspondence</Tabs.Tab>
-      </Tabs.List>
+    <Tabs orientation="vertical">
+      <Stack>
+        <Text order={5}>Pick a demo:</Text>
+        <Tabs.List>
+          <Tabs.Tab value="pd">Primitive Delta</Tabs.Tab>
+          <Tabs.Tab value="corr">Correspondence</Tabs.Tab>
+        </Tabs.List>
+      </Stack>
       <Tabs.Panel value="pd">
         <DemoPD/>
       </Tabs.Panel>

+ 6 - 3
src/frontend/d3_state.ts

@@ -15,14 +15,17 @@ export class D3GraphStateUpdater implements GraphStateListener {
 
   // SVG coordinates for newly created nodes
   // This information cannot be part of our NodeCreation deltas, but it must come from somewhere...
-  x: number = 0;
-  y: number = 0;
+  x: number;
+  y: number;
 
-  constructor(setGraph: (cb: (prevGraph: GraphType) => GraphType) => void) {
+  constructor(setGraph: (cb: (prevGraph: GraphType) => GraphType) => void, x, y) {
     this.setGraph = setGraph;
+    this.x = x;
+    this.y = y;
   }
 
   createNode(ns: INodeState) {
+    console.log('create node')
     this.setGraph(prevGraph => ({
       nodes: [...prevGraph.nodes, {
         id: nodeNodeId(ns.creation.id.value),

+ 134 - 492
src/frontend/demo_corr.tsx

@@ -1,543 +1,185 @@
-import { Buffer } from "buffer"; // NodeJS library
-
 import * as React from "react";
-import {Grid, Text, Title, Group, Stack, SimpleGrid, Button, Space, Textarea, Tabs, HoverCard, ActionIcon} from "@mantine/core";
-import {IconPlayerTrackPrev, IconPlayerTrackNext, IconInfoCircle} from "@tabler/icons";
+import {Grid, Text, Title, Group, Stack, Button, Space, Textarea, Tabs, HoverCard, ActionIcon} from "@mantine/core";
 
-import {GraphState} from "../onion/graph_state"; 
-import {CompositeDelta, CompositeLevel} from "../onion/composite_delta";
-import {embed, Version, VersionRegistry} from "../onion/version";
-import {PrimitiveDelta, PrimitiveRegistry} from "../onion/primitive_delta";
-import {PrimitiveValue, UUID} from "../onion/types";
+import {PrimitiveRegistry} from "../onion/primitive_delta";
 import {mockUuid} from "../onion/test_helpers";
-import {Delta} from "../onion/delta";
 
+import {embed, Version, VersionRegistry} from "../onion/version";
+import {GraphState} from "../onion/graph_state"; 
 import {TrivialParser} from "../parser/trivial_parser";
-
-import {d3Types, Graph} from "./graph"
-import {EditableGraph, UserEditCallback, SetNodePositionCallback, GraphType, NodeType, LinkType} from "./editable_graph";
-import {D3GraphStateUpdater} from "./d3_state";
-import {emptyGraph, graphForces} from "./constants";
-import {RountangleEditor} from "./rountangleEditor/RountangleEditor";
-import {
-  HistoryGraphType,
-  DependencyGraphType,
-  versionToNode,
-  setCurrentVersion,
-  appendToHistoryGraph,
-  fullDeltaId,
-  setDeltaActive,
-  setDeltaInactive,
-  deltaToDepGraphNode,
-  dependencyToDepGraphLink,
-  conflictToDepGraphLink,
-  fullVersionId,
-  addDeltaAndActivate,
-  initialHistoryGraph,
-} from "./app_state";
-
 import {visitPartialOrdering} from "../util/partial_ordering";
 
-interface VersionedModelState {
-  version: Version; // the 'current version'
-  graph: GraphType; // the state what is displayed in the leftmost panel
-  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
-  graphState: GraphState;
-  versionRegistry: VersionRegistry;
-  compositeLevel: CompositeLevel;
-}
-
-export interface VersionedModelProps {
-  title: string;
-  generateUUID: () => UUID;
-  primitiveRegistry: PrimitiveRegistry;
-
-  readonly?: boolean;
-
-  onUserEdit?: UserEditCallback;
-  setNextNodePosition: SetNodePositionCallback;
-  onUndoClicked: (parentVersion: Version, deltaToUndo: Delta) => void;
-  onRedoClicked: (childVersion: Version, deltaToRedo: Delta) => void;
-  onVersionClicked: (Version) => void;
-
-  state: VersionedModelState;
-  setState: (callback: (VersionedModelState) => VersionedModelState) => void;
-
-  defaultTab0: string;
-  defaultTab1: string;
-  tabs: string[];
-}
-
-class VersionedModel extends React.Component<VersionedModelProps, {}> {
-  readonly textarearef: React.RefObject<HTMLTextAreaElement>;
-
-  constructor(props) {
-    super(props);
-    this.textarearef = React.createRef();
+import {
+  newVersionedModel,
+  VersionedModelState,
+} from "./versioned_model";
+import * as HelpIcons from "./help_icons";
+
+// Pure function
+// Replays all deltas in a version to compute the graph state of that version.
+function getGraphState(version: Version): GraphState {
+  const graphState = new GraphState();
+  for (const d of [...version].reverse()) {
+    graphState.exec(d);
   }
+  return graphState;
+}
 
-  render() {
-    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.props.versionRegistry.lookup(buf);
-      //     });
-      //   } catch (e) {
-      //     alert("Input error:" + e.toString());
-      //     return;
-      //   }
+export function getDemoCorr() {
+  const generateUUID = mockUuid();
+  const primitiveRegistry = new PrimitiveRegistry();
 
-      //   const mergedVersions = this.props.versionRegistry.merge(versionsToMerge);
+  const commonStuff = {generateUUID, primitiveRegistry};
 
-      //   console.log("mergedVersions:", mergedVersions);
+  const cs = newVersionedModel({readonly: false, ...commonStuff});
+  const corr = newVersionedModel({readonly: true, ...commonStuff});
+  const as = newVersionedModel({readonly: false, ...commonStuff});
 
-      //   const add = v => {
-      //     this.props.setState(({historyGraph: prevHistoryGraph, ...rest}) => {
-      //       return {
-      //         historyGraph: appendToHistoryGraph(prevHistoryGraph, v),
-      //         ...rest,
-      //       };
-      //     });
-      //   };
+  const parser = new TrivialParser(primitiveRegistry, generateUUID);
 
-      //   // 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) {
-      return (
-        <HoverCard shadow="md">
-          <HoverCard.Target>
-            <ActionIcon>
-              <IconInfoCircle size={18}/>
-            </ActionIcon>
-          </HoverCard.Target>
-          <HoverCard.Dropdown>
-            {contents}
-          </HoverCard.Dropdown>
-        </HoverCard>
-      );
-    }
+  const corrToCs: Map<Version,Version> = new Map([
+    [corr.initialState.version, cs.initialState.version]
+  ]);
+  const corrToAs: Map<Version,Version> = new Map([
+    [corr.initialState.version, as.initialState.version]
+  ]);
 
-    const makeTabs = (defaultTab, tabs) => (
-      <Tabs defaultValue={defaultTab} keepMounted={false}>
+  // returns functional react component
+  return function() {
+    const makeTabs = (components, defaultTab, tabs) => {
+      return <Tabs defaultValue={defaultTab} keepMounted={false}>
         <Tabs.List>
           {tabs.map(tab => {
             if (tab === "editor")
-              return <Tabs.Tab key={tab} value="editor">Editor</Tabs.Tab>;
+              return <Tabs.Tab key={tab} value={tab}>Editor</Tabs.Tab>;
             if (tab === "state")
-              return <Tabs.Tab key={tab} value="state">State</Tabs.Tab>;
+              return <Tabs.Tab key={tab} value={tab}>State</Tabs.Tab>;
             if (tab === "history")
-              return <Tabs.Tab key={tab} value="history">History</Tabs.Tab>;
+              return <Tabs.Tab key={tab} value={tab}>History</Tabs.Tab>;
             if (tab === "dependencyL1")
-              return <Tabs.Tab key={tab} value="dependencyL1">Deltas (L1)</Tabs.Tab>;
+              return <Tabs.Tab key={tab} value={tab}>Deltas (L1)</Tabs.Tab>;
             if (tab === "dependencyL0")
-              return <Tabs.Tab key={tab} value="dependencyL0">Deltas (L0)</Tabs.Tab>;
+              return <Tabs.Tab key={tab} value={tab}>Deltas (L0)</Tabs.Tab>;
           })}
         </Tabs.List>
-
         <Tabs.Panel value="state">
-          {makeInfoHoverCardIcon(<>
-            <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>
-          </>)}
-          {this.props.readonly?(
-            <Graph graph={this.props.state.graph} forces={graphForces} />
-          ):(
-            <EditableGraph
-              graph={this.props.state.graph}
-              graphState={this.props.state.graphState}
-              forces={graphForces}
-              generateUUID={this.props.generateUUID}
-              primitiveRegistry={this.props.primitiveRegistry}
-              setNextNodePosition={this.props.setNextNodePosition}
-              onUserEdit={this.props.onUserEdit} />
-          )}
+          {components.graphStateComponent}
         </Tabs.Panel>
-
         <Tabs.Panel value="editor">
-          {makeInfoHoverCardIcon(
-            <Text>New Rountangle by Alt + Click</Text>
-          )}
-          <RountangleEditor
-            graph={this.props.state.graph}
-            generateUUID={this.props.generateUUID}
-            primitiveRegistry={this.props.primitiveRegistry}
-            onUserEdit={this.props.onUserEdit}
-            graphState={this.props.state.graphState} />
+          {components.rountangleEditor}
         </Tabs.Panel>
-
         <Tabs.Panel value="dependencyL1">
-          {makeInfoHoverCardIcon(
-            <Text>Active deltas are bold.</Text>
-          )}
-          <Graph graph={this.props.state.dependencyGraphL1} forces={graphForces} />
+          {components.depGraphL1Component}
         </Tabs.Panel>
-
         <Tabs.Panel value="dependencyL0">
-          {makeInfoHoverCardIcon(
-            <Text>Active deltas are bold.</Text>
-          )}
-          <Graph graph={this.props.state.dependencyGraphL0} forces={graphForces} />
+          {components.depGraphL0Component}
         </Tabs.Panel>
-
         <Tabs.Panel value="history">
-          {makeInfoHoverCardIcon(<>
-            <Text>All links are parent links.</Text>
-            <Text>Right or middle mouse button: Load version.</Text>
-          </>)}
-          <Graph graph={this.props.state.historyGraph} forces={graphForces}
-            mouseUpHandler={(e, {x, y}, node) => node ? this.props.onVersionClicked(node.obj) : undefined} />
+          {components.historyComponent}
         </Tabs.Panel>
-      </Tabs>
-    );
-
-    const undoButtons = this.props.state.version.parents.map(([parentVersion,deltaToUndo]) => {
-      return (
-        <div key={fullVersionId(parentVersion)}>
-          <Button fullWidth={true} compact={true} leftIcon={<IconPlayerTrackPrev size={18}/>} onClick={this.props.onUndoClicked.bind(null, parentVersion, deltaToUndo)}>
-            UNDO {deltaToUndo.getDescription()}
-          </Button>
-          <Space h="xs"/>
-        </div>
-      );
-    });
-    const redoButtons = this.props.state.version.children.map(([childVersion,deltaToRedo]) => {
-      return (
-        <div key={fullVersionId(childVersion)}>
-          <Button style={{width: "100%"}} compact={true} rightIcon={<IconPlayerTrackNext size={18}/>} onClick={this.props.onRedoClicked.bind(null, childVersion, deltaToRedo)}>
-            REDO {deltaToRedo.getDescription()}
-          </Button>
-          <Space h="xs"/>
-        </div>
-      );
-    });
-
-    return (
-      <>
-        <Title order={4}>{this.props.title}</Title>
-        <Stack>
-          {makeTabs(this.props.defaultTab0, this.props.tabs)}
-          {makeTabs(this.props.defaultTab1, this.props.tabs)}
-        </Stack>
-
-        <SimpleGrid cols={2}>
-          <div>
-            {undoButtons}
-          </div>
-          <div>
-            {redoButtons}
-          </div>
-        </SimpleGrid>
-
-{/*        <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>
-*/}      </>
-    );
-  }
-}
-
-interface DemoCorrState {
-  cs: VersionedModelState;
-  corr: VersionedModelState;
-  as: VersionedModelState;
-}
-
-export class DemoCorr extends React.Component<{}, DemoCorrState> {
-  generateUUID = mockUuid();
-  primitiveRegistry = new PrimitiveRegistry();
-  parser: TrivialParser;
-
-  parsedTo: Map<Version,{corr: Version, target: Version}>;
-  renderedTo: Map<Version,{corr: Version, target: Version}>;
-
-  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 versionRegistry = new VersionRegistry();
-      const d3StateUpdater = new D3GraphStateUpdater(setGraph);
-      return {
-        version: versionRegistry.initialVersion,
-        graph: emptyGraph,
-        historyGraph: initialHistoryGraph(versionRegistry.initialVersion),
-        dependencyGraphL1: emptyGraph,
-        dependencyGraphL0: emptyGraph,
-        compositeLevel: new CompositeLevel(),
-        versionRegistry,
-        // d3StateUpdater,
-        graphState: new GraphState(d3StateUpdater),
-      };
-    }
-
-    this.state = {
-      cs: makeModelState(this.setCsState.bind(this)),
-      corr: makeModelState(this.setCorrState.bind(this)),
-      as: makeModelState(this.setAsState.bind(this)),
-    };
-
-    this.parsedTo = new Map([[this.state.cs.versionRegistry.initialVersion, {corr:this.state.corr.versionRegistry.initialVersion, target:this.state.as.versionRegistry.initialVersion}]]);
-    this.renderedTo = new Map([[this.state.as.versionRegistry.initialVersion, {corr:this.state.corr.versionRegistry.initialVersion, target:this.state.cs.versionRegistry.initialVersion}]]);
-
-    this.parser = new TrivialParser(this.primitiveRegistry, this.generateUUID);
-  }
-
-  render() {
-    const addVersionAndDeltas = ({version, historyGraph, dependencyGraphL1, dependencyGraphL0, ...rest}: VersionedModelState, newVersion: Version, composite: CompositeDelta) => {
-      return {
-        version: newVersion,
-        // add new version to history graph + highlight the new version as the current version:
-        historyGraph: setCurrentVersion(appendToHistoryGraph(historyGraph, newVersion), version, newVersion),
-        // add the composite delta to the L1-graph + highlight it as 'active':
-        dependencyGraphL1: composite.deltas.length > 0 ? addDeltaAndActivate(dependencyGraphL1, composite) : dependencyGraphL1, // never add an empty composite
-        // 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 undoWithoutUpdatingHistoryGraph = (state, setState, deltaToUndo) => {
-      state.graphState.unexec(deltaToUndo);
-      setState(({dependencyGraphL0: prevDepGraphL0, dependencyGraphL1: prevDepGraphL1, ...rest}) => ({
-        dependencyGraphL1: setDeltaInactive(prevDepGraphL1, deltaToUndo),
-        dependencyGraphL0: deltaToUndo.deltas.reduce((prevDepGraphL0, delta) => setDeltaInactive(prevDepGraphL0, delta), prevDepGraphL0),
-        ...rest,
-      }));
-    };
-    const redoWithoutUpdatingHistoryGraph = (state, setState, deltaToRedo) => {
-      state.graphState.exec(deltaToRedo);
-      setState(({dependencyGraphL0: prevDepGraphL0, dependencyGraphL1: prevDepGraphL1, ...rest}) => ({
-        dependencyGraphL1: setDeltaActive(prevDepGraphL1, deltaToRedo),
-        dependencyGraphL0: deltaToRedo.deltas.reduce((prevDepGraphL0, delta) => setDeltaActive(prevDepGraphL0, delta), prevDepGraphL0),
-        ...rest,
-      }));
-    };
-
-    const onUndoClicked = (state, setState, parentVersion, deltaToUndo) => {
-      undoWithoutUpdatingHistoryGraph(state, setState, deltaToUndo);
-      setState(({historyGraph: prevHistoryGraph, version: prevVersion, ...rest}) => ({
-        version: parentVersion,
-        historyGraph: setCurrentVersion(prevHistoryGraph, prevVersion, parentVersion),
-        ...rest,
-      }));
-    };
-    const onRedoClicked = (state, setState, childVersion, deltaToRedo) => {
-      redoWithoutUpdatingHistoryGraph(state, setState, deltaToRedo);
-      setState(({historyGraph: prevHistoryGraph, version: prevVersion, ...rest}) => ({
-        version: childVersion,
-        historyGraph: setCurrentVersion(prevHistoryGraph, prevVersion, childVersion),
-        ...rest,
-      }));
-    };
-
-    const gotoVersion = (state, setState, chosenVersion: Version) => {
-      const path = state.version.findPathTo(chosenVersion);
-      if (path === undefined) {
-        throw new Error("Could not find path to version!");
-      }
-      for (const [linkType, delta] of path) {
-        if (linkType === 'p') {
-          undoWithoutUpdatingHistoryGraph(state, setState, delta);
-        }
-        else if (linkType === 'c') {
-          redoWithoutUpdatingHistoryGraph(state, setState, delta);
-        }
-      }
-      setState(({historyGraph, version, ...rest}) => ({
-        version: chosenVersion,
-        historyGraph: setCurrentVersion(historyGraph, version, chosenVersion),
-        ...rest,
-      }));
+      </Tabs>;
     }
 
-    // Pure function
-    // Replays all deltas in a version to compute the graph state of that version.
-    function getGraphState(version: Version, additionalDeltas: PrimitiveDelta[]):GraphState {
-      const graphState = new GraphState();
-      for (const d of [...version].reverse()) {
-        graphState.exec(d);
-      }
-      for (const d of additionalDeltas) {
-        graphState.exec(d);
-      }
-      return graphState;
+    const gotoCorrVersion = (corrVersion: Version) => {
+      corrCallbacks.gotoVersion(corrVersion);
+      csCallbacks.gotoVersion(corrToCs.get(corrVersion)!);
+      asCallbacks.gotoVersion(corrToAs.get(corrVersion)!);
     }
 
 
-    const parseOrRender = (sourceState: VersionedModelState, targetState: VersionedModelState,
-          mapping, parse: boolean) => {
-      return (sourceDeltas: PrimitiveDelta[], description: string) => {
-
-        const targetDescription = (parse ? "parse:" : "render:") + description;
-
-        const sourceParent = sourceState.version;
-        const {corr: corrParent, target: targetParent} = mapping.get(sourceParent) || (()=>{throw new Error("X")})();
-
-        // SLOW! When performance becomes a problem, look for a faster alternative
-        const sourceGraphState = getGraphState(sourceParent, []);
-        const corrGraphState = getGraphState(corrParent, []);
-        const targetGraphState = getGraphState(targetParent, []);
-
-        const {corrDeltas, targetDeltas, sourceOverrides, targetOverrides} = this.parser.propagate_change(parse, sourceDeltas, sourceGraphState, corrGraphState, targetGraphState);
-
-        // console.log({corrDeltas})
-
-        const sourceComposite = sourceState.compositeLevel.createComposite(sourceDeltas, description);
-        const newSourceVersion = sourceState.versionRegistry.createVersion(sourceState.version, sourceComposite);
-
-        const targetComposite = targetState.compositeLevel.createComposite(targetDeltas, targetDescription);
-        const newTargetVersion =  (targetComposite.deltas.length > 0) ?
-          targetState.versionRegistry.createVersion(targetParent, targetComposite)
-          : targetState.version; // do not create a new target version
-
-        const csComposite = parse ? sourceComposite : targetComposite;
-        const asComposite = parse ? targetComposite : sourceComposite;
-        const newCsVersion = parse ? newSourceVersion : newTargetVersion;
-        const newAsVersion = parse ? newTargetVersion : newSourceVersion;
-        const csOverrides = parse ? sourceOverrides : targetOverrides;
-        const asOverrides = parse ? targetOverrides : sourceOverrides;
+    const [csState, setCsState] = React.useState<VersionedModelState>(cs.initialState);
+    const [corrState, setCorrState] = React.useState<VersionedModelState>(corr.initialState);
+    const [asState, setAsState] = React.useState<VersionedModelState>(as.initialState);
 
+    const csTabs = ["editor", "state", "history", "dependencyL1", "dependencyL0"];
+    const csCallbacks = cs.getCallbacks({state: csState, setState: setCsState});
+    const csComponents = cs.getReactComponents({
+      state: csState,
+      callbacks: {
+        onUserEdit: (csDeltas, description: string) => {
+          const [csGS, corrGS, asGS] = [csState.version, corrState.version, asState.version].map(v => getGraphState(v));
 
-        const corrDeltasOrderedByDependency: PrimitiveDelta[] = [];
-        visitPartialOrdering([
-          ...sourceDeltas.map(d => sourceOverrides.get(d) || d),
-          ...targetDeltas.map(d => targetOverrides.get(d) || d),
-          ...corrDeltas],
-          (d: Delta) => d.getDependencies(),
-          (d: Delta) => corrDeltasOrderedByDependency.push(d));
+          const {corrDeltas, asDeltas} = parser.parse(csDeltas, csGS, corrGS, asGS);
 
-        // console.log({corrDeltasOrderedByDependency});
+          const newCsVersion = csCallbacks.createAndGotoNewVersion(csDeltas, description);
+          const newCorrVersion = corrCallbacks.createAndGotoNewVersion(corrDeltas, description);
+          const newAsVersion = asCallbacks.createAndGotoNewVersion(asDeltas, "parse:"+description);
 
-        const corrComposite = this.state.corr.compositeLevel.createComposite(corrDeltasOrderedByDependency, targetDescription);
-        const newCorrVersion = this.state.corr.versionRegistry.createVersion(corrParent, corrComposite,
-          embed(
-            ["cs", newCsVersion, csOverrides],
-            ["as", newAsVersion, asOverrides],
-          ));
-
-        this.parsedTo.set(newCsVersion, {corr: newCorrVersion, target: newAsVersion});
-        this.renderedTo.set(newAsVersion, {corr: newCorrVersion, target: newCsVersion});
-
-        // Update state:
-        this.setState(({cs, corr, as}) => {
-          const result = {
-            cs: addVersionAndDeltas(cs, newCsVersion, csComposite),
-            corr: addVersionAndDeltas(corr, newCorrVersion, corrComposite),
-            as: addVersionAndDeltas(as, newAsVersion, asComposite),
-          };
-          return result;
-        });
+          corrToCs.set(newCorrVersion, newCsVersion);
+          corrToAs.set(newCorrVersion, newAsVersion);
+        },
+        onUndoClicked: csCallbacks.undo,
+        onRedoClicked: csCallbacks.redo,
+        onVersionClicked: csCallbacks.gotoVersion,
+      },
+    });
 
-        gotoVersion(this.state.cs, this.setCsState.bind(this), newCsVersion);
-        gotoVersion(this.state.corr, this.setCorrState.bind(this), newCorrVersion);
-        gotoVersion(this.state.as, this.setAsState.bind(this), newAsVersion);
-      };
-    };
+    const corrTabs = ["state", "history", "dependencyL1", "dependencyL0"];
+    const corrCallbacks = corr.getCallbacks({state: corrState, setState: setCorrState});
+    const corrComponents = corr.getReactComponents({
+      state: corrState,
+      callbacks: {
+        onUserEdit: corrCallbacks.createAndGotoNewVersion,
+        onUndoClicked: gotoCorrVersion,
+        onRedoClicked: gotoCorrVersion,
+        onVersionClicked: gotoCorrVersion,
+      },
+    });
 
-    const parseCsChange = parseOrRender(this.state.cs, this.state.as, this.parsedTo, true);
-    const renderAsChange = parseOrRender(this.state.as, this.state.cs, this.renderedTo, false);
+    const asTabs = ["state", "history", "dependencyL1", "dependencyL0"];
+    const asCallbacks = as.getCallbacks({state: asState, setState: setAsState});
+    const asComponents = as.getReactComponents({
+      state: asState,
+      callbacks: {
+        onUserEdit: (asDeltas, description: string) => {
+          const [csGS, corrGS, asGS] = [csState.version, corrState.version, asState.version].map(v => getGraphState(v));
+          
+          const {corrDeltas, csDeltas} = parser.render(asDeltas, csGS, corrGS, asGS);
+
+          const newCsVersion = csCallbacks.createAndGotoNewVersion(csDeltas, "render:"+description);
+          const newCorrVersion = corrCallbacks.createAndGotoNewVersion(corrDeltas, description);
+          const newAsVersion = asCallbacks.createAndGotoNewVersion(asDeltas, description);
+
+          corrToCs.set(newCorrVersion, newCsVersion);
+          corrToAs.set(newCorrVersion, newAsVersion);
+        },
+        onUndoClicked: asCallbacks.undo,
+        onRedoClicked: asCallbacks.redo,
+        onVersionClicked: asCallbacks.gotoVersion,
+      },
+    });
 
-    return (<>
-      <Grid grow>
-        <Grid.Col span={1}>
-          <VersionedModel title="Concrete Syntax"
-            generateUUID={this.generateUUID}
-            primitiveRegistry={this.primitiveRegistry}
-            tabs={["editor", "state", "history", "dependencyL1", "dependencyL0"]}
-            defaultTab0="editor"
-            defaultTab1="history"
-            state={this.state.cs}
-            setState={this.setCsState.bind(this)}
-            setNextNodePosition={(x,y)=>{this.state.cs.d3StateUpdater.x = x; this.state.cs.d3StateUpdater.y = y;}}
-            onUserEdit={parseCsChange}
-            onUndoClicked={onUndoClicked.bind(null, this.state.cs, this.setCsState.bind(this))}
-            onRedoClicked={onRedoClicked.bind(null, this.state.cs, this.setCsState.bind(this))}
-            onVersionClicked={version => gotoVersion(this.state.cs, this.setCsState.bind(this), version)}
-          />
+    return (
+      <Grid grow columns={12}>
+        <Grid.Col span={4}>
+          <Title order={4}>Concrete Syntax</Title>
+          <Stack>
+            {makeTabs(csComponents, "editor", csTabs)}
+            {makeTabs(csComponents, "history", csTabs)}
+            {/*{csComponents.undoRedoButtons}*/}
+          </Stack>
         </Grid.Col>
-        <Grid.Col span={1}>
-          <VersionedModel title="Correspondence" readonly
-            generateUUID={this.generateUUID}
-            primitiveRegistry={this.primitiveRegistry}
-            tabs={["state", "history", "dependencyL1", "dependencyL0"]}
-            defaultTab0="state"
-            defaultTab1="history"
-            setNextNodePosition={(x,y)=>{this.state.corr.d3StateUpdater.x = x; this.state.corr.d3StateUpdater.y = y;}}
-            state={this.state.corr}
-            setState={this.setCorrState.bind(this)}
-            onUndoClicked={onUndoClicked.bind(null, this.state.corr, this.setCorrState.bind(this))}
-            onRedoClicked={onRedoClicked.bind(null, this.state.corr, this.setCorrState.bind(this))}
-            onVersionClicked={version => gotoVersion(this.state.corr, this.setCorrState.bind(this), version)}
-          />
-
+        <Grid.Col span={4}>
+          <Title order={4}>Correspondence</Title>
+          <Stack>
+            {makeTabs(corrComponents, "state", corrTabs)}
+            {makeTabs(corrComponents, "history", corrTabs)}
+          </Stack>
         </Grid.Col>
-        <Grid.Col span={1}>
-          <VersionedModel title="Abstract Syntax"
-            generateUUID={this.generateUUID}
-            primitiveRegistry={this.primitiveRegistry}
-            tabs={["state", "history", "dependencyL1", "dependencyL0"]}
-            defaultTab0="state"
-            defaultTab1="history"
-            setNextNodePosition={(x,y)=>{this.state.as.d3StateUpdater.x = x; this.state.as.d3StateUpdater.y = y;}}
-            state={this.state.as}
-            setState={this.setAsState.bind(this)}
-            onUserEdit={renderAsChange}
-            onUndoClicked={onUndoClicked.bind(null, this.state.as, this.setAsState.bind(this))}
-            onRedoClicked={onRedoClicked.bind(null, this.state.as, this.setAsState.bind(this))}
-            onVersionClicked={version => gotoVersion(this.state.as, this.setAsState.bind(this), version)}
-          />
+        <Grid.Col span={4}>
+          <Title order={4}>Abstract Syntax</Title>
+          <Stack>
+            {makeTabs(asComponents, "state", asTabs)}
+            {makeTabs(asComponents, "history", asTabs)}
+            {/*{asComponents.undoRedoButtons}*/}
+          </Stack>
         </Grid.Col>
+        <Grid.Col span={2}/>
+        <Grid.Col span={8}>
+          {corrComponents.undoRedoButtons}
+        </Grid.Col>
+        <Grid.Col span={2}/>
       </Grid>
-    </>);
+    );
   }
 }

+ 32 - 61
src/frontend/demo_pd.tsx

@@ -24,73 +24,44 @@ import {
   initialHistoryGraph,
 } from "./app_state";
 
-interface DemoPDProps {
-}
-
-interface DemoPDState {
-  graph: GraphType;
-  dependencyGraphL0: DependencyGraphType;
-  history: HistoryGraphType;
-}
+import {
+  newVersionedModel,
+  VersionedModelState,
+} from "./versioned_model";
 
-export class DemoPD extends React.Component<DemoPDProps, DemoPDState> {
-  readonly primitiveRegistry = new PrimitiveRegistry();
-  readonly d3Listener = new D3GraphStateUpdater(
-      callback => this.setState(({graph, ...rest}) => ({graph: callback(graph), ...rest}))
-    );
-  readonly graphState = new GraphState(this.d3Listener);
-  readonly generateUUID = mockUuid();
-  readonly versionRegistry = new VersionRegistry();
-  readonly compositeLvl = new CompositeLevel();
+export function getDemoPD() {
+  const primitiveRegistry = new PrimitiveRegistry();
+  const generateUUID = mockUuid();
 
-  constructor(props) {
-    super(props);
+  const model = newVersionedModel({readonly: false, generateUUID, primitiveRegistry});
 
-    this.state = {
-      graph: emptyGraph,
-      dependencyGraphL0: emptyGraph,
-      history: initialHistoryGraph(this.versionRegistry.initialVersion),
-    };
-  }
+  return function() {
+    const [state, setState] = React.useState<VersionedModelState>(model.initialState);
 
-  render() {
-    const onUserEdit = (deltas: PrimitiveDelta[], description: string) => {
-      // const composite = this.compositeLvl.createComposite(deltas);
-      // const version = this.versionRegistry.
-      deltas.forEach(d => {
-        this.graphState.exec(d);
-        this.setState(({dependencyGraphL0, ...rest}) => ({dependencyGraphL0: addDeltaAndActivate(dependencyGraphL0, d), ...rest}));
-      });
-    }
+    const callbacks = model.getCallbacks({state, setState});
+    const components = model.getReactComponents({state,
+      callbacks: {
+          onUserEdit: callbacks.createAndGotoNewVersion,
+          onUndoClicked: callbacks.undo,
+          onRedoClicked: callbacks.redo,
+          onVersionClicked: callbacks.gotoVersion,
+      }});
 
     return <>
-        <Title order={4}>State</Title>
-        <EditableGraph
-          graph={this.state.graph}
-          primitiveRegistry={this.primitiveRegistry}
-          graphState={this.graphState}
-          forces={graphForces}
-          generateUUID={this.generateUUID}
-          setNextNodePosition={(x,y) => {this.d3Listener.x = x; this.d3Listener.y = y}}
-          onUserEdit={onUserEdit}
-        />
+      <Title order={4}>State</Title>
+      {components.graphStateComponent}
 
-        <Grid grow>
-          <Grid.Col span={1}>
-            <Title order={4}>Deltas</Title>
-            <Graph
-             graph={this.state.dependencyGraphL0}
-             forces={graphForces}
-            />
-          </Grid.Col>
-          <Grid.Col span={1}>
-            <Title order={4}>History</Title>
-            <Graph
-              graph={this.state.history}
-              forces={graphForces}
-            />
-          </Grid.Col>
-        </Grid>
-      </>;
+      <Grid grow>
+        <Grid.Col span={1}>
+          <Title order={4}>Deltas</Title>
+          {components.depGraphL0Component}
+        </Grid.Col>
+        <Grid.Col span={1}>
+          <Title order={4}>History</Title>
+          {components.historyComponent}
+        </Grid.Col>
+      </Grid>
+      {components.undoRedoButtons}
+    </>;
   }
 }

+ 46 - 0
src/frontend/help_icons.tsx

@@ -0,0 +1,46 @@
+import * as React from "react";
+import {Text, HoverCard, ActionIcon} from "@mantine/core";
+import {IconInfoCircle} from "@tabler/icons";
+
+export function makeInfoHoverCardIcon(contents) {
+  return (
+    <HoverCard shadow="md" width={300}>
+      <HoverCard.Target>
+        <ActionIcon>
+          <IconInfoCircle size={18}/>
+        </ActionIcon>
+      </HoverCard.Target>
+      <HoverCard.Dropdown>
+        {contents}
+      </HoverCard.Dropdown>
+    </HoverCard>
+  );
+}
+
+export const graphEditor = makeInfoHoverCardIcon(<>
+  <Text><b>Left-Drag</b>: Drag Node</Text>
+  <Text><b>Middle-Click</b>: Delete Node</Text>
+  <Text><b>Right-Click</b>: Create Node or Edge</Text>
+  <Text><b>Wheel</b>: Zoom</Text>
+</>);
+
+export const graphEditorReadonly = makeInfoHoverCardIcon(<>
+  <Text><b>Left-Drag</b>: Drag Node</Text>
+  <Text><b>Wheel</b>: Zoom</Text>
+</>);
+
+export const rountangleEditor = makeInfoHoverCardIcon(<>
+  <Text><b>Alt + Left-Click</b>: Create/Delete Rountangle</Text>
+  <Text><b>Left-Drag</b>: Move Rountangle</Text>
+</>);
+
+export const depGraph = makeInfoHoverCardIcon(<>
+  <Text><b>Left-Drag</b>: Drag Node</Text>
+  <Text>Active deltas are <b>bold</b>.</Text>
+</>);
+
+export const historyGraph = makeInfoHoverCardIcon(<>
+  <Text><b>Left-Drag</b>: Drag Node</Text>
+  <Text><b>Right-Click</b>: Goto Version</Text>
+  <Text>Current version is <b>bold</b>.</Text>
+</>);

+ 218 - 132
src/frontend/versioned_model.tsx

@@ -1,4 +1,38 @@
-interface VersionedModelState {
+import * as React from "react";
+import * as Mantine from "@mantine/core";
+import * as Icons from "@tabler/icons";
+
+import {D3GraphStateUpdater} from "./d3_state";
+import {EditableGraph, UserEditCallback, SetNodePositionCallback, GraphType, NodeType, LinkType} from "./editable_graph";
+import {
+  HistoryGraphType,
+  DependencyGraphType,
+  versionToNode,
+  setCurrentVersion,
+  appendToHistoryGraph,
+  fullDeltaId,
+  setDeltaActive,
+  setDeltaInactive,
+  deltaToDepGraphNode,
+  dependencyToDepGraphLink,
+  conflictToDepGraphLink,
+  fullVersionId,
+  addDeltaAndActivate,
+  initialHistoryGraph,
+} from "./app_state";
+import {d3Types, Graph} from "./graph"
+import {emptyGraph, graphForces} from "./constants";
+import {RountangleEditor} from "./rountangleEditor/RountangleEditor";
+import * as HelpIcons from "./help_icons";
+
+import {embed, Version, VersionRegistry} from "../onion/version";
+import {PrimitiveDelta, PrimitiveRegistry} from "../onion/primitive_delta";
+import {PrimitiveValue, UUID} from "../onion/types";
+import {CompositeDelta, CompositeLevel} from "../onion/composite_delta";
+import {GraphState} from "../onion/graph_state"; 
+import {Delta} from "../onion/delta";
+
+export interface VersionedModelState {
   version: Version; // the 'current version'
   graph: GraphType; // the state what is displayed in the leftmost panel
   historyGraph: HistoryGraphType; // the state of what is displayed in the middle panel
@@ -6,153 +40,205 @@ interface VersionedModelState {
   dependencyGraphL0: DependencyGraphType; // the state of what is displayed in the rightmost panel
 }
 
-export interface VersionedModelProps {
-  title: string;
-  readonly?: boolean;
-  
-  generateUUID: () => UUID;
-  primitiveRegistry: PrimitiveRegistry;
-  versionRegistry: VersionRegistry;
-  compositeLevel: CompositeLevel;
-  d3Updater: D3GraphStateUpdater;
-  graphState: GraphState;
-
-
+interface VersionedModelCallbacks {
   onUserEdit?: UserEditCallback;
-  onUndoClicked: (parentVersion: Version, deltaToUndo: Delta) => void;
-  onRedoClicked: (childVersion: Version, deltaToRedo: Delta) => void;
-  onVersionClicked: (Version) => void;
+  onUndoClicked?: (parentVersion: Version, deltaToUndo: Delta) => void;
+  onRedoClicked?: (childVersion: Version, deltaToRedo: Delta) => void;
+  onVersionClicked?: (Version) => void;
+}
 
-  state: VersionedModelState;
-  setState: (callback: (VersionedModelState) => VersionedModelState) => void;
+function makeOverlayHelpIcon(background, helpIcon) {
+  return (
+    <div style={{position:"relative"}}>
+      {background}
+      <div style={{position: "absolute", top: 0, right: 0}}>
+        {helpIcon}
+      </div>
+    </div>
+  );
 }
 
-// All of these are pure functions:
-
-function getReactComponents(props: VersionedModelProps) {
-
-  const addVersionAndDeltas = (newVersion: Version, composite: CompositeDelta) => {
-    props.setState(({version, historyGraph, dependencyGraphL1, dependencyGraphL0, ...rest}) => ({
-      version: newVersion,
-      // add new version to history graph + highlight the new version as the current version:
-      historyGraph: setCurrentVersion(appendToHistoryGraph(historyGraph, newVersion), version, newVersion),
-      // add the composite delta to the L1-graph + highlight it as 'active':
-      dependencyGraphL1: composite.deltas.length > 0 ? addDeltaAndActivate(dependencyGraphL1, composite) : dependencyGraphL1, // never add an empty composite
-      // 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 undoWithoutUpdatingHistoryGraph = (deltaToUndo) => {
-    props.graphState.unexec(deltaToUndo);
-    props.setState(({dependencyGraphL0: prevDepGraphL0, dependencyGraphL1: prevDepGraphL1, ...rest}) => ({
-      dependencyGraphL1: setDeltaInactive(prevDepGraphL1, deltaToUndo),
-      dependencyGraphL0: deltaToUndo.deltas.reduce((prevDepGraphL0, delta) => setDeltaInactive(prevDepGraphL0, delta), prevDepGraphL0),
-      ...rest,
-    }));
-  };
-  const redoWithoutUpdatingHistoryGraph = (deltaToRedo) => {
-    props.graphState.exec(deltaToRedo);
-    props.setState(({dependencyGraphL0: prevDepGraphL0, dependencyGraphL1: prevDepGraphL1, ...rest}) => ({
-      dependencyGraphL1: setDeltaActive(prevDepGraphL1, deltaToRedo),
-      dependencyGraphL0: deltaToRedo.deltas.reduce((prevDepGraphL0, delta) => setDeltaActive(prevDepGraphL0, delta), prevDepGraphL0),
-      ...rest,
-    }));
-  };
-  const undo = (parentVersion, deltaToUndo) => {
-    undoWithoutUpdatingHistoryGraph(deltaToUndo);
-    props.setState(({historyGraph: prevHistoryGraph, version: prevVersion, ...rest}) => ({
-      version: parentVersion,
-      historyGraph: setCurrentVersion(prevHistoryGraph, prevVersion, parentVersion),
-      ...rest,
-    }));
-  };
-  const redo = (childVersion, deltaToRedo) => {
-    redoWithoutUpdatingHistoryGraph(deltaToRedo);
-    props.setState(({historyGraph: prevHistoryGraph, version: prevVersion, ...rest}) => ({
-      version: childVersion,
-      historyGraph: setCurrentVersion(prevHistoryGraph, prevVersion, childVersion),
-      ...rest,
-    }));
-  };
-  const gotoVersion = (chosenVersion: Version) => {
-    const path = props.state.version.findPathTo(chosenVersion);
-    if (path === undefined) {
-      throw new Error("Could not find path to version!");
-    }
-    for (const [linkType, delta] of path) {
-      if (linkType === 'p') {
-        undoWithoutUpdatingHistoryGraph(state, setState, delta);
+
+export function newVersionedModel({generateUUID, primitiveRegistry, readonly}) {
+  const versionRegistry = new VersionRegistry();
+  const graphState = new GraphState();
+  const compositeLevel = new CompositeLevel();
+
+  let x = 0;
+  let y = 0;
+
+  function getCallbacks({state, setState}) {
+    const setGraph = callback =>
+      setState(({graph, ...rest}) => ({graph: callback(graph), ...rest}));
+
+    const addVersionAndDeltas = (newVersion: Version, composite: CompositeDelta) => {
+      setState(({version, historyGraph, dependencyGraphL1, dependencyGraphL0, ...rest}) => ({
+        version: newVersion,
+        // add new version to history graph + highlight the new version as the current version:
+        historyGraph: setCurrentVersion(appendToHistoryGraph(historyGraph, newVersion), version, newVersion),
+        // add the composite delta to the L1-graph + highlight it as 'active':
+        dependencyGraphL1: composite.deltas.length > 0 ? addDeltaAndActivate(dependencyGraphL1, composite) : dependencyGraphL1, // never add an empty composite
+        // 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 createAndGotoNewVersion = (deltas: PrimitiveDelta[], description: string, embeddings = new Map()) => {
+      const composite = compositeLevel.createComposite(deltas, description);
+      const version = versionRegistry.createVersion(state.version, composite, embeddings);
+      addVersionAndDeltas(version, composite);
+      gotoVersion(version);
+      return version;
+    };
+    const undoWithoutUpdatingHistoryGraph = (deltaToUndo) => {
+      const d3Updater = new D3GraphStateUpdater(setGraph, x, y);
+      graphState.unexec(deltaToUndo, d3Updater);
+      setState(({dependencyGraphL0: prevDepGraphL0, dependencyGraphL1: prevDepGraphL1, ...rest}) => ({
+        dependencyGraphL1: setDeltaInactive(prevDepGraphL1, deltaToUndo),
+        dependencyGraphL0: deltaToUndo.deltas.reduce((prevDepGraphL0, delta) => setDeltaInactive(prevDepGraphL0, delta), prevDepGraphL0),
+        ...rest,
+      }));
+    };
+    const redoWithoutUpdatingHistoryGraph = (deltaToRedo) => {
+      const d3Updater = new D3GraphStateUpdater(setGraph, x, y);
+      graphState.exec(deltaToRedo, d3Updater);
+      setState(({dependencyGraphL0: prevDepGraphL0, dependencyGraphL1: prevDepGraphL1, ...rest}) => ({
+        dependencyGraphL1: setDeltaActive(prevDepGraphL1, deltaToRedo),
+        dependencyGraphL0: deltaToRedo.deltas.reduce((prevDepGraphL0, delta) => setDeltaActive(prevDepGraphL0, delta), prevDepGraphL0),
+        ...rest,
+      }));
+    };
+    const undo = (parentVersion, deltaToUndo) => {
+      undoWithoutUpdatingHistoryGraph(deltaToUndo);
+      setState(({historyGraph: prevHistoryGraph, version: prevVersion, ...rest}) => ({
+        version: parentVersion,
+        historyGraph: setCurrentVersion(prevHistoryGraph, prevVersion, parentVersion),
+        ...rest,
+      }));
+    };
+    const redo = (childVersion, deltaToRedo) => {
+      redoWithoutUpdatingHistoryGraph(deltaToRedo);
+      setState(({historyGraph: prevHistoryGraph, version: prevVersion, ...rest}) => ({
+        version: childVersion,
+        historyGraph: setCurrentVersion(prevHistoryGraph, prevVersion, childVersion),
+        ...rest,
+      }));
+    };
+    const gotoVersion = (chosenVersion: Version) => {
+      const path = state.version.findPathTo(chosenVersion);
+      if (path === undefined) {
+        throw new Error("Could not find path to version!");
       }
-      else if (linkType === 'c') {
-        redoWithoutUpdatingHistoryGraph(state, setState, delta);
+      for (const [linkType, delta] of path) {
+        if (linkType === 'p') {
+          undoWithoutUpdatingHistoryGraph(delta);
+        }
+        else if (linkType === 'c') {
+          redoWithoutUpdatingHistoryGraph(delta);
+        }
       }
-    }
-    props.setState(({historyGraph, version, ...rest}) => ({
-      version: chosenVersion,
-      historyGraph: setCurrentVersion(historyGraph, version, chosenVersion),
-      ...rest,
-    }));
-  };
-
+      setState(({historyGraph, version, ...rest}) => ({
+        version: chosenVersion,
+        historyGraph: setCurrentVersion(historyGraph, version, chosenVersion),
+        ...rest,
+      }));
+    };
+    return {
+      gotoVersion,
+      createAndGotoNewVersion,
+      addVersionAndDeltas,
+      undo,
+      redo,
+    };
+  }
 
-  const graphStateComponent = props.readonly ? 
-    <Graph graph={props.state.graph} forces={graphForces} />
-    : <EditableGraph
-      graph={props.state.graph}
-      graphState={props.graphState}
-      forces={graphForces}
-      generateUUID={props.generateUUID}
-      primitiveRegistry={props.primitiveRegistry}
-      setNextNodePosition={(x,y) => {props.d3Updater.x = x; props.d3Updater.y = y;}}
-      onUserEdit={props.onUserEdit} />;
+  // this function may only be called from a functional react component!
+  function getReactComponents({state, callbacks}: {state: VersionedModelState, callbacks: VersionedModelCallbacks}) {
+    const graphStateComponent = makeOverlayHelpIcon(readonly ? 
+      <Graph graph={state.graph} forces={graphForces} />
+      : <EditableGraph
+          graph={state.graph}
+          graphState={graphState}
+          forces={graphForces}
+          generateUUID={generateUUID}
+          primitiveRegistry={primitiveRegistry}
+          setNextNodePosition={(newX,newY) => {x = newX; y = newY;}}
+          onUserEdit={callbacks.onUserEdit}
+        />, readonly ? HelpIcons.graphEditorReadonly : HelpIcons.graphEditor);
 
-  const depGraphL1Component = <Graph graph={props.state.dependencyGraphL1} forces={graphForces} />;
-  const depGraphL0Component = <Graph graph={props.state.dependencyGraphL0} forces={graphForces} />;
+    const depGraphL1Component = makeOverlayHelpIcon(
+      <Graph graph={state.dependencyGraphL1} forces={graphForces} />,
+      HelpIcons.depGraph);
+    const depGraphL0Component = makeOverlayHelpIcon(
+      <Graph graph={state.dependencyGraphL0} forces={graphForces} />,
+      HelpIcons.depGraph);
 
-  const historyComponent = <Graph graph={props.state.historyGraph} forces={graphForces}
-      mouseUpHandler={(e, {x, y}, node) => node ? props.onVersionClicked(node.obj) : undefined} />;
+    const historyComponent = makeOverlayHelpIcon(
+      <Graph graph={state.historyGraph} forces={graphForces}
+        mouseUpHandler={(e, {x, y}, node) => node ? callbacks.onVersionClicked?.(node.obj) : undefined} />,
+      HelpIcons.historyGraph);
 
+    const rountangleEditor = makeOverlayHelpIcon(
+      <RountangleEditor
+        graph={state.graph}
+        generateUUID={generateUUID}
+        primitiveRegistry={primitiveRegistry}
+        graphState={graphState}
+        onUserEdit={callbacks.onUserEdit}
+      />,
+      HelpIcons.rountangleEditor);
 
-  const undoButtons = props.state.version.parents.map(([parentVersion,deltaToUndo]) => {
-    return (
-      <div key={fullVersionId(parentVersion)}>
-        <Button fullWidth={true} compact={true} leftIcon={<IconPlayerTrackPrev size={18}/>} onClick={props.onUndoClicked.bind(null, parentVersion, deltaToUndo)}>
-          UNDO {deltaToUndo.getDescription()}
-        </Button>
-        <Space h="xs"/>
-      </div>
+    const undoButtons = state.version.parents.map(([parentVersion,deltaToUndo]) => {
+      return (
+        <div key={fullVersionId(parentVersion)}>
+          <Mantine.Button fullWidth={true} compact={true} leftIcon={<Icons.IconPlayerTrackPrev size={18}/>} onClick={callbacks.onUndoClicked?.bind(null, parentVersion, deltaToUndo)}>
+            UNDO {deltaToUndo.getDescription()}
+          </Mantine.Button>
+          <Mantine.Space h="xs"/>
+        </div>
+      );
+    });
+    const redoButtons = state.version.children.map(([childVersion,deltaToRedo]) => {
+      return (
+        <div key={fullVersionId(childVersion)}>
+          <Mantine.Button style={{width: "100%"}} compact={true} rightIcon={<Icons.IconPlayerTrackNext size={18}/>} onClick={callbacks.onRedoClicked?.bind(null, childVersion, deltaToRedo)}>
+            REDO {deltaToRedo.getDescription()}
+          </Mantine.Button>
+          <Mantine.Space h="xs"/>
+        </div>
+      );
+    });
+    const undoRedoButtons = (
+      <Mantine.SimpleGrid cols={2}>
+        <div>{undoButtons}</div>
+        <div>{redoButtons}</div>
+      </Mantine.SimpleGrid>
     );
-  });
-  const redoButtons = props.state.version.children.map(([childVersion,deltaToRedo]) => {
-    return (
-      <div key={fullVersionId(childVersion)}>
-        <Button style={{width: "100%"}} compact={true} rightIcon={<IconPlayerTrackNext size={18}/>} onClick={props.onRedoClicked.bind(null, childVersion, deltaToRedo)}>
-          REDO {deltaToRedo.getDescription()}
-        </Button>
-        <Space h="xs"/>
-      </div>
-    );
-  });
 
-
-  return {
-    components: {
+    return {
       graphStateComponent,
+      rountangleEditor,
       depGraphL1Component,
       depGraphL0Component,
       historyComponent,
-      undoButtons,
-      redoButtons,
-    },
-    callbacks: {
-      gotoVersion,
-      addVersionAndDeltas,
-      undo,
-      redo,
+      // undoButtons,
+      // redoButtons,
+      undoRedoButtons,
+    };
+  }
+
+  return {
+    initialState: {
+      version: versionRegistry.initialVersion,
+      graph: emptyGraph,
+      historyGraph: initialHistoryGraph(versionRegistry.initialVersion),
+      dependencyGraphL1: emptyGraph,
+      dependencyGraphL0: emptyGraph,
     },
+    getCallbacks,
+    getReactComponents,
   };
-}
+}

+ 45 - 44
src/onion/graph_state.ts

@@ -307,48 +307,49 @@ export class GraphState {
   readonly nodes: Map<PrimitiveValue, NodeState> = new Map();
   readonly values: Map<PrimitiveValue, ValueState> = new Map();
 
-  private readonly listener: GraphStateListener;
+  // private readonly listener: GraphStateListener;
 
-  constructor(listener: GraphStateListener = DUMMY) {
-    this.listener = listener;
-  }
+  // constructor(listener: GraphStateListener = DUMMY) {
+  //   this.listener = listener;
+  // }
 
-  exec(delta: Delta) {
+  exec(delta: Delta, listener: GraphStateListener = DUMMY) {
+    console.log('exec...')
     if (delta instanceof CompositeDelta) {
-      delta.deltas.forEach(d => this.exec(d));
+      delta.deltas.forEach(d => this.exec(d, listener));
     }
     else if (delta instanceof NodeCreation) {
-      this.execNodeCreation(delta);
+      this.execNodeCreation(delta, listener);
     }
     else if (delta instanceof NodeDeletion) {
-      this.execNodeDeletion(delta);
+      this.execNodeDeletion(delta, listener);
     }
     else if (delta instanceof EdgeCreation) {
-      this.execEdgeCreation(delta);
+      this.execEdgeCreation(delta, listener);
     }
     else if (delta instanceof EdgeUpdate) {
-      this.execEdgeUpdate(delta);
+      this.execEdgeUpdate(delta, listener);
     }
     else {
       throw new Error("Assertion failed: Unexpected delta type");
     }
   }
-  unexec(delta: Delta) {
+  unexec(delta: Delta, listener: GraphStateListener = DUMMY) {
     if (delta instanceof CompositeDelta) {
       // must un-exec them in reverse order:
-      delta.deltas.reduceRight((_, currentDelta) => {this.unexec(currentDelta); return null;}, null);
+      delta.deltas.reduceRight((_, currentDelta) => {this.unexec(currentDelta, listener); return null;}, null);
     }
     else if (delta instanceof NodeCreation) {
-      this.unexecNodeCreation(delta);
+      this.unexecNodeCreation(delta, listener);
     }
     else if (delta instanceof NodeDeletion) {
-      this.unexecNodeDeletion(delta);
+      this.unexecNodeDeletion(delta, listener);
     }
     else if (delta instanceof EdgeCreation) {
-      this.unexecEdgeCreation(delta);
+      this.unexecEdgeCreation(delta, listener);
     }
     else if (delta instanceof EdgeUpdate) {
-      this.unexecEdgeUpdate(delta);
+      this.unexecEdgeUpdate(delta, listener);
     }
     else {
       throw new Error("Assertion failed: Unexpected delta type");
@@ -372,19 +373,19 @@ export class GraphState {
     return vs;
   }
 
-  execNodeCreation(delta: NodeCreation) {
+  execNodeCreation(delta: NodeCreation, listener: GraphStateListener) {
     // console.log("execNodeCreation", delta)
     const nodeState = new NodeState(delta);
     this.nodes.set(delta.id.value, nodeState);
-    this.listener.createNode(nodeState);
+    listener.createNode(nodeState);
   }
-  unexecNodeCreation(delta: NodeCreation) {
+  unexecNodeCreation(delta: NodeCreation, listener: GraphStateListener) {
     // console.log("unexecNodeCreation", delta)
     this.nodes.delete(delta.id.value);
-    this.listener.deleteNode(delta.id.value);
+    listener.deleteNode(delta.id.value);
   }
 
-  execNodeDeletion(delta: NodeDeletion) {
+  execNodeDeletion(delta: NodeDeletion, listener: GraphStateListener) {
     // console.log("execNodeDeletion", delta)
     const id = delta.creation.id.value;
     const nodeState = this.nodes.get(id);
@@ -396,38 +397,38 @@ export class GraphState {
       const target = outgoingEdgeOperation.target.getTarget();
       const targetState = this._getEdgeTargetState(target);
       if (targetState !== undefined) {
-        targetState.replaceIncoming(outgoingEdgeOperation, delta, this.listener);
+        targetState.replaceIncoming(outgoingEdgeOperation, delta, listener);
         const outgoingEdgeCreation = outgoingEdgeOperation.getCreation();
         const sourceId = outgoingEdgeCreation.source.id.value;
         const label = outgoingEdgeCreation.label;
-        this.listener.deleteLink(sourceId, label);
+        listener.deleteLink(sourceId, label);
       }
     }
-    this.listener.deleteNode(id);    
+    listener.deleteNode(id);    
   }
-  unexecNodeDeletion(delta: NodeDeletion) {
+  unexecNodeDeletion(delta: NodeDeletion, listener: GraphStateListener) {
     // restore outgoing links
     const id = delta.creation.id.value;
     const nodeState = this.nodes.get(id);
     if (nodeState === undefined) {
       throw new Error("Assertion failed: deleted node does not exist")
     }
-    this.listener.createNode(nodeState);
+    listener.createNode(nodeState);
     // For every outgoing edge of deleted node, restore in the target node the incoming edge operation by whatever was there before
     for (const outgoingEdgeOperation of nodeState.outgoing.values()) {
       const target = outgoingEdgeOperation.target.getTarget();
       const targetState = this._getEdgeTargetState(target);
       if (targetState !== undefined) {
-        targetState.replaceIncoming(delta, outgoingEdgeOperation, this.listener);
+        targetState.replaceIncoming(delta, outgoingEdgeOperation, listener);
         const outgoingEdgeCreation = outgoingEdgeOperation.getCreation();
         const sourceId = outgoingEdgeCreation.source.id.value;
         const label = outgoingEdgeCreation.label;
-        targetState.createLinkTo(sourceId, label, this.listener);
+        targetState.createLinkTo(sourceId, label, listener);
       }
     }
   }
 
-  execEdgeCreation(delta: EdgeCreation) {
+  execEdgeCreation(delta: EdgeCreation, listener: GraphStateListener) {
     // console.log("execEdgeCreation", delta)
     const sourceId = delta.source.id.value;
     const target = delta.target.getTarget();
@@ -445,11 +446,11 @@ export class GraphState {
     }
     sourceState.outgoing.set(label, delta);
     sourceState.outgoingStates.set(label, targetState);
-    targetState.addIncoming(delta, this.listener);
+    targetState.addIncoming(delta, listener);
     targetState.incomingStates.push([label, sourceState]);
-    targetState.createLinkTo(sourceId, label, this.listener);
+    targetState.createLinkTo(sourceId, label, listener);
   }
-  unexecEdgeCreation(delta: EdgeCreation) {
+  unexecEdgeCreation(delta: EdgeCreation, listener: GraphStateListener) {
     const sourceId = delta.source.id.value;
     const target = delta.target.getTarget();
     if (target === null) {
@@ -466,12 +467,12 @@ export class GraphState {
     }
     sourceState.outgoing.delete(label);
     sourceState.outgoingStates.delete(label);
-    targetState.removeIncoming(delta, this.listener);
+    targetState.removeIncoming(delta, listener);
     targetState.incomingStates.splice(targetState.incomingStates.findIndex(([l,s]) => l===label && s===sourceState), 1);
-    this.listener.deleteLink(sourceId, label);
+    listener.deleteLink(sourceId, label);
   }
 
-  execEdgeUpdate(delta: EdgeUpdate) {
+  execEdgeUpdate(delta: EdgeUpdate, listener: GraphStateListener) {
     // console.log("execEdgeUpdate", delta)
     // Delete link to old target
     const edgeCreation = delta.getCreation();
@@ -489,9 +490,9 @@ export class GraphState {
       const oldTargetState = this._getEdgeTargetState(oldTarget);
       // Delete from old target's incoming edges:
       if (oldTargetState !== undefined) {
-        oldTargetState.replaceIncoming(overwrittenEdgeOperation, delta, this.listener);
+        oldTargetState.replaceIncoming(overwrittenEdgeOperation, delta, listener);
         oldTargetState.incomingStates.splice(oldTargetState.incomingStates.findIndex(([l,s]) => l===label && s===sourceState), 1);
-        this.listener.deleteLink(sourceId, label);
+        listener.deleteLink(sourceId, label);
       }
     }
     // Create link to new target (if there is a target)
@@ -502,10 +503,10 @@ export class GraphState {
       // Add to new target's incoming edges
       if (newTargetState !== undefined) {
         if (newTarget !== oldTarget) { // if newTarget === oldTarget, the 'delta' is already part of 'incoming'
-          newTargetState.addIncoming(delta, this.listener);
+          newTargetState.addIncoming(delta, listener);
         }
         newTargetState.incomingStates.push([label, sourceState]);
-        newTargetState.createLinkTo(sourceId, label, this.listener);
+        newTargetState.createLinkTo(sourceId, label, listener);
         sourceState.outgoingStates.set(label, newTargetState);
       }
     } else {
@@ -513,7 +514,7 @@ export class GraphState {
     }
     sourceState.outgoing.set(label, delta);
   }
-  unexecEdgeUpdate(delta: EdgeUpdate) {
+  unexecEdgeUpdate(delta: EdgeUpdate, listener: GraphStateListener) {
     // console.log("execEdgeUpdate", delta)
     // Delete link to old target
     const edgeCreation = delta.getCreation();
@@ -531,9 +532,9 @@ export class GraphState {
       const newTargetState = this._getEdgeTargetState(newTarget);
       // Add to new target's incoming edges
       if (newTargetState !== undefined) {
-        newTargetState.removeIncoming(delta, this.listener);
+        newTargetState.removeIncoming(delta, listener);
         newTargetState.incomingStates.splice(newTargetState.incomingStates.findIndex(([l,s]) => l===label && s===sourceState), 1);
-        this.listener.deleteLink(sourceId, label);
+        listener.deleteLink(sourceId, label);
       }
     }
     // Restore link to old target
@@ -543,9 +544,9 @@ export class GraphState {
       const oldTargetState = this._getEdgeTargetState(oldTarget);
       // Delete from old target's incoming edges:
       if (oldTargetState !== undefined) {
-        oldTargetState.replaceIncoming(delta, overwrittenEdgeOperation, this.listener);
+        oldTargetState.replaceIncoming(delta, overwrittenEdgeOperation, listener);
         oldTargetState.incomingStates.push([label, sourceState]);
-        oldTargetState.createLinkTo(sourceId, label, this.listener);
+        oldTargetState.createLinkTo(sourceId, label, listener);
         sourceState.outgoingStates.set(label, oldTargetState);
       }
     } else {

+ 0 - 31
src/onion/version.ts

@@ -63,7 +63,6 @@ export class Version {
     for (const [parent,delta] of parents) {
       parent.children.push([this, delta]);
     }
-    // this.embeddings = embeddings;
     this.hash = hash;
     this.size = size;
   }
@@ -155,36 +154,6 @@ export class Version {
 
 const initialHash = Buffer.alloc(32); // all zeros
 
-// export class InitialVersion extends Version {
-//   private static instance: InitialVersion = new InitialVersion(); // singleton pattern
-//   // private static embedded: EmbeddedVersion = {embedded: InitialVersion.instance, overriddenDeltas: new Map()};
-
-//   private constructor() {
-//     super([], initialHash, 0);
-//   }
-
-//   static getInstance(): InitialVersion {
-//     return InitialVersion.instance;
-//   }
-
-//   // // override: we pretend that the initial version has all other empty models (also represented by the initial version) embedded into it.
-//   // getEmbedded(id: string): EmbeddedVersion| undefined {
-//   //   return InitialVersion.embedded;
-//   // }
-
-//   // // override: we pretend that the initial version has all other empty models (also represented by the initial version) embedded into it.
-//   // getEmbeddedOrThrow(id: string): EmbeddedVersion {
-//   //   return InitialVersion.embedded;
-//   // }
-// }
-
-// // The initial, empty version.
-// export const initialVersion = InitialVersion.getInstance();
-
-// export function makeInitialVersion() {
-//   return new Version([], initialHash, )
-// }
-
 export class VersionRegistry {
   readonly initialVersion: Version = new Version([], initialHash, 0);
 

+ 169 - 169
src/parser/insideness_parser.ts

@@ -4,179 +4,179 @@ import {
   ParseOrRenderResult,
 } from "./parser";
 
-import {Delta} from "../onion/delta";
-import {UUID} from "../onion/types";
-import {visitPartialOrdering} from "../util/partial_ordering";
+// import {Delta} from "../onion/delta";
+// import {UUID} from "../onion/types";
+// import {visitPartialOrdering} from "../util/partial_ordering";
 
-import {
-  Version,
-  VersionRegistry,
-  embed,
-} from "../onion/version";
-
-import {
-  NodeCreation,
-  NodeDeletion,
-  EdgeCreation,
-  EdgeUpdate,
-  PrimitiveRegistry,
-  PrimitiveDelta,
-} from "../onion/primitive_delta";
-
-import {
-  GraphState,
-  INodeState,
-} from "../onion/graph_state";
+// import {
+//   Version,
+//   VersionRegistry,
+//   embed,
+// } from "../onion/version";
 
-import {PrimitiveValue} from "../onion/types";
+// import {
+//   NodeCreation,
+//   NodeDeletion,
+//   EdgeCreation,
+//   EdgeUpdate,
+//   PrimitiveRegistry,
+//   PrimitiveDelta,
+// } from "../onion/primitive_delta";
 
 // import {
 //   GraphState,
-//   Node,
-//   Value,
-//   Edge,
+//   INodeState,
 // } from "../onion/graph_state";
 
-import {getDeltasForDelete} from "../onion/delete_node";
-
-
-export interface Geometry2DRect {
-  x: number;
-  y: number;
-  w: number;
-  h: number;
-}
-
-const labels = ["x", "y", "width", "height"];
-
-// Whether a is inside of b
-const isInside = (a: Geometry2DRect, b: Geometry2DRect): boolean => 
-    a.x > b.x && a.y > b.y && a.x+a.w < b.x+b.w && a.y+a.h < b.y+b.h;
-
-// A parser that creates an AS-node for every CS-node, with a Corr-node in between.
-export class InsidenessParser {
-  readonly getUuid: () => UUID;
-
-  readonly primitiveRegistry: PrimitiveRegistry;
-
-  constructor(primitiveRegistry: PrimitiveRegistry, getUuid: () => UUID) {
-    this.primitiveRegistry = primitiveRegistry;
-    this.getUuid = getUuid;
-  }
-
-  parse(csDeltas: PrimitiveDelta[], csState: GraphState, corrState: GraphState, asState: GraphState) {
-    csDeltas.forEach(d => csState.exec(d));
-
-    const asDeltas: Delta[] = []; // deltas that are only part of AS model
-    const corrDeltas: Delta[] = []; // deltas that are part of correspondence model, but NOT AS model
-
-    const csOverrides: Map<Delta,Delta> = new Map();
-    const asOverrides: Map<Delta,Delta> = new Map();
-
-    const updatedGeometryNodes: Map<PrimitiveValue, NodeCreation> = new Map();
-    for (const csPrimitive of csDeltas) {
-      if (csPrimitive instanceof EdgeCreation || csPrimitive instanceof EdgeUpdate) {
-        const edgeCreation = csPrimitive.getCreation();
-        for (const label of labels) {
-          if (edgeCreation.label === label) {
-            updatedGeometryNodes.set(edgeCreation.source.id.value, edgeCreation.source);
-          }
-        }
-      }
-    }
-
-    function getGeometry(nodeId: PrimitiveValue): Geometry2DRect | undefined {
-      const node = csState.nodes.get(nodeId);
-      if (node !== undefined) {
-        try {
-          return {
-            x: (node.getOutgoingEdges().get("x")?.asTarget()) as number,
-            y: (node.getOutgoingEdges().get("y")?.asTarget()) as number,
-            w: (node.getOutgoingEdges().get("width")?.asTarget()) as number,
-            h: (node.getOutgoingEdges().get("height")?.asTarget()) as number,
-          };
-        }
-        catch(e) {}
-      }
-    }
-
-
-    const findCorrespondingAsNode = (csNode: INodeState) => {
-      const pair = csNode.getIncomingEdges().find(([label]) => label==="cs");
-      if (pair === undefined) {
-        throw new Error("No incoming 'cs' edge.");
-      }
-      const [_, corrNodeState] = pair;
-      // const corr2Cs = csNode.incoming.find(edge => edge.label==="cs") as Edge;
-      // const corrNode = corrNodeState.creation;
-      const asState = corrNodeState.getOutgoingEdges().get("as");
-      if (asState === undefined) {
-        throw new Error("Found correspondence node, but it has no outgoing 'as' edge.")
-      }
-      return asState as INodeState;
-    }
-
-    for (const [updatedNodeId, updatedNodeCreation] of updatedGeometryNodes.entries()) {
-      const updatedNode = corrState.nodes.get(updatedNodeId) as INodeState;
-      const updatedGeometry = getGeometry(updatedNodeId);
-      if (updatedGeometry === undefined) {
-        continue; // only interested in nodes that have a geometry
-      }
-
-      // 1. Check if existing insideness relations still hold
-      const updatedAsNode = findCorrespondingAsNode(updatedNode);
-
-      // 2. Check if an insideness relation exists with every other node
-      for (const otherNode of corrState.nodes.values()) {
-        if (otherNode === updatedNode) {
-          continue; // only compare with other nodes
-        }
-        const otherGeometry = getGeometry(otherNode.creation.id.value);
-        if (otherGeometry === undefined) {
-          continue; // only interested in nodes that have a geometry
-        }
-
-        const updatedNodeInside = isInside(updatedGeometry, otherGeometry);
-        const updatedNodeOutside = isInside(otherGeometry, updatedGeometry);
-
-        if (updatedNodeInside || updatedNodeOutside) {
-          console.log("INSIDENESS DETECTED");
-
-          const corrInsideness = this.primitiveRegistry.newNodeCreation(this.getUuid());
-          const corr2UpdatedNode = this.primitiveRegistry.newEdgeCreation(corrInsideness, updatedNodeInside ? "cs-inside" : "cs-outside", updatedNodeCreation);
-          const corr2OtherNode = this.primitiveRegistry.newEdgeCreation(corrInsideness, updatedNodeInside? "cs-outside" : "cs-inside", otherNode.creation);
-
-
-          const otherAsNode = findCorrespondingAsNode(otherNode);
-
-          const asInsidenessLink = this.primitiveRegistry.newEdgeCreation(
-            updatedNodeInside ? updatedAsNode.creation : otherAsNode.creation,
-            "inside",
-            updatedNodeInside ? otherAsNode.creation : updatedAsNode.creation);
-
-          asDeltas.push(asInsidenessLink);
-          corrDeltas.push(corrInsideness, corr2UpdatedNode, corr2OtherNode);
-        }
-      }
-    }
-
-    const result = {
-      corrDeltas: corrDeltas,
-      targetDeltas: asDeltas,
-      sourceOverrides: csOverrides,
-      targetOverrides: asOverrides,
-    };
-    // console.log(result);
-    return result;
-  }
-
-  render(asDeltas: PrimitiveDelta[], csState: GraphState, corrState: GraphState, asState: GraphState) {
-    return {
-      corrDeltas: [],
-      targetDeltas: [],
-      csOverrides: new Map(),
-      asOverrides: new Map(),
-    }
-  }
-
-}
+// import {PrimitiveValue} from "../onion/types";
+
+// // import {
+// //   GraphState,
+// //   Node,
+// //   Value,
+// //   Edge,
+// // } from "../onion/graph_state";
+
+// import {getDeltasForDelete} from "../onion/delete_node";
+
+
+// export interface Geometry2DRect {
+//   x: number;
+//   y: number;
+//   w: number;
+//   h: number;
+// }
+
+// const labels = ["x", "y", "width", "height"];
+
+// // Whether a is inside of b
+// const isInside = (a: Geometry2DRect, b: Geometry2DRect): boolean => 
+//     a.x > b.x && a.y > b.y && a.x+a.w < b.x+b.w && a.y+a.h < b.y+b.h;
+
+// // A parser that creates an AS-node for every CS-node, with a Corr-node in between.
+// export class InsidenessParser {
+//   readonly getUuid: () => UUID;
+
+//   readonly primitiveRegistry: PrimitiveRegistry;
+
+//   constructor(primitiveRegistry: PrimitiveRegistry, getUuid: () => UUID) {
+//     this.primitiveRegistry = primitiveRegistry;
+//     this.getUuid = getUuid;
+//   }
+
+//   parse(csDeltas: PrimitiveDelta[], csState: GraphState, corrState: GraphState, asState: GraphState) {
+//     csDeltas.forEach(d => csState.exec(d));
+
+//     const asDeltas: Delta[] = []; // deltas that are only part of AS model
+//     const corrDeltas: Delta[] = []; // deltas that are part of correspondence model, but NOT AS model
+
+//     const csOverrides: Map<Delta,Delta> = new Map();
+//     const asOverrides: Map<Delta,Delta> = new Map();
+
+//     const updatedGeometryNodes: Map<PrimitiveValue, NodeCreation> = new Map();
+//     for (const csPrimitive of csDeltas) {
+//       if (csPrimitive instanceof EdgeCreation || csPrimitive instanceof EdgeUpdate) {
+//         const edgeCreation = csPrimitive.getCreation();
+//         for (const label of labels) {
+//           if (edgeCreation.label === label) {
+//             updatedGeometryNodes.set(edgeCreation.source.id.value, edgeCreation.source);
+//           }
+//         }
+//       }
+//     }
+
+//     function getGeometry(nodeId: PrimitiveValue): Geometry2DRect | undefined {
+//       const node = csState.nodes.get(nodeId);
+//       if (node !== undefined) {
+//         try {
+//           return {
+//             x: (node.getOutgoingEdges().get("x")?.asTarget()) as number,
+//             y: (node.getOutgoingEdges().get("y")?.asTarget()) as number,
+//             w: (node.getOutgoingEdges().get("width")?.asTarget()) as number,
+//             h: (node.getOutgoingEdges().get("height")?.asTarget()) as number,
+//           };
+//         }
+//         catch(e) {}
+//       }
+//     }
+
+
+//     const findCorrespondingAsNode = (csNode: INodeState) => {
+//       const pair = csNode.getIncomingEdges().find(([label]) => label==="cs");
+//       if (pair === undefined) {
+//         throw new Error("No incoming 'cs' edge.");
+//       }
+//       const [_, corrNodeState] = pair;
+//       // const corr2Cs = csNode.incoming.find(edge => edge.label==="cs") as Edge;
+//       // const corrNode = corrNodeState.creation;
+//       const asState = corrNodeState.getOutgoingEdges().get("as");
+//       if (asState === undefined) {
+//         throw new Error("Found correspondence node, but it has no outgoing 'as' edge.")
+//       }
+//       return asState as INodeState;
+//     }
+
+//     for (const [updatedNodeId, updatedNodeCreation] of updatedGeometryNodes.entries()) {
+//       const updatedNode = corrState.nodes.get(updatedNodeId) as INodeState;
+//       const updatedGeometry = getGeometry(updatedNodeId);
+//       if (updatedGeometry === undefined) {
+//         continue; // only interested in nodes that have a geometry
+//       }
+
+//       // 1. Check if existing insideness relations still hold
+//       const updatedAsNode = findCorrespondingAsNode(updatedNode);
+
+//       // 2. Check if an insideness relation exists with every other node
+//       for (const otherNode of corrState.nodes.values()) {
+//         if (otherNode === updatedNode) {
+//           continue; // only compare with other nodes
+//         }
+//         const otherGeometry = getGeometry(otherNode.creation.id.value);
+//         if (otherGeometry === undefined) {
+//           continue; // only interested in nodes that have a geometry
+//         }
+
+//         const updatedNodeInside = isInside(updatedGeometry, otherGeometry);
+//         const updatedNodeOutside = isInside(otherGeometry, updatedGeometry);
+
+//         if (updatedNodeInside || updatedNodeOutside) {
+//           console.log("INSIDENESS DETECTED");
+
+//           const corrInsideness = this.primitiveRegistry.newNodeCreation(this.getUuid());
+//           const corr2UpdatedNode = this.primitiveRegistry.newEdgeCreation(corrInsideness, updatedNodeInside ? "cs-inside" : "cs-outside", updatedNodeCreation);
+//           const corr2OtherNode = this.primitiveRegistry.newEdgeCreation(corrInsideness, updatedNodeInside? "cs-outside" : "cs-inside", otherNode.creation);
+
+
+//           const otherAsNode = findCorrespondingAsNode(otherNode);
+
+//           const asInsidenessLink = this.primitiveRegistry.newEdgeCreation(
+//             updatedNodeInside ? updatedAsNode.creation : otherAsNode.creation,
+//             "inside",
+//             updatedNodeInside ? otherAsNode.creation : updatedAsNode.creation);
+
+//           asDeltas.push(asInsidenessLink);
+//           corrDeltas.push(corrInsideness, corr2UpdatedNode, corr2OtherNode);
+//         }
+//       }
+//     }
+
+//     const result = {
+//       corrDeltas: corrDeltas,
+//       targetDeltas: asDeltas,
+//       sourceOverrides: csOverrides,
+//       targetOverrides: asOverrides,
+//     };
+//     // console.log(result);
+//     return result;
+//   }
+
+//   render(asDeltas: PrimitiveDelta[], csState: GraphState, corrState: GraphState, asState: GraphState) {
+//     return {
+//       corrDeltas: [],
+//       targetDeltas: [],
+//       csOverrides: new Map(),
+//       asOverrides: new Map(),
+//     }
+//   }
+
+// }

+ 13 - 12
src/parser/trivial_parser.ts

@@ -7,14 +7,6 @@ import {Delta} from "../onion/delta";
 import {PrimitiveValue, UUID} from "../onion/types";
 import {visitPartialOrdering} from "../util/partial_ordering";
 
-import {
-  Version,
-  VersionRegistry,
-  embed,
-} from "../onion/version";
-
-// import {GraphState} from "../onion/graph_state";
-
 import {
   NodeCreation,
   NodeDeletion,
@@ -25,7 +17,6 @@ import {
 } from "../onion/primitive_delta";
 
 import {
-  // DummyListener,
   GraphState,
   INodeState,
 } from "../onion/graph_state";
@@ -324,8 +315,18 @@ export class TrivialParser  {
       revertState.reduceRight((_,callback) => {callback(); return null;}, null);
     }
 
+    const corrDeltasOrderedByDependency: PrimitiveDelta[] = [];
+    visitPartialOrdering([
+      ...sourceDeltas.map(d => sourceOverrides.get(d) || d),
+      ...targetDeltas.map(d => targetOverrides.get(d) || d),
+      ...corrDeltas],
+      (d: Delta) => d.getDependencies(),
+      (d: Delta) => corrDeltasOrderedByDependency.push(d)
+    );
+
+
     const result = {
-      corrDeltas,
+      corrDeltas: corrDeltasOrderedByDependency,
       targetDeltas,
       sourceOverrides,
       targetOverrides,
@@ -342,7 +343,7 @@ export class TrivialParser  {
       targetOverrides: asOverrides,
     } = this.propagate_change(true, csDeltas, csState, corrState, asState);
 
-    return { corrDeltas, targetDeltas, csOverrides, asOverrides };
+    return { corrDeltas, asDeltas: targetDeltas, csOverrides, asOverrides };
   }
 
   render(asDeltas: PrimitiveDelta[], csState: GraphState, corrState: GraphState, asState: GraphState) {
@@ -353,6 +354,6 @@ export class TrivialParser  {
       targetOverrides: csOverrides,
     } = this.propagate_change(false, asDeltas, asState, corrState, csState);
 
-    return { corrDeltas, targetDeltas, csOverrides, asOverrides };
+    return { corrDeltas, csDeltas: targetDeltas, csOverrides, asOverrides };
   }
 }