Pārlūkot izejas kodu

WIP: restructure modules (broken)

Joeri Exelmans 2 gadi atpakaļ
vecāks
revīzija
1972f6c082
38 mainītis faili ar 440 papildinājumiem un 603 dzēšanām
  1. 5 5
      src/frontend/app.tsx
  2. 0 212
      src/frontend/app_state.ts
  3. 0 8
      src/frontend/constants.ts
  4. 0 0
      src/frontend/demos/assets/corr_as.svg
  5. 0 0
      src/frontend/demos/assets/corr_corr.svg
  6. 0 0
      src/frontend/demos/assets/corr_cs.svg
  7. 0 0
      src/frontend/demos/assets/editor.svg
  8. 0 0
      src/frontend/demos/assets/pd.svg
  9. 0 0
      src/frontend/demos/blocks.tsx
  10. 0 0
      src/frontend/demos/d3graph/d3graph.css
  11. 64 64
      src/frontend/graph.tsx
  12. 17 21
      src/frontend/editable_graph.tsx
  13. 7 6
      src/frontend/d3_state.ts
  14. 4 4
      src/frontend/demo_bm.tsx
  15. 4 4
      src/frontend/demo_corr.tsx
  16. 3 3
      src/frontend/demo_editor.tsx
  17. 4 4
      src/frontend/demo_pd.tsx
  18. 0 0
      src/frontend/demos/demo_welcome.tsx
  19. 0 0
      src/frontend/demos/help_icons.tsx
  20. 17 18
      src/frontend/manual_renderer.tsx
  21. 1 1
      src/frontend/rountangleEditor/RountangleActions.ts
  22. 1 1
      src/frontend/rountangleEditor/RountangleComponent.tsx
  23. 0 0
      src/frontend/demos/rountangleEditor/RountangleEditor.css
  24. 8 9
      src/frontend/rountangleEditor/RountangleEditor.tsx
  25. 1 1
      src/frontend/rountangleEditor/RountangleResizeHandleComponent.tsx
  26. 0 0
      src/frontend/demos/rountangleEditor/RountangleStore.ts
  27. 10 11
      src/frontend/correspondence.tsx
  28. 135 0
      src/frontend/demos/versioned_model/delta_graph.ts
  29. 87 0
      src/frontend/demos/versioned_model/history_graph.ts
  30. 41 41
      src/frontend/versioned_model.tsx
  31. 17 0
      src/frontend/demos/versioned_model/useVersionedModel.ts
  32. 2 2
      src/frontend/index.tsx
  33. 0 182
      src/parser/insideness_parser.ts
  34. 3 3
      src/parser/parser.ts
  35. 1 1
      src/parser/trivial_parser.test.ts
  36. 1 1
      src/parser/trivial_parser.ts
  37. 4 1
      tsconfig.json
  38. 3 0
      webpack.config.js

+ 5 - 5
src/frontend/app.tsx

@@ -5,12 +5,12 @@ import {IconExternalLink} from '@tabler/icons';
 import {Allotment} from "allotment";
 import "allotment/dist/style.css";
 
-import {demo_PD_description, getDemoPD} from "./demo_pd";
-import {demo_Corr_description, getDemoCorr} from "./demo_corr";
-import {demo_BM_description, getDemoBM} from "./demo_bm";
+import {demo_PD_description, getDemoPD} from "./demos/demo_pd";
+import {demo_Corr_description, getDemoCorr} from "./demos/demo_corr";
+import {demo_BM_description, getDemoBM} from "./demos/demo_bm";
 import {Styledtabs} from "./styledtabs";
-import {demo_Editor_description, getDemoEditor} from "./demo_editor";
-import {demo_Welcome_description, getWelcome} from "./demo_welcome";
+import {demo_Editor_description, getDemoEditor} from "./demos/demo_editor";
+import {demo_Welcome_description, getWelcome} from "./demos/demo_welcome";
 
 export function getApp() {
     const Welcome = getWelcome();

+ 0 - 212
src/frontend/app_state.ts

@@ -1,212 +0,0 @@
-import {Version} from "../onion/version";
-import {Delta} from "../onion/delta";
-import {NodeCreation, NodeDeletion, EdgeCreation, EdgeUpdate} from "../onion/primitive_delta";
-import {CompositeDelta} from "../onion/composite_delta";
-import {d3Types} from "./graph";
-
-export type HistoryGraphType = d3Types.d3Graph<Version|null,Delta>;
-export type DependencyGraphType = d3Types.d3Graph<Delta,null>;
-
-// This module contains functions for updating the state of the 'Graph' React/D3 component.
-
-///// ALL OF THESE ARE PURE FUNCTIONS: //////
-
-export function initialHistoryGraph(initialVersion) {
-  return {
-    nodes: [
-      versionToNode(initialVersion, true),
-    ],
-    links: [],
-  };
-}
-
-export function fullVersionId(version: Version): string {
-  return version.hash.toString('base64');
-}
-
-export function fullDeltaId(delta: Delta): string {
-  return delta.getHash().toString('base64');
-}
-export function versionToNode(version: Version, highlight: boolean, x?: number, y?: number): d3Types.d3Node<Version> {
-  return {
-    id: fullVersionId(version),
-    label: (version.parents.length === 0 ? "initial" : ""),
-    color: (version.parents.length === 0 ? "grey" : "purple"),
-    obj: version,
-    highlight,
-    x, y,
-  }
-}
-export function setCurrentVersion(prevHistoryGraph: HistoryGraphType, prevCurrentVersion, newCurrentVersion): HistoryGraphType {
-  if (prevCurrentVersion === newCurrentVersion) {
-    return prevHistoryGraph;
-  }
-  let links;
-  return {
-    nodes: prevHistoryGraph.nodes.map(n => {
-      if (n.obj === prevCurrentVersion) {
-        return versionToNode(prevCurrentVersion, false, n.x, n.y);
-      }
-      if (n.obj === newCurrentVersion) {
-        return versionToNode(newCurrentVersion, true, n.x, n.y);
-      }
-      return n;
-    }),
-    // must re-create links for re-created nodes:
-    links: prevHistoryGraph.links.map(l => {
-      if (l.source.obj === prevCurrentVersion || l.target.obj === prevCurrentVersion || l.source.obj === newCurrentVersion || l.target.obj === newCurrentVersion) {
-        return parentLinkToHistoryGraphLink(l.source.obj, l.target.obj, l.obj);
-      }
-      return l;
-    }),
-  }
-}
-export function parentLinkToHistoryGraphLink(childVersion: Version, parentVersion: Version, delta: Delta): d3Types.d3Link<Delta> {
-  return {
-    source: fullVersionId(childVersion),
-    label: delta.getDescription(),
-    target: fullVersionId(parentVersion),
-    color: 'black',
-    obj: delta,
-  };
-}
-// Appends new version to history graph as a node, and its 'parent'-links as links.
-// Idempotent
-export function appendToHistoryGraph(prevHistoryGraph: HistoryGraphType, version: Version): HistoryGraphType {
-  const newLinks = version.parents.map(([parentVersion,delta]) => parentLinkToHistoryGraphLink(version, parentVersion, delta)).filter(link => !prevHistoryGraph.links.some(prevLink => prevLink.source.id === link.source && prevLink.target.id === link.target));
-
-  return {
-    nodes: prevHistoryGraph.nodes.some(node => node.id === fullVersionId(version)) ?
-        prevHistoryGraph.nodes : prevHistoryGraph.nodes.concat(versionToNode(version, false)),
-    links: prevHistoryGraph.links.concat(...newLinks),
-  };
-}
-
-export function deltaToDepGraphNode(delta: Delta, highlight: boolean, x?: number, y?: number): d3Types.d3Node<Delta> {
-  return {
-    id: fullDeltaId(delta),
-    label: delta.getDescription(),
-    color: getDeltaColor(delta),
-    highlight,
-    obj: delta,
-    x, y,
-  };
-}
-export function dependencyToDepGraphLink(fromDelta: Delta, toDelta: Delta, label: string): d3Types.d3Link<null> {
-  return {
-    source: fullDeltaId(fromDelta),
-    label,
-    color: 'black',
-    target: fullDeltaId(toDelta),
-    obj: null,
-  };
-}
-export function conflictToDepGraphLink(fromDelta: Delta, toDelta: Delta): d3Types.d3Link<null> {
-  return {
-    source: fullDeltaId(fromDelta),
-    label: "",
-    color: 'DarkGoldenRod',
-    bidirectional: true,
-    target: fullDeltaId(toDelta),
-    obj: null,
-  };
-}
-// Idempotent
-export function setDeltaActive(prevDepGraph: DependencyGraphType, delta: Delta): DependencyGraphType {
-  const deltaId = fullDeltaId(delta);
-  return {
-    nodes: prevDepGraph.nodes.map(n => {
-      if (n.id !== deltaId) {
-        return n;
-      }
-      else {
-        return deltaToDepGraphNode(delta, true, n.x, n.y);
-      }
-    }),
-    // re-create all links whose source or target was touched:
-    links: prevDepGraph.links.map(l => {
-      if (l.source.id === deltaId || l.target.id === deltaId) {
-        if (l.bidirectional) {
-          return conflictToDepGraphLink(l.source.obj, l.target.obj);
-        } else {
-          return dependencyToDepGraphLink(l.source.obj, l.target.obj, l.label);
-        }
-      }
-      return l;
-    }),
-  }
-}
-// Idempotent
-export function setDeltaInactive(prevDepGraph: DependencyGraphType, delta: Delta): DependencyGraphType {
-  const deltaId = fullDeltaId(delta);
-  return {
-    nodes: prevDepGraph.nodes.map(n => {
-      if (n.id !== deltaId) {
-        return n;
-      }
-      else {
-        return deltaToDepGraphNode(delta, false, n.x, n.y);
-      }
-    }),
-    // re-create all links whose source or target was touched:
-    links: prevDepGraph.links.map(l => {
-      if (l.source.id === deltaId || l.target.id === deltaId) {
-        if (l.bidirectional) {
-          return conflictToDepGraphLink(l.source.obj, l.target.obj);
-        } else {
-          return dependencyToDepGraphLink(l.source.obj, l.target.obj, l.label);
-        }
-      }
-      return l;
-    }),
-  }
-}
-export function addDeltaAndActivate(prevDepGraph: DependencyGraphType, delta: Delta, active: boolean = true): DependencyGraphType {
-  if (prevDepGraph.nodes.some(node => node.id === fullDeltaId(delta))) {
-    // We already have this delta (remember that delta's are identified by the hash of their contents, so it is possible that different people concurrently create the same deltas, e.g., by deleting the same node concurrently)
-    // Also, when re-doing after undoing, it is possible that a delta is 'added' that we already have.
-    return setDeltaActive(prevDepGraph, delta);
-  }
-  return {
-    // add one extra node that represents the new delta:
-    nodes: prevDepGraph.nodes.concat(deltaToDepGraphNode(delta, /*highlight: */ active)),
-    // for every dependency and conflict, add a link:
-    links: prevDepGraph.links.concat(
-        ...delta.getTypedDependencies().map(([dep,depSummary]) => dependencyToDepGraphLink(delta,dep,depSummary)),
-        ...delta.getConflicts().filter(conflictingDelta => prevDepGraph.nodes.some(n => n.id === fullDeltaId(conflictingDelta))).map(conflictingDelta => conflictToDepGraphLink(delta,conflictingDelta)),
-      ),
-  };
-}
-
-
-
-function shortDeltaId(delta: Delta) {
-  return delta.getHash().toString('hex').slice(0,8);
-}
-function getDeltaColor(delta: Delta) {
-  // determine delta color by "averaging":
-  let numCreations = 0; // makes delta more green
-  let numDeletions = 0; // makes delta more red
-  let numEdgeUpdates = 0; // makes delta more blue
-  let numDeltas = 0;
-  function countDeltas(delta) {
-    if (delta instanceof CompositeDelta) {
-      delta.deltas.forEach(countDeltas);
-    }
-    else {
-      numDeltas++;
-      if (delta instanceof NodeCreation) {
-        numCreations++;
-      }
-      else if (delta instanceof NodeDeletion) {
-        numDeletions++;
-      }
-      else if (delta instanceof EdgeCreation || delta instanceof EdgeUpdate) {
-        numEdgeUpdates++;
-      }
-    }
-  }
-  countDeltas(delta);
-  const color = `rgb(${255*numDeletions/numDeltas}, ${255*numCreations/numDeltas}, ${255*numEdgeUpdates/numDeltas})`;
-  return color;
-}

+ 0 - 8
src/frontend/constants.ts

@@ -1,8 +0,0 @@
-import {d3Types} from "./graph"
-
-export const emptyGraph: d3Types.d3Graph<any,any> = {
-  nodes: [],
-  links: [],
-};
-
-export const graphForces = {charge: -200, center: 0.2, link: 2};

src/frontend/assets/corr_as.svg → src/frontend/demos/assets/corr_as.svg


src/frontend/assets/corr_corr.svg → src/frontend/demos/assets/corr_corr.svg


src/frontend/assets/corr_cs.svg → src/frontend/demos/assets/corr_cs.svg


src/frontend/assets/editor.svg → src/frontend/demos/assets/editor.svg


src/frontend/assets/pd.svg → src/frontend/demos/assets/pd.svg


src/frontend/blocks.tsx → src/frontend/demos/blocks.tsx


src/frontend/graph.css → src/frontend/demos/d3graph/d3graph.css


+ 64 - 64
src/frontend/graph.tsx

@@ -4,34 +4,38 @@
 import * as React from 'react';
 import * as d3 from "d3";
 
-export namespace d3Types {
-  export type d3Node<NodeType> = {
-    id: string,
-    label: string,
-    color: string,
-    obj: NodeType;
-    x?: number,
-    y?: number,
-    highlight: boolean,
-  };
-
-  export type d3Link<LinkType> = {
-    source: any, // initially string, but d3 replaces it by a d3Node<NodeType> (lol)
-    target: any, // initially string, but d3 replaces it by a d3Node<NodeType> (lol)
-    label: string,
-    color: string,
-    bidirectional?: boolean,
-    obj: LinkType;
-  };
-
-  export type d3Graph<NodeType,LinkType> = {
-    nodes: d3Node<NodeType>[],
-    links: d3Link<LinkType>[],
-  };
-}
-
-
-class Link<LinkType> extends React.Component<{ link: d3Types.d3Link<LinkType> }, {}> {
+export type D3NodeData<NodeType> = {
+  id: string,
+  label: string,
+  color: string,
+  obj: NodeType;
+  x?: number,
+  y?: number,
+  highlight: boolean,
+};
+
+export type D3LinkData<LinkType> = {
+  source: any, // initially string, but d3 replaces it by a d3Node<NodeType> (lol)
+  target: any, // initially string, but d3 replaces it by a d3Node<NodeType> (lol)
+  label: string,
+  color: string,
+  bidirectional?: boolean,
+  obj: LinkType;
+};
+
+export type D3GraphData<NodeType,LinkType> = {
+  nodes: D3NodeData<NodeType>[],
+  links: D3LinkData<LinkType>[],
+};
+
+export const emptyGraph: D3GraphData<any,any> = {
+  nodes: [],
+  links: [],
+};
+
+export const defaultGraphForces = {charge: -200, center: 0.2, link: 2};
+
+class D3Link<LinkType> extends React.Component<{ link: D3LinkData<LinkType> }, {}> {
   ref: React.RefObject<SVGLineElement> = React.createRef<SVGLineElement>();
   refLabel: React.RefObject<SVGTextElement> = React.createRef<SVGTextElement>();
 
@@ -73,8 +77,8 @@ class Link<LinkType> extends React.Component<{ link: d3Types.d3Link<LinkType> },
   }
 }
 
-class Links<LinkType> extends React.Component<{ links: d3Types.d3Link<LinkType>[] }, {}> {
-  links: Array<Link<LinkType> | null> = [];
+class D3Links<LinkType> extends React.Component<{ links: D3LinkData<LinkType>[] }, {}> {
+  links: Array<D3Link<LinkType> | null> = [];
 
   ticked() {
     this.links.forEach(l => l ? l.ticked() : null);
@@ -83,8 +87,8 @@ class Links<LinkType> extends React.Component<{ links: d3Types.d3Link<LinkType>[
   render() {
     const nodeId = sourceOrTarget => sourceOrTarget.id ? sourceOrTarget.id : sourceOrTarget;
     const key = link => nodeId(link.source)+nodeId(link.target)+link.label;
-    const links = this.props.links.map((link: d3Types.d3Link<LinkType>, index: number) => {
-      return <Link ref={link => this.links.push(link)} key={key(link)} link={link} />;
+    const links = this.props.links.map((link: D3LinkData<LinkType>, index: number) => {
+      return <D3Link ref={link => this.links.push(link)} key={key(link)} link={link} />;
     });
 
     return (
@@ -95,18 +99,18 @@ class Links<LinkType> extends React.Component<{ links: d3Types.d3Link<LinkType>[
   }
 }
 
-interface NodeProps<NodeType> {
-  node: d3Types.d3Node<NodeType>;
+interface D3NodeProps<NodeType> {
+  node: D3NodeData<NodeType>;
   simulation: any;
   mouseDownHandler: (event) => void;
   mouseUpHandler: (event) => void;
 }
-interface NodeState {
+interface D3NodeState {
   dragging: boolean;
 }
 
 
-class Node<NodeType> extends React.Component<NodeProps<NodeType>, NodeState> {
+class D3Node<NodeType> extends React.Component<D3NodeProps<NodeType>, D3NodeState> {
   ref: React.RefObject<SVGCircleElement> = React.createRef<SVGCircleElement>();
 
   constructor(props) {
@@ -176,16 +180,16 @@ class Node<NodeType> extends React.Component<NodeProps<NodeType>, NodeState> {
   }
 }
 
-interface NodesProps<NodeType> {
-  nodes: d3Types.d3Node<NodeType>[],
+interface D3NodesProps<NodeType> {
+  nodes: D3NodeData<NodeType>[],
   simulation: any,
   mouseDownHandler: (event, node) => void;
   mouseUpHandler: (event, node) => void;
 }
 
-class Nodes<NodeType> extends React.Component<NodesProps<NodeType>, {}> {
+class D3Nodes<NodeType> extends React.Component<D3NodesProps<NodeType>, {}> {
   ref: React.RefObject<SVGGElement> = React.createRef<SVGGElement>();
-  nodes: Array<Node<NodeType> | null> = [];
+  nodes: Array<D3Node<NodeType> | null> = [];
 
   ticked() {
     this.nodes.forEach(n => n ? n.ticked() : null);
@@ -193,8 +197,8 @@ class Nodes<NodeType> extends React.Component<NodesProps<NodeType>, {}> {
 
   render() {
     this.nodes = [];
-    const nodes = this.props.nodes.map((node: d3Types.d3Node<NodeType>, index: number) => {
-      return <Node
+    const nodes = this.props.nodes.map((node: D3NodeData<NodeType>, index: number) => {
+      return <D3Node
           key={node.id}
           ref={node => this.nodes.push(node)}
           mouseDownHandler={event => this.props.mouseDownHandler(event, node)}
@@ -210,7 +214,7 @@ class Nodes<NodeType> extends React.Component<NodesProps<NodeType>, {}> {
   }
 }
 
-class Label<NodeType> extends React.Component<{ node: d3Types.d3Node<NodeType> }, {}> {
+class D3Label<NodeType> extends React.Component<{ node: D3NodeData<NodeType> }, {}> {
   ref: React.RefObject<SVGTextElement>;
 
   constructor(props) {
@@ -239,16 +243,16 @@ class Label<NodeType> extends React.Component<{ node: d3Types.d3Node<NodeType> }
   }
 }
 
-class Labels<NodeType> extends React.Component<{ nodes: d3Types.d3Node<NodeType>[] }, {}> {
-  labels: Array<Label<NodeType> | null> = [];
+class D3Labels<NodeType> extends React.Component<{ nodes: D3NodeData<NodeType>[] }, {}> {
+  labels: Array<D3Label<NodeType> | null> = [];
 
   ticked() {
     this.labels.forEach(l => l ? l.ticked() : null);
   }
 
   render() {
-    const labels = this.props.nodes.map((node: d3Types.d3Node<NodeType>, index: number) => {
-      return <Label ref={label => this.labels.push(label)} key={node.id} node={node} />;
+    const labels = this.props.nodes.map((node: D3NodeData<NodeType>, index: number) => {
+      return <D3Label ref={label => this.labels.push(label)} key={node.id} node={node} />;
     });
 
     return (
@@ -259,29 +263,25 @@ class Labels<NodeType> extends React.Component<{ nodes: d3Types.d3Node<NodeType>
   }
 }
 
-export interface Forces {
+export interface D3Forces {
   charge: number;
   center: number;
   link: number;
 }
 
-export interface GraphProps<NodeType,LinkType> {
-  graph: d3Types.d3Graph<NodeType,LinkType>;
-  forces: Forces;
-  mouseDownHandler?: (e: React.SyntheticEvent, svgCoords: {x: number, y: number}, node?: d3Types.d3Node<NodeType>) => void;
-  mouseUpHandler?: (e: React.SyntheticEvent, svgCoords: {x: number, y: number}, node?: d3Types.d3Node<NodeType>) => void;
-}
-
-interface GraphState<NodeType,LinkType> {
-  zoom: number;
+interface Props<NodeType,LinkType> {
+  graph: D3GraphData<NodeType,LinkType>;
+  forces: D3Forces;
+  mouseDownHandler?: (e: React.SyntheticEvent, svgCoords: {x: number, y: number}, node?: D3NodeData<NodeType>) => void;
+  mouseUpHandler?: (e: React.SyntheticEvent, svgCoords: {x: number, y: number}, node?: D3NodeData<NodeType>) => void;
 }
 
-export class Graph<NodeType,LinkType> extends React.Component<GraphProps<NodeType,LinkType>, GraphState<NodeType,LinkType>> {
+export class D3Graph<NodeType,LinkType> extends React.Component<Props<NodeType,LinkType>, { zoom: number; }> {
   simulation: any;
   refSVG: React.RefObject<SVGSVGElement> = React.createRef<SVGSVGElement>();
-  refNodes: React.RefObject<Nodes<NodeType>> = React.createRef<Nodes<NodeType>>();
-  refLabels: React.RefObject<Labels<NodeType>> = React.createRef<Labels<NodeType>>();
-  refLinks: React.RefObject<Links<LinkType>> = React.createRef<Links<LinkType>>();
+  refNodes: React.RefObject<D3Nodes<NodeType>> = React.createRef<D3Nodes<NodeType>>();
+  refLabels: React.RefObject<D3Labels<NodeType>> = React.createRef<D3Labels<NodeType>>();
+  refLinks: React.RefObject<D3Links<LinkType>> = React.createRef<D3Links<LinkType>>();
 
   constructor(props) {
     super(props);
@@ -339,9 +339,9 @@ export class Graph<NodeType,LinkType> extends React.Component<GraphProps<NodeTyp
           </marker>
         </defs>
 
-        <Links ref={this.refLinks} links={graph.links} />
-        <Labels ref={this.refLabels} nodes={graph.nodes} />
-        <Nodes ref={this.refNodes} nodes={graph.nodes} simulation={this.simulation}
+        <D3Links ref={this.refLinks} links={graph.links} />
+        <D3Labels ref={this.refLabels} nodes={graph.nodes} />
+        <D3Nodes ref={this.refNodes} nodes={graph.nodes} simulation={this.simulation}
           mouseDownHandler={(e, node) => clientToSvgCoords(e, (e,coords) => this.props.mouseDownHandler?.(e,coords,node))}
           mouseUpHandler={(e, node) => clientToSvgCoords(e, (e,coords) => this.props.mouseUpHandler?.(e,coords,node))}
         />

+ 17 - 21
src/frontend/editable_graph.tsx

@@ -1,5 +1,5 @@
 import * as React from 'react';
-import {d3Types, Graph, GraphProps, Forces} from "./graph";
+import {D3Graph, D3GraphData, D3NodeData, D3LinkData, D3Forces} from "./d3graph";
 
 import {
   NodeCreation,
@@ -8,57 +8,53 @@ import {
   EdgeUpdate,
   PrimitiveDelta,
   PrimitiveRegistry,
-} from "../onion/primitive_delta";
+} from "onion/primitive_delta";
 
-import {INodeState, IValueState, GraphState} from "../onion/graph_state";
+import {INodeState, IValueState, GraphState} from "onion/graph_state";
 
 
-import {PrimitiveValue, UUID} from "../onion/types";
+import {PrimitiveValue, UUID} from "onion/types";
 
 import {
   Version,
   VersionRegistry,
-} from "../onion/version";
+} from "onion/version";
 
-import {getDeltasForDelete} from "../onion/delete_node";
+import {getDeltasForDelete} from "onion/delete_node";
 
-export type NodeType = d3Types.d3Node<INodeState|IValueState>;
-export type LinkType = d3Types.d3Link<null>;
-export type GraphType = d3Types.d3Graph<INodeState|IValueState,null>;
+export type D3OnionNodeData = D3NodeData<INodeState|IValueState>;
+export type D3OnionLinkData = D3LinkData<null>;
+export type D3OnionGraphData = D3GraphData<INodeState|IValueState,null>;
 
 export type UserEditCallback = (deltas: PrimitiveDelta[], description: string) => void;
 export type SetNodePositionCallback = (x:number, y:number) => void;
 
-export interface EditableGraphProps {
-  graph: GraphType;
+export interface D3GraphEditableProps {
+  graph: D3OnionGraphData;
   primitiveRegistry: PrimitiveRegistry;
   graphState: GraphState;
-  forces: Forces;
+  forces: D3Forces;
   generateUUID: () => UUID;
   setNextNodePosition: SetNodePositionCallback;
   onUserEdit?: UserEditCallback;
 }
 
-interface EditableGraphState {
-}
-
-
-export class EditableGraph extends React.Component<EditableGraphProps, EditableGraphState> {
-  mouseDownNode: NodeType | null = null; // only while mouse button is down, does this record the d3Node that was under the cursor when the mouse went down.
+export class D3GraphEditable extends React.Component<D3GraphEditableProps, {}> {
+  mouseDownNode: D3OnionNodeData | null = null; // only while mouse button is down, does this record the d3Node that was under the cursor when the mouse went down.
 
   constructor(props) {
     super(props);
     this.state = {showCrossHair: false};
   }
 
-  mouseDownHandler = (event, {x,y}, mouseDownNode: NodeType | undefined) => {
+  mouseDownHandler = (event, {x,y}, mouseDownNode: D3OnionNodeData | undefined) => {
     event.stopPropagation();
     if (mouseDownNode) {
       this.mouseDownNode = mouseDownNode;
     }
   }
 
-  mouseUpHandler = (event, {x,y}, mouseUpNode: NodeType | undefined) => {
+  mouseUpHandler = (event, {x,y}, mouseUpNode: D3OnionNodeData | undefined) => {
     event.stopPropagation();
 
     // Construct the delta(s) that capture the user's change:
@@ -127,7 +123,7 @@ export class EditableGraph extends React.Component<EditableGraphProps, EditableG
 
   render() {
     return (
-      <Graph
+      <D3Graph
         graph={this.props.graph}
         forces={this.props.forces}
 

+ 7 - 6
src/frontend/d3_state.ts

@@ -1,6 +1,6 @@
-import {PrimitiveValue} from "../onion/types";
-import {INodeState, IValueState, GraphStateListener} from "../onion/graph_state"; 
-import {GraphType} from "./editable_graph";
+import {PrimitiveValue} from "onion/types";
+import {INodeState, IValueState, GraphStateListener} from "onion/graph_state"; 
+import {D3OnionGraphData} from "./d3graph_editable";
 
 function nodeNodeId(nodeId: PrimitiveValue) {
   return "N"+JSON.stringify(nodeId);
@@ -10,15 +10,16 @@ function valueNodeId(value: PrimitiveValue) {
   return "V"+JSON.stringify(value);
 }
 
-export class D3GraphStateUpdater implements GraphStateListener {
-  readonly setGraph: (cb: (prevGraph: GraphType) => GraphType) => void;
+// Responds to changes to a GraphState object by updating the React state of a d3Graph component.
+export class D3GraphUpdater implements GraphStateListener {
+  readonly setGraph: (cb: (prevGraph: D3OnionGraphData) => D3OnionGraphData) => void;
 
   // SVG coordinates for newly created nodes
   // This information cannot be part of our NodeCreation deltas, but it must come from somewhere...
   x: number;
   y: number;
 
-  constructor(setGraph: (cb: (prevGraph: GraphType) => GraphType) => void, x, y) {
+  constructor(setGraph: (cb: (prevGraph: D3OnionGraphData) => D3OnionGraphData) => void, x, y) {
     this.setGraph = setGraph;
     this.x = x;
     this.y = y;

+ 4 - 4
src/frontend/demo_bm.tsx

@@ -1,11 +1,11 @@
 import * as React from 'react';
 import {Center, Grid, Group, Space, Stack, Text, Title} from '@mantine/core';
 
-import {PrimitiveRegistry} from '../onion/primitive_delta';
-import {mockUuid} from '../onion/test_helpers';
+import {PrimitiveRegistry} from '../../onion/primitive_delta';
+import {mockUuid} from '../../onion/test_helpers';
 import {makeInfoHoverCardIcon} from './help_icons';
-import {newVersionedModel, undoButtonHelpText, VersionedModelState} from './versioned_model';
-import {newCorrespondence, undoButtonHelpTextCorr} from './correspondence';
+import {newVersionedModel, undoButtonHelpText, VersionedModelState} from './versioned_model/single_model';
+import {newCorrespondence, undoButtonHelpTextCorr} from './versioned_model/correspondence';
 import {ManualRendererProps, newManualRenderer} from './manual_renderer';
 import {Actionblock, Resultblock} from './blocks';
 

+ 4 - 4
src/frontend/demo_corr.tsx

@@ -1,11 +1,11 @@
 import * as React from "react";
 import {SimpleGrid, Text, Title, Stack, Center, Image, Group, Space} from "@mantine/core";
 
-import {PrimitiveRegistry} from "../onion/primitive_delta";
-import {mockUuid} from "../onion/test_helpers";
+import {PrimitiveRegistry} from "onion/primitive_delta";
+import {mockUuid} from "onion/test_helpers";
 
-import {newVersionedModel, VersionedModelState, undoButtonHelpText} from "./versioned_model";
-import {newCorrespondence, undoButtonHelpTextCorr} from "./correspondence";
+import {newVersionedModel, VersionedModelState, undoButtonHelpText} from "./versioned_model/single_model";
+import {newCorrespondence, undoButtonHelpTextCorr} from "./versioned_model/correspondence";
 import {newManualRenderer, ManualRendererProps} from "./manual_renderer";
 import {makeInfoHoverCardIcon} from "./help_icons";
 import corr_csImage from './assets/corr_cs.svg';

+ 3 - 3
src/frontend/demo_editor.tsx

@@ -1,10 +1,10 @@
 import * as React from "react";
 import {SimpleGrid, Text, Title, Stack, Center, Group, Space, Image} from "@mantine/core";
 
-import {PrimitiveRegistry} from "../onion/primitive_delta";
-import {mockUuid} from "../onion/test_helpers";
+import {PrimitiveRegistry} from "onion/primitive_delta";
+import {mockUuid} from "onion/test_helpers";
 
-import {newVersionedModel, VersionedModelState} from "./versioned_model";
+import {newVersionedModel, VersionedModelState} from "./versioned_model/single_model";
 import {newManualRenderer, ManualRendererProps} from "./manual_renderer";
 import {makeInfoHoverCardIcon} from "./help_icons";
 import {Actionblock, Resultblock} from "./blocks";

+ 4 - 4
src/frontend/demo_pd.tsx

@@ -2,12 +2,12 @@ import * as React from 'react';
 import * as Icons from '@tabler/icons';
 import {Button, Divider, Group, Image, SimpleGrid, Space, Text, Title,} from '@mantine/core';
 
-import {PrimitiveRegistry} from '../onion/primitive_delta';
-import {PrimitiveValue} from '../onion/types';
-import {mockUuid} from '../onion/test_helpers';
+import {PrimitiveRegistry} from 'onion/primitive_delta';
+import {PrimitiveValue} from 'onion/types';
+import {mockUuid} from 'onion/test_helpers';
 import {Actionblock, Resultblock} from './blocks';
 import pdImage from './assets/pd.svg';
-import {newVersionedModel, undoButtonHelpText, VersionedModelState,} from './versioned_model';
+import {newVersionedModel, undoButtonHelpText, VersionedModelState,} from './versioned_model/single_model';
 import {makeInfoHoverCardIcon} from './help_icons';
 
 export const demo_PD_description =

src/frontend/demo_welcome.tsx → src/frontend/demos/demo_welcome.tsx


src/frontend/help_icons.tsx → src/frontend/demos/help_icons.tsx


+ 17 - 18
src/frontend/manual_renderer.tsx

@@ -3,20 +3,19 @@ import {IconChevronLeft, IconChevronRight, IconAlertCircle, IconCircleCheck, Ico
 import {SimpleGrid, Group, Grid, Stack, Title, Button, Text, Modal, Center, List, Space, Table} from "@mantine/core";
 
 import {RountangleEditor} from "./rountangleEditor/RountangleEditor";
-import {Version} from "../onion/version";
-import {emptyGraph, graphForces} from "./constants";
+import {Version} from "onion/version";
 import {makeOverlayHelpIcon} from "./help_icons";
-import {d3Types, Graph} from "./graph";
-import {PrimitiveDelta, EdgeCreation, EdgeUpdate, PrimitiveRegistry, NodeCreation, NodeDeletion} from "../onion/primitive_delta";
-import {GraphType} from "./editable_graph";
-import {UUID, PrimitiveValue} from "../onion/types";
-import {GraphState, INodeState} from "../onion/graph_state";
-import {Geometry2DRect, getGeometry, isInside} from "../parser/trivial_parser";
-import {D3GraphStateUpdater} from "./d3_state";
+import {D3Graph, emptyGraph, defaultGraphForces} from "./d3graph/d3graph";
+import {D3OnionGraphData} from "./d3graph/d3graph_editable";
+import {PrimitiveDelta, EdgeCreation, EdgeUpdate, PrimitiveRegistry, NodeCreation, NodeDeletion} from "onion/primitive_delta";
+import {UUID, PrimitiveValue} from "onion/types";
+import {GraphState, INodeState} from "onion/graph_state";
+import {Geometry2DRect, getGeometry, isInside} from "../../parser/rountangle_parser";
+import {D3GraphUpdater} from "./d3graph/d3graph_updater";
 
 export interface ManualRendererProps {
-  asGraph: GraphType;
-  csGraph: GraphType;
+  asGraph: D3OnionGraphData;
+  csGraph: D3OnionGraphData;
 
   asGraphState: GraphState;
   csGraphState: GraphState;
@@ -33,7 +32,7 @@ export interface ManualRendererProps {
 // Replay all deltas in version to get graph state (+ D3 graph state)
 function getD3GraphState(version: Version, setGraph) {
   const graphState = new GraphState();
-  const d3Updater = new D3GraphStateUpdater(setGraph, 0, 0);
+  const d3Updater = new D3GraphUpdater(setGraph, 0, 0);
   for (const d of [...version].reverse()) {
     graphState.exec(d, d3Updater)
   }
@@ -70,7 +69,7 @@ export function newManualRenderer({generateUUID, primitiveRegistry}) {
 
   // Return functional React component
   function ManualRenderer(props: ManualRendererProps) {
-    const [csGraph, setCsGraph] = React.useState<GraphType>(props.csGraph);
+    const [csGraph, setCsGraph] = React.useState<D3OnionGraphData>(props.csGraph);
     const [csGraphState] = React.useState<GraphState>(props.csGraphState);
     const [csDeltas, setCsDeltas] = React.useState<PrimitiveDelta[]>([]);
 
@@ -143,13 +142,13 @@ export function newManualRenderer({generateUUID, primitiveRegistry}) {
                 const csDelta = csDeltas[csDeltas.length-1];
                 setCsDeltas(csDeltas.slice(0,-1));
                 setUndone(undone.concat(csDelta));
-                csGraphState.unexec(csDelta, new D3GraphStateUpdater(setCsGraph, 0, 0));
+                csGraphState.unexec(csDelta, new D3GraphUpdater(setCsGraph, 0, 0));
               }} compact leftIcon={<IconChevronLeft/>}>Undo</Button>
               <Button disabled={undone.length === 0} onClick={() => {
                 const csDelta = undone[undone.length-1];
                 setCsDeltas(csDeltas.concat(csDelta));
                 setUndone(undone.slice(0,-1));
-                csGraphState.exec(csDelta, new D3GraphStateUpdater(setCsGraph, 0, 0));
+                csGraphState.exec(csDelta, new D3GraphUpdater(setCsGraph, 0, 0));
               }} compact rightIcon={<IconChevronRight/>}>Redo</Button>
             </Group>
           </Group>
@@ -164,7 +163,7 @@ export function newManualRenderer({generateUUID, primitiveRegistry}) {
                 const filteredDeltas = deltas.filter(d => d instanceof EdgeUpdate);
                 setCsDeltas(prevCsDeltas => prevCsDeltas.concat(filteredDeltas));
                 setUndone([]);
-                filteredDeltas.forEach(d => csGraphState.exec(d, new D3GraphStateUpdater(setCsGraph, 0, 0)));
+                filteredDeltas.forEach(d => csGraphState.exec(d, new D3GraphUpdater(setCsGraph, 0, 0)));
                 setInconsistencies(findInconsistencies(csGraphState, props.asGraphState));
               }}
             />,
@@ -189,9 +188,9 @@ export function newManualRenderer({generateUUID, primitiveRegistry}) {
         </Stack>
         <Stack>
           <Title order={5}>Abstract Syntax (read-only)</Title>
-          {makeOverlayHelpIcon(<Graph
+          {makeOverlayHelpIcon(<D3Graph
             graph={props.asGraph}
-            forces={graphForces}
+            forces={defaultGraphForces}
           />, <Text><b>Left-Drag</b>: Drag Node</Text>)}
         </Stack>
       </Group>

+ 1 - 1
src/frontend/rountangleEditor/RountangleActions.ts

@@ -1,4 +1,4 @@
-import {PrimitiveValue} from "../../onion/types";
+import {PrimitiveValue} from "onion/types";
 
 interface CreateRountangle  {tag: 'createRountangle',  id: PrimitiveValue, posX: number, posY: number, width: number, height: number}
 interface MoveRountangle    {tag: 'moveRountangle',    id: PrimitiveValue, newPosX: number, newPosY: number}

+ 1 - 1
src/frontend/rountangleEditor/RountangleComponent.tsx

@@ -2,7 +2,7 @@ import * as React from "react";
 import {RountangleAction} from "./RountangleActions";
 import {RountangleResizeHandleComponent} from "./RountangleResizeHandleComponent";
 import {Rountangle} from "./RountangleEditor";
-import {PrimitiveValue} from "../../onion/types";
+import {PrimitiveValue} from "onion/types";
 
 export interface RountangleProps extends Rountangle {
     id:       PrimitiveValue;

src/frontend/rountangleEditor/RountangleEditor.css → src/frontend/demos/rountangleEditor/RountangleEditor.css


+ 8 - 9
src/frontend/rountangleEditor/RountangleEditor.tsx

@@ -1,12 +1,11 @@
 import * as React from "react";
 import {RountangleComponent} from "./RountangleComponent";
-import {PrimitiveValue, UUID} from "../../onion/types";
-import {GraphType} from "../editable_graph";
+import {PrimitiveValue, UUID} from "onion/types";
+import {D3OnionGraphData, D3OnionNodeData, D3OnionLinkData} from "../d3graph/d3graph_editable";
 import {RountangleAction} from "./RountangleActions";
-import {INodeState, IValueState, GraphState} from "../../onion/graph_state";
-import {PrimitiveDelta, PrimitiveRegistry} from "../../onion/primitive_delta";
-import {assert, assertNever} from "../../util/assert";
-import {d3Types} from "../graph";
+import {INodeState, IValueState, GraphState} from "onion/graph_state";
+import {PrimitiveDelta, PrimitiveRegistry} from "onion/primitive_delta";
+import {assert, assertNever} from "../../../util/assert";
 
 export interface Rountangle {
     // readonly name:   string;
@@ -16,7 +15,7 @@ export interface Rountangle {
     readonly height: number;
 }
 
-export function isRountangle(d3node: d3Types.d3Node<INodeState | IValueState>) {
+export function isRountangle(d3node: D3OnionNodeData) {
     if (!(d3node.obj.type === "node")) return false;
     const nodeState = d3node.obj as unknown as INodeState;
     const outgoing = nodeState.getOutgoingEdges();
@@ -30,7 +29,7 @@ export function isRountangle(d3node: d3Types.d3Node<INodeState | IValueState>) {
 }
 
 // Precondition: isRountangle(d3Node)
-export function graphStateToRountangle(d3node: d3Types.d3Node<INodeState | IValueState>): [PrimitiveValue, Rountangle]  {
+export function graphStateToRountangle(d3node: D3OnionNodeData): [PrimitiveValue, Rountangle]  {
     if (!isRountangle(d3node)) {
         throw new Error(`RountangleParser Cannot parse: ${d3node}`);
     }
@@ -47,7 +46,7 @@ export function graphStateToRountangle(d3node: d3Types.d3Node<INodeState | IValu
 }
 
 export interface RountangleEditorProps {
-  graph: GraphType;
+  graph: D3OnionGraphData;
   graphState: GraphState;
   generateUUID: () => UUID;
   primitiveRegistry: PrimitiveRegistry;

+ 1 - 1
src/frontend/rountangleEditor/RountangleResizeHandleComponent.tsx

@@ -1,6 +1,6 @@
 import * as React from "react";
 import {RountangleState} from "./RountangleComponent";
-import {PrimitiveValue} from "../../onion/types";
+import {PrimitiveValue} from "onion/types";
 
 interface RountangleResizeHandleProps {
     id:               PrimitiveValue;

src/frontend/rountangleEditor/RountangleStore.ts → src/frontend/demos/rountangleEditor/RountangleStore.ts


+ 10 - 11
src/frontend/correspondence.tsx

@@ -2,15 +2,14 @@ import * as React from "react";
 import * as Mantine from "@mantine/core";
 import * as Icons from "@tabler/icons";
 
-import {newVersionedModel, VersionedModelState, undoButtonHelpText} from "./versioned_model";
-import {emptyGraph, graphForces} from "./constants";
-import {ManualRendererProps} from "./manual_renderer";
-import {D3GraphStateUpdater} from "./d3_state";
-import {TrivialParser} from "../parser/trivial_parser";
-import {Version} from "../onion/version";
-import {GraphState} from "../onion/graph_state"; 
-import {CompositeDelta} from "../onion/composite_delta";
-import {PrimitiveDelta} from "../onion/primitive_delta";
+import {newVersionedModel, VersionedModelState, undoButtonHelpText} from "./single_model";
+import {emptyGraph} from "../d3graph/d3graph";
+import {D3GraphUpdater} from "../d3graph/d3graph_updater";
+import {RountangleParser} from "../../../parser/rountangle_parser";
+import {Version} from "onion/version";
+import {GraphState} from "onion/graph_state"; 
+import {CompositeDelta} from "onion/composite_delta";
+import {PrimitiveDelta} from "onion/primitive_delta";
 
 export const undoButtonHelpTextCorr =
   `Navigating to a version in the correspondence model,
@@ -34,7 +33,7 @@ function getGraphState(version: Version, listener?): GraphState {
 export function newCorrespondence({generateUUID, primitiveRegistry, cs, as}) {
   const {initialState, getCurrentVersion, getReducer: getReducerOrig, getReactComponents} = newVersionedModel({readonly: true, generateUUID, primitiveRegistry});
 
-  const parser = new TrivialParser(primitiveRegistry, generateUUID);
+  const parser = new RountangleParser(primitiveRegistry, generateUUID);
 
   // Mapping from correspondence model version to CS and AS model version.
   const corrMap: Map<Version, {csVersion: Version, asVersion: Version}> = new Map([
@@ -161,7 +160,7 @@ export function newCorrespondence({generateUUID, primitiveRegistry, cs, as}) {
           function getD3State(version: Version, additionalDeltas: PrimitiveDelta[] = []) {
             let graph = emptyGraph;
             const setGraph = callback => (graph = callback(graph));
-            const d3Updater = new D3GraphStateUpdater(setGraph, 0, 0);
+            const d3Updater = new D3GraphUpdater(setGraph, 0, 0);
             const graphState = getGraphState(version, d3Updater);
             for (const d of additionalDeltas) {
               graphState.exec(d, d3Updater);

+ 135 - 0
src/frontend/demos/versioned_model/delta_graph.ts

@@ -0,0 +1,135 @@
+import {Delta} from "onion/delta";
+import {NodeCreation, NodeDeletion, EdgeCreation, EdgeUpdate} from "onion/primitive_delta";
+import {CompositeDelta} from "onion/composite_delta";
+import {D3GraphData, D3NodeData, D3LinkData} from "../d3graph/d3graph";
+
+export type DeltaGraphState = D3GraphData<Delta,null>;
+
+interface SetDeltaActive {type: 'setDeltaActive', delta: Delta}
+interface SetDeltaInactive {type: 'setDeltaInactive', delta: Delta}
+interface AddDelta {type: 'addDelta', delta: Delta, active: boolean}
+
+export type DeltaGraphAction = Readonly<SetDeltaActive> | Readonly<SetDeltaInactive> | Readonly<AddDelta>
+
+export function deltaGraphReducer(prevState: DeltaGraphState, action: DeltaGraphAction): DeltaGraphState {
+  const delta = action.delta;
+  const deltaId = fullDeltaId(delta);
+  switch (action.type) {
+    case 'setDeltaActive': {
+      return {
+        nodes: prevState.nodes.map(n => n.id === deltaId ? deltaToDepGraphNode(delta, true, n.x, n.y) : n),
+
+        // re-create all links whose source or target was touched:
+        links: prevState.links.map(l => {
+          if (l.source.id === deltaId || l.target.id === deltaId) {
+            if (l.bidirectional) {
+              return conflictToDepGraphLink(l.source.obj, l.target.obj);
+            } else {
+              return dependencyToDepGraphLink(l.source.obj, l.target.obj, l.label);
+            }
+          }
+          return l;
+        }),
+      }
+    }
+    case 'setDeltaInactive': {
+      return {
+        nodes: prevState.nodes.map(n => n.id === deltaId ? deltaToDepGraphNode(delta, false, n.x, n.y) : n),
+
+        // re-create all links whose source or target was touched:
+        links: prevState.links.map(l => {
+          if (l.source.id === deltaId || l.target.id === deltaId) {
+            if (l.bidirectional) {
+              return conflictToDepGraphLink(l.source.obj, l.target.obj);
+            } else {
+              return dependencyToDepGraphLink(l.source.obj, l.target.obj, l.label);
+            }
+          }
+          return l;
+        }),
+      }
+    }
+    case 'addDelta': {
+      if (prevState.nodes.some(node => node.id === deltaId)) {
+        // We already have this delta (remember that delta's are identified by the hash of their contents, so it is possible that different people concurrently create the same deltas, e.g., by deleting the same node concurrently)
+        // Also, when re-doing after undoing, it is possible that a delta is 'added' that we already have.
+        return deltaGraphReducer(prevState, {type:'setDeltaActive', delta: delta});
+      }
+      return {
+        // add one extra node that represents the new delta:
+        nodes: prevState.nodes.concat(deltaToDepGraphNode(delta, /*highlight: */ action.active)),
+        // for every dependency and conflict, add a link:
+        links: prevState.links.concat(
+            ...delta.getTypedDependencies().map(([dep,depSummary]) => dependencyToDepGraphLink(delta, dep, depSummary)),
+            ...delta.getConflicts().filter(conflictingDelta => prevState.nodes.some(n => n.id === fullDeltaId(conflictingDelta))).map(conflictingDelta => conflictToDepGraphLink(delta, conflictingDelta)),
+          ),
+      };
+    }
+  }
+}
+
+export function fullDeltaId(delta: Delta): string {
+  return delta.getHash().toString('base64');
+}
+
+// Helpers
+
+function deltaToDepGraphNode(delta: Delta, highlight: boolean, x?: number, y?: number): D3NodeData<Delta> {
+  return {
+    id: fullDeltaId(delta),
+    label: delta.getDescription(),
+    color: getDeltaColor(delta),
+    highlight,
+    obj: delta,
+    x, y,
+  };
+}
+function dependencyToDepGraphLink(fromDelta: Delta, toDelta: Delta, label: string): D3LinkData<null> {
+  return {
+    source: fullDeltaId(fromDelta),
+    label,
+    color: 'black',
+    target: fullDeltaId(toDelta),
+    obj: null,
+  };
+}
+function conflictToDepGraphLink(fromDelta: Delta, toDelta: Delta): D3LinkData<null> {
+  return {
+    source: fullDeltaId(fromDelta),
+    label: "",
+    color: 'DarkGoldenRod',
+    bidirectional: true,
+    target: fullDeltaId(toDelta),
+    obj: null,
+  };
+}
+// function shortDeltaId(delta: Delta) {
+//   return delta.getHash().toString('hex').slice(0,8);
+// }
+function getDeltaColor(delta: Delta) {
+  // determine delta color by "averaging":
+  let numCreations = 0; // makes delta more green
+  let numDeletions = 0; // makes delta more red
+  let numEdgeUpdates = 0; // makes delta more blue
+  let numDeltas = 0;
+  function countDeltas(delta) {
+    if (delta instanceof CompositeDelta) {
+      delta.deltas.forEach(countDeltas);
+    }
+    else {
+      numDeltas++;
+      if (delta instanceof NodeCreation) {
+        numCreations++;
+      }
+      else if (delta instanceof NodeDeletion) {
+        numDeletions++;
+      }
+      else if (delta instanceof EdgeCreation || delta instanceof EdgeUpdate) {
+        numEdgeUpdates++;
+      }
+    }
+  }
+  countDeltas(delta);
+  const color = `rgb(${255*numDeletions/numDeltas}, ${255*numCreations/numDeltas}, ${255*numEdgeUpdates/numDeltas})`;
+  return color;
+}

+ 87 - 0
src/frontend/demos/versioned_model/history_graph.ts

@@ -0,0 +1,87 @@
+import {Version} from "onion/version";
+import {Delta} from "onion/delta";
+import {D3GraphData, D3NodeData, D3LinkData} from "../d3graph/d3graph";
+
+export type HistoryGraphState = D3GraphData<Version|null,Delta>;
+
+// This module contains functions for updating the state of the 'Graph' React/D3 component.
+
+///// ALL OF THESE ARE PURE FUNCTIONS: //////
+
+interface SetCurrentVersion {type:'setCurrentVersion', prev: Version, new: Version}
+interface AppendToHistoryGraph {type:'appendToHistoryGraph', version: Version}
+
+export type HistoryGraphAction = Readonly<SetCurrentVersion> | Readonly<AppendToHistoryGraph>;
+
+export function historyGraphReducer(prevState: HistoryGraphState, action: HistoryGraphAction): HistoryGraphState {
+  switch (action.type) {
+    case 'appendVersion': {
+      const newLinks = action.version.parents.map(([parentVersion,delta]) => parentLinkToHistoryGraphLink(action.version, parentVersion, delta)).filter(link => !prevState.links.some(prevLink => prevLink.source.id === link.source && prevLink.target.id === link.target));
+
+      return {
+        nodes: prevState.nodes.some(node => node.id === fullVersionId(action.version)) ?
+            prevState.nodes : prevState.nodes.concat(versionToNode(action.version, false)),
+        links: prevState.links.concat(...newLinks),
+      };
+    }
+    case 'setCurrentVersion': {
+      if (action.prev === action.new) {
+        return prevState;
+      }
+      let links;
+      return {
+        nodes: prevState.nodes.map(n => {
+          if (n.obj === action.prev) {
+            return versionToNode(action.prev, false, n.x, n.y);
+          }
+          if (n.obj === action.new) {
+            return versionToNode(action.new, true, n.x, n.y);
+          }
+          return n;
+        }),
+        // must re-create links for re-created nodes:
+        links: prevState.links.map(l => {
+          if (l.source.obj === action.prev || l.target.obj === action.prev || l.source.obj === action.new || l.target.obj === action.new) {
+            return parentLinkToHistoryGraphLink(l.source.obj, l.target.obj, l.obj);
+          }
+          return l;
+        }),
+      }
+    }
+  }
+}
+
+export function initialHistoryGraph(initialVersion) {
+  return {
+    nodes: [
+      versionToNode(initialVersion, true),
+    ],
+    links: [],
+  };
+}
+
+export function fullVersionId(version: Version): string {
+  return version.hash.toString('base64');
+}
+
+// Helpers
+
+function versionToNode(version: Version, highlight: boolean, x?: number, y?: number): D3NodeData<Version> {
+  return {
+    id: fullVersionId(version),
+    label: (version.parents.length === 0 ? "initial" : ""),
+    color: (version.parents.length === 0 ? "grey" : "purple"),
+    obj: version,
+    highlight,
+    x, y,
+  }
+}
+function parentLinkToHistoryGraphLink(childVersion: Version, parentVersion: Version, delta: Delta): D3LinkData<Delta> {
+  return {
+    source: fullVersionId(childVersion),
+    label: delta.getDescription(),
+    target: fullVersionId(parentVersion),
+    color: 'black',
+    obj: delta,
+  };
+}

+ 41 - 41
src/frontend/versioned_model.tsx

@@ -2,35 +2,32 @@ 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 {D3GraphUpdater} from "../d3graph/d3graph_updater";
+import {D3GraphEditable, UserEditCallback, SetNodePositionCallback, D3OnionGraphData} from "../d3graph/d3graph_editable";
+
 import {
-  HistoryGraphType,
-  DependencyGraphType,
-  versionToNode,
-  setCurrentVersion,
-  appendToHistoryGraph,
+  DeltaGraphState,
   fullDeltaId,
-  setDeltaActive,
-  setDeltaInactive,
-  deltaToDepGraphNode,
-  dependencyToDepGraphLink,
-  conflictToDepGraphLink,
-  fullVersionId,
-  addDeltaAndActivate,
+  deltaGraphReducer,
+} from "./delta_graph";
+
+import {
+  HistoryGraphState,
   initialHistoryGraph,
-} from "./app_state";
-import {d3Types, Graph} from "./graph"
-import {emptyGraph, graphForces} from "./constants";
-import {RountangleEditor} from "./rountangleEditor/RountangleEditor";
-import {makeOverlayHelpIcon} 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";
+  fullVersionId,
+  historyGraphReducer,
+} from "./history_graph";
+
+import {D3Graph, emptyGraph, defaultGraphForces} from "../d3graph/d3graph";
+import {RountangleEditor} from "../rountangleEditor/RountangleEditor";
+import {makeOverlayHelpIcon} 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";
 
 const graphEditorLegend = <>
   <Mantine.Divider label="Legend" labelPosition="center"/>
@@ -112,10 +109,10 @@ export const undoButtonHelpText = "Use the Undo/Redo buttons or the History pane
 
 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
-  dependencyGraphL1: DependencyGraphType; // the state of what is displayed in the rightmost panel
-  dependencyGraphL0: DependencyGraphType; // the state of what is displayed in the rightmost panel
+  graph: D3OnionGraphData; // the state what is displayed in the leftmost panel
+  historyGraph: HistoryGraphState; // the state of what is displayed in the middle panel
+  dependencyGraphL1: DeltaGraphState; // the state of what is displayed in the rightmost panel
+  dependencyGraphL0: DeltaGraphState; // the state of what is displayed in the rightmost panel
 }
 
 interface VersionedModelCallbacks {
@@ -159,8 +156,6 @@ export function newVersionedModel({generateUUID, primitiveRegistry, readonly}) {
   //  - Callback functions for updating the state
   //  - A callback that constructs all React components (to be used in React render function)
   function getReducer(setState) {
-    const setGraph = callback =>
-      setState(({graph, ...rest}) => ({graph: callback(graph), ...rest}));
 
     // Create and add a new version, and its deltas, without changing the current version
     const addDeltasAndVersion = (deltas: PrimitiveDelta[], description: string, parentHash: Buffer) => {
@@ -178,7 +173,7 @@ export function newVersionedModel({generateUUID, primitiveRegistry, readonly}) {
         setState(({historyGraph, dependencyGraphL1, dependencyGraphL0, ...rest}) => {
           return {
             // add new version to history graph + highlight the new version as the current version:
-            historyGraph: appendToHistoryGraph(historyGraph, newVersion),
+            historyGraph: historyGraphReducer(historyGraph, {type:'appendVersion', version: newVersion}),
             // add the composite delta to the L1-graph + highlight it as 'active':
             dependencyGraphL1: composite.deltas.length > 0 ? addDeltaAndActivate(dependencyGraphL1, composite, false) : dependencyGraphL1, // never add an empty composite
             // add the primitive L0-deltas to the L0-graph + highlight them as 'active':
@@ -198,8 +193,13 @@ export function newVersionedModel({generateUUID, primitiveRegistry, readonly}) {
       gotoVersion(newVersion);
       return newVersion;
     };
+
+    // helper
+    const setGraph = callback =>
+      setState(({graph, ...rest}) => ({graph: callback(graph), ...rest}));
+
     const undoWithoutUpdatingHistoryGraph = (deltaToUndo) => {
-      const d3Updater = new D3GraphStateUpdater(setGraph, x, y);
+      const d3Updater = new D3GraphUpdater(setGraph, x, y);
       graphState.unexec(deltaToUndo, d3Updater);
       setState(({dependencyGraphL0: prevDepGraphL0, dependencyGraphL1: prevDepGraphL1, ...rest}) => ({
         dependencyGraphL1: setDeltaInactive(prevDepGraphL1, deltaToUndo),
@@ -208,7 +208,7 @@ export function newVersionedModel({generateUUID, primitiveRegistry, readonly}) {
       }));
     };
     const redoWithoutUpdatingHistoryGraph = (deltaToRedo) => {
-      const d3Updater = new D3GraphStateUpdater(setGraph, x, y);
+      const d3Updater = new D3GraphUpdater(setGraph, x, y);
       graphState.exec(deltaToRedo, d3Updater);
       setState(({dependencyGraphL0: prevDepGraphL0, dependencyGraphL1: prevDepGraphL1, ...rest}) => ({
         dependencyGraphL1: setDeltaActive(prevDepGraphL1, deltaToRedo),
@@ -266,11 +266,11 @@ export function newVersionedModel({generateUUID, primitiveRegistry, readonly}) {
 
   function getReactComponents(state: VersionedModelState, callbacks: VersionedModelCallbacks) {
     const graphStateComponent = makeOverlayHelpIcon(readonly ? 
-      <Graph graph={state.graph} forces={graphForces} />
-      : <EditableGraph
+      <D3Graph graph={state.graph} forces={defaultGraphForces} />
+      : <D3GraphEditable
           graph={state.graph}
           graphState={graphState}
-          forces={graphForces}
+          forces={defaultGraphForces}
           generateUUID={generateUUID}
           primitiveRegistry={primitiveRegistry}
           setNextNodePosition={(newX,newY) => {x = newX; y = newY;}}
@@ -278,14 +278,14 @@ export function newVersionedModel({generateUUID, primitiveRegistry, readonly}) {
         />, readonly ? helpText.graphEditorReadonly : helpText.graphEditor);
 
     const depGraphL1Component = makeOverlayHelpIcon(
-      <Graph graph={state.dependencyGraphL1} forces={graphForces} />,
+      <D3Graph graph={state.dependencyGraphL1} forces={defaultGraphForces} />,
       helpText.depGraph);
     const depGraphL0Component = makeOverlayHelpIcon(
-      <Graph graph={state.dependencyGraphL0} forces={graphForces} />,
+      <D3Graph graph={state.dependencyGraphL0} forces={defaultGraphForces} />,
       helpText.depGraph);
 
     const historyComponent = makeOverlayHelpIcon(
-      <Graph graph={state.historyGraph} forces={graphForces}
+      <D3Graph graph={state.historyGraph} forces={defaultGraphForces}
         mouseUpHandler={(e, {x, y}, node) => node ? callbacks.onVersionClicked?.(node.obj) : undefined} />,
       helpText.historyGraph);
 

+ 17 - 0
src/frontend/demos/versioned_model/useVersionedModel.ts

@@ -0,0 +1,17 @@
+import * as React from "react";
+
+export function useVersionedModel() {
+  const [deltaGraph, dispatchDeltaGraph] = React.useReducer(deltaGraphReducer, emptyGraph);
+  const [historyGraph, dispatchHistoryGraph] = React.useReducer(historyGraphReducer, initialHistoryGraph);
+}
+
+  function versionedModelReducer(prevState, action) {
+    switch (action.type) {
+      case "userEdit": {
+
+      }
+      case "gotoVersion": {
+        
+      }
+    }
+  }

+ 2 - 2
src/frontend/index.tsx

@@ -1,7 +1,7 @@
 import * as React from 'react';
 import {createRoot} from 'react-dom/client';
-import './rountangleEditor/RountangleEditor.css';
-import './graph.css';
+import './demos/rountangleEditor/RountangleEditor.css';
+import './demos/d3graph/d3graph.css';
 import './index.css';
 
 import {getApp} from "./app";

+ 0 - 182
src/parser/insideness_parser.ts

@@ -1,182 +0,0 @@
-import {
-  // Parser,
-  // Renderer,
-  ParseOrRenderResult,
-} from "./parser";
-
-// 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 {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(),
-//     }
-//   }
-
-// }

+ 3 - 3
src/parser/parser.ts

@@ -1,7 +1,7 @@
 // import {Version} from "../onion/version";
-import {GraphState} from "../onion/graph_state";
-import {Delta} from "../onion/delta";
-import {PrimitiveDelta} from "../onion/primitive_delta";
+import {GraphState} from "onion/graph_state";
+import {Delta} from "onion/delta";
+import {PrimitiveDelta} from "onion/primitive_delta";
 
 export interface ParseOrRenderResult {
   corrDeltas: PrimitiveDelta[];

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

@@ -1,4 +1,4 @@
-import {TrivialParser} from "./trivial_parser";
+import {RountangleParser} from "./rountangle_parser";
 
 import {
   VersionRegistry,

+ 1 - 1
src/parser/trivial_parser.ts

@@ -54,7 +54,7 @@ export function getGeometry(sourceState: GraphState, nodeId: PrimitiveValue): Ge
 }
 
 // A parser that creates an AS-node for every CS-node, with a Corr-node in between.
-export class TrivialParser  {
+export class RountangleParser  {
   readonly getUuid: () => UUID;
 
   readonly primitiveRegistry: PrimitiveRegistry;

+ 4 - 1
tsconfig.json

@@ -3,6 +3,9 @@
     "types": ["mocha", "node", "react"],
     "target": "es6",
     "jsx": "react",
+    "paths": {
+      "onion/*": ["./src/onion/*"],
+    },
     "sourceMap": true,
     "noImplicitThis": true,
     "strictBindCallApply": true,
@@ -11,5 +14,5 @@
     "strictPropertyInitialization": true,
     "alwaysStrict": true,
     "moduleResolution": "nodenext",
-  }
+  },
 }

+ 3 - 0
webpack.config.js

@@ -24,6 +24,9 @@ module.exports = {
   resolve: {
     extensions: ['.tsx', '.ts', '.js'],
     fallback: {
+      // allow using absolute paths when referring to 'onion' library.
+      onion: path.resolve(__dirname, 'src', 'onion'),
+
       util: path.resolve(__dirname, 'src', 'onion', 'mock_node_util.ts'),
 
       // The following are needed to make NodeJS' 'crypto' module work in the browser: