Browse Source

Create fork/package containing only library (no frontend)

Joeri Exelmans 2 years ago
parent
commit
196218cdfb
55 changed files with 316 additions and 7969 deletions
  1. 0 3
      .gitignore
  2. 0 43
      .gitlab-ci.yml
  3. 0 4
      Dockerfile
  4. 0 11
      dist/index.html
  5. 2 28
      package.json
  6. 314 1267
      pnpm-lock.yaml
  7. 0 139
      src/frontend/app.tsx
  8. 0 4
      src/frontend/custom.d.ts
  9. 0 30
      src/frontend/d3graph/d3graph.css
  10. 0 367
      src/frontend/d3graph/d3graph.tsx
  11. 0 100
      src/frontend/d3graph/d3graph_editable.tsx
  12. 0 138
      src/frontend/d3graph/reducers/delta_graph.ts
  13. 0 136
      src/frontend/d3graph/reducers/history_graph.ts
  14. 0 148
      src/frontend/d3graph/reducers/onion_graph.ts
  15. 0 3
      src/frontend/demos/assets/corr_as.svg
  16. 0 3
      src/frontend/demos/assets/corr_corr.svg
  17. 0 3
      src/frontend/demos/assets/corr_cs.svg
  18. 0 3
      src/frontend/demos/assets/editor.svg
  19. 0 4
      src/frontend/demos/assets/pd.svg
  20. 0 18
      src/frontend/demos/blocks.tsx
  21. 0 261
      src/frontend/demos/demo_bm.tsx
  22. 0 221
      src/frontend/demos/demo_corr.tsx
  23. 0 125
      src/frontend/demos/demo_editor.tsx
  24. 0 194
      src/frontend/demos/demo_le.tsx
  25. 0 603
      src/frontend/demos/demo_live.tsx
  26. 0 130
      src/frontend/demos/demo_pd.tsx
  27. 0 45
      src/frontend/demos/demo_welcome.tsx
  28. 0 19
      src/frontend/graphviz.tsx
  29. 0 77
      src/frontend/index.css
  30. 0 26
      src/frontend/index.tsx
  31. 0 33
      src/frontend/info_hover_card.tsx
  32. 0 29
      src/frontend/onion_context.tsx
  33. 0 13
      src/frontend/rountangleEditor/RountangleActions.ts
  34. 0 180
      src/frontend/rountangleEditor/RountangleComponent.tsx
  35. 0 45
      src/frontend/rountangleEditor/RountangleEditor.css
  36. 0 280
      src/frontend/rountangleEditor/RountangleEditor.tsx
  37. 0 90
      src/frontend/rountangleEditor/RountangleResizeHandleComponent.tsx
  38. 0 0
      src/frontend/rountangleEditor/RountangleStore.ts
  39. 0 28
      src/frontend/styledtabs.tsx
  40. 0 11
      src/frontend/use_const.ts
  41. 0 319
      src/frontend/versioned_model/correspondence.tsx
  42. 0 116
      src/frontend/versioned_model/graph_view.tsx
  43. 0 77
      src/frontend/versioned_model/help_text.tsx
  44. 0 228
      src/frontend/versioned_model/manual_renderer.tsx
  45. 0 243
      src/frontend/versioned_model/merge_view.tsx
  46. 0 373
      src/frontend/versioned_model/single_model.tsx
  47. 0 206
      src/onion/legacy/delta.ts.legacy
  48. 0 75
      src/onion/legacy/delta_parser.ts.legacy
  49. 0 82
      src/onion/legacy/delta_registry.ts.legacy
  50. 0 635
      src/onion/legacy/primitive_delta.ts.legacy
  51. 0 19
      src/parser/parser.ts
  52. 0 98
      src/parser/rountangle_parser.test.ts
  53. 0 370
      src/parser/rountangle_parser.ts
  54. 0 203
      src/util/names.ts
  55. 0 63
      webpack.config.cjs

+ 0 - 3
.gitignore

@@ -2,6 +2,3 @@ node_modules/
 
 # coverage analysis output
 .nyc_output/
-
-# this file will be generated when running webpack:
-dist/*

+ 0 - 43
.gitlab-ci.yml

@@ -1,43 +0,0 @@
-stages:
-  - compile
-  - build
-
-# Workaround for ipv6 issues
-default:
-  before_script:
-    - alias npm="node --dns-result-order=ipv4first $(which npm)"
-
-compile:
-  stage: compile
-  image: node:lts-alpine
-  script:
-    - npm install
-    - npm run webpack
-  artifacts:
-    paths:
-      - dist/
-    expire_in: 1h
-
-build-docker-image:
-  stage: build
-  variables:
-    COMPONENT_NAME: demonstrator
-    VERSION_TAG: latest
-  image:
-    name: gcr.io/kaniko-project/executor:debug
-    entrypoint: [""]
-  rules:
-    - if: '$CI_COMMIT_BRANCH == "master"'
-      when: on_success
-  dependencies:
-    - compile
-
-  script:
-    - mkdir -p /kaniko/.docker
-    - echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json
-    - >-
-      /kaniko/executor
-      --context "${CI_PROJECT_DIR}"
-      --dockerfile "${CI_PROJECT_DIR}/Dockerfile"
-      --destination "${CI_REGISTRY_IMAGE}/${COMPONENT_NAME}:${VERSION_TAG}"
-

+ 0 - 4
Dockerfile

@@ -1,4 +0,0 @@
-FROM httpd:alpine
-
-COPY --chown=www-data:www-data dist/ /usr/local/apache2/htdocs
-

+ 0 - 11
dist/index.html

@@ -1,11 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="UTF-8"/>
-    <title>Onion VCS Demo</title>
-  </head>
-  <body>
-    <div id="root"></div>
-    <script src="./bundle.js"></script>
-  </body>
-</html>

+ 2 - 28
package.json

@@ -2,35 +2,13 @@
 	"name": "onioncollab",
 	"sideEffects": false,
 	"dependencies": {
-		"@mantine/core": "^6.0.19",
-		"@mantine/hooks": "^6.0.19",
-		"@mantine/modals": "^6.0.19",
-		"@viz-js/viz": "^3.1.0",
-		"allotment": "^1.19.0",
 		"buffer": "^6.0.3",
 		"crypto-browserify": "^3.12.0",
-		"css-loader": "^6.7.3",
-		"d3": "^7.8.4",
-		"d3-drag": "^3.0.0",
-		"d3-force": "^3.0.0",
-		"d3-scale": "^4.0.2",
-		"d3-selection": "^3.0.0",
-		"react": "^18.2.0",
-		"react-dom": "^18.2.0",
-		"stream-browserify": "^3.0.0",
-		"style-loader": "^3.3.1"
+		"stream-browserify": "^3.0.0"
 	},
 	"devDependencies": {
-		"@emotion/react": "^11.11.0",
-		"@tabler/icons": "^1.119.0",
-		"@types/d3": "^7.4.0",
-		"@types/d3-drag": "^3.0.2",
-		"@types/d3-force": "^3.0.4",
-		"@types/d3-selection": "^3.0.4",
 		"@types/mocha": "^10.0.1",
 		"@types/node": "^18.16.12",
-		"@types/react": "^18.2.6",
-		"@types/react-dom": "^18.2.4",
 		"fork-ts-checker-webpack-plugin": "^7.3.0",
 		"lodash": "^4.17.21",
 		"mocha": "^10.2.0",
@@ -47,10 +25,6 @@
 		"test": "mocha --require ts-node/register",
 		"test-all": "mocha --require ts-node/register './src/**/*.test.ts'",
 		"test-with-coverage": "nyc --reporter=text mocha --require ts-node/register",
-		"test-all-with-coverage": "nyc --reporter=text mocha --require ts-node/register './src/**/*.test.ts'",
-		"webpack": "webpack --mode=production --progress",
-		"webpack-help": "webpack --help",
-		"dev-server": "webpack serve --mode=development --devtool=eval-source-map",
-		"webpack-profile": "webpack --profile --json > webpack-stats.json"
+		"test-all-with-coverage": "nyc --reporter=text mocha --require ts-node/register './src/**/*.test.ts'"
 	}
 }

File diff suppressed because it is too large
+ 314 - 1267
pnpm-lock.yaml


+ 0 - 139
src/frontend/app.tsx

@@ -1,139 +0,0 @@
-import * as React from "react";
-import {Title, ScrollArea, Text, Tabs, MantineProvider, Divider, Stack, Anchor} from "@mantine/core";
-import {useColorScheme} from '@mantine/hooks';
-import {IconExternalLink} from '@tabler/icons';
-import {Allotment} from "allotment";
-import "allotment/dist/style.css";
-
-import {Styledtabs} from "./styledtabs";
-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 {demo_Editor_description, getDemoEditor} from "./demos/demo_editor";
-import {demo_Welcome_description, Welcome} from "./demos/demo_welcome";
-import {demo_LE_description, getDemoLE} from "./demos/demo_le";
-import {demo_Live_description, getDemoLive} from "./demos/demo_live";
-
-// Set by WebPack during build:
-declare const REVISION: string;
-
-export function getApp() {
-    const DemoEditor = getDemoEditor();
-    const DemoPD = getDemoPD();
-    const DemoCorr = getDemoCorr();
-    const DemoBM = getDemoBM();
-    const DemoLE = getDemoLE();
-    const DemoLive = getDemoLive();
-
-    return function App(props) {
-        React.useEffect(() => {
-            if(navigator.userAgent.indexOf("Safari") > -1 && navigator.userAgent.indexOf("Chrome") === -1) {
-                alert('We are very sorry: This demonstrator does not support Apple\'s Safari browser.');
-            }
-        }, [])
-
-        // detect if user has light or dark color scheme going on :)
-        const preferredColorScheme = useColorScheme();
-
-        const tabStyle = {width: '100%', border: 0};
-
-        return <>
-            <MantineProvider theme={{colorScheme: preferredColorScheme}} withGlobalStyles withNormalizeCSS>
-                <Styledtabs defaultValue="welcome" orientation="vertical" style={{height: '100%'}}>
-                    <Allotment maxSize={'50%'}>
-                        <Allotment.Pane preferredSize={250} minSize={150} snap>
-                            <Stack style={{height: '100%', direction: 'column', gap: '0px'}}>
-                                {/* <Title order={4} style={{paddingLeft: '5px'}}>Demo</Title> */}
-                                <Tabs.List>
-                                    <Tabs.Tab style={tabStyle} value="welcome">Welcome</Tabs.Tab>
-                                    <Tabs.Tab style={tabStyle} value="pd">Primitive Deltas</Tabs.Tab>
-                                    <Tabs.Tab style={tabStyle} value="editor">Rountangle
-                                        Editor</Tabs.Tab>
-                                    <Tabs.Tab style={tabStyle} value="corr">Correspondence</Tabs.Tab>
-                                    <Tabs.Tab style={tabStyle} value="bm">Blended Modeling</Tabs.Tab>
-                                    <Tabs.Tab style={tabStyle} value="le">List Editor</Tabs.Tab>
-                                    <Tabs.Tab style={tabStyle} value="sem">Live Modeling</Tabs.Tab>
-                                </Tabs.List>
-                                <Divider my="md"/>
-                                <div style={{overflow: 'hidden', paddingLeft: '5px'}}>
-                                    {/* @ts-ignore */}
-                                    <ScrollArea scrollbarSize='7px' style={{height: '100%'}}>
-                                        <div style={{paddingRight: '17px'}}>
-                                            <Tabs.Panel value="welcome" style={{height: '100%'}}>
-                                                {demo_Welcome_description}
-                                            </Tabs.Panel>
-                                            <Tabs.Panel value="pd" style={{height: '100%'}}>
-                                                {demo_PD_description}
-                                            </Tabs.Panel>
-                                            <Tabs.Panel value="editor" style={{height: '100%'}}>
-                                                {demo_Editor_description}
-                                            </Tabs.Panel>
-                                            <Tabs.Panel value="corr" style={{height: '100%'}}>
-                                                {demo_Corr_description}
-                                            </Tabs.Panel>
-                                            <Tabs.Panel value="bm" style={{height: '100%'}}>
-                                                {demo_BM_description}
-                                            </Tabs.Panel>
-                                            <Tabs.Panel value="le" style={{height: '100%'}}>
-                                                {demo_LE_description}
-                                            </Tabs.Panel>
-                                            <Tabs.Panel value="sem" style={{height: '100%'}}>
-                                                {demo_Live_description}
-                                            </Tabs.Panel>
-                                        </div>
-                                    </ScrollArea>
-                                </div>
-                            </Stack>
-                        </Allotment.Pane>
-                        <Allotment.Pane>
-                            <div style={{height: '100%', overflow: 'hidden'}}>
-                                {/* @ts-ignore */}
-                                <ScrollArea scrollbarSize='7px' style={{height: '100%'}}>
-                                    <div style={{padding: '10px 10px 10px 10px'}}>
-                                        <Tabs.Panel value="welcome">
-                                            <Welcome/>
-                                        </Tabs.Panel>
-                                        <Tabs.Panel value="pd">
-                                            <DemoPD/>
-                                        </Tabs.Panel>
-                                        <Tabs.Panel value="editor">
-                                            <DemoEditor/>
-                                        </Tabs.Panel>
-                                        <Tabs.Panel value="corr">
-                                            <DemoCorr/>
-                                        </Tabs.Panel>
-                                        <Tabs.Panel value="bm">
-                                            <DemoBM/>
-                                        </Tabs.Panel>
-                                        <Tabs.Panel value="le">
-                                            <DemoLE/>
-                                        </Tabs.Panel>
-                                        <Tabs.Panel value="sem">
-                                            <DemoLive/>
-                                        </Tabs.Panel>
-                                        <div style={{position: "absolute", bottom: 8, right: 8 }}>{
-                                            // it's possible that the git revision is unknown (e.g., if the source was downloaded as ZIP)
-                                            REVISION === "unknown" ?
-                                            <>
-                                                <Anchor href="https://msdl.uantwerpen.be/git/jexelmans/onioncollab" target="_blank">
-                                                    Source Code
-                                                    <IconExternalLink size={16} style={{marginLeft: 4}}/>
-                                                </Anchor>
-                                            </>
-                                            : <>git revision:&nbsp;
-                                                <Anchor href={"https://msdl.uantwerpen.be/git/jexelmans/onioncollab/src/"+REVISION} target="_blank">
-                                                    {REVISION.substring(0,8)}
-                                                    <IconExternalLink size={16} style={{marginLeft: 4}}/>
-                                                </Anchor>
-                                            </>
-                                        }</div>
-                                    </div>
-                                </ScrollArea>
-                            </div>
-                        </Allotment.Pane>
-                    </Allotment>
-                </Styledtabs>
-            </MantineProvider>
-        </>;
-    }
-}

+ 0 - 4
src/frontend/custom.d.ts

@@ -1,4 +0,0 @@
-declare module "*.svg" {
-    const content:string;
-    export default content;
-}

+ 0 - 30
src/frontend/d3graph/d3graph.css

@@ -1,30 +0,0 @@
-.graphNode {
-  stroke: #000;
-  stroke-width: 1.5px;
-}
-.graphNode:hover {
-  cursor: grab;
-}
-.graphNode:hover.dragging {
-  cursor: grabbing;
-}
-
-.graphLink {
-  stroke: #000;
-  stroke-opacity: 1;
-}
-
-.graphArrowHead {
-  fill: #000;
-}
-
-.graphNodeLabel {
-  font-size: 8pt;
-  fill: #000;
-}
-
-.graphLinkLabel {
-  font-size: 8pt;
-  font-style: italic;
-  fill: #000;
-}

+ 0 - 367
src/frontend/d3graph/d3graph.tsx

@@ -1,367 +0,0 @@
-// Adopted from the following example, and patched to work with React v18 and D3 v7.6:
-// https://github.com/korydondzila/React-TypeScript-D3/tree/master/src
-
-import * as React from 'react';
-import * as d3 from "d3";
-import {useConst} from "../use_const";
-
-export type D3NodeData<NodeType> = {
-  id: string,
-  label: string,
-  color: string,
-  obj: NodeType;
-  x?: number,
-  y?: number,
-  bold: 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.1, link: 1};
-
-class D3Link<LinkType> extends React.Component<{ link: D3LinkData<LinkType>, svgDefs: any }, {}> {
-  ref: React.RefObject<SVGLineElement> = React.createRef<SVGLineElement>();
-  refLabel: React.RefObject<SVGTextElement> = React.createRef<SVGTextElement>();
-
-  componentDidMount() {
-    d3.select(this.ref.current).data([this.props.link]);
-    d3.select(this.refLabel.current).data([this.props.link]);
-  }
-
-  ticked() {
-    d3.select(this.ref.current)
-      .attr("x1", (d: any) => d.source.x)
-      .attr("y1", (d: any) => d.source.y)
-      .attr("x2", (d: any) => d.target.x)
-      .attr("y2", (d: any) => d.target.y)
-    ;
-    d3.select(this.refLabel.current)
-      .attr("x", (d: any) => (d.source.x + d.target.x)/2)
-      .attr("y", (d: any) => (d.source.y + d.target.y)/2)
-    ;
-  }
-
-  componentDidUpdate() {
-    d3.select(this.ref.current).data([this.props.link]);
-    d3.select(this.refLabel.current).data([this.props.link]);
-  }
-
-  render() {
-    const textStyle = {
-      fill: this.props.link.color,
-    };
-    const arrowStyle = {
-      stroke: this.props.link.color,
-    }
-    return (
-      <g>
-        <line className="graphLink" ref={this.ref} style={arrowStyle} markerEnd={this.props.link.bidirectional ? "" : `url(#${this.props.svgDefs.arrowEndId})`}/>
-        <text className="graphLinkLabel" ref={this.refLabel} style={textStyle}>{this.props.link.label}</text>
-      </g>);
-  }
-}
-
-class D3Links<LinkType> extends React.Component<{ links: D3LinkData<LinkType>[], svgDefs: any }, {}> {
-  links: Array<D3Link<LinkType> | null> = [];
-
-  ticked() {
-    this.links.forEach(l => l ? l.ticked() : null);
-  }
-
-  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: D3LinkData<LinkType>, index: number) => {
-      return <D3Link ref={link => this.links.push(link)} key={key(link)} link={link} svgDefs={this.props.svgDefs} />;
-    });
-
-    return (
-      <g>
-        {links}
-      </g>
-    );
-  }
-}
-
-interface D3NodeProps<NodeType> {
-  node: D3NodeData<NodeType>;
-  simulation: any;
-  mouseDownHandler: (event) => void;
-  mouseUpHandler: (event) => void;
-}
-interface D3NodeState {
-  dragging: boolean;
-}
-
-
-class D3Node<NodeType> extends React.Component<D3NodeProps<NodeType>, D3NodeState> {
-  ref: React.RefObject<SVGCircleElement> = React.createRef<SVGCircleElement>();
-
-  constructor(props) {
-    super(props);
-    this.state = {dragging: false};
-  }
-
-  componentDidMount() {
-    d3.select(this.ref.current).data([this.props.node]);
-
-    const onDragStart = (event, d: any) => {
-      if (!event.active) {
-        this.props.simulation.alphaTarget(0.1).restart();
-      }
-      d.fx = d.x;
-      d.fy = d.y;
-      this.setState({dragging: true});
-    }
-    const onDrag = (event, d: any) => {
-      d.fx = event.x;
-      d.fy = event.y;
-    }
-    const onDragEnd = (event, d: any) => {
-      if (!event.active) {
-        this.props.simulation.alphaTarget(0);
-      }
-      d.fx = null;
-      d.fy = null;
-      this.setState({dragging: false});
-    }
-    const dragBehavior = d3.drag()
-      .on("start", onDragStart)
-      .on("drag", onDrag)
-      .on("end", onDragEnd);
-    if (this.ref.current !== null) {
-      // @ts-ignore: Doesn't work
-      dragBehavior(d3.select(this.ref.current));
-    }
-  }
-
-  componentDidUpdate() {
-    d3.select(this.ref.current).data([this.props.node]);
-  }
-
-  ticked() {
-    if (this.ref.current !== null)
-    d3.select(this.ref.current)
-      .attr("cx", (d: any) => d.x)
-      .attr("cy", (d: any) => d.y)
-    ;
-  }
-
-  render() {
-    return (
-      <circle
-        ref={this.ref}
-        className={"graphNode" + (this.state.dragging ? " dragging" : "")}
-        onMouseDown={this.props.mouseDownHandler}
-        onMouseUp={this.props.mouseUpHandler}
-        r={5}
-        fill={this.props.node.color}
-        style={{strokeWidth:this.props.node.bold?"3px":"1px"}}
-      >
-        <title>{this.props.node.id}</title>
-      </circle>
-    );
-  }
-}
-
-interface D3NodesProps<NodeType> {
-  nodes: D3NodeData<NodeType>[],
-  simulation: any,
-  mouseDownHandler: (event, node) => void;
-  mouseUpHandler: (event, node) => void;
-}
-
-class D3Nodes<NodeType> extends React.Component<D3NodesProps<NodeType>, {}> {
-  ref: React.RefObject<SVGGElement> = React.createRef<SVGGElement>();
-  nodes: Array<D3Node<NodeType> | null> = [];
-
-  ticked() {
-    this.nodes.forEach(n => n ? n.ticked() : null);
-  }
-
-  render() {
-    this.nodes = [];
-    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)}
-          mouseUpHandler={event => this.props.mouseUpHandler(event, node)}
-          node={node} simulation={this.props.simulation} />;
-    });
-
-    return (
-      <g ref={this.ref}>
-        {nodes}
-      </g>
-    );
-  }
-}
-
-class D3Label<NodeType> extends React.Component<{ node: D3NodeData<NodeType> }, {}> {
-  ref: React.RefObject<SVGTextElement>;
-
-  constructor(props) {
-    super(props);
-    this.ref = React.createRef<SVGTextElement>();
-  }
-
-  componentDidMount() {
-    d3.select(this.ref.current).data([this.props.node]);
-  }
-
-  ticked() {
-    d3.select(this.ref.current)
-      .attr("x", (d: any) => d.x + 10)
-      .attr("y", (d: any) => d.y + 5);
-  }
-
-  componentDidUpdate() {
-    d3.select(this.ref.current).data([this.props.node]);
-  }
-
-  render() {
-    return <text className="graphNodeLabel" ref={this.ref} fontWeight={this.props.node.bold?"bold":"normal"}>
-      {this.props.node.label}
-    </text>;
-  }
-}
-
-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: D3NodeData<NodeType>, index: number) => {
-      return <D3Label ref={label => this.labels.push(label)} key={node.id} node={node} />;
-    });
-
-    return (
-      <g>
-        {labels}
-      </g>
-    );
-  }
-}
-
-export interface D3Forces {
-  charge: number;
-  center: number;
-  link: 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 function D3Graph<NodeType,LinkType>(props: Props<NodeType,LinkType>) {
-  const refSVG = React.useRef<SVGSVGElement>(null);
-  const refNodes = React.useRef<D3Nodes<NodeType>>(null);
-  const refLabels = React.useRef<D3Labels<NodeType>>(null);
-  const refLinks = React.useRef<D3Links<LinkType>>(null);
-
-  const [zoom, setZoom] = React.useState<number>(1.5);
-
-  const simulation = useConst<any>(() => {
-    const s = d3.forceSimulation()
-      .force("link", d3.forceLink().id((d: any) => d.id).strength(props.forces.link))
-      .force("charge", d3.forceManyBody().strength(props.forces.charge))
-      // .force("x", d3.forceX().strength(props.forces.center))
-      // .force("y", d3.forceY().strength(props.forces.center))
-    s.on("tick", () => ticked());
-    return s;
-  });
-
-  const update = () => {
-    simulation.nodes(props.graph.nodes);
-    simulation.force("link").links(props.graph.links);
-  }
-
-  const ticked = () => {
-    refLinks.current?.ticked();
-    refNodes.current?.ticked();
-    refLabels.current?.ticked();
-  }
-
-  React.useEffect(() => {
-    refSVG.current?.addEventListener('wheel', e => e.preventDefault());
-    return () => {
-      refSVG.current?.removeEventListener('wheel', e => e.preventDefault());
-    };
-  }, []);
-
-  React.useEffect(() => {
-    update();
-    // "warmup" simulation a bit:
-    simulation.alpha(0.1).restart().tick();
-    ticked();
-  }, [props.graph]);
-
-  const clientToSvgCoords = React.useCallback((event, callback) => {
-    // Translate mouse event to SVG coordinates:
-    if (refSVG.current !== null) {
-      const ctm = refSVG.current?.getScreenCTM();
-      if (ctm !== undefined && ctm !== null) {
-        const pt = refSVG.current!.createSVGPoint();
-        pt.x = event.clientX;
-        pt.y = event.clientY;
-        callback(event, pt.matrixTransform(ctm.inverse()));
-      }
-    }
-  }, []);
-
-  const svgDefs = {
-    arrowStartId: React.useId(),
-    arrowEndId: React.useId(),
-  };
-
-  return <svg
-    className="canvas"
-    ref={refSVG}
-    viewBox={`${-200/zoom} ${-200/zoom} ${400/zoom} ${400/zoom}`}
-    onMouseDown={e => props.mouseDownHandler ? clientToSvgCoords(e, props.mouseDownHandler) : null}
-    onMouseUp={e => props.mouseUpHandler ? clientToSvgCoords(e, props.mouseUpHandler) : null}
-    onContextMenu={e => e.preventDefault()}
-    onWheel={e => {
-      setZoom(prevZoom => prevZoom - prevZoom * Math.min(Math.max(e.deltaY*0.5,-150), 150) / 200);
-    }}
-  >
-    <defs>
-      <marker id={svgDefs.arrowStartId} markerWidth="10" markerHeight="10" refX="14" refY="3" orient="auto-start-reverse" markerUnits="strokeWidth">
-        <path d="M0,0 L0,6 L9,3 z" className="arrowHead" />
-      </marker>
-      <marker id={svgDefs.arrowEndId} markerWidth="10" markerHeight="10" refX="14" refY="3" orient="auto" markerUnits="strokeWidth">
-        <path d="M0,0 L0,6 L9,3 z" className="arrowHead" />
-      </marker>
-    </defs>
-    <D3Links ref={refLinks} links={props.graph.links} svgDefs={svgDefs} />
-    <D3Labels ref={refLabels} nodes={props.graph.nodes} />
-    <D3Nodes ref={refNodes} nodes={props.graph.nodes} simulation={simulation}
-      mouseDownHandler={(e, node) => clientToSvgCoords(e, (e,coords) => props.mouseDownHandler?.(e,coords,node))}
-      mouseUpHandler={(e, node) => clientToSvgCoords(e, (e,coords) => props.mouseUpHandler?.(e,coords,node))}
-    />
-  </svg>;
-}

+ 0 - 100
src/frontend/d3graph/d3graph_editable.tsx

@@ -1,100 +0,0 @@
-import * as React from 'react';
-import {D3Graph, D3Forces} from "./d3graph";
-import {D3OnionGraphData, D3OnionNodeData} from "./reducers/onion_graph";
-
-import {OnionContext, OnionContextType} from "../onion_context";
-import {PrimitiveDelta} from "onion/delta";
-import {INodeState, IValueState, GraphState} from "onion/graph_state";
-
-export type UserEditCallback = (deltas: PrimitiveDelta[], description: string) => void;
-type SetNodePositionCallback = (x:number, y:number) => void;
-
-export interface D3GraphEditableProps {
-  graph: D3OnionGraphData;
-  graphState: GraphState;
-  forces: D3Forces;
-  setNextNodePosition: SetNodePositionCallback;
-  onUserEdit?: UserEditCallback;
-}
-
-export function D3GraphEditable(props: D3GraphEditableProps) {
-  const [mouseDownNode, setMouseDownNode] = React.useState<D3OnionNodeData | null>(null);
-  const onionContext = React.useContext<OnionContextType>(OnionContext);
-
-  const mouseDownHandler = React.useCallback((event, {x,y}, mouseDownNode: D3OnionNodeData | undefined) => {
-    event.stopPropagation();
-    if (mouseDownNode) {
-      setMouseDownNode(mouseDownNode);
-    }
-  }, []);
-  const mouseUpHandler = React.useCallback((event, {x,y}, mouseUpNode: D3OnionNodeData | undefined) => {
-    event.stopPropagation();
-    // Construct the delta(s) that capture the user's change:
-    const deltas: PrimitiveDelta[] = (() => {
-      if (event.button === 2) { // right mouse button
-        if (mouseDownNode !== null && mouseDownNode.obj.type === "node") {
-          // create outgoing edge...
-          if (mouseUpNode !== undefined && mouseUpNode.obj.type === "node") {
-            const targetNodeCreation = (mouseUpNode.obj as INodeState).creation;
-            // right mouse button was dragged from one node to another -> create/update edge from one node to the other
-            const label = prompt("Edge label (ESC to cancel):", "label");
-            if (label !== null) {
-              return [mouseDownNode.obj.getDeltaForSetEdge(onionContext.deltaRegistry, label, targetNodeCreation)];
-            }
-          }
-          else {
-            // right mouse button was dragged from a node to empty space -> create/update edge from node to a value
-            const label = prompt("Edge label (ESC to cancel):", "label");
-            if (label !== null) {
-              let enteredValue: string|null = "\"42\"";
-              while (true) {
-                enteredValue = prompt("Target value (enter a JSON-parsable(!) string (with quotes), number, boolean or null):\n(ESC to cancel)", enteredValue);
-                if (enteredValue === null) {
-                  break;
-                }
-                let parsedValue;
-                try {
-                  parsedValue = JSON.parse(enteredValue);
-                } catch (err) {
-                  alert("Invalid JSON!");
-                  continue;
-                }
-                const typeofParsedValue = typeof parsedValue;
-                if (parsedValue !== null && typeofParsedValue !== "string" && typeofParsedValue !== "number" && typeofParsedValue !== "boolean") {
-                  alert("Expected string, number, boolean or null. Got: " + typeofParsedValue);
-                  continue;
-                }
-                return [mouseDownNode.obj.getDeltaForSetEdge(onionContext.deltaRegistry, label, parsedValue)];
-              }
-            }
-          }
-        }
-        else {
-          // right mouse button clicked -> create node
-          const uuid = onionContext.generateUUID();
-          props.setNextNodePosition(x,y);
-          return [onionContext.deltaRegistry.newNodeCreation(uuid)];
-        }
-      }
-      else if (event.button === 1) { // middle mouse button
-        if (mouseUpNode !== undefined) {
-          // middle mouse button click on node -> delete node (and incoming/outgoing edges)
-          return mouseUpNode.obj.getDeltasForDelete(onionContext.deltaRegistry);
-        }
-      }
-      return [];
-    })();
-    if (deltas.length > 0) {
-      // Let the world know that there was a user edit:
-      props.onUserEdit?.(deltas, deltas.map(d => d.description).join("+"));
-    }
-    setMouseDownNode(null);
-  }, [mouseDownNode, onionContext, props.onUserEdit]);
-
-  return <D3Graph
-    graph={props.graph}
-    forces={props.forces}
-    mouseDownHandler={mouseDownHandler}
-    mouseUpHandler={mouseUpHandler}
-  />;
-}

+ 0 - 138
src/frontend/d3graph/reducers/delta_graph.ts

@@ -1,138 +0,0 @@
-import {Delta, Transaction} from "onion/delta";
-import {NodeCreation, NodeDeletion, EdgeUpdate} from "onion/delta";
-import {D3GraphData, D3NodeData, D3LinkData} from "../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, l.label);
-            } 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, l.label);
-            } 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, /* active: */ action.active)),
-        // for every dependency and conflict, add a link:
-        links: prevState.links.concat(
-            ...delta.getDependencies().map(([dep,depSummary]) => dependencyToDepGraphLink(delta, dep, depSummary)),
-            ...delta.conflictsWith
-              .filter(([conflictingDelta]) => prevState.nodes.some(n => n.id === fullDeltaId(conflictingDelta)))
-              .map(([conflictingDelta, kind]) => conflictToDepGraphLink(delta, conflictingDelta, kind)),
-          ),
-      };
-    }
-  }
-}
-
-export function fullDeltaId(delta: Delta): string {
-  return delta.hash.toString('hex');
-}
-
-// Helpers
-
-function deltaToDepGraphNode(delta: Delta, active: boolean, x?: number, y?: number): D3NodeData<Delta> {
-  return {
-    id: fullDeltaId(delta),
-    label: delta.description,
-    color: getDeltaColor(delta),
-    bold: active,
-    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, kind: string): D3LinkData<null> {
-  return {
-    source: fullDeltaId(fromDelta),
-    label: kind,
-    color: 'red',
-    bidirectional: true,
-    target: fullDeltaId(toDelta),
-    obj: null,
-  };
-}
-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 Transaction) {
-      delta.deltas.forEach(countDeltas);
-    }
-    else {
-      numDeltas++;
-      if (delta instanceof NodeCreation) {
-        numCreations++;
-      }
-      else if (delta instanceof NodeDeletion) {
-        numDeletions++;
-      }
-      else if(delta instanceof EdgeUpdate) {
-        numEdgeUpdates++;
-      }
-    }
-  }
-  countDeltas(delta);
-  const toHex = (num, min) => {
-    const s = Math.min( Math.round(num/numDeltas*255)+min, 255).toString(16);
-    return s.length < 2 ? '0'+s : s; // add leading zero
-  }
-  // make color light enough such that black text onto a color background is readable:
-  const color = `#${toHex(numDeletions,140)}${toHex(numCreations,140)}${toHex(numEdgeUpdates,140)}`;
-  return color;
-}

+ 0 - 136
src/frontend/d3graph/reducers/history_graph.ts

@@ -1,136 +0,0 @@
-import {Version} from "onion/version";
-import {Delta} from "onion/delta";
-import {D3GraphData, D3NodeData, D3LinkData} from "../d3graph";
-
-export type HistoryGraphState = D3GraphData<Version,Delta|null>;
-
-interface AppendToHistoryGraph {type:'addVersion', version: Version}
-interface HighlightVersion {type:'highlightVersion', version: Version, bold?: boolean, overrideColor?: string, resetColor?: boolean}
-
-export type HistoryGraphAction = Readonly<HighlightVersion> | Readonly<AppendToHistoryGraph>;
-
-export function historyGraphReducer(prevState: HistoryGraphState, action: HistoryGraphAction): HistoryGraphState {
-  switch (action.type) {
-    case 'addVersion': {
-      const isExistingLink = link => !prevState.links.some(prevLink => 
-          (prevLink.source.id === link.source
-        || prevLink.source === link.source)
-       && (prevLink.target.id === link.target
-        || prevLink.target === link.target));
-
-
-      const existingNode = prevState.nodes.find(node => node.id === fullVersionId(action.version));
-
-      const [nodes, node] = existingNode ?
-        [prevState.nodes, existingNode]
-        : (() => {
-          const newNode = versionToNode(action.version, false);
-          return [prevState.nodes.concat(newNode), newNode];
-        })();
-
-      const newLinks = action.version.parents.map(([parentVersion,delta]) => parentLinkToHistoryGraphLink(action.version, parentVersion, delta, nodes))
-        // don't add links that are already there:
-        .filter(isExistingLink);
-
-      const newEmbeddingLinks = ([...action.version.embeddings.entries()].map(([guestId, {version, overridings}]) => {
-        const target = nodes.find(n => n.obj === version);
-        if (target === undefined) {
-          console.log("not adding embedding link");
-          return null;
-          // throw new Error("Cannot add version to history graph: one of its embeddings doesn't have a node yet.")
-        }
-        return {
-          source: node,
-          label: guestId + (overridings.size > 0 ? '!' : ''),
-          target,
-          color: 'blue',
-          obj: null,
-        }}).filter(x => x !== null) as Array<D3LinkData<Delta|null>>).filter(isExistingLink);
-
-      return {
-        nodes,
-        links: prevState.links.concat(...newLinks, ...newEmbeddingLinks),
-      };
-    }
-    case 'highlightVersion': {
-      const nodes = prevState.nodes.map(n => {
-        if (n.obj === action.version) {
-          const {color, bold, ...rest} = n;
-          return {
-            color: action.overrideColor ? action.overrideColor : action.resetColor ? versionColor(n.obj) : color,
-            bold: action.bold !== undefined ? action.bold : bold,
-            ...rest,
-          }
-          // return versionToNode(action.version, action.bold, n.x, n.y, action.overrideColor);
-        }
-        else {
-          return n;
-        }
-      });
-      return {
-        nodes,
-        // must re-create links for re-created nodes:
-        links: prevState.links.map(l => {
-          const {source, target, ...rest} = l;
-          if (source.obj === action.version || target.obj === action.version) {
-            return {
-              source: nodes.find(n => n.obj === source.obj),
-              target: nodes.find(n => n.obj === target.obj),
-              ...rest,
-            };
-          }
-          else {
-            return l;
-          }
-        }),
-      }
-    }
-  }
-}
-
-export function initialHistoryGraph(initialVersion) {
-  return {
-    nodes: [
-      versionToNode(initialVersion, true),
-    ],
-    links: [],
-  };
-}
-
-export function fullVersionId(version: Version): string {
-  return version.hash.toString('hex');
-}
-
-// Helpers
-
-function versionColor(version: Version): string {
-  return (version.parents.length === 0 ? "#808080" : "#a28eca");
-}
-
-function versionToNode(version: Version, bold?: boolean, x?: number, y?: number, overrideColor?: string): D3NodeData<Version> {
-  return {
-    id: fullVersionId(version),
-    label: (version.parents.length === 0 ? "initial" : ""),
-    color: overrideColor ? overrideColor : versionColor(version),
-    obj: version,
-    bold: bold === true, // may be true, false or undefined
-    x, y,
-  }
-}
-function parentLinkToHistoryGraphLink(childVersion: Version, parentVersion: Version, delta: Delta, nodes): D3LinkData<Delta> {
-  const source = nodes.find(n => n.obj === childVersion);
-  const target = nodes.find(n => n.obj === parentVersion);
-  if (source === undefined) {
-    throw new Error("Cannot create history graph link: source (childVersion) is undefined");
-  }
-  if (target === undefined) {
-    throw new Error("Cannot create history graph link: target (parentVersion) is undefined");
-  }
-  return {
-    source,
-    label: delta.description,
-    target,
-    color: 'black',
-    obj: delta,
-  };
-}

+ 0 - 148
src/frontend/d3graph/reducers/onion_graph.ts

@@ -1,148 +0,0 @@
-// The React state and reducer of a D3Graph component showing an onion graph state.
-
-import {PrimitiveValue, UUID} from "onion/types";
-import {INodeState, IValueState, GraphStateListener} from "onion/graph_state";
-import {D3GraphData, D3NodeData, D3LinkData} from "../d3graph";
-
-export type D3OnionNodeData = D3NodeData<INodeState|IValueState>;
-export type D3OnionLinkData = D3LinkData<null>;
-export type D3OnionGraphData = D3GraphData<INodeState|IValueState,null>;
-
-interface AddNode {type:'addNode', ns: INodeState, x: number, y: number}
-interface RemoveNode {type:'removeNode', id: UUID}
-interface AddValue {type:'addValue', vs: IValueState, x: number, y: number}
-interface RemoveValue {type:'removeValue', value: PrimitiveValue}
-interface AddLinkToNode {type:'addLinkToNode', sourceId: UUID, label: string, targetId: UUID}
-interface AddLinkToValue {type:'addLinkToValue', sourceId: UUID, label: string, targetValue: PrimitiveValue}
-interface RemoveLink {type:'removeLink', sourceId: UUID, label: string}
-
-export type D3OnionGraphAction =
-    Readonly<AddNode>
-  | Readonly<RemoveNode>
-  | Readonly<AddValue>
-  | Readonly<RemoveValue>
-  | Readonly<AddLinkToNode>
-  | Readonly<AddLinkToValue>
-  | Readonly<RemoveLink>;
-
-export function onionGraphReducer(prevState: D3OnionGraphData, action: D3OnionGraphAction): D3OnionGraphData {
-  switch (action.type) {
-    case 'addNode': {
-      return {
-        nodes: [...prevState.nodes, {
-          id: nodeNodeId(action.ns.creation.id),
-          label: JSON.stringify(action.ns.creation.id),
-          x: action.x,
-          y: action.y,
-          color: "darkturquoise",
-          obj: action.ns,
-          bold: false,
-        }],
-        links: prevState.links,
-      };
-    }
-    case 'removeNode': {
-      return {
-        nodes: prevState.nodes.filter(n => !n.obj.isNode(action.id)),
-        links: prevState.links,
-      };
-    }
-    case 'addValue': {
-      return {      
-        nodes: [...prevState.nodes, {
-          id: valueNodeId(action.vs.value),
-          label: JSON.stringify(action.vs.value),
-          x: action.x,
-          y: action.y,
-          color: "darkorange",
-          obj: action.vs,
-          bold: false,
-        }],
-        links: prevState.links,
-      };
-    }
-    case 'removeValue': {
-      return {
-        nodes: prevState.nodes.filter(n => !n.obj.isValue(action.value)),
-        links: prevState.links,
-      };
-    }
-    case 'addLinkToNode': {
-      return {    
-        nodes: prevState.nodes,
-        links: [...prevState.links, {
-          source: prevState.nodes.find(n => n.obj.isNode(action.sourceId)),   // AR: here is the problem!
-          target: prevState.nodes.find(n => n.obj.isNode(action.targetId)),
-          label: action.label,
-          color: 'black',
-          obj: null,
-        }],
-      };
-    }
-    case 'addLinkToValue': {
-      return {    
-        nodes: prevState.nodes,
-        links: [...prevState.links, {
-          source: prevState.nodes.find(n => n.obj.isNode(action.sourceId)),
-          target: prevState.nodes.find(n => n.obj.isValue(action.targetValue)),
-          label: action.label,
-          color: 'black',
-          obj: null,
-        }],
-      };
-    }
-    case 'removeLink': {
-      return {    
-        nodes: prevState.nodes,
-        links: prevState.links.filter(l => l.source.obj.creation.id !== action.sourceId || l.label !== action.label),
-      };
-    }
-  }
-}
-
-function nodeNodeId(nodeId: UUID) {
-  return "N"+JSON.stringify(nodeId);
-}
-
-function valueNodeId(value: PrimitiveValue) {
-  return "V"+JSON.stringify(value);
-}
-
-
-// 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: D3OnionGraphData) => D3OnionGraphData) => void, x, y) {
-    this.setGraph = setGraph;
-    this.x = x;
-    this.y = y;
-  }
-
-  createNode(ns: INodeState) {
-    this.setGraph(prevGraph => onionGraphReducer(prevGraph, {type: 'addNode', ns, x: this.x, y: this.y}));
-  }
-  createValue(vs: IValueState) {
-    this.setGraph(prevGraph => onionGraphReducer(prevGraph, {type: 'addValue', vs, x: this.x, y: this.y}));
-  }
-  deleteNode(id: UUID) {
-    this.setGraph(prevGraph => onionGraphReducer(prevGraph, {type: 'removeNode', id}));
-  }
-  deleteValue(value: PrimitiveValue) {
-    this.setGraph(prevGraph => onionGraphReducer(prevGraph, {type: 'removeValue', value}));
-  }
-  createLinkToNode(sourceId: UUID, label: string, targetId: UUID) {
-    this.setGraph(prevGraph => onionGraphReducer(prevGraph, {type: 'addLinkToNode', sourceId, label, targetId}));
-  }
-  createLinkToValue(sourceId: UUID, label: string, targetValue: PrimitiveValue) {
-    this.setGraph(prevGraph => onionGraphReducer(prevGraph, {type: 'addLinkToValue', sourceId, label, targetValue}));
-  }
-  deleteLink(sourceId: UUID, label: string) {
-    this.setGraph(prevGraph => onionGraphReducer(prevGraph, {type: 'removeLink', sourceId, label}));
-  }
-}

File diff suppressed because it is too large
+ 0 - 3
src/frontend/demos/assets/corr_as.svg


File diff suppressed because it is too large
+ 0 - 3
src/frontend/demos/assets/corr_corr.svg


File diff suppressed because it is too large
+ 0 - 3
src/frontend/demos/assets/corr_cs.svg


File diff suppressed because it is too large
+ 0 - 3
src/frontend/demos/assets/editor.svg


File diff suppressed because it is too large
+ 0 - 4
src/frontend/demos/assets/pd.svg


+ 0 - 18
src/frontend/demos/blocks.tsx

@@ -1,18 +0,0 @@
-import {Blockquote, BlockquoteProps} from '@mantine/core';
-import * as React from 'react';
-
-export function Actionblock(props: BlockquoteProps) {
-    return (
-        <Blockquote icon={null} action="true"
-            {...props}
-        />
-    );
-}
-
-export function Resultblock(props: BlockquoteProps) {
-    return (
-        <Blockquote icon={null} result="true"
-                    {...props}
-        />
-    );
-}

+ 0 - 261
src/frontend/demos/demo_bm.tsx

@@ -1,261 +0,0 @@
-import * as React from 'react';
-import {Center, Group, SimpleGrid, Space, Stack, Text, Title} from '@mantine/core';
-
-import {OnionContext} from "../onion_context";
-import {DeltaRegistry} from "onion/delta_registry";
-import {VersionRegistry} from "onion/version";
-import {mockUuid} from "../../util/test_helpers";
-import {InfoHoverCard} from '../info_hover_card';
-import {newOnion, undoButtonHelpText} from "../versioned_model/single_model";
-import {newCorrespondence, undoButtonHelpTextCorr} from '../versioned_model/correspondence';
-import {ManualRendererProps, ModalManualRenderer} from '../versioned_model/manual_renderer';
-import {Actionblock, Resultblock} from './blocks';
-
-export const demo_BM_description =
-    <>
-        <Title order={4}>
-            Blended Modeling
-        </Title>
-        <Text>
-            This demo introduces a second concrete syntax which is for sake of simplicity the same as the first one:
-            a rountangle editor.
-            The difference to the previous Correspondence demo is that the AS can not be edited directly anymore, but only
-            indirectly via one of the two concrete syntaxes.
-        </Text>
-        <Text>
-            The connection between the different canvas on the right side is as follows:
-        </Text>
-        <Text>
-            CS1 &harr; Corr1 &harr; AS &harr; Corr2 &harr; CS2
-        </Text>
-        <Text>
-            Thus, we have now two Correspondence models which both creates correspondences between elements of the
-            central AS and their linked CS.
-        </Text>
-        <Text>
-            In consequence, we also have two independent parsing and rendering functions for each CS which can be
-            triggered automatically or manually.
-        </Text>
-        <Text>
-            With this scenario, we simulate a blended modeling environment: One can create rountangles in the first CS.
-            All changes are reflected immediately in the AS. At some point, the user switches over to the other CS. At
-            this point, the rendering from the AS to the second CS has to be triggered and, if necessary, missing
-            information, such as layout information, has to be entered.
-        </Text>
-        <Text>
-            Note, that the layout in the second CS can (and most likely will) differ from the layout of the first CS.
-            Nevertheless, both models are parsed into the same AS model and are thus semantically equivalent.
-        </Text>
-        <Text>
-            From now on, the user can edit the rountangles in the second CS and – if the auto parsing is switched on –
-            all changes are reflected in the AS.
-            At some point, the user can switch back to CS 1, likely involving another rendering step to enter missing
-            information.
-        </Text>
-        <Text>
-            Since all models are built upon our versioning approach, all changes are kept in Deltas and, thus, each step
-            can be undone or redone: One can navigate to any previous version on any side and all conflicts on any level are
-            detected and properly handled. This includes simultaneous changes on both sides. That means, collaborative
-            blended modeling is directly supported.
-        </Text>
-        <Text>
-            Switching off the "auto parsing" simulates asynchronous collaboration where several changes are accumulated
-            and applied to the AS at once at a later point in time.
-        </Text>
-        <Text>
-            One possible walk-through of this demo is very similar to the steps of the Correspondence demo:
-        </Text>
-        <Actionblock>
-            1. Create a rountangle!
-        </Actionblock>
-        <Resultblock>
-            Automatic parsing recognizes this rountangle as a state in the AS with ID = 2 and connects both via the
-            Correspondence model by introducing a "connection node" with ID = 1.
-        </Resultblock>
-        <Resultblock>
-            As visualized in the picture above, the Correspondence model contains the complete CS and AS model plus some
-            more information.
-        </Resultblock>
-        <Actionblock>
-            2. Draw a second rountangle and move and resize it such that the new rountangle completely surrounds the
-            first one!
-        </Actionblock>
-        <Resultblock>
-            The parser recognizes the (visual) containment relation between the two rountangles and translates this to a
-            "has-parent" relation between the two (abstract) states. Again, you can find this relation also in the
-            correspondence model.
-        </Resultblock>
-        <Actionblock>
-            3. Press the "Render &gt;&gt; "-button at the top of the AS!
-        </Actionblock>
-        <Resultblock>
-            The rendering process detects missing information while it tries to create a model for the second CS.
-        </Resultblock>
-        <Resultblock>
-            A new dialog appears where one can position and resize the rectangles in the second CS.
-        </Resultblock>
-        <Actionblock>
-            4. Position the new state according to the given constraints!
-        </Actionblock>
-        <Resultblock>
-            Your CS 2 model is updated and the rountangles are visualized accordingly.
-        </Resultblock>
-        <Actionblock>
-            5. Add, move, resize, or delete the rountangles in either of the two CS and press from time to time the
-            "Render"-buttons for any of the both directions.
-        </Actionblock>
-        <Resultblock>
-            The changes are exchanged properly, where necessary, the system asks for missing information.
-        </Resultblock>
-        <Resultblock>
-            Any conflicting Deltas are identified and the user has to choose, which of the non-conflicting version he/she wants to apply.
-            A merging view that allows to combine changes (merge action) is in preparation.
-        </Resultblock>
-    </>;
-
-export function getDemoBM() {
-    const deltaRegistry = new DeltaRegistry();
-    const generateUUID = mockUuid();
-    const versionRegistry = new VersionRegistry();
-
-    const as = newOnion({readonly: false, deltaRegistry, versionRegistry});
-
-    function createCs() {
-        const cs = newOnion({readonly: false, deltaRegistry, versionRegistry});
-        const corr = newCorrespondence({deltaRegistry, generateUUID, versionRegistry});
-        return {cs, corr};
-    }
-
-    const css = [createCs(), createCs()];
-
-    // returns functional react component
-    return function () {
-        const onionAs = as.useOnion(reducer => ({
-            onUserEdit: (deltas, description) => {
-                const newVersion = onionAs.reducer.createAndGotoNewVersion(deltas, description);
-                onions.forEach(o => {
-                    if (o.autoRender) {
-                        o.onionCorr.reducer.renderExistingVersion(newVersion, setManualRendererState);
-                    }
-                });
-            },
-        }));
-        const onions = css.map(({cs, corr}, i) => {
-            const [autoParse, setAutoParse] = React.useState(true);
-            const [autoRender, setAutoRender] = React.useState(false);
-
-            const onionCs = cs.useOnion(reducer => ({
-                onUserEdit: (deltas, description) => {
-                    const newVersion = reducer.createAndGotoNewVersion(deltas, description);
-                    if (autoParse) {
-                        onionCorr.reducer.parseExistingVersion(newVersion);
-                    }
-                },
-            }));
-            const onionCorr = corr.useCorrespondence(onionCs, onionAs);
-            return {onionCs, onionCorr, autoParse, setAutoParse, autoRender, setAutoRender};
-        });
-
-        const [manualRendererState, setManualRendererState] = React.useState<null | ManualRendererProps>(null);
-
-        const csTabs = ["editor", "state", "merge", "deltaL1", "deltaL0"];
-        const corrTabs = ["state", "merge", "deltaL1", "deltaL0"];
-        const asTabs = ["state", "merge", "deltaL1", "deltaL0"];
-
-        function centeredCaption(text) {
-            return <Center><Title order={4}>{text}</Title></Center>;
-        }
-
-        return (<div style={{minWidth: 2400}}>
-            <OnionContext.Provider value={{generateUUID, deltaRegistry}}>
-            <ModalManualRenderer manualRendererState={manualRendererState}/>
-            <SimpleGrid cols={5}>
-                <Stack>
-                    {centeredCaption("Concrete Syntax 0")}
-                    <Group position="right">
-                        {onions[0].onionCorr.components.getCaptionWithParseButton(onions[0].autoParse, onions[0].setAutoParse)}
-                    </Group>
-                    <Stack>
-                        {onions[0].onionCs.components.makeTabs("editor", csTabs)}
-                        {onions[0].onionCs.components.makeTabs("merge", csTabs)}
-                        <Center>
-                            {onions[0].onionCs.components.undoRedoButtons}
-                            <Space w="sm"/>
-                            <InfoHoverCard>
-                                {undoButtonHelpText}
-                            </InfoHoverCard>
-                        </Center>
-                    </Stack>
-                </Stack>
-                <Stack>
-                    {centeredCaption("Correspondence 0")}
-                    <Stack>
-                        {onions[0].onionCorr.components.makeTabs("state", corrTabs)}
-                        {onions[0].onionCorr.components.makeTabs("merge", corrTabs)}
-                        <Center>
-                            {onions[0].onionCorr.components.undoRedoButtons}
-                            <Space w="sm"/>
-                            <InfoHoverCard>
-                                {undoButtonHelpText}
-                                <Space h="sm"/>
-                                {undoButtonHelpTextCorr}
-                            </InfoHoverCard>
-                        </Center>
-                    </Stack>
-                </Stack>
-                <Stack>
-                    {centeredCaption("Abstract Syntax")}
-                    <Group position="apart">
-                        {onions[0].onionCorr.components.getCaptionWithRenderButton(onions[0].autoRender, onions[0].setAutoRender, setManualRendererState)}
-                        {onions[1].onionCorr.components.getCaptionWithRenderButton(onions[1].autoRender, onions[1].setAutoRender, setManualRendererState, "right")}
-                    </Group>
-                    <Stack>
-                        {onionAs.components.makeTabs("state", asTabs)}
-                        {onionAs.components.makeTabs("merge", asTabs)}
-                        <Center>
-                            {onionAs.components.undoRedoButtons}
-                            <Space w="sm"/>
-                            <InfoHoverCard>
-                                {undoButtonHelpText}
-                            </InfoHoverCard>
-                        </Center>
-                    </Stack>
-                </Stack>
-                <Stack>
-                    {centeredCaption("Correspondence 1")}
-                    <Stack>
-                        {onions[1].onionCorr.components.makeTabs("state", corrTabs)}
-                        {onions[1].onionCorr.components.makeTabs("merge", corrTabs)}
-                        <Center>
-                            {onions[1].onionCorr.components.undoRedoButtons}
-                            <Space w="sm"/>
-                            <InfoHoverCard>
-                                {undoButtonHelpText}
-                                <Space h="sm"/>
-                                {undoButtonHelpTextCorr}
-                            </InfoHoverCard>
-                        </Center>
-                    </Stack>
-                </Stack>
-                <Stack>
-                    {centeredCaption("Concrete Syntax 1")}
-                    <Group position="left">
-                        {onions[1].onionCorr.components.getCaptionWithParseButton(onions[1].autoParse, onions[1].setAutoParse, "left")}
-                    </Group>
-                    <Stack>
-                        {onions[1].onionCs.components.makeTabs("editor", csTabs)}
-                        {onions[1].onionCs.components.makeTabs("merge", csTabs)}
-                        <Center>
-                            {onions[1].onionCs.components.undoRedoButtons}
-                            <Space w="sm"/>
-                            <InfoHoverCard>
-                                {undoButtonHelpText}
-                            </InfoHoverCard>
-                        </Center>
-                    </Stack>
-                </Stack>
-            </SimpleGrid>
-            </OnionContext.Provider>
-        </div>);
-    }
-}

+ 0 - 221
src/frontend/demos/demo_corr.tsx

@@ -1,221 +0,0 @@
-import * as React from "react";
-import {SimpleGrid, Text, Title, Stack, Center, Image, Group, Space} from "@mantine/core";
-
-import {DeltaRegistry} from "onion/delta_registry";;
-import {mockUuid} from "../../util/test_helpers";
-import {OnionContext} from "../onion_context";
-import {VersionRegistry} from "onion/version";
-import {newOnion, undoButtonHelpText} from "../versioned_model/single_model";
-import {newCorrespondence, undoButtonHelpTextCorr} from "../versioned_model/correspondence";
-import {ModalManualRenderer, ManualRendererProps} from "../versioned_model/manual_renderer";
-import {InfoHoverCard} from "../info_hover_card";
-import corr_csImage from './assets/corr_cs.svg';
-import corr_asImage from './assets/corr_as.svg';
-import corr_corrImage from './assets/corr_corr.svg';
-import {Actionblock, Resultblock} from "./blocks";
-
-
-export const demo_Corr_description =
-    <>
-        <Title order={4}>
-            Correspondence Model
-        </Title>
-        <Text>
-            The different parts we have seen in the previous demos, are now in combined in different tabs in one column.
-            The second row is just a copy of the upper one and shows exactly the same model(s). This allows the simultaneous observation of changes in the
-            different models.
-        </Text>
-        <Text>
-            The following figure shows the connection of the different models combined in the "Concrete Syntax" (CS)
-            column.
-            The two abstraction levels of Deltas L1 and L0 are combined as "CS Delta(s)" and the History is omitted in the
-            figure.
-        </Text>
-        <Image src={corr_csImage}/>
-        <Text>
-            Analogously, the third column combines the different models shown in the first demo "Primitive Delta" into
-            a single view, representing the "Abstract Syntax model" (AS).
-            The difference to the Concrete Syntax in the first column is, that there is no additional "user-friendly"
-            editor for editing the state. Instead, the state graph can be manipulated directly as already introduced in the first
-            demo "Primitive Deltas". This way of manipulation is also illustrated in the following picture.
-        </Text>
-        <Image src={corr_asImage}/>
-        <Text>
-            Between the CS and AS the "Correspondence model" (Corr) encapsulates and connects the Deltas of the CS and
-            AS models as visualized in the following picture.
-        </Text>
-        <Image src={corr_corrImage}/>
-        <Text>
-            The creation of Deltas of the Correspondence model and AS Deltas is triggered by the "Parsing" process.
-            Parsing can either be automatically started after each user interaction or interactively at specific points
-            in time, simulated by pressing the "Parse &gt;&gt;" button in the upper right corner of the first column.
-        </Text>
-        <Text>
-            In turn, changes to the AS model are taken into account in the rendering process. Since the CS can contain
-            information the AS does not contain, such as layout information, some user interaction
-            may be required to add this missing information. This process is not automatically triggered by default.
-        </Text>
-        <Text>
-            Common to all these three models (CS, Corr, AS) is the central foundation in Deltas encoding any change of
-            the state graph. On this generic basis all other functionality is built on: conflict detection, versioning,
-            undo/redo, etc. That is also the reason why these features can be presented in a homogeneous way for each of
-            the models.
-        </Text>
-        <Text>
-            This demo focuses on the parsing (CS &rarr; AS) and rendering (AS &rarr; CS).
-        </Text>
-        <Actionblock>
-            1. Create a Rountangle!
-        </Actionblock>
-        <Resultblock>
-            Below the rountangle editor, the new State graph of the CS is shown.
-        </Resultblock>
-        <Resultblock>
-            Automatic parsing recognizes this rountangle as a state in the AS with ID = 2 and connects both via the
-            Correspondence model by introducing a "connection node" with ID = 1.
-        </Resultblock>
-        <Resultblock>
-            As visualized in the above picture, the Correspondence model contains the complete CS and AS model plus some
-            more information relevant to maintain the relations between elements contained in the AS and the CS.
-        </Resultblock>
-        <Actionblock>
-            2. Draw a second Rountangle and move and resize it such that the new rountangle completely surrounds the
-            first one!
-        </Actionblock>
-        <Resultblock>
-            The parser recognizes the (visual) containment relation between the two rountangles and translates this to a
-            "has-parent" relation between the two (abstract) states. Again, you can find this relation also in the correspondence
-            model.
-        </Resultblock>
-        <Actionblock>
-            3. Create a new State in the AS by a right click in the canvas!
-        </Actionblock>
-        <Actionblock>
-            4. Press the "&lt;&lt; Render"-button at the top!
-        </Actionblock>
-        <Resultblock>
-            The rendering process detects missing information while it tries to create a CS model for the new state.
-        </Resultblock>
-        <Resultblock>
-            A new dialog appears where one can position and resize the new rectangle.
-        </Resultblock>
-        <Resultblock>
-            The system does not allow any position/size whose parsing would result in a different AS (e.g., additional or missing "hasParent" relation).
-        </Resultblock>
-        <Actionblock>
-            5. Position the new state according to the given constraints!
-        </Actionblock>
-        <Resultblock>
-            Your CS model is updated and the rountangles are visualized accordingly.
-        </Resultblock>
-        {/* TODO: remove z-index!
-            TODO: deleted nodes/Rountangles are still considered in the "Missing information"-dialog
-            TODO: everywhere: Arrows with directions!
-        */}
-    </>;
-
-export function getDemoCorr() {
-    const deltaRegistry = new DeltaRegistry();
-    const generateUUID = mockUuid();
-    const versionRegistry = new VersionRegistry();
-
-    const as = newOnion({readonly: false, deltaRegistry, versionRegistry});
-    const cs = newOnion({readonly: false, deltaRegistry, versionRegistry});
-    const corr = newCorrespondence({deltaRegistry, generateUUID, versionRegistry});
-
-    // returns functional react component
-    return function () {
-        const onionAs = as.useOnion(reducer => ({
-            onUserEdit: (deltas, description) => {
-                const newVersion = onionAs.reducer.createAndGotoNewVersion(deltas, description);
-                if (autoRender) {
-                    onionCorr.reducer.renderExistingVersion(newVersion, setManualRendererState);
-                }
-            },
-        }));
-        const onionCs = cs.useOnion(reducer => ({
-            onUserEdit: (deltas, description) => {
-                const newVersion = reducer.createAndGotoNewVersion(deltas, description);
-                if (autoParse) {
-                    onionCorr.reducer.parseExistingVersion(newVersion);
-                }
-            },
-        }));
-
-        const onionCorr = corr.useCorrespondence(onionCs, onionAs);
-
-        const [autoParse, setAutoParse] = React.useState<boolean>(true);
-        const [autoRender, setAutoRender] = React.useState<boolean>(false);
-
-        const [manualRendererState, setManualRendererState] = React.useState<null | ManualRendererProps>(null);
-
-        const csTabs = ["editor", "state", "merge", "deltaL1", "deltaL0"];
-        const corrTabs = ["state", "merge", "deltaL1", "deltaL0"];
-        const asTabs = ["state", "merge", "deltaL1", "deltaL0"];
-
-        return (<div style={{minWidth: 1300}}>
-            <OnionContext.Provider value={{generateUUID, deltaRegistry}}>
-            <ModalManualRenderer manualRendererState={manualRendererState}/>
-            <SimpleGrid cols={3}>
-                <div>
-                    <Group position="apart">
-                        <Title order={4}>Concrete Syntax</Title>
-                        {onionCorr.components.getCaptionWithParseButton(
-                            autoParse, setAutoParse)}
-                    </Group>
-                    <Space h="sm"/>
-                    <Stack>
-                        {onionCs.components.makeTabs("editor", csTabs)}
-                        {onionCs.components.makeTabs("merge", csTabs)}
-                        <Center>
-                            {onionCs.components.undoRedoButtons}
-                            <Space w="sm"/>
-                            <InfoHoverCard>
-                                {undoButtonHelpText}
-                            </InfoHoverCard>
-                        </Center>
-                    </Stack>
-                </div>
-                <div>
-                    <Group position="center">
-                        <Title order={4}>Correspondence</Title>
-                    </Group>
-                    <Space h="sm"/>
-                    <Stack>
-                        {onionCorr.components.makeTabs("state", corrTabs)}
-                        {onionCorr.components.makeTabs("merge", corrTabs)}
-                        <Center>
-                            {onionCorr.components.undoRedoButtons}
-                            <Space w="sm"/>
-                            <InfoHoverCard>
-                                {undoButtonHelpText}
-                                <Space h="sm"/>
-                                {undoButtonHelpTextCorr}
-                            </InfoHoverCard>
-                        </Center>
-                    </Stack>
-                </div>
-                <div>
-                    <Group position="apart">
-                        {onionCorr.components.getCaptionWithRenderButton(
-                            autoRender, setAutoRender, setManualRendererState)}
-                        <Title order={4}>Abstract Syntax</Title>
-                    </Group>
-                    <Space h="sm"/>
-                    <Stack>
-                        {onionAs.components.makeTabs("state", asTabs)}
-                        {onionAs.components.makeTabs("merge", asTabs)}
-                        <Center>
-                            {onionAs.components.undoRedoButtons}
-                            <Space w="sm"/>
-                            <InfoHoverCard>
-                                {undoButtonHelpText}
-                            </InfoHoverCard>
-                        </Center>
-                    </Stack>
-                </div>
-            </SimpleGrid>
-            </OnionContext.Provider>
-        </div>);
-    }
-}

+ 0 - 125
src/frontend/demos/demo_editor.tsx

@@ -1,125 +0,0 @@
-import * as React from "react";
-import {SimpleGrid, Text, Title, Stack, Center, Group, Space, Image} from "@mantine/core";
-
-import {DeltaRegistry} from "onion/delta_registry";;
-import {VersionRegistry} from "onion/version";
-import {mockUuid} from "../../util/test_helpers";
-
-import {newOnion} from "../versioned_model/single_model";
-import {OnionContext} from "../onion_context";
-import {InfoHoverCard} from "../info_hover_card";
-import {Actionblock, Resultblock} from "./blocks";
-import editor from './assets/editor.svg';
-
-export const demo_Editor_description =
-    <>
-        <Title order={4}>
-            Rountangle Editor
-        </Title>
-        <Text>
-            Next to this text you can see three columns <strong>Rountangle Editor</strong>, <strong>Deltas</strong>,
-            and <strong>State Graph</strong>. In the Rountangle Editor, you can create (unnamed) rountangles by
-            right-clicking on the grey area (see also the small info in the upper right corner
-            of each canvas). You can move these rountangle around and resize them.
-        </Text>
-        <Text>
-            In this demo, the editable state graph of the "Primitive Delta"-demo is replaced by a Rountangle Editor.
-            Again, any action in the Rountangle Editor results in a set of deltas (shown at the center) that are applied to the (empty) state graph (on the right).
-            Note that this state graph is no longer (directly) editable. The current model state is visualized in the Rountangle Editor from which the user interaction originated.
-        </Text>
-        <Text>
-            In technical terms, the Rountangle Editor is a <em>projectional editor</em> for the state graph (on the right).
-        </Text>
-        <Image src={editor}/>
-        <Actionblock>
-            1. Create a rountangle by right-clicking the grey area in the left column!
-        </Actionblock>
-        <Resultblock>
-            In the center column, one composite delta (or L1 delta) "createRountangle" was created.
-        </Resultblock>
-        <Resultblock>
-            In the second tab "Deltas (L0)" of the center column, you can see the primitive deltas a "createRountangle" composite delta consists of.
-        </Resultblock>
-        <Resultblock>
-            The result of an application of all primitive deltas to an empty initial state is shown in the third column "State Graph".
-        </Resultblock>
-        <Resultblock>
-            The ID<sub>CS</sub> in each rountangle indicates the node ID of the state graph that is responsible for the rendering of this rountangle.
-        </Resultblock>
-        <Actionblock>
-            2. Move your created rountangle!
-        </Actionblock>
-        <Resultblock>
-            For each movement a new "moveRountangle" composite delta is created that depends on the previous one.
-        </Resultblock>
-        <Resultblock>
-            On the level of primitive deltas (L0), you can observe that only the "x" and "y" edges are updated (UPD).
-        </Resultblock>
-        <Resultblock>
-            On the State Graph, only the "x" and "y" edges are updated to point to other values.
-        </Resultblock>
-        <Actionblock>
-            3. Switch to the "History" tab in the "Deltas" column and trigger some "Undo" operations (below the Rountangle Editor)!
-        </Actionblock>
-        <Resultblock>
-            The currently active version has a bold border. All versions are kept.
-        </Resultblock>
-        <Resultblock>
-            In the "Delta (L1)" and "Delta (L0)" tabs, the deltas corresponding to the current version are also highlighted.
-        </Resultblock>
-        <Space h="5px"/>
-        <Text>
-            Feel free to play around with the editor and watch the changes in the other views.
-        </Text>
-    </>;
-
-export function getDemoEditor() {
-    const deltaRegistry = new DeltaRegistry();
-    const generateUUID = mockUuid();
-    const versionRegistry = new VersionRegistry();
-    const onion = newOnion({readonly: true, deltaRegistry, versionRegistry});
-
-    // returns functional react component
-    return function () {
-        const {state, reducer, components} = onion.useOnion(reducer => ({}));
-
-        const deltaTabs = ["deltaL1", "deltaL0", "merge"];
-
-        const undoButtonHelpText = "Use the Undo/Redo buttons or the History panel to navigate to any version.";
-
-        return (<div style={{minWidth: 1300}}>
-            <OnionContext.Provider value={{generateUUID, deltaRegistry}}>
-            <SimpleGrid cols={3}>
-                <div>
-                    <Group position="apart">
-                        <Title order={4}>Rountangle Editor</Title>
-                    </Group>
-                    <Space h="48px"/>
-                    <Stack>
-                        {components.rountangleEditor}
-                        <Center>
-                            {components.undoRedoButtons}
-                            <Space w="sm"/>
-                            <InfoHoverCard>
-                                {undoButtonHelpText}
-                            </InfoHoverCard>
-                        </Center>
-                    </Stack>
-                </div>
-                <div>
-                    <Group position="center">
-                        <Title order={4}>Deltas</Title>
-                    </Group>
-                    <Space h="sm"/>
-                    {components.makeTabs("deltaL1", deltaTabs)}
-                </div>
-                <div>
-                    <Title order={4}>Graph State</Title>
-                    <Space h="48px"/>
-                    {components.graphStateComponent}
-                </div>
-            </SimpleGrid>
-            </OnionContext.Provider>
-        </div>);
-    }
-}

+ 0 - 194
src/frontend/demos/demo_le.tsx

@@ -1,194 +0,0 @@
-import * as React from "react";
-import {SimpleGrid, Text, Title, Stack, Center, Group, Space, Image, Button, Paper, Alert, createStyles, Switch} from "@mantine/core";
-import {IconTrash, IconRowInsertTop, IconRowInsertBottom, IconAlertCircle} from '@tabler/icons';
-
-import {DeltaRegistry} from "onion/delta_registry";
-import {PrimitiveDelta, TargetNode, TargetValue} from "onion/delta";
-import {VersionRegistry} from "onion/version";
-import {mockUuid} from "../../util/test_helpers";
-import {PrimitiveValue} from "onion/types";
-import {INodeState, IValueState} from "onion/graph_state";
-
-import {newOnion} from "../versioned_model/single_model";
-import {InfoHoverCard} from "../info_hover_card";
-import {OnionContext} from "../onion_context";
-import {names} from "../../util/names";
-
-export const demo_LE_description =
-    <>
-        <Title order={4}>
-            List Editor
-        </Title>
-        <Text>
-            This demo was not mentioned in our (submitted) paper, and was added in response to a question from one of the reviewers. The reviewer wanted to know if we can support data structures other than objects diagrams with primitive values in the objects' slots.
-        </Text>
-        <Text>
-            This demo shows one particular way of encoding an ordered sequence as a graph structure (namely as a doubly-linked list). The list itself is represented by a single node in the graph state. The user can insert/remove items at any point in the sequence. Concurrent insertions can be merged, as long as the insertions are not at the same location. Deletions never give conflicts.
-        </Text>
-    </>;
-
-export function getDemoLE() {
-    // We manually create our own OnionContext, because we need access to it before we even create our React component.
-    const deltaRegistry = new DeltaRegistry();
-    const generateUUID = mockUuid();
-    const versionRegistry = new VersionRegistry();
-    const listNodeId = generateUUID();
-    const onion = newOnion({readonly: true, deltaRegistry, versionRegistry});
-
-    let nextVal = 0;
-
-    // returns functional react component
-    return function () {
-        const {state, reducer, components} = onion.useOnion(reducer => ({}));
-        const [pseudoDelete, setPseudoDelete] = React.useState<boolean>(true);
-
-        React.useEffect(() => {
-            // idempotent:
-            const listNodeCreation = deltaRegistry.newNodeCreation(listNodeId);
-            reducer.createAndGotoNewVersion([
-                listNodeCreation,
-                deltaRegistry.newEdgeUpdate(listNodeCreation.createOutgoingEdge("next"), listNodeCreation),
-                deltaRegistry.newEdgeUpdate(listNodeCreation.createOutgoingEdge("prev"), listNodeCreation),
-            ], "createList", versionRegistry.initialVersion);
-        }, []);
-
-        const listNode = onion.graphState.nodes.get(listNodeId) as INodeState;
-
-        // Inserts list item after prevNode and before nextNode.
-        function insertBetween(prevNode: INodeState, nextNode: INodeState, val: PrimitiveValue) {
-            if (prevNode.outgoing.get("next") !== nextNode 
-             || nextNode.outgoing.get("prev") !== prevNode) {
-                throw new Error("Assertion failed");
-            }
-
-            onion.graphState.pushState(); // we will go back to this checkpoint
-
-            const newItemCreation = deltaRegistry.newNodeCreation(generateUUID());
-            onion.graphState.exec(newItemCreation);
-            const updateNext = prevNode.getDeltaForSetEdge(deltaRegistry, "next", newItemCreation);
-            onion.graphState.exec(updateNext);
-            const updatePrev = nextNode.getDeltaForSetEdge(deltaRegistry, "prev", newItemCreation);
-            onion.graphState.exec(updatePrev);
-
-            onion.graphState.exec(deltaRegistry.newEdgeUpdate(newItemCreation.createOutgoingEdge("value"), val));
-            onion.graphState.exec(deltaRegistry.newEdgeUpdate(newItemCreation.createOutgoingEdge("prev"), prevNode.creation));
-            onion.graphState.exec(deltaRegistry.newEdgeUpdate(newItemCreation.createOutgoingEdge("next"), nextNode.creation));
-
-            const deltas = onion.graphState.popState();
-
-            reducer.createAndGotoNewVersion(deltas as PrimitiveDelta[], "insert"+JSON.stringify(val));
-        }
-
-        function insertBefore(node: INodeState, val: PrimitiveValue) {
-            const prevNode = node.outgoing.get("prev") as INodeState;
-            return insertBetween(prevNode, node, val);
-        }
-
-        function insertAfter(node: INodeState, val: PrimitiveValue) {
-            const nextNode = node.outgoing.get("next") as INodeState;
-            return insertBetween(node, nextNode, val);
-        }
-
-        function deleteItem(node: INodeState) {
-            const prevNode = node.outgoing.get("prev") as INodeState;
-            const nextNode = node.outgoing.get("next") as INodeState;
-
-            onion.graphState.pushState(); // we will go back to this checkpoint
-
-            onion.graphState.exec(prevNode.getDeltaForSetEdge(deltaRegistry, "next", nextNode.creation));
-            onion.graphState.exec(nextNode.getDeltaForSetEdge(deltaRegistry, "prev", prevNode.creation));
-            node.getDeltasForDelete(deltaRegistry).forEach(d => onion.graphState.exec(d));
-
-            const deltas = onion.graphState.popState();
-
-            reducer.createAndGotoNewVersion(deltas as PrimitiveDelta[], "delete"+JSON.stringify((node.outgoing.get("value") as IValueState).value));
-        }
-
-        // Alternative implementation of deletion - just add a property 'deleted' to the deleted node.
-        function deleteItemAlt(node: INodeState) {
-            const delta = node.getDeltaForSetEdge(deltaRegistry, "deleted", true);
-
-            reducer.createAndGotoNewVersion([delta], "delete"+JSON.stringify((node.outgoing.get("value") as IValueState).value));
-        }
-
-        const undoButtonHelpText = "Use the Undo/Redo buttons or the History panel to navigate to any version.";
-
-        // Recursively renders list elements as a vertical stack
-        function renderList(head: INodeState, stopAt: INodeState) {
-            const nextItem = head.outgoing.get("next") as INodeState;
-            const nextDOM = nextItem === stopAt ? <></> : renderList(nextItem, stopAt);
-            if (head.outgoing.get("deleted")?.asTarget() === true) {
-                return nextDOM; // skip deleted items
-            }
-            const value = head.outgoing.get("value");
-            const itemText = value === undefined ? "Start of list" : "List item: " + JSON.stringify((value as IValueState).value);
-            return <>
-                <Paper shadow="sm" radius="md" p="md" withBorder>
-                    <Group>
-                        {itemText}
-                        <Button color="green" onClick={() => insertBefore(head, names[nextVal++])} compact leftIcon={<IconRowInsertBottom/>}>Insert before</Button>
-                        <Button color="green" onClick={() => insertAfter(head, names[nextVal++])} compact leftIcon={<IconRowInsertTop/>}>Insert after</Button>
-                        {value === undefined ? <></> : <Button color="red" compact onClick={() => pseudoDelete ? deleteItemAlt(head) : deleteItem(head)} leftIcon={<IconTrash />}>Delete</Button>}
-                    </Group>
-                </Paper>
-                {nextDOM}
-            </>;
-        }
-
-        return <div style={{minWidth: 1300}}>
-            <OnionContext.Provider value={{generateUUID, deltaRegistry}}>
-                <SimpleGrid cols={3}>
-                    <div>
-                        <Stack>
-                            <Title order={5}>List Editor</Title>
-                            <Group>
-                                <Switch label="Enable Pseudo-Delete"
-                                    labelPosition="right"
-                                    checked={pseudoDelete}
-                                    onChange={(event) => setPseudoDelete(event.currentTarget.checked)}
-                                  />
-                                <InfoHoverCard>
-                                    <Text>
-                                        <p>Deletions of list items can be performed in two ways: Pseudo-deletion and real deletion. With pseudo-delete, deleted list items are kept in the doubly-linked list, and are only marked "deleted". "Real" deletions on the other hand, alter the "prev" and "next" edges of the neighboring nodes of deleted items.</p>
-                                        <p>Pseudo-deletions are never conflicting with any other operations, which may be desired, depending on the context. Real deletions can give conflicts with both insertions and other deletions.</p>
-                                    </Text>
-                                </InfoHoverCard>
-                            </Group>
-                            {
-                            listNode === undefined ?
-                                <Alert icon={<IconAlertCircle/>} title="There exists no list!" radius="md">
-                                    Did you perhaps <b>undo</b> the list creation?
-                                </Alert>
-                            :
-                                <Stack>
-                                    {renderList(listNode, listNode)}
-                                </Stack>
-                            }
-                        </Stack>
-                    </div>
-                    <div>
-                        <Stack>
-                            <Title order={5}>History</Title>
-                            <Center>
-                                {components.undoRedoButtons}
-                                <Space w="sm"/>
-                                <InfoHoverCard>
-                                    {undoButtonHelpText}
-                                </InfoHoverCard>
-                            </Center>
-                            {components.historyComponentWithMerge}
-                            <Title order={5}>Deltas</Title>
-                            {components.makeTabs("deltaL1", ["deltaL1", "deltaL0"])}
-                        </Stack>
-                    </div>
-                    <div>
-                        <Stack>
-                            <Title order={5}>Graph State (read-only)</Title>
-                            {components.graphStateComponent}
-                        </Stack>
-                    </div>
-                </SimpleGrid>
-            </OnionContext.Provider>
-        </div>;
-    }
-}

+ 0 - 603
src/frontend/demos/demo_live.tsx

@@ -1,603 +0,0 @@
-import * as React from 'react';
-import * as Icons from '@tabler/icons';
-import {Button, Divider, Group, SimpleGrid, Text, Title, TextInput, Select, Stack, Paper, Alert, Center, MantineProvider, Tabs} from '@mantine/core';
-import { modals, ModalsProvider } from '@mantine/modals';
-
-import {GraphvizComponent} from "../graphviz";
-
-import {newOnion} from '../versioned_model/single_model';
-import {InfoHoverCard, InfoHoverCardOverlay} from "../info_hover_card";
-import {OnionContext} from "../onion_context";
-
-import {mockUuid} from "../../util/test_helpers";
-import {NodeDeletion, EdgeUpdate} from "onion/delta";
-import {DeltaRegistry} from "onion/delta_registry";
-import {VersionRegistry} from "onion/version";
-import {Delta} from "onion/delta";
-import {INodeState, IValueState} from "onion/graph_state";
-import {Version} from "onion/version";
-import {GraphState} from "onion/graph_state"; 
-
-export const demo_Live_description = <>
-  <Title order={4}>
-    Live Modeling
-  </Title>
-  <Text>This demo allows you to edit a Finite State Automaton (FSA) via a projectional editor, and to execute it via an interpreter.
-  </Text>
-  <Text>The model can be edited during its execution (called <em>live modeling</em> in literature). Live modeling can be done collaboratively as well. This works because the abstract syntax and the execution state are just versioned graphs.
-  </Text>
-</>;
-
-const getStateName = (s: INodeState) => (s.outgoing.get("name") as IValueState).value as string;
-
-export function getDemoLive() {
-  const deltaRegistry = new DeltaRegistry();
-  const generateUUID = mockUuid();
-  const versionRegistry = new VersionRegistry();
-  const onion = newOnion({readonly: true, deltaRegistry, versionRegistry});
-
-  // To efficiently create a delta in the context of the design model only
-  const designModelGraphState = new GraphState();
-
-  const modelId = generateUUID();
-
-  return function DemoSem() {
-
-    const {state, reducer, components} = onion.useOnion(reducer => ({
-      onUndoClicked: (parentVersion, deltaToUndo) => {
-        gotoMatchingDesignVersion(parentVersion);
-        reducer.undo(parentVersion, deltaToUndo);
-      },
-      onRedoClicked: (childVersion, deltaToRedo) => {
-        gotoMatchingDesignVersion(childVersion);
-        reducer.redo(childVersion, deltaToRedo);
-      },
-      onVersionClicked: (version: Version) => {
-        gotoMatchingDesignVersion(version);
-        reducer.gotoVersion(version);
-      },
-    }));
-
-    const gotoMatchingDesignVersion = (rtVersion: Version) => {
-      const currentDesignVersion = state.version.getEmbedding("design").version;
-      const newDesignVersion = rtVersion.getEmbedding("design").version;
-      const path = currentDesignVersion.findPathTo(newDesignVersion)!;
-      for (const [linkType, delta] of path) {
-        if (linkType === 'p') {
-          designModelGraphState.unexec(delta);
-        }
-        else if (linkType === 'c') {
-          designModelGraphState.exec(delta);
-        }
-      }
-    }
-
-    // We start out with the 'createList' delta already having occurred:
-    React.useEffect(() => {
-        // idempotent:
-        const modelCreation = deltaRegistry.newNodeCreation(modelId);
-        const newVersion = reducer.createAndGotoNewVersion([
-            modelCreation,
-            deltaRegistry.newEdgeUpdate(modelCreation.createOutgoingEdge("type"), "DesignModel"),
-          ], "createDesignModel", versionRegistry.initialVersion,
-          newDesignVersion => new Map([
-            ["design", {version: newDesignVersion, overridings: new Map()}],
-          ]));
-        gotoMatchingDesignVersion(newVersion);
-    }, []);
-
-    type Transition = [string, string, string, INodeState];
-
-    const getDesignModelStuff = (graphState: GraphState) => {
-      const states: Array<[string, INodeState]> = [];
-      const transitions: Array<Transition> = [];
-      const events: Array<[string,Array<Transition>]> = [];
-      let modelNode: INodeState|null = null;
-      let initial: INodeState|undefined;      
-      let initialStateName : string|null = null;
-
-      for (const nodeState of iterNonDeletedNodes(graphState)) {
-        const nodeType = getProperty<string>(nodeState, "type")!;
-        ({
-          "State": () => {
-            states.push([getStateName(nodeState), nodeState]);
-          },
-          "Transition": () => {
-            const event = getProperty<string>(nodeState, "event")!;
-            const transition: Transition = [
-              getProperty<string>(getLink(nodeState, "src")!, "name")!,
-              getProperty<string>(getLink(nodeState, "tgt")!, "name")!,
-              event,
-              nodeState,
-            ];
-            transitions.push(transition);
-            const existingEvent = events.find(([e, transition]) => e === event);
-            if (existingEvent === undefined) {
-              events.push([event, [transition]]);
-            }
-            else {
-              existingEvent[1].push(transition);
-            }
-          },
-          "DesignModel": () => {
-            modelNode = nodeState;
-            initial = getLink(nodeState, "initial");
-            initialStateName = initial ? getStateName(initial) : null;
-          },
-        }[nodeType])?.();
-      }
-
-      const result: {
-        states: [string,INodeState][],
-        transitions: [string,string,string,INodeState][],
-        modelNode: INodeState|null,
-        initial: INodeState|undefined,
-        initialStateName: string|null,
-        events: Array<[string,Array<Transition>]>,
-      } = {
-        states,
-        transitions,
-        modelNode,
-        initial,
-        initialStateName,
-        events,
-      };
-      return result;
-    }
-
-    const getRuntimeModelStuff = (graphState: GraphState) => {
-      const designModelStuff = getDesignModelStuff(graphState);
-      let runtimeModelNode: INodeState|null = null;
-      let currentStateName : string|null = null;
-      let current;
-
-      for (const nodeState of iterNonDeletedNodes(graphState)) {
-        const nodeType = getProperty<string>(nodeState, "type")!;
-        ({
-          RuntimeModel: () => {
-            runtimeModelNode = nodeState;
-            current = getLink(nodeState, "current");
-            currentStateName = current ? getStateName(current) : null;
-          },
-        }[nodeType])?.();
-      }
-
-      const dotGraph = `digraph {
-        bgcolor="transparent";
-        rankdir="LR";
-        ${designModelStuff.initial !== undefined ? "I [shape=point, width=0.1]; I -> S_"+designModelStuff.initialStateName:""}
-        ${designModelStuff.states.map(([name])=>'S_'+name+`[label=${name}, fillcolor=white, style=filled]`
-          + (name === currentStateName ? `[penwidth=4.0, shape=circle]`:`[shape=circle]`)
-        ).join(' ')}
-        ${designModelStuff.transitions.map(([src,tgt,label])=> 'S_'+src + ' -> ' + 'S_'+tgt + ' [label='+label+']').join('\n')}
-      }`;
-
-      const result: {
-        runtimeModelNode: INodeState|null,
-        current: INodeState|null,
-        currentStateName: string|null,
-        dotGraph: string
-      } = {
-        runtimeModelNode,
-        current,
-        currentStateName,
-        dotGraph};
-      return Object.assign(result, designModelStuff);
-    };
-
-    // Whenever the current version changes, we calculate a bunch of values that are needed in the UI and in its callbacks
-    const runtimeStuff = React.useMemo(() => getRuntimeModelStuff(onion.graphState),
-      [state.version]); // memoize the values for each version
-
-    const designStuff = React.useMemo(() => getDesignModelStuff(designModelGraphState),
-      [state.version.getEmbedding("design").version]); // memoize the values for each version
-
-    // If the current (runtime) version, or any of its ancestors overrides some delta, we know that there was a conflict between a delta of the runtime version and its embedded design version, meaning that the current execution trace cannot be replayed.
-    const notReplayable = (() => {
-      let v = state.version;
-      while (v !== undefined) {
-        const designEmbedding = v.embeddings.get("design");
-        if (designEmbedding && designEmbedding.overridings.size > 0) {
-          return true;
-        }
-        v = v.parents[0]?.[0];
-      }
-      return false;
-    })();
-
-    const [addStateName, setAddStateName] = React.useState<string>("A");
-    const [addTransitionSrc, setAddTransitionSrc] = React.useState<string|null>(null);
-    const [addTransitionTgt, setAddTransitionTgt] = React.useState<string|null>(null);
-    const [addTransitionEvent, setAddTransitionEvent] = React.useState<string>("e");
-
-    const execTransaction = (graphState, modelNode, callback) => {
-      graphState.pushState();
-      const {compositeLabel} = callback(graphState, modelNode);
-      const deltas = graphState.popState();
-      return {compositeLabel, deltas};
-    }
-
-    const editDesignModel = callback => {
-      if (runtimeStuff.modelNode !== null) {
-        const currentDesignVersion = state.version.getEmbedding("design").version;
-
-        // perform edit on run-time model:
-        const {compositeLabel, deltas: runtimeDeltas} = execTransaction(onion.graphState, runtimeStuff.modelNode, callback);
-
-        // see if the same delta can be applied also on the design model:
-        // TODO: this try-catch is waaaayyyy too implicit and error-prone - need to check explicitly whether there are missing dependencies or not.
-        let newDesignVersion;
-        let designDeltas;
-        try {
-          newDesignVersion = reducer.createVersion(runtimeDeltas, "design:"+compositeLabel, currentDesignVersion.hash,
-            newDesignVersion => new Map([
-              ["design", {version: newDesignVersion, overridings: new Map()}],
-            ]))!; // will throw if any of runtimeDeltas depends on a delta that is not in currentDesignVersion.
-          designDeltas = runtimeDeltas;
-        }
-        catch (e) {
-          // alert("could not apply this exact same delta on the design model - probably there's a missing dependency:\n" + e.toString() + "\nTherefore, I will attempt to create a new delta with the same /intent/ on the design model.");
-
-          const {compositeLabel, deltas} = execTransaction(designModelGraphState, designStuff.modelNode, callback);
-          designDeltas = deltas;
-          newDesignVersion = reducer.createVersion(designDeltas, "design':"+compositeLabel, currentDesignVersion.hash,
-            newDesignVersion => new Map([
-              ["design", {version: newDesignVersion, overridings: new Map()}],
-            ]))!; // won't throw this time
-        }
-
-        const overridings: Map<Delta,Delta> = new Map(designDeltas
-          .map(designDelta => runtimeDeltas.includes(designDelta) ? null // no override - runtimeDeltas includes the exact same delta
-            // otherwise, lookup override (which will probably have some additional dependencies on RT deltas)
-            : [designDelta, runtimeDeltas.find(runtimeDelta => {
-                if (designDelta instanceof NodeDeletion) {
-                  return runtimeDelta instanceof NodeDeletion && runtimeDelta.node === designDelta.node;
-                }
-                else if (designDelta instanceof EdgeUpdate) {
-                  return runtimeDelta instanceof EdgeUpdate && runtimeDelta.overwrites === runtimeDelta.overwrites;
-                }
-                throw new Error(`I don't know yet how to override this delta: ${runtimeDelta.constructor.name}: ${runtimeDelta.description}`);
-              })!])
-          .filter(x => x!==null)
-        );
-        const newRtVersion = reducer.createAndGotoNewVersion(runtimeDeltas, "design:"+compositeLabel,
-          state.version, // parent version
-          // embeddings:
-          newRtVersion => newRtVersion === newDesignVersion ? 
-            new Map([
-              ["design", {version: newDesignVersion, overridings}],
-            ])
-            : new Map([
-              ["design", {version: newDesignVersion, overridings}],
-              ["runtime", {version: newRtVersion, overridings: new Map()}],
-            ]));
-
-        gotoMatchingDesignVersion(newRtVersion);
-      }
-    }
-    const editRuntimeModel = (callback, gotoNewVersion = true) => {
-      const currentDesignVersion = state.version.getEmbedding("design").version;
-
-      // perform edit on run-time model:
-      const {compositeLabel, deltas} = execTransaction(onion.graphState, runtimeStuff.modelNode, callback);
-      const newRtVersion = reducer.createVersion(deltas, "runtime:"+compositeLabel,
-        state.version.hash, // parent version
-          newRtVersion => new Map([
-          ["design", {version: currentDesignVersion, overridings: new Map()}],
-          ["runtime", {version: newRtVersion, overridings: new Map()}],
-        ]));
-      if (gotoNewVersion) {
-        reducer.gotoVersion(newRtVersion!);
-      }
-    }
-
-
-    function* iterNonDeletedNodes(graphState: GraphState) {
-      for (const nodeState of graphState.nodes.values()) {
-        if (!nodeState.isDeleted) {
-          yield nodeState;
-        }
-      }
-    }
-    function* iterType(graphState: GraphState, type: string) {
-      for (const nodeState of iterNonDeletedNodes(graphState)) {
-        const nodeType = (nodeState.outgoing.get("type") as IValueState)?.value as string;
-        if (nodeType === type) {
-          yield nodeState;
-        }
-      }
-    }
-    function getProperty<T>(nodeState: INodeState, property: string): (T|undefined) {
-      return (nodeState.outgoing.get(property) as IValueState)?.value as T;
-    }
-    function getLink(nodeState: INodeState, linkName: string): (INodeState|undefined) {
-      return (nodeState.outgoing.get(linkName) as INodeState);
-    }
-    const findState = (graphState: GraphState, stateName: string) => {
-      for (const nodeState of iterType(graphState, "State")) {
-        if (getProperty<string>(nodeState, "name") === stateName) {
-          return nodeState;
-        }
-      }
-    }
-    const findTransition = (graphState: GraphState, srcName: string, tgtName: string, event: string) => {
-      for (const nodeState of iterType(graphState, "Transition")) {
-        const src = getLink(nodeState, "src");
-        const tgt = getLink(nodeState, "tgt");
-        const event = getProperty(nodeState, "event");
-        if (src && tgt) {
-          if (getProperty(src, "name") === srcName && getProperty(tgt, "name") && event === event) {
-            return nodeState
-          }
-        }
-      }
-    }
-
-    function onAddState() {
-      editDesignModel((graphState, modelNode) => {
-        const nodeId = generateUUID();
-        const nodeCreation = deltaRegistry.newNodeCreation(nodeId);
-        graphState.exec(nodeCreation);
-        graphState.exec(deltaRegistry.newEdgeUpdate(nodeCreation.createOutgoingEdge("type"), "State"));
-        graphState.exec(deltaRegistry.newEdgeUpdate(nodeCreation.createOutgoingEdge("name"), addStateName));
-
-        graphState.exec(modelNode.getDeltaForSetEdge(deltaRegistry, "contains-"+JSON.stringify(nodeId), nodeCreation));
-
-        // Bonus feature: Auto-increment state names
-        if (addStateName.match(/[A-Z]/i)) {
-          setAddStateName(String.fromCharCode(addStateName.charCodeAt(0) + 1));
-        }
-
-        return {compositeLabel: "addState:"+addStateName};
-      });
-    }
-    function onAddTransition() {
-      editDesignModel((graphState, modelNode) => {
-        const nodeId = generateUUID();
-        const nodeCreation = deltaRegistry.newNodeCreation(nodeId);
-        graphState.exec(nodeCreation);
-        graphState.exec(deltaRegistry.newEdgeUpdate(nodeCreation.createOutgoingEdge("type"), "Transition"));
-        graphState.exec(deltaRegistry.newEdgeUpdate(nodeCreation.createOutgoingEdge("event"), addTransitionEvent));
-        graphState.exec(deltaRegistry.newEdgeUpdate(nodeCreation.createOutgoingEdge("src"), findState(graphState, addTransitionSrc!)!.creation));
-        graphState.exec(deltaRegistry.newEdgeUpdate(nodeCreation.createOutgoingEdge("tgt"), findState(graphState, addTransitionTgt!)!.creation));
-
-        graphState.exec(modelNode.getDeltaForSetEdge(deltaRegistry, "contains-"+JSON.stringify(nodeId), nodeCreation));
-
-        return {compositeLabel: "addTransition:"+addTransitionSrc+"--"+addTransitionEvent+"->"+addTransitionTgt};
-      });
-    }
-    function onDeleteState(name: string) {
-      editDesignModel((graphState) => {
-        const nodeState = findState(graphState, name)!;
-        while (true) {
-          // Delete all incoming and outgoing transitions:
-          const found = nodeState.getIncomingEdges().find(([label]) => label === "src" || label === "tgt");
-          if (found === undefined) break;
-          const [_, from] = found;
-          const deltas = from.getDeltasForDelete(deltaRegistry);
-          deltas.forEach(d => graphState.exec(d));
-        }
-        nodeState.getDeltasForDelete(deltaRegistry).forEach(d => {
-          graphState.exec(d);
-        });
-        return {compositeLabel: "deleteState:"+name};
-      });
-    }
-    function onDeleteTransition(src: string, event: string, tgt: string) {
-      editDesignModel((graphState) => {
-        const nodeState = findTransition(graphState, src, event, tgt)!;
-        nodeState.getDeltasForDelete(deltaRegistry).forEach(d => {
-          graphState.exec(d);
-        });
-        return {compositeLabel: "deleteTransition:"+transitionKey([src, tgt, event])};
-      });
-    }
-    function onInitialStateChange(value) {
-      editDesignModel((graphState, modelNode) => {
-        graphState.exec(modelNode.getDeltaForSetEdge(deltaRegistry, "initial", findState(graphState, value)?.creation || null));
-        return {compositeLabel: "setInitial:"+value};
-      });
-    }
-
-    function onInitialize() {
-      if (runtimeStuff.modelNode !== null && runtimeStuff.initial !== null) {
-        editRuntimeModel((graphState) => {
-          const runtimeId = generateUUID();
-          const nodeCreation = deltaRegistry.newNodeCreation(runtimeId);
-          graphState.exec(nodeCreation);
-          graphState.exec(deltaRegistry.newEdgeUpdate(nodeCreation.createOutgoingEdge("type"), "RuntimeModel"));
-          graphState.exec(deltaRegistry.newEdgeUpdate(nodeCreation.createOutgoingEdge("design"), runtimeStuff.modelNode!.creation));
-          // set the 'current' pointer - read dependency on 'initial'.
-          graphState.exec(deltaRegistry.newEdgeUpdate(nodeCreation.createOutgoingEdge("current"), runtimeStuff.initial!.creation, [(runtimeStuff.modelNode!.outgoingDeltas.get("initial")!.read())]));
-          return {compositeLabel: "initialize"};
-        });
-      }
-    }
-    function onAbort() {
-      if (runtimeStuff.runtimeModelNode !== null) {
-        editRuntimeModel((graphState) => {
-          runtimeStuff.runtimeModelNode!.getDeltasForDelete(deltaRegistry).forEach(d => graphState.exec(d));
-          return {compositeLabel: "abort"};
-        });
-      }
-    }
-    function onCurrentStateChange(value) {
-      if (runtimeStuff.runtimeModelNode !== null) {
-        editRuntimeModel((graphState) => {
-          graphState.exec(runtimeStuff.runtimeModelNode!.getDeltaForSetEdge(deltaRegistry, "current", findState(graphState, value)?.creation || null, []));
-          return {compositeLabel: "setCurrent:"+value}
-        });
-      }
-    }
-    function onExecuteStep([srcName, tgtName, label, transition]: Transition, gotoNewVersion = false) {
-      if (runtimeStuff.runtimeModelNode !== null) {
-        editRuntimeModel((graphState) => {
-          graphState.exec(
-            runtimeStuff.runtimeModelNode!.getDeltaForSetEdge(deltaRegistry,
-              "current", // label
-              findState(graphState, tgtName)?.creation || null, // target
-              // read dependency on src,tgt,label of transition:
-              ["src", "tgt", "event"].map(label => transition.outgoingDeltas.get(label)!.read()),
-            )
-          );
-          return {compositeLabel: "executeStep:"+transitionKey([srcName, tgtName, label])};
-        }, gotoNewVersion);
-      }
-    }
-    function onExecuteNonDeterministicStep(transitions: Transition[]) {
-      if (runtimeStuff.runtimeModelNode !== null) {
-        const deterministic = transitions.length <= 1;
-        if (!deterministic) {
-          modals.open({
-            title: (<b>Non-determinism!</b>),
-            children: (
-              <>
-                <Text>Multiple futures exist.</Text>
-                <Text>Use <b>Redo</b> or <b>History</b> to select your desired future.</Text>
-                <Button fullWidth onClick={modals.closeAll} mt="md">
-                  OK
-                </Button>
-              </>
-            ),
-          });
-        }
-        for (const transition of transitions) {
-          onExecuteStep(transition, deterministic /* only go to next version if only one future exists */);
-        }
-      }
-    }
-
-    const transitionKey = ([srcName, tgtName, label]) =>
-      srcName+'--('+label+')-->'+tgtName;
-
-    return <>
-      <MantineProvider>
-      <ModalsProvider>
-      <OnionContext.Provider value={{generateUUID, deltaRegistry}}>
-        <SimpleGrid cols={2}>
-          <Stack>
-            <Group grow>
-              {components.undoRedoButtons}
-            </Group>
-            <Divider label="(Projectional) Model Editor" labelPosition="centered" />
-            {
-            runtimeStuff.modelNode === null ?
-              <Alert icon={<Icons.IconAlertCircle/>} title="No design model!" color="blue">
-                Did you undo its creation?
-              </Alert>
-              :<></>
-            }
-            <Group grow>
-            <Paper shadow="xs" p="xs" withBorder>
-            <Group grow>
-              <TextInput value={addStateName} onChange={e => setAddStateName(e.currentTarget.value)} label="State Name" withAsterisk/>
-              <Button onClick={onAddState} disabled={addStateName===""||runtimeStuff.modelNode===null||runtimeStuff.states.some(([name]) => name ===addStateName)} leftIcon={<Icons.IconPlus/>} color="green">State</Button>
-            </Group>
-            </Paper>
-              <Select disabled={runtimeStuff.modelNode===null} searchable clearable
-                label="Initial State"
-                data={runtimeStuff.states.map(([stateName]) => ({value:stateName, label:stateName}))}
-                value={runtimeStuff.initialStateName}
-                onChange={onInitialStateChange}
-              />
-            </Group>
-            <div style={{minHeight: 26}}>
-              <Group>{
-                runtimeStuff.states.map(([stateName]) => {
-                  return <Button compact color="red" key={stateName} leftIcon={<Icons.IconX/>} onClick={() => onDeleteState(stateName)}>State {stateName}</Button>
-                })
-              }</Group>
-            </div>
-            <Paper shadow="xs" p="xs" withBorder>
-            <Group grow>
-              <Select searchable withAsterisk clearable label="Source" data={runtimeStuff.states.map(([stateName]) => ({value:stateName, label:stateName}))} value={addTransitionSrc} onChange={setAddTransitionSrc}/>
-              <Select searchable withAsterisk clearable label="Target" data={runtimeStuff.states.map(([stateName]) => ({value:stateName, label:stateName}))} value={addTransitionTgt} onChange={setAddTransitionTgt}/>
-              <TextInput value={addTransitionEvent} onChange={e => setAddTransitionEvent(e.currentTarget.value)}  label="Event" />
-              <Button disabled={addTransitionSrc === null || addTransitionTgt === null} onClick={onAddTransition} leftIcon={<Icons.IconPlus/>} color="green">Transition</Button>
-            </Group>
-            </Paper>
-            <div style={{minHeight: 26}}>
-              <Group>{
-                runtimeStuff.transitions.map(([srcName, tgtName, label, tNodeState]) => {
-                  const key = srcName+'--('+label+')-->'+tgtName;
-                  return <Button compact color="red" key={key} leftIcon={<Icons.IconX/>} onClick={() => onDeleteTransition(srcName, label, tgtName)}>Transition {key}</Button>
-                })
-              }</Group>
-            </div>
-
-            <Divider label="Execution" labelPosition="centered" />
-
-            {notReplayable ?
-              <Alert variant="light" color="orange" title="" icon={<Icons.IconAlertTriangle/>}>
-                Current trace cannot be replayed on current design model.
-              </Alert>
-              : <></>
-            }
-
-            <Group grow>
-              <Button disabled={runtimeStuff.initialStateName === null || runtimeStuff.runtimeModelNode !== null} onClick={onInitialize} leftIcon={<Icons.IconPlayerPlay/>}>Init</Button>
-              <Button color="red" disabled={runtimeStuff.runtimeModelNode === null} onClick={onAbort} leftIcon={<Icons.IconPlayerStop/>}>Abort</Button>
-              <Select disabled={runtimeStuff.currentStateName === null || runtimeStuff.runtimeModelNode === null} data={runtimeStuff.transitions.filter(([srcName]) => srcName === runtimeStuff.currentStateName).map(t => {
-                  // @ts-ignore:
-                  const key = transitionKey(t);
-                  return {
-                    value: key,
-                    label: key,
-                  }
-              })} label="Execute Step" onChange={key => {
-                const t = runtimeStuff.transitions.find(t =>
-                  // @ts-ignore:
-                  transitionKey(t) == key);
-                onExecuteStep(t!);
-              }}/>
-              <Select disabled={runtimeStuff.runtimeModelNode === null} searchable clearable label={<Group>Current State <InfoHoverCard>
-                <Text>Current state updates when executing an execution step, but can also be overridden at any point in time ("god event").</Text>
-              </InfoHoverCard></Group>} data={runtimeStuff.states.map(([stateName]) => ({value:stateName, label:stateName}))} value={runtimeStuff.currentStateName} onChange={onCurrentStateChange}/>
-            </Group>
-
-            <div style={{minHeight: 26}}>
-              <Group>{
-                designStuff.events.map(([event, transitions]) => {
-                  const enabledTransitions = transitions.filter(([sourceName]) => runtimeStuff.currentStateName === sourceName);
-                  return <Button compact disabled={runtimeStuff.runtimeModelNode === null || enabledTransitions.length === 0} color="orange" key={event} leftIcon={<Icons.IconActivity/>} onClick={() => onExecuteNonDeterministicStep(enabledTransitions)}>{event}</Button>
-                })
-              }</Group>
-            </div>
-
-            <Tabs defaultValue="cs" keepMounted={false}>
-              <Tabs.List grow>
-                <Tabs.Tab value="cs">Concrete Syntax (projectional)</Tabs.Tab>
-                <Tabs.Tab value="as">Abstract Syntax</Tabs.Tab>
-              </Tabs.List>
-              <Tabs.Panel value="cs">
-                <InfoHoverCardOverlay contents={<>
-                    <Text>Simple auto-generated concrete syntax, from abstract syntax.</Text>
-                    <Text>Powered by GraphViz and WebAssembly.</Text>
-                  </>}>
-                  <div style={{backgroundColor:"#eee"}}>{
-                    runtimeStuff.states.length > 0 ?
-                    <GraphvizComponent dot={runtimeStuff.dotGraph} />
-                    : <Center position="center" style={{padding:20}}>
-                        <Text>FSA will appear here.</Text>
-                      </Center>
-                  }</div>
-                </InfoHoverCardOverlay>
-              </Tabs.Panel>
-              <Tabs.Panel value="as">
-                {components.graphStateComponent}
-              </Tabs.Panel>
-            </Tabs>
-          </Stack>
-          <Stack>
-            {components.makeTabs("deltaL1", ["state", "merge", "historyGraphviz", "deltaL1", "deltaL0"])}
-            {components.makeTabs("merge", ["state", "merge", "historyGraphviz", "deltaL1", "deltaL1Graphviz", "deltaL0", "deltaL0Graphviz"])}
-            Run-time model version: {state.version.hash.toString('hex').substring(0,8)}<br/>
-            DesignModel version: {state.version.getEmbedding("design").version.hash.toString('hex').substring(0,8)}
-          </Stack>
-        </SimpleGrid>
-      </OnionContext.Provider>
-      </ModalsProvider>
-      </MantineProvider>
-    </>;
-  }
-}

+ 0 - 130
src/frontend/demos/demo_pd.tsx

@@ -1,130 +0,0 @@
-import * as React from 'react';
-import * as Icons from '@tabler/icons';
-import {Button, Divider, Group, Image, SimpleGrid, Space, Text, Title, Stack} from '@mantine/core';
-
-import {OnionContext} from "../onion_context";
-import {DeltaRegistry} from "onion/delta_registry";
-import {VersionRegistry} from "onion/version";
-import {PrimitiveValue} from 'onion/types';
-import {mockUuid} from '../../util/test_helpers';
-import {Actionblock, Resultblock} from './blocks';
-import pdImage from './assets/pd.svg';
-import {newOnion, undoButtonHelpText, VersionedModelState} from '../versioned_model/single_model';
-import {InfoHoverCard} from '../info_hover_card';
-
-export const demo_PD_description =
-    <>
-        <Title order={4}>
-            Primitive Deltas
-        </Title>
-        { /*
-            TODO: elaborate more on the structure of the state graph.
-        */ }
-        <Text>
-            On the right side, you see three canvases "State", "History", and "Deltas(L0)".
-        </Text>
-        <Text>
-            "State" shows the state graph, which represents the state of our model. If you click with the right
-            mouse button on a free area in the canvas, this action is converted into a "Delta". The set of one or many Deltas
-            is then in turn applied to the state graph, resulting in the visualization of a new node. The following image visualizes this.
-        </Text>
-        <Image src={pdImage}/>
-        <Actionblock>
-            1. Click with your right mouse button into the "State" canvas!
-        </Actionblock>
-        <Resultblock>
-            A "NEW(0)"-delta occurs in the "Delta(L0)" canvas.
-        </Resultblock>
-        <Resultblock>
-            The "0" corresponds to the ID of the created node.
-        </Resultblock>
-        <Resultblock>
-            A new version appears in the "History" canvas that depends on the previous (initial version).
-        </Resultblock>
-        <Actionblock>
-            2. Hover with your mouse over the circle with an (i) inside the "State" canvas!
-        </Actionblock>
-        <Resultblock>
-            A tooltip with information about possible interactions with this canvas is shown.
-        </Resultblock>
-        <Actionblock>
-            3. Create an edge to a value by right-dragging from the created node into the canvas!
-        </Actionblock>
-        <Resultblock>
-            The generated delta "EDG(..)" depends on the creation (NEW(0)) of the node from which the edge originates.
-        </Resultblock>
-        <Space h="10px"/>
-        <Text>This detailed recording of each action allows to easily detect conflicts between two versions or to merge
-            conflict-free versions.</Text>
-        <Space h="10px"/>
-        <Actionblock>
-            4. Press the button "Undo" once!
-        </Actionblock>
-        <Resultblock>
-            The version before creating the edge is selected in the "History" canvas.
-        </Resultblock>
-        <Resultblock>
-            In the "Delta"-view, the set of Deltas that are associated with the currently selected version are drawn with
-            a bold border.
-        </Resultblock>
-        <Actionblock>
-            5. Press the "Clone"-Button!
-        </Actionblock>
-        <Actionblock>
-            6. Delete the node in the cloned branch by a click with the middle mouse button!
-        </Actionblock>
-        <Actionblock>
-            7. Press the "Redo"-Button in the "master"-branch!
-        </Actionblock>
-        <Actionblock>
-            8. Press the "Union"-Button in the lower branch!
-        </Actionblock>
-        <Resultblock>
-            While merging the sets of Deltas of both branches, the framework recognizes a deletion-update conflict
-            between the previously created edge and the deletion of the node from which the edge originated (shown by
-            the red line between them).
-        </Resultblock>
-        <Resultblock>
-            In the History, you can now choose between two "Restore" actions, but they cannot be applied simultaneously due to the conflict.
-        </Resultblock>
-    </>;
-
-
-export function getDemoPD() {
-    const deltaRegistry = new DeltaRegistry();
-    const generateUUID = mockUuid();
-    const versionRegistry = new VersionRegistry();
-
-    const onion = newOnion({readonly: false, deltaRegistry, versionRegistry});
-
-    // Functional component
-    return function () {
-        const {state, reducer, components} = onion.useOnion(reducer => ({}));
-
-        return <div style={{minWidth: 1300}}>
-            <OnionContext.Provider value={{generateUUID, deltaRegistry}}>
-            <SimpleGrid cols={3}>
-                <Stack>
-                    <Title order={4}>State</Title>
-                    {components.graphStateComponent}
-                    <Group position="center">
-                        {components.undoButton}
-                        {components.redoButton}
-                        <InfoHoverCard>
-                            {undoButtonHelpText}
-                        </InfoHoverCard>
-                    </Group>
-                </Stack>
-                <Stack>
-                    <Title order={4}>History</Title>
-                    {components.historyComponentWithMerge}
-                </Stack>
-                <Stack>
-                    <Title order={4}>Deltas</Title>
-                    {components.deltaGraphL0Component}
-                </Stack>
-            </SimpleGrid>
-            </OnionContext.Provider>
-        </div>;
-    }
-}

+ 0 - 45
src/frontend/demos/demo_welcome.tsx

@@ -1,45 +0,0 @@
-import * as React from 'react';
-import {List, Space, Text, Title} from '@mantine/core';
-import {Actionblock, Resultblock} from './blocks';
-
-export const demo_Welcome_description =
-    <>
-        <Title order={4}>
-            Welcome to our demonstrator!
-        </Title>
-        <Text>
-            This demo site performs two tasks:
-        </Text>
-        <List type="ordered">
-            <List.Item>Presenting the possibilities of our approach.</List.Item>
-            <List.Item>Improving the understandability by example walk-throughs.</List.Item>
-        </List>
-        <Text>
-            In our explanations of the different views we use the following styled text-boxes:
-            <List type="unordered">
-                <List.Item>Every block with an <Text span c='darkorange'><strong>orange</strong></Text> mark on the left side contains an <strong>action</strong> that <strong>you should do.</strong></List.Item>
-                <List.Item>Each block with a <Text span c="royalblue"><strong>blue</strong></Text> border describes what <strong>you can observe</strong>, once the action has been executed.</List.Item>
-            </List>
-        </Text>
-        <Space h='15px'/>
-        <Text fw={700}>
-            Example:
-        </Text>
-        <Actionblock>
-            1. Perform this action!
-        </Actionblock>
-        <Resultblock>
-            This block describes the generated output you can observe.
-        </Resultblock>
-        <Space h='5px'/>
-        <Text>
-            Please select one of the demos above. We recommend that you complete the demos in the listed order.
-        </Text>
-        <Space h="20px"/>
-    </>;
-
-
-export function Welcome() {
-    return <></>
-}
-

+ 0 - 19
src/frontend/graphviz.tsx

@@ -1,19 +0,0 @@
-import * as React from "react";
-
-const viz = import("@viz-js/viz").then(module => module.instance());
-
-export function GraphvizComponent({dot}) {
-  const [svg, setSvg] = React.useState<string>("");
-
-  React.useEffect(() => {
-    viz.then(viz => {
-      // TODO: move this function to a Web Worker
-      const {output} = viz.render(dot, {format: 'svg'});
-      if (output !== undefined) {
-        setSvg(output);
-      }
-    });
-  }, [dot]);
-
-  return <div dangerouslySetInnerHTML={{__html:svg}}/>;
-}

+ 0 - 77
src/frontend/index.css

@@ -1,77 +0,0 @@
-html,body, #root {
-    -webkit-user-select: none;
-    -moz-user-select: none;
-    -ms-user-select: none;
-    user-select: none;
-    height: 100%;
-}
-
-#root {
-    height: 100vh;
-    font-size: 14px;
-}
-
-.canvas {
-  background-color: #eee;
-  width: 100%;
-  height: 350px;
-  vertical-align:top;
-}
-
-@media (prefers-color-scheme: dark) {
-  .canvas {
-    background-color: #888;
-  }
-}
-
-.mantine-List-item {
-    font-size: 14px;
-}
-
-.mantine-Text-root + .mantine-Text-root {
-    margin-bottom: 10px;
-}
-
-.mantine-Blockquote-root {
-    font-size: 14px;
-    background-color: #eee;
-    padding: 4px;
-}
-
-[action] {
-    border-left: 4px solid darkorange;
-    margin-bottom: 2px;
-    font-weight: bolder;
-}
-
-[result] {
-    border-left: 4px solid royalblue;
-    margin-left: 4px;
-    margin-bottom: 2px;
-    background-color: #f8f8f8;
-}
-
-@media (prefers-color-scheme: dark) {
-    .mantine-Blockquote-root {
-        background-color: #444;
-    }
-
-    [result] {
-        background-color: #282828;
-    }
-}
-
-.mantine-ScrollArea-scrollbar {
-    padding: 0;
-}
-
-.mantine-Tabs-panel {
-    user-select: text;
-    padding-left: 5px;
-    padding-bottom: 10px;
-}
-
-.mantine-Tabs-panel img {
-    margin: 20px auto;
-    max-width: 400px;
-}

+ 0 - 26
src/frontend/index.tsx

@@ -1,26 +0,0 @@
-import * as React from 'react';
-import {createRoot} from 'react-dom/client';
-import './rountangleEditor/RountangleEditor.css';
-import './d3graph/d3graph.css';
-import './index.css';
-
-import {getApp} from "./app";
-import {css, Global} from "@emotion/react";
-
-const container = document.getElementById('root');
-const root = createRoot(container!);
-const App = getApp();
-
-root.render(
-  <React.StrictMode>
-      <Global
-          styles={css`
-          body {
-            margin: 0;
-            padding: 0;
-          }
-        `}
-      />
-    <App/>
-  </React.StrictMode>
-);

+ 0 - 33
src/frontend/info_hover_card.tsx

@@ -1,33 +0,0 @@
-import * as React from "react";
-import {ActionIcon, HoverCard} from "@mantine/core";
-import {IconInfoCircle} from "@tabler/icons";
-
-// Creates an 'i' symbol with a hovercard dropdown.
-export function InfoHoverCard({children}) {
-  return (
-    <HoverCard shadow="md" width={300}>
-      <HoverCard.Target>
-        <ActionIcon>
-          <IconInfoCircle size={18}/>
-        </ActionIcon>
-      </HoverCard.Target>
-      <HoverCard.Dropdown>
-        {children}
-      </HoverCard.Dropdown>
-    </HoverCard>
-  );
-}
-
-// This is used to display the 'i' symbol on the top-right corner of the graph (SVG) view
-export function InfoHoverCardOverlay({children, contents}) {
-  return (
-    <div style={{position:"relative"}}>
-      {children}
-      <div style={{position: "absolute", top: 0, right: 0}}>
-        <InfoHoverCard>
-          {contents}
-        </InfoHoverCard>
-      </div>
-    </div>
-  );
-}

+ 0 - 29
src/frontend/onion_context.tsx

@@ -1,29 +0,0 @@
-import * as React from "react";
-
-import {DeltaRegistry} from "onion/delta_registry";;
-import {UUID} from "onion/types";
-import {mockUuid} from "../util/test_helpers";
-import {useConst} from "./use_const";
-
-export interface OnionContextType {
-  deltaRegistry: DeltaRegistry;
-  generateUUID: ()=>UUID;
-}
-
-export const OnionContext = React.createContext<OnionContextType>({
-  deltaRegistry: new DeltaRegistry(),
-  generateUUID: () => {
-    throw new Error("Trying to generate a UUID, but missing <OnionContextProvider> in React DOM");
-  }
-});
-
-export function OnionContextProvider({children}) {
-  const generateUUID = useConst(() => mockUuid());
-  const deltaRegistry = useConst(() => new DeltaRegistry());
-
-  return (
-    <OnionContext.Provider value={{generateUUID, deltaRegistry}}>
-      {children}
-    </OnionContext.Provider>
-  );
-}

+ 0 - 13
src/frontend/rountangleEditor/RountangleActions.ts

@@ -1,13 +0,0 @@
-import {PrimitiveValue, UUID} from "onion/types";
-
-interface CreateRountangle  {tag: 'createRountangle',  id: UUID, posX: number, posY: number, width: number, height: number}
-interface MoveRountangle    {tag: 'moveRountangle',    id: PrimitiveValue, newPosX: number, newPosY: number}
-interface ResizeRountangle  {tag: 'resizeRountangle',  id: PrimitiveValue, width: number, height: number}
-interface DeleteRountangle  {tag: 'deleteRountangle',  id: PrimitiveValue}
-
-export type RountangleAction =
-    Readonly<CreateRountangle>
-    | Readonly<MoveRountangle>
-    | Readonly<ResizeRountangle>
-    | Readonly<DeleteRountangle>
-;

+ 0 - 180
src/frontend/rountangleEditor/RountangleComponent.tsx

@@ -1,180 +0,0 @@
-import * as React from "react";
-import {RountangleAction} from "./RountangleActions";
-import {RountangleResizeHandleComponent} from "./RountangleResizeHandleComponent";
-import {Rountangle} from "./RountangleEditor";
-import {UUID} from "onion/types";
-
-export interface RountangleProps extends Rountangle {
-    id:       UUID;
-    dispatch: (action: RountangleAction) => void;
-    zoom:     number;
-}
-
-export interface RountangleState {
-    dragging:     boolean;
-    moved:        boolean;
-    movementsX:   number;
-    movementsY:   number;
-    resizeWidth:  number;
-    resizeHeight: number;
-}
-
-export class RountangleComponent extends React.Component<RountangleProps, RountangleState> {
-    shouldComponentUpdate(nextProps: Readonly<RountangleProps>, nextState: Readonly<RountangleState>, nextContext: any): boolean {
-        return this.props.posX       !== nextProps.posX
-            || this.props.posY       !== nextProps.posY
-            || this.props.width      !== nextProps.width
-            || this.props.height     !== nextProps.height
-            || this.state.dragging   !== nextState.dragging
-            || this.state.movementsX !== nextState.movementsX
-            || this.state.movementsY !== nextState.movementsY
-            || this.state.resizeWidth    !== nextState.resizeWidth
-            || this.state.resizeHeight    !== nextState.resizeHeight;
-    }
-
-    constructor(props: RountangleProps) {
-        super(props);
-        this.state = {
-            dragging: false,
-            moved:    false,
-            movementsX: 0,
-            movementsY: 0,
-            resizeWidth: 0,
-            resizeHeight: 0
-        }
-    }
-
-    onPointerDown = (event: React.PointerEvent<SVGRectElement>) => {
-        // only left mouse button
-        if (event.button !== 0) return;
-
-        event.currentTarget.setPointerCapture(event.pointerId);
-
-        this.setState({dragging: true, moved: false, movementsX: 0, movementsY: 0});
-        event.stopPropagation();
-        event.preventDefault();
-    }
-
-    onPointerMove = (event: React.PointerEvent<SVGRectElement>) => {
-        if (!this.state.dragging) return;
-
-        if (event.movementX !== 0 || event.movementY !== 0) {
-            this.setState({
-                moved: true,
-                movementsX: this.state.movementsX + event.movementX * this.props.zoom,
-                movementsY: this.state.movementsY + event.movementY * this.props.zoom
-            });
-        }
-
-        event.stopPropagation();
-        event.preventDefault();
-    }
-
-    onPointerUp = (event: React.PointerEvent<SVGRectElement>) => {
-        event.currentTarget.releasePointerCapture(event.pointerId);
-        event.stopPropagation();
-        event.preventDefault();
-
-        // if left button, complete dragging
-        if (event.button === 0) {
-            if (this.state.dragging) {
-                if (this.state.moved) {
-                    this.props.dispatch({
-                        tag: 'moveRountangle',
-                        id: this.props.id,
-                        newPosX: this.props.posX + this.state.movementsX,
-                        newPosY: this.props.posY + this.state.movementsY
-                    });
-                }
-                this.setState({dragging: false, movementsX: 0, movementsY: 0});
-            }
-
-            // + alt key deletes block
-            if (event.altKey) {
-                this.props.dispatch({tag: 'deleteRountangle', id: this.props.id});
-            }
-        }
-        // delete on middle click
-        else if (event.button === 1) {
-            this.props.dispatch({tag: 'deleteRountangle', id: this.props.id});
-        }
-    }
-
-    // onDoubleClick = (event: React.MouseEvent) => {
-    //     const newRountangleName = prompt('Rename', this.props.name);
-    //     if (newRountangleName && newRountangleName !== this.props.name) {
-    //         this.props.dispatch({
-    //             tag: 'renameRountangle',
-    //             id: this.props.id,
-    //             newName: newRountangleName
-    //         });
-    //     }
-    // }
-
-    onResize = (deltaHeight: number, deltaWidth: number) => {
-        if (deltaHeight !== 0 || deltaWidth !== 0) {
-            this.setState({
-                resizeWidth:  this.state.resizeWidth  + deltaHeight * this.props.zoom,
-                resizeHeight: this.state.resizeHeight + deltaWidth * this.props.zoom
-            });
-        }
-    }
-
-    onResizeComplete = () => {
-        this.props.dispatch({
-            tag:    'resizeRountangle',
-            id:     this.props.id,
-            width:  Math.max(50, this.props.width  + this.state.resizeWidth),
-            height: Math.max(50, this.props.height + this.state.resizeHeight)
-        });
-
-        this.setState({
-            resizeWidth:  0,
-            resizeHeight: 0
-        });
-    }
-
-    render() {
-        return(
-            <g
-                className={`re-rountangle-wrapper ${this.state.dragging ? 'dragging' : ''}`}
-            >
-                <rect
-                    rx={5}
-                    ry={5}
-                    x={this.props.posX + this.state.movementsX}
-                    y={this.props.posY + this.state.movementsY}
-                    width={this.props.width + this.state.resizeWidth}
-                    height={this.props.height + this.state.resizeHeight}
-                    className={'re-rountangle'}
-                />
-                <foreignObject
-                    x={this.props.posX + this.state.movementsX}
-                    y={this.props.posY + this.state.movementsY}
-                    width={this.props.width + this.state.resizeWidth}
-                    height={this.props.height + this.state.resizeHeight}
-                    onPointerDown={this.onPointerDown}
-                    onPointerMove={this.onPointerMove}
-                    onPointerUp={this.onPointerUp}
-                    // onDoubleClick={this.onDoubleClick}
-                >
-                    <div
-                        className={'re-rountangle-id'}
-                        title={this.props.id.toString()}
-                    >
-                        <span>ID<sub>CS</sub>: {JSON.stringify(this.props.id)}</span>
-                    </div>
-                </foreignObject>
-                <RountangleResizeHandleComponent
-                    id={this.props.id}
-                    x={this.props.posX + this.state.movementsX}
-                    y={this.props.posY + this.state.movementsY}
-                    height={this.props.height + this.state.resizeHeight}
-                    width={this.props.width + this.state.resizeWidth}
-                    onResize={this.onResize}
-                    onResizeComplete={this.onResizeComplete}
-                />
-            </g>
-        );
-    }
-}

+ 0 - 45
src/frontend/rountangleEditor/RountangleEditor.css

@@ -1,45 +0,0 @@
-.re-background {
-    position: relative;
-    font-family: sans-serif;
-}
-.re-background.dragging {
-    cursor: move;
-}
-
-.re-rountangle-wrapper {
-    filter: drop-shadow(.1rem .1rem .1em deeppink);
-}
-.re-rountangle {
-    stroke: black;
-    stroke-width: 3px;
-    overflow: hidden;
-    font-weight: bold;
-    fill: transparent;
-}
-.re-rountangle-wrapper:hover {
-    cursor: grab;
-}
-.re-rountangle-wrapper:hover.dragging {
-    cursor: grabbing;
-}
-.re-rountangle-name {
-    font-weight: 600;
-    text-overflow: ellipsis;
-    text-align: center;
-    height: 100%;
-    max-height: 100%;
-    display: flex;
-    justify-content: center;
-    flex-direction: column;
-    pointer-events: none;
-}
-.re-rountangle-id {
-    position: absolute;
-    left: 5px;
-    top: 2px;
-    font-size: 0.8em;
-}
-.re-rountangle-resize-handle {
-    fill: deeppink;
-    cursor: nwse-resize;
-}

+ 0 - 280
src/frontend/rountangleEditor/RountangleEditor.tsx

@@ -1,280 +0,0 @@
-import * as React from "react";
-import {RountangleComponent} from "./RountangleComponent";
-import {OnionContext, OnionContextType} from "../onion_context";
-import {PrimitiveValue, UUID} from "onion/types";
-import {D3OnionGraphData, D3OnionNodeData, D3OnionLinkData} from "../d3graph/reducers/onion_graph";
-import {RountangleAction} from "./RountangleActions";
-import {INodeState, IValueState, GraphState} from "onion/graph_state";
-import {PrimitiveDelta, TargetValue} from "onion/delta";
-import {assert, assertNever} from "../../util/assert";
-
-export interface Rountangle {
-    readonly posX:   number;
-    readonly posY:   number;
-    readonly width:  number;
-    readonly height: number;
-}
-
-export function isRountangle(d3node: D3OnionNodeData) {
-    if (!(d3node.obj.type === "node")) return false;
-    const nodeState = d3node.obj as unknown as INodeState;
-    const outgoing = nodeState.outgoing;
-    if (!(outgoing.get('type')?.asTarget() === 'Rountangle')) return false;
-    if (!(typeof outgoing.get('x')?.asTarget()       === "number")) return false;
-    if (!(typeof outgoing.get('y')?.asTarget()       === "number")) return false;
-    if (!(typeof outgoing.get('width')?.asTarget()   === "number")) return false;
-    if (!(typeof outgoing.get('height')?.asTarget()  === "number")) return false;
-
-    return true;
-}
-
-// Precondition: isRountangle(d3Node)
-export function graphStateToRountangle(d3node: D3OnionNodeData): [UUID, Rountangle]  {
-    if (!isRountangle(d3node)) {
-        throw new Error(`RountangleParser Cannot parse: ${d3node}`);
-    }
-
-    const nodeState = d3node.obj as INodeState;
-    const outgoing = nodeState.outgoing;
-    return [nodeState.creation.id, {
-        posX: outgoing.get("x")!.asTarget() as number,
-        posY: outgoing.get("y")!.asTarget() as number,
-        width: outgoing.get("width")!.asTarget() as number,
-        height: outgoing.get("height")!.asTarget() as number,
-    }]
-}
-
-export interface RountangleEditorProps {
-  graph: D3OnionGraphData;
-  graphState: GraphState;
-  onUserEdit?: (deltas: PrimitiveDelta[], description: string) => void;
-}
-
-interface ViewPort {
-    translateX:    number;
-    translateY:    number;
-    zoom:          number;
-}
-
-interface DragState {
-    dragging: boolean;
-    moved: boolean;
-}
-
-export function RountangleEditor(props: RountangleEditorProps) {
-    const baseWidth = 350;
-    const baseHeight = 350;
-
-    const [dragState, setDragState] = React.useState<DragState>({
-        dragging: false,
-        moved: false,
-    });
-    const [viewPort, setViewPort] = React.useState<ViewPort>({
-        translateX: 0,
-        translateY: 0,
-        zoom: 1.0,        
-    });
-    const canvasRef = React.useRef<SVGSVGElement>(null);
-    const onionContext = React.useContext<OnionContextType>(OnionContext);
-
-    const createValueDeltas = React.useCallback((nodeState: INodeState, values: [string,PrimitiveValue][]): PrimitiveDelta[] => {
-        const deltas: PrimitiveDelta[] = [];
-        for (const [edgeLabel, value] of values) {
-            deltas.push(nodeState.getDeltaForSetEdge(onionContext.deltaRegistry, edgeLabel, value));
-        }
-        return deltas;
-    }, []);
-
-    const dispatch = React.useCallback((action: RountangleAction) => {
-        const deltas: PrimitiveDelta[] = [];
-        let nodeState: INodeState | undefined;
-
-        switch (action.tag) {
-            case "createRountangle":
-                const createRountangleNodeDelta = onionContext.deltaRegistry.newNodeCreation(action.id);
-                deltas.push(createRountangleNodeDelta);
-                const edgeSpec: [string,PrimitiveValue][] = [
-                    ["type","Rountangle"],
-                    ["x",action.posX],
-                    ["y",action.posY],
-                    ["width",action.width],
-                    ["height",action.height],
-                ];
-                deltas.push(... edgeSpec.map(([edgeLabel, value]) =>
-                    onionContext.deltaRegistry.newEdgeUpdate(createRountangleNodeDelta.createOutgoingEdge(edgeLabel), value)));
-                break;
-            case 'moveRountangle':
-                // get nodeState from clicked node
-                nodeState = props.graphState.nodes.get(action.id);
-                if (nodeState !== undefined) {
-                    deltas.push(...createValueDeltas(nodeState, [["x", action.newPosX], ["y", action.newPosY]]));
-                }
-                break;
-            case 'deleteRountangle':
-                 // get nodeState from clicked node
-                 nodeState = props.graphState.nodes.get(action.id);
-                 if (nodeState !== undefined) {
-                     deltas.push(...nodeState.getDeltasForDelete(onionContext.deltaRegistry));
-                 }
-                 break;
-            case 'resizeRountangle':
-                nodeState = props.graphState.nodes.get(action.id);
-                if (nodeState !== undefined) {
-                    deltas.push(...createValueDeltas(nodeState, [["width",action.width],["height",action.height]]));
-                }
-                break;
-            default: assertNever(action);
-        }
-        if (deltas.length > 0) {
-            props.onUserEdit?.(deltas, action.tag);
-        }
-    }, [props.onUserEdit]);
-
-    const onPointerDown = React.useCallback((event: React.PointerEvent<SVGSVGElement>) => {
-        event.stopPropagation();
-        event.preventDefault();
-
-        // only left mouse button
-        if (event.button === 0) {
-            event.currentTarget.setPointerCapture(event.pointerId);
-            setDragState({
-                dragging: true,
-                moved: false,
-            })
-        }
-    }, []);
-
-    const onPointerMove = React.useCallback((event: React.PointerEvent<SVGSVGElement>) => {
-        if (!dragState.dragging) return;
-
-        if (event.movementY !== 0 ||event.movementX !== 0) {
-            setViewPort(({translateX, translateY, zoom, ...rest}) => ({
-                translateX: translateX - event.movementX * zoom,
-                translateY: translateY - event.movementY * zoom,
-                zoom,
-                ...rest
-            }));
-            setDragState(({moved, ...rest}) => ({
-                moved: true,
-                ...rest,
-            }));
-        }
-
-        event.stopPropagation();
-        event.preventDefault();
-    }, [dragState.dragging]);
-
-    const clickToSVGPos = React.useCallback((x:number, y: number): DOMPoint | undefined => {
-        // point transformation adapted from https://stackoverflow.com/a/70595400
-        if (!canvasRef.current) return undefined;
-
-        const screenCTM = canvasRef.current.getScreenCTM();
-        if (screenCTM === null) return undefined;
-
-        const refPoint = new DOMPoint(x, y);
-        return refPoint.matrixTransform(screenCTM.inverse());
-    }, []);
-
-    const onContextMenu = React.useCallback((event: React.PointerEvent<SVGSVGElement>) => {
-        event.stopPropagation();
-        event.preventDefault();
-    }, []);
-
-    const onPointerUp = React.useCallback((event: React.PointerEvent<SVGSVGElement>) => {
-        event.stopPropagation();
-        event.preventDefault();
-        event.currentTarget.releasePointerCapture(event.pointerId);
-
-        setDragState({
-            dragging: false,
-            moved: false,
-        });
-
-        // add new state on left mouse button and ALT-Key pressed
-        if (event.button === 2) {
-            const cursorPoint = clickToSVGPos(event.clientX, event.clientY);
-
-            if (cursorPoint) {
-                dispatch({
-                    tag: 'createRountangle',
-                    id: onionContext.generateUUID(),
-                    posX: cursorPoint.x,
-                    posY: cursorPoint.y,
-                    width: 100,
-                    height: 66,
-                });
-            }
-        }
-    }, [dispatch]);
-
-    const onWheel = React.useCallback((event: WheelEvent) => {
-        event.preventDefault();
-        event.stopPropagation();
-        if (event.deltaY === 0) return;
-        if(!canvasRef.current) return;
-
-        setViewPort(({translateX, translateY, zoom}) => {
-            const newZoom = event.deltaY > 0 ? Math.min(zoom * 1.1, 3.0) : Math.max(zoom * 0.9, 0.1);
-            return {
-                zoom: newZoom,
-                translateX: translateX + (baseWidth * zoom - baseWidth * newZoom)/2,
-                translateY: translateY + (baseHeight * zoom - baseHeight * newZoom)/2,
-            };
-        });
-    }, []);
-
-    React.useEffect(() => {
-        // WORKAROUND - we cannot use React's onWheel event (because then preventDefault() doesn't work)
-        canvasRef.current?.addEventListener('wheel', onWheel);
-        return () => {
-            canvasRef.current?.removeEventListener('wheel', onWheel);
-        };
-    }, []);
-
-    return <svg
-           className={`re-background canvas ${dragState.dragging ? 'dragging' : ''}`}
-           onPointerDown={onPointerDown}
-           onPointerMove={onPointerMove}
-           onPointerUp={onPointerUp}
-           onContextMenu={onContextMenu}
-           width='350px'
-           height='100%'
-           viewBox={`${viewPort.translateX} ${viewPort.translateY} ${baseWidth * viewPort.zoom} ${baseHeight * viewPort.zoom}`}
-           ref={canvasRef}
-       >
-           {
-               Array.from(props.graph.nodes)
-               .filter(isRountangle)
-                   .map(graphStateToRountangle)
-                   .sort((node1, node2) => {
-                       const [, rountangle1] = node1;
-                       const [, rountangle2] = node2;
-                       const area1 = rountangle1.height * rountangle1.width;
-                       const area2 = rountangle2.height * rountangle2.width;
-
-                       if (area1 > area2) {
-                           return -1;
-                       }
-                       else if (area1 < area2) {
-                           return 1;
-                       }
-                       else {
-                           return 0;
-                       }
-                   })
-                   .map(node => {
-                    const [id, rountangle] = node;
-                    return <RountangleComponent
-                        key={JSON.stringify(id)}
-                        id={id}
-                        posX={rountangle.posX}
-                        posY={rountangle.posY}
-                        width={rountangle.width}
-                        height={rountangle.height}
-                        zoom={viewPort.zoom}
-                        dispatch={dispatch}
-                    />
-               })
-           }
-    </svg>
-}

+ 0 - 90
src/frontend/rountangleEditor/RountangleResizeHandleComponent.tsx

@@ -1,90 +0,0 @@
-import * as React from "react";
-import {RountangleState} from "./RountangleComponent";
-import {PrimitiveValue} from "onion/types";
-
-interface RountangleResizeHandleProps {
-    id:               PrimitiveValue;
-    x:                number;
-    y:                number;
-    width:            number;
-    height:           number;
-    onResize:         (deltaX: number, deltaY: number) => void;
-    onResizeComplete: () => void;
-}
-
-export interface RountangleResizeHandleState {
-    dragging:     boolean;
-    moved:        boolean;
-}
-
-export class RountangleResizeHandleComponent extends React.Component<RountangleResizeHandleProps, RountangleResizeHandleState> {
-    shouldComponentUpdate(nextProps: Readonly<RountangleResizeHandleProps>, nextState: Readonly<RountangleState>, nextContext: any): boolean {
-        return this.props.width !== nextProps.width
-            || this.props.height !== nextProps.height
-            || this.props.x !== nextProps.x
-            || this.props.y !== nextProps.y
-    }
-
-    constructor(props: RountangleResizeHandleProps) {
-        super(props);
-        this.state = {
-            dragging:    false,
-            moved:       false
-        }
-    }
-
-    onPointerDown = (event: React.PointerEvent<SVGSVGElement>) => {
-        // only left mouse button
-        if (event.button !== 0) return;
-
-        event.currentTarget.setPointerCapture(event.pointerId);
-
-        this.setState({
-            dragging:       true,
-            moved:          false
-        });
-
-        event.stopPropagation();
-        event.preventDefault();
-    }
-
-    onPointerMove = (event: React.PointerEvent<SVGSVGElement>) => {
-        if (!this.state.dragging) return;
-
-        this.props.onResize(event.movementX, event.movementY);
-
-        event.stopPropagation();
-        event.preventDefault();
-    }
-
-    onPointerUp = (event: React.PointerEvent<SVGSVGElement>) => {
-        event.currentTarget.releasePointerCapture(event.pointerId);
-        event.stopPropagation();
-        event.preventDefault();
-
-        // if left button, complete dragging
-        if (event.button === 0) {
-            if (this.state.dragging) {
-                this.setState({dragging: false, moved: false});
-                this.props.onResizeComplete();
-            }
-        }
-    }
-
-    render() {
-        return <svg
-            x={this.props.x + this.props.width - 16.5}
-            y={this.props.y + this.props.height - 16.5}
-            width={15}
-            height={15}
-            className="re-rountangle-resize-handle"
-            onPointerDown={this.onPointerDown}
-            onPointerMove={this.onPointerMove}
-            onPointerUp={this.onPointerUp}
-        >
-            <polygon
-                points={"15,0 15,15, 0,15"}
-            />
-        </svg>
-    }
-}

+ 0 - 0
src/frontend/rountangleEditor/RountangleStore.ts


+ 0 - 28
src/frontend/styledtabs.tsx

@@ -1,28 +0,0 @@
-import { Tabs, TabsProps } from '@mantine/core';
-import * as React from 'react';
-
-export function Styledtabs(props: TabsProps) {
-    return (
-        <Tabs
-            styles={(theme) => ({
-                tab: {
-                    ...theme.fn.focusStyles(),
-                    cursor: 'pointer',
-                    borderTopLeftRadius: 0,
-                    borderBottomLeftRadius: 0,
-                    width: 250,
-
-                    '&[data-active]': {
-                        backgroundColor: theme.colors.blue[7],
-                        borderColor: theme.colors.blue[7],
-                        color: theme.white,
-                    },
-                },
-                tabsList: {
-                    border: 0,
-                }
-            })}
-            {...props}
-        />
-    );
-}

+ 0 - 11
src/frontend/use_const.ts

@@ -1,11 +0,0 @@
-import * as React from "react";
-
-// Why doesn't React have this functionality built-in? (and implemented more efficiently than hacking with useRef)
-export function useConst<T>(initialCb: ()=>T): T {
-  const ref = React.useRef<T>();
-  if (ref.current === undefined) {
-    const initialValue = initialCb();
-    ref.current = initialValue;
-  }
-  return ref.current!;
-}

+ 0 - 319
src/frontend/versioned_model/correspondence.tsx

@@ -1,319 +0,0 @@
-import * as React from "react";
-import * as Mantine from "@mantine/core";
-import * as Icons from "@tabler/icons";
-
-import {newOnion} from "./single_model";
-import {emptyGraph} from "../d3graph/d3graph";
-import {D3GraphUpdater} from "../d3graph/reducers/onion_graph";
-import {RountangleParser} from "../../parser/rountangle_parser";
-import {Version} from "onion/version";
-import {GraphState} from "onion/graph_state"; 
-import {PrimitiveDelta} from "onion/delta";
-
-export const undoButtonHelpTextCorr =
-  `Navigating to a version in the correspondence model,
-   will also navigate to the embedded concrete and abstract syntax model versions.`;
-
-// Pure function
-// Replays all deltas in a version to compute the graph state of that version.
-function getGraphState(version: Version, listener?): GraphState {
-  const graphState = new GraphState();
-  for (const d of [...version].reverse()) {
-    if (listener !== undefined) {
-      graphState.exec(d, listener);
-    }
-    else {
-      graphState.exec(d);
-    }
-  }
-  return graphState;
-}
-
-export function newCorrespondence({deltaRegistry, generateUUID, versionRegistry}) {
-  const {useOnion, ...onion} = newOnion({readonly: true, deltaRegistry, versionRegistry});
-
-  // All versions that are part of this correspondence
-  // We need this to know if some CS/AS version has already been parsed/rendered.
-  const corrVersions = new Set<Version>([versionRegistry.initialVersion]);
-  const isPartOfThisCorrespondence = v => corrVersions.has(v);
-
-  function useCorrespondence(
-    {state: {version: csVersion}, reducer: csReducer},
-    {state: {version: asVersion}, reducer: asReducer},
-  ) {
-    // "override" gotoVersion from corr-onion.
-    const gotoVersion = (version: Version) => {
-      const csVersion = version.embeddings.get("cs")?.version;
-      const asVersion = version.embeddings.get("as")?.version;
-
-      if (version === versionRegistry.initialVersion) {
-        reducer.gotoVersion(version);
-        csReducer.gotoVersion(version);
-        asReducer.gotoVersion(version);        
-      }
-
-      if (csVersion !== undefined && asVersion !== undefined) {
-        // user clicked on a correspondence model version
-        reducer.gotoVersion(version);
-        csReducer.gotoVersion(csVersion);
-        asReducer.gotoVersion(asVersion);
-        return;
-      }
-
-      // if (version.reverseEmbeddings.get("cs").some(isPartOfThisCorrespondence)) {
-      //   csReducer.gotoVersion(version);
-      //   return;
-      // }
-      // if (version.reverseEmbeddings.get("as").some(isPartOfThisCorrespondence)) {
-      //   asReducer.gotoVersion(version);
-      //   return;
-      // }
-    };
-
-    const {state, reducer, components} = useOnion(reducer => ({
-      onUndoClicked: gotoVersion,
-      onRedoClicked: gotoVersion,
-      onVersionClicked: gotoVersion,
-      onMerge: (versions: Version[]) => {
-        versions.forEach(v => corrVersions.add(v));
-        reducer.appendVersions(versions);
-        csReducer.appendVersions(versions.map(v => v.embeddings.get("cs")?.version).filter(v => v!==undefined));
-        asReducer.appendVersions(versions.map(v => v.embeddings.get("as")?.version).filter(v => v!==undefined));
-      }
-    }));
-
-    // Helper
-    const filterCorrParents = (corrParentVersions: Version[]) => {
-      // when parsing/rendering, if one CORR-parent is a parent of another CORR-parent, then drop this one.
-      return corrParentVersions.filter(parent => !corrParentVersions.some(child => child !== parent && parent.findDescendant(child) !== undefined));
-    }
-
-    // Reducer
-
-    const parseExistingVersion = (csVersion: Version) => {
-      for (const [csParentVersion, csTx] of csVersion.parents) {
-        const csDeltas = [...csTx.iterPrimitiveDeltas()];
-        const description = csTx.description;
-
-        // Recursively parse parent versions, if not parsed yet.
-        if (!csParentVersion.reverseEmbeddings.get("cs")?.some(isPartOfThisCorrespondence)) {
-          parseExistingVersion(csParentVersion);
-        }
-
-        const corrParentVersions = csParentVersion.getReverseEmbeddings("cs").filter(isPartOfThisCorrespondence);
-        if (corrParentVersions.length === 0) {
-          throw new Error("Assertion failed: CS has a parent, but this parent is not yet part of a CORR version.");
-        }
-
-        // Even though parsing is deterministic, it is still possible that the same CS version is part of multiple CORR versions.
-        // For instance, when merging at the level of CS, and then rendering the change (with a conflict between the CORR-parents), results in a single CS version embedded in multiple CORR versions.
-
-        const filteredCorrParentVersions = filterCorrParents(corrParentVersions);
-        // And if then, there are still multiple CORR versions to choose from, we just pick one:
-        // (it would be better to ask the user which one, but whatever)
-        for (const corrParentVersion of filteredCorrParentVersions) {
-          const asParentVersion = corrParentVersion.getEmbedding("as").version;
-          if (asParentVersion === undefined) {
-            throw new Error("Assertion failed: CS's parent is part of a CORR version, but that CORR version does not embed an AS version.");
-          }
-  
-          const [csGS, corrGS, asGS] = [csParentVersion, corrParentVersion, asParentVersion].map(v => getGraphState(v));
-  
-          const parser = new RountangleParser(deltaRegistry, generateUUID);
-          const {corrDeltas, asDeltas, csOverrides, asOverrides} = parser.parse(csDeltas, csGS, corrGS, asGS);
-  
-          const asVersion = asDeltas.length > 0 ? asReducer.createAndGotoNewVersion(asDeltas, "as:"+description, asParentVersion) : asParentVersion;
-  
-          reducer.appendVersions([csVersion, asVersion]);
-  
-          const corrVersion = reducer.createAndGotoNewVersion(corrDeltas, "corr:"+description, corrParentVersion,
-            () => new Map([
-              ["cs", {version: csVersion, overridings: csOverrides}],
-              ["as", {version: asVersion, overridings: asOverrides}],
-            ]));
-  
-          corrVersions.add(corrVersion);
-  
-        }
-      }
-    };
-    const renderExistingVersion = async (asVersion: Version, setManualRendererState) => {
-      for (const [asParentVersion, asTx] of asVersion.parents) {
-        const asDeltas = [...asTx.iterPrimitiveDeltas()];
-        const description = asTx.description;
-
-        const render = () => {
-          const corrParentVersions = asParentVersion.getReverseEmbeddings("as").filter(isPartOfThisCorrespondence);
-          if (corrParentVersions.length === 0) {
-            throw new Error("Assertion failed: AS has a parent, but this parent is not yet part of a CORR version.");
-          }
-          // AS version may be embedded into multiple CORR versions
-          // If one CORR version is a child of another (parent), then we only render based on the child:
-          const filteredCorrParentVersions = filterCorrParents(corrParentVersions);
-          // And if then, there are still multiple CORR versions to choose from, we just pick one:
-          // (it would be better to ask the user which one, but whatever)
-          const [corrParentVersion] = filteredCorrParentVersions;
-          console.log({corrParentVersion, filteredCorrParentVersions, corrParentVersions});
-          const csParentVersion = corrParentVersion.getEmbedding("cs").version;
-          if (csParentVersion === undefined) {
-            console.log("Cannot parse - no parent CS version");
-            return;
-          }
-
-          const [csGS, corrGS, asGS] = [csParentVersion, corrParentVersion, asParentVersion].map(v => getGraphState(v));
-
-          const parser = new RountangleParser(deltaRegistry, generateUUID);
-          const {corrDeltas, csDeltas, complete, csOverrides, asOverrides} = parser.render(asDeltas, csGS, corrGS, asGS);
-
-          function finishRender({corrDeltas, csDeltas}) {
-            const csVersion = csDeltas.length > 0 ? csReducer.createAndGotoNewVersion(csDeltas, "cs:"+description, csParentVersion) : csParentVersion;
-
-            reducer.appendVersions([csVersion, asVersion]);
-
-            const corrVersion = reducer.createAndGotoNewVersion(corrDeltas, "corr:"+description, corrParentVersion,
-              () => new Map([
-                ["cs", {version: csVersion, overridings: csOverrides}],
-                ["as", {version: asVersion, overridings: asOverrides}],
-              ]));
-
-            corrVersions.add(corrVersion);
-          }
-
-          if (complete) {
-            finishRender({corrDeltas, csDeltas});
-            return Promise.resolve();
-          }
-          else {
-            function getD3State(version: Version, additionalDeltas: PrimitiveDelta[] = []) {
-              let graph = emptyGraph;
-              const setGraph = callback => (graph = callback(graph));
-              const d3Updater = new D3GraphUpdater(setGraph, 0, 0);
-              const graphState = getGraphState(version, d3Updater);
-              for (const d of additionalDeltas) {
-                graphState.exec(d, d3Updater);
-              }
-              return {graph, graphState};
-            }
-
-            const {graph: asGraph, graphState: asGraphState} = getD3State(asVersion);
-            const {graph: csGraph, graphState: csGraphState} = getD3State(csParentVersion, csDeltas);
-
-            const csToAs = new Map();
-            const asToCs = new Map();
-
-            for (const corrNode of corrGS.nodes.values()) {
-              const csNode = corrNode.outgoing.get("cs");
-              if (csNode?.type !== "node") {
-                continue; // corrNode is not a correspondence node
-              }
-              const asNode = corrNode.outgoing.get("as");
-              if (asNode?.type !== "node") {
-                continue; // corrNode is not a correspondence node
-              }
-              csToAs.set(csNode.creation.id, asNode.creation.id);
-              asToCs.set(asNode.creation.id, csNode.creation.id);
-            }
-
-            return new Promise((resolve: (csDeltas: PrimitiveDelta[]) => void, reject: () => void) => {
-              setManualRendererState({
-                asGraph,
-                csGraph,
-                asGraphState,
-                csGraphState,
-                csToAs,
-                asToCs,
-                asDeltasToRender: asDeltas,
-                done: resolve,
-                cancel: reject,
-              });
-            })
-            .then((additionalCsDeltas: PrimitiveDelta[]) => {
-              finishRender({
-                corrDeltas: corrDeltas.concat(additionalCsDeltas),
-                csDeltas: csDeltas.concat(additionalCsDeltas),
-              });
-            })
-            .catch()
-            .finally(() => setManualRendererState(null));
-          }
-        }
-
-        if (asParentVersion.getReverseEmbeddings("as").filter(isPartOfThisCorrespondence).length === 0) {
-          return renderExistingVersion(asParentVersion, setManualRendererState).then(render);
-        }
-        else {
-          return render();
-        }
-      }
-    };
-
-    // React components
-
-    const getParseButton = (dir: "left"|"right" = "right") => {
-      return <Mantine.Button compact
-          disabled={(csVersion.reverseEmbeddings.get("cs") || []).some(v => corrVersions.has(v))}
-          onClick={() => parseExistingVersion(csVersion)}
-          rightIcon={dir === "right" ? <Icons.IconChevronsRight/>: null}
-          leftIcon={dir === "left" ? <Icons.IconChevronsLeft/>: null}
-        >Parse</Mantine.Button>;
-    };
-    const getRenderButton = (setManualRendererState, dir: "left"|"right" = "left") => {
-      return <Mantine.Button compact
-          disabled={(asVersion.reverseEmbeddings.get("as") || []).some(v => corrVersions.has(v))}
-          onClick={() => renderExistingVersion(asVersion, setManualRendererState)}
-          rightIcon={dir === "right" ? <Icons.IconChevronsRight/>: null}
-          leftIcon={dir === "left" ? <Icons.IconChevronsLeft/>: null}
-        >Render</Mantine.Button>;
-    };
-    const getCaptionWithParseButton = (autoParseState, setAutoParseState, dir: "left"|"right" = "right") => {
-      const sw = <Mantine.Switch label="Auto"
-        labelPosition={dir}
-        checked={autoParseState}
-        onChange={(event) => setAutoParseState(event.currentTarget.checked)}
-      />;
-      const button = getParseButton(dir);
-      if (dir === "left") {
-        return <Mantine.Group>{button}{sw}</Mantine.Group>;
-      }
-      else {
-        return <Mantine.Group>{sw}{button}</Mantine.Group>;
-      }
-    };
-    const getCaptionWithRenderButton = (autoRenderState, setAutoRenderState, setManualRendererState, dir: "left"|"right" = "left") => {
-      const sw = <Mantine.Switch label="Auto"
-        labelPosition={dir}
-        checked={autoRenderState}
-        onChange={(event) => setAutoRenderState(event.currentTarget.checked)}
-      />
-      const button = getRenderButton(setManualRendererState, dir);
-      if (dir === "left") {
-        return <Mantine.Group>{button}{sw}</Mantine.Group>;
-      }
-      else {
-        return <Mantine.Group>{sw}{button}</Mantine.Group>;
-      }
-    };
-
-    return {
-      state,
-      reducer: {
-        ... reducer,
-        parseExistingVersion,
-        renderExistingVersion,
-        gotoVersion,
-      },
-      components: {
-        ... components,
-        getParseButton,
-        getRenderButton,
-        getCaptionWithParseButton,
-        getCaptionWithRenderButton,        
-      }
-    };
-  }
-
-  return {
-    ... onion,
-    useCorrespondence,
-  };
-}

+ 0 - 116
src/frontend/versioned_model/graph_view.tsx

@@ -1,116 +0,0 @@
-import * as React from "react";
-import * as Mantine from "@mantine/core";
-import { GraphvizComponent } from '../graphviz';
-
-import {D3Graph, D3GraphData, D3NodeData, defaultGraphForces} from "../d3graph/d3graph";
-import {InfoHoverCardOverlay} from "../info_hover_card";
-import { D3OnionGraphData } from "../d3graph/reducers/onion_graph";
-import { D3GraphEditable, UserEditCallback } from "../d3graph/d3graph_editable";
-import { GraphState } from "onion/graph_state";
-
-function esc(str) {
-  return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
-}
-
-function esc2(str) {
-  return encodeURIComponent(str);
-}
-
-// I know global variables are bad, but a GraphViz URL can only call a global function:
-const graphvizMap = new Map();
-(window as any).graphvizClicked = function(nodeId, button) {
-  const callback = graphvizMap.get(nodeId);
-  if (callback) callback(button);
-}
-
-export type Renderer = 'd3' | 'graphviz';
-
-export function genericGraphVizLayout<N,L>(graphViewId: string, graphData: D3GraphData<N,L>) {
-  return `digraph {
-    bgcolor="transparent";
-    ${graphData.nodes.map(node => `"${esc(node.id)}" [${node.label===""?`shape=circle, width=0.3, height=0.3`:`shape=box`}, label="${esc(node.label)}", fillcolor="${node.color}", style="filled,rounded", ${node.bold?`penwidth=4.0,`:``} URL="javascript:${esc2(`graphvizClicked('${graphViewId+node.id}', 2)`)}"]`).join('\n')}
-    ${graphData.links.map(link => `"${esc(link.source.id || link.source)}" -> "${esc(link.target.id || link.target)}" [label="${esc(link.label)}", fontcolor="${link.color}", color="${link.color}"${link.bidirectional?`, dir=none`:``}]`).join('\n')}
-  }`;
-}
-
-function outgoingIsValue(o) {
-  return (o.target.id || o.target).startsWith("V");
-}
-
-export function objectLikeGraphVizLayout(graphViewId: string, graphData: D3OnionGraphData) {
-  return `digraph {
-    bgcolor="transparent";
-    node[shape=record];
-    ${graphData.nodes.filter(node => !node.id.startsWith("V")).map(node => {
-      const outgoing = graphData.links.filter(link => (link.source.id || link.source) === node.id);
-      return `"${esc(node.id)}" [label="{ ${["ID", ...outgoing.filter(o => outgoingIsValue(o)).map(o => `${o.label}`)].join('|')} } | { ${[esc(node.label), ...outgoing.filter(o => outgoingIsValue(o)).map(o => `${esc(o.target.label)}`)].join('|')} }", fillcolor="white", style="filled,rounded", ${node.bold?`penwidth=4.0,`:``} URL="javascript:${esc2(`graphvizClicked('${graphViewId+node.id}', 2)`)}"]`;
-    }).join('\n')}
-
-    ${graphData.links.filter(link => !link.target.id.startsWith("V")).map(link => `"${esc(link.source.id || link.source)}":"L${link.label}" -> "${esc(link.target.id || link.target)}" [label="${esc(link.label)}", fontcolor="${link.color}", color="${link.color}"${link.bidirectional?`, dir=none`:``}]`).join('\n')}
-  }`;
-}
-
-
-interface GraphViewProps<N,L> {
-  graphvizLayout: (graphViewId: string, graphData: D3GraphData<N,L>) => string;
-  graphData: D3GraphData<N,L>;
-  help: JSX.Element;
-  graphvizHelp?: JSX.Element;
-  mouseUpHandler: (event, mouse:{x:number, y:number}, node?: D3NodeData<N>) => void;
-  defaultRenderer: Renderer;
-  editorCallbacks?: {
-    graphState: GraphState,
-    setNextNodePosition: (newX: number, newY: number) => void,
-    onUserEdit: UserEditCallback,
-  };
-  children?: any;
-}
-
-export function GraphView<N,L>(props: GraphViewProps<N,L>) {
-  const {graphData, help, mouseUpHandler, defaultRenderer} = props;
-
-  const [renderer, setRenderer] = React.useState<Renderer>(defaultRenderer || "d3");
-
-  const id = React.useId();
-
-  if (mouseUpHandler) {
-    for (const node of graphData.nodes) {
-      graphvizMap.set(id+node.id, (button) => mouseUpHandler({button}, {x:0, y:0}, node));
-    }
-  }
-
-  // @ts-ignore:
-  const graphviz = <Mantine.ScrollArea style={{backgroundColor:"#eee"}}>
-      <GraphvizComponent dot={props.graphvizLayout(id, graphData)}
-      />
-    </Mantine.ScrollArea>;
-
-  return <Mantine.Stack>
-    <Mantine.Group position="center">
-      <Mantine.Tooltip withArrow openDelay={300} label={<Mantine.Text><b>D3</b>: Fast, poor layout<br/><b>Graphviz</b>: Slow, nice layout</Mantine.Text>}>
-      <Mantine.SegmentedControl
-        data={[
-          { label: 'D3' + (props.editorCallbacks ? ' (editable)' : ''), value: 'd3' },
-          { label: 'Graphviz' + (props.editorCallbacks ? ' (read-only)' : ''), value: 'graphviz' },
-        ]}
-        value={renderer}
-        onChange={setRenderer}
-      />
-      </Mantine.Tooltip>
-      {props.children}
-      {/* <Mantine.Button onClick={()=>alert(dot2)}>Get Graphviz dot</Mantine.Button> */}
-    </Mantine.Group>
-    {renderer==="d3"?
-      <InfoHoverCardOverlay contents={help}>
-        {props.editorCallbacks ?
-            <D3GraphEditable graph={graphData as D3OnionGraphData} forces={defaultGraphForces} graphState={props.editorCallbacks.graphState} setNextNodePosition={props.editorCallbacks.setNextNodePosition} onUserEdit={props.editorCallbacks.onUserEdit} />
-          : <D3Graph graph={graphData} forces={defaultGraphForces} mouseUpHandler={mouseUpHandler} />
-        }
-      </InfoHoverCardOverlay>
-      :
-      props.graphvizHelp ?
-        <InfoHoverCardOverlay contents={props.help}>{graphviz}</InfoHoverCardOverlay>
-        : graphviz
-    }
-  </Mantine.Stack>;
-}

+ 0 - 77
src/frontend/versioned_model/help_text.tsx

@@ -1,77 +0,0 @@
-import * as React from "react";
-import * as Mantine from "@mantine/core";
-
-const graphEditorLegend = <>
-  <Mantine.Divider label="Legend" labelPosition="center"/>
-  <Mantine.Text>
-    <b>Blue node</b>: Ordinary node (with GUID)<br/>
-    <b>Orange node</b>: Value node<br/>
-    <b>Arrow</b>: Edge (with label)<br/>
-  </Mantine.Text>
-</>;
-
-const rountangleEditorLegend = <>
-  <Mantine.Divider label="Legend" labelPosition="center"/>
-  <Mantine.Text>
-    <b>Rountangle</b>: Rountangle (with GUID)<br/>
-  </Mantine.Text>
-</>;
-
-export const graphEditor = <>
-  {graphEditorLegend}
-  <Mantine.Divider label="Controls" labelPosition="center"/>
-  <Mantine.Text>
-    <b>Left-Drag</b>: Drag Node<br/>
-    <b>Middle-Click</b>: Delete Node<br/>
-    <b>Right-Click</b>: Create Node<br/>
-    <b>Right-Drag</b>: Create Edge (to Node or Value)<br/>
-    <b>Wheel</b>: Zoom<br/>
-  </Mantine.Text>
-</>;
-
-export const graphEditorReadonly = <>
-  {graphEditorLegend}
-  <Mantine.Divider label="Controls (read-only mode)" labelPosition="center"/>
-  <Mantine.Text>
-    <b>Left-Drag</b>: Drag Node<br/>
-    <b>Wheel</b>: Zoom<br/>
-  </Mantine.Text>
-</>;
-
-export const rountangleEditor = <>
-  <Mantine.Divider label="Controls" labelPosition="center"/>
-  <Mantine.Text>
-    <b>Right-Click</b>: Create Rountangle<br/>
-    <b>Alt + Left-Click or Middle-Click</b>: Delete Rountangle<br/>
-    <b>Left-Drag</b>: Move/Resize Rountangle / Pan Canvas<br/>
-    <b>Wheel</b>: Zoom<br/>
-  </Mantine.Text>
-</>;
-
-export const deltaGraph = <>
-  <Mantine.Divider label="Legend" labelPosition="center"/>
-  <Mantine.Text>
-    <b>Node</b>: Delta<br/>
-    <b>Arrow</b>: Dependency<br/>
-    <b>Red line</b>: Conflict<br/>
-    Active deltas are <b>bold</b>.
-  </Mantine.Text>
-  <Mantine.Divider label="Controls" labelPosition="center"/>
-  <Mantine.Text>
-    <b>Left-Drag</b>: Drag Node<br/>
-  </Mantine.Text>
-</>;
-
-export const historyGraph = <>
-  <Mantine.Divider label="Legend" labelPosition="center"/>
-  <Mantine.Text>
-    <b>Node</b>: Version<br/>
-    <b>Arrow</b>: Parent-version-link<br/>
-    Current version is <b>bold</b>.
-  </Mantine.Text>
-  <Mantine.Divider label="Controls" labelPosition="center"/>
-  <Mantine.Text>
-    <b>Left-Drag</b>: Drag Node<br/>
-    <b>Right-Click</b>: Goto Version<br/>
-  </Mantine.Text>
-</>;

+ 0 - 228
src/frontend/versioned_model/manual_renderer.tsx

@@ -1,228 +0,0 @@
-import * as React from "react";
-import {
-  IconAlertCircle,
-  IconArrowsHorizontal,
-  IconChevronLeft,
-  IconChevronRight,
-  IconCircleCheck,
-  IconInfoCircle
-} from "@tabler/icons";
-import {Button, Group, List, Modal, Stack, Table, Text, Title} from "@mantine/core";
-
-import {RountangleEditor} from "../rountangleEditor/RountangleEditor";
-import {Version} from "onion/version";
-import {InfoHoverCardOverlay} from "../info_hover_card";
-import {D3Graph, defaultGraphForces} from "../d3graph/d3graph";
-import {D3OnionGraphData, D3GraphUpdater} from "../d3graph/reducers/onion_graph";
-import {EdgeUpdate, NodeCreation, NodeDeletion} from "onion/delta";
-import {PrimitiveDelta, ExistingEdge} from "onion/delta";
-import {DeltaRegistry} from "onion/delta_registry";
-import {PrimitiveValue} from "onion/types";
-import {GraphState, INodeState} from "onion/graph_state";
-import {Geometry2DRect, getGeometry, isInside} from "../../parser/rountangle_parser";
-
-export interface ManualRendererProps {
-  asGraph: D3OnionGraphData;
-  csGraph: D3OnionGraphData;
-
-  asGraphState: GraphState;
-  csGraphState: GraphState;
-
-  csToAs: Map<PrimitiveValue, PrimitiveValue>;
-  asToCs: Map<PrimitiveValue, PrimitiveValue>;
-
-  asDeltasToRender: PrimitiveDelta[];
-
-  done: (csDeltas: PrimitiveDelta[]) => void;
-  cancel: () => void;
-}
-
-// Replay all deltas in version to get graph state (+ D3 graph state)
-function getD3GraphState(version: Version, setGraph) {
-  const graphState = new GraphState();
-  const d3Updater = new D3GraphUpdater(setGraph, 0, 0);
-  for (const d of [...version].reverse()) {
-    graphState.exec(d, d3Updater)
-  }
-  return graphState;
-}
-
-function getInsidenesses(csGraphState: GraphState) {
-  // get every CS node and its rountangle-geometry:
-  const geometries: Array<[INodeState, Geometry2DRect]> = [];
-  for (const csNode of csGraphState.nodes.values()) {
-    const geometry = getGeometry(csGraphState, csNode.creation.id);
-    if (geometry === undefined) continue;
-    geometries.push([csNode, geometry]);
-  }
-
-  // sort by surface area, from small to large:
-  geometries.sort(([_, geomA], [__, geomB]) => (geomA.w * geomA.h) - (geomB.w * geomB.h));
-
-  // array of pairs (a,b), meaning a is inside b.
-  const insidenesses: Array<[PrimitiveValue, PrimitiveValue]> = [];
-
-  for (const [csNode1, geom1] of geometries) {
-    for (const [csNode2, geom2] of geometries) {
-      if (isInside(geom1, geom2)) {
-        insidenesses.push([csNode1.creation.id, csNode2.creation.id]);
-        break; // we're only interested in the smallest other rountangle that we're inside
-      }
-    }
-  }
-  return insidenesses;
-}
-
-export function ManualRenderer(props: ManualRendererProps) {
-  const [csGraph, setCsGraph] = React.useState<D3OnionGraphData>(props.csGraph);
-  const [csGraphState] = React.useState<GraphState>(props.csGraphState);
-  const [csDeltas, setCsDeltas] = React.useState<PrimitiveDelta[]>([]);
-
-  // Is the parent-hierarchy in CS consistent with AS?
-  function findInconsistencies(csGraphState, asGraphState) {
-    const inconsistencies: string[] = [];
-    const insidenesses = getInsidenesses(csGraphState);
-    // For every insideness in CS, there must be a parent-link in AS:
-    for (const [insideCs, outsideCs] of insidenesses) {
-      const childAs = asGraphState.nodes.get(props.csToAs.get(insideCs)!);
-      const parentAs = asGraphState.nodes.get(props.csToAs.get(outsideCs)!);
-      if (childAs.outgoing.get("hasParent") !== parentAs) {
-        inconsistencies.push("CS rountangle " + JSON.stringify(insideCs) + " must not be inside " + JSON.stringify(outsideCs));
-      }
-    }
-    // For every parent-link in AS, there must be an "insideness" in CS:
-    for (const childAs of asGraphState.nodes.values()) {
-      const parentAs = childAs.outgoing.get("hasParent");
-      if (parentAs !== undefined) {
-        const insideCsId = props.asToCs.get(childAs.creation.id)!;
-        const outsideCsId = props.asToCs.get(parentAs.creation.id)!
-        const insideGeometry = getGeometry(csGraphState, insideCsId)!;
-        const outsideGeometry = getGeometry(csGraphState, outsideCsId)!;
-        if (!isInside(insideGeometry, outsideGeometry)) {
-          inconsistencies.push("CS rountangle " + JSON.stringify(insideCsId) + " must be inside " + JSON.stringify(outsideCsId));
-        }
-      }
-    }
-    return inconsistencies;
-  }
-
-  const [inconsistencies, setInconsistencies] = React.useState<string[]>(findInconsistencies(props.csGraphState, props.asGraphState));
-
-  const [undone, setUndone] = React.useState<PrimitiveDelta[]>([]);
-
-  const changelog = props.asDeltasToRender.filter(d => !(d instanceof NodeDeletion)).map(d => {
-    if (d instanceof NodeCreation) {
-      return "Newly created rountangle " + JSON.stringify(props.asToCs.get(d.id)!) + " needs geometry";
-    }
-    // else if (d instanceof EdgeCreation) {
-    //   return "Rountangle " + JSON.stringify(props.asToCs.get(d.source.id)!) + " needs to be moved inside rountangle " + JSON.stringify((props.asToCs.get((d.target.getTarget() as NodeCreation).id)!));
-    // }
-    else if (d instanceof EdgeUpdate) {
-      const oldTarget = d.overwrites instanceof ExistingEdge ? d.overwrites.delta.target.value : null;
-      const newTarget = d.target.value;
-      const sourceId = d.overwrites.source.id;
-      if (oldTarget === null && newTarget !== null) {
-        return "Rountangle " + JSON.stringify(props.asToCs.get(sourceId)!) + " needs to be moved inside rountangle " + JSON.stringify(props.asToCs.get((newTarget as NodeCreation).id));
-      }
-      else if (oldTarget !== null && newTarget === null) {
-        return "Rountangle " + JSON.stringify(props.asToCs.get(sourceId)!) + " is no longer inside rountangle " + JSON.stringify(props.asToCs.get((oldTarget as NodeCreation).id)!);
-      }
-      else if (oldTarget !== null && newTarget !== null) {
-        return "Rountangle " + JSON.stringify(props.asToCs.get(sourceId)!) + " is no longer inside rountangle " + JSON.stringify(props.asToCs.get((oldTarget as NodeCreation).id)!) + " but instead inside rountangle " + JSON.stringify(props.asToCs.get((newTarget as NodeCreation).id)!);
-      }
-    }
-  });
-
-  return <Stack>
-    <List icon={<IconInfoCircle/>}>
-    {changelog.map((line, i) => <List.Item key={i}>{line}</List.Item>)}
-    </List>
-    <Group>
-      <Stack>
-        <Group position="apart">
-          <Title order={5}>Concrete Syntax (please adjust geometries)</Title>
-          <Group>
-            <Button disabled={csDeltas.length === 0} onClick={() => {
-              const csDelta = csDeltas[csDeltas.length-1];
-              setCsDeltas(csDeltas.slice(0,-1));
-              setUndone(undone.concat(csDelta));
-              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 D3GraphUpdater(setCsGraph, 0, 0));
-            }} compact rightIcon={<IconChevronRight/>}>Redo</Button>
-          </Group>
-        </Group>
-        <InfoHoverCardOverlay contents={<Text><b>Left-Drag</b>: Move/Resize Rountangle / Pan Canvas</Text>}>
-          <RountangleEditor
-            graph={csGraph}
-            graphState={csGraphState}
-            onUserEdit={(deltas: PrimitiveDelta[], description: string) => {
-              // the user is not allowed to create/remove rountangles, so we only respond to EdgeUpdates
-              const filteredDeltas = deltas.filter(d => d instanceof EdgeUpdate);
-              setCsDeltas(prevCsDeltas => prevCsDeltas.concat(filteredDeltas));
-              setUndone([]);
-              filteredDeltas.forEach(d => csGraphState.exec(d, new D3GraphUpdater(setCsGraph, 0, 0)));
-              setInconsistencies(findInconsistencies(csGraphState, props.asGraphState));
-            }}
-          />
-        </InfoHoverCardOverlay>
-      </Stack>
-      <Stack align="flex-start">
-        <Stack style={{height:"100%"}}>
-        <Table>
-          <caption>ID-Mapping</caption>
-          <tbody>
-          {[...props.csToAs.entries()].map(([csId, asId], i) => (
-            <tr key={i}>
-              <td>{JSON.stringify(csId)}</td>
-              <td><IconArrowsHorizontal stroke={1.2}/></td>
-              <td>{JSON.stringify(asId)}</td>
-            </tr>
-          ))}
-          </tbody>
-        </Table>
-        </Stack>
-      </Stack>
-      <Stack>
-        <Title order={5}>Abstract Syntax (read-only)</Title>
-        <InfoHoverCardOverlay contents={<Text><b>Left-Drag</b>: Drag Node</Text>}>
-          <D3Graph
-            graph={props.asGraph}
-            forces={defaultGraphForces}
-          />
-        </InfoHoverCardOverlay>
-      </Stack>
-    </Group>
-    <Group position="apart">
-      {inconsistencies.length > 0 ?
-        <List icon={<IconAlertCircle color="red"/>}>
-          {inconsistencies.map(inconsistencyText => <List.Item key={inconsistencyText}>{inconsistencyText}</List.Item>)}
-        </List>
-      :
-        <List icon={<IconCircleCheck color="green"/>}>
-          <List.Item color="green">No inconsistencies</List.Item>
-        </List>
-      }
-      <Group position="right">
-        <Button variant="subtle" onClick={() => props.cancel()}>Cancel</Button>
-        <Button onClick={() => props.done(csDeltas)} disabled={inconsistencies.length > 0}>Done</Button>
-      </Group>
-    </Group>
-    </Stack>;
-}
-
-export function ModalManualRenderer(props: {manualRendererState: ManualRendererProps | null}) {
-  return <Modal
-      opened={props.manualRendererState !== null}
-      onClose={() => props.manualRendererState?.cancel?.()}
-      title="Rendering: Missing Geometry Information"
-      size="auto"
-      centered
-    >
-    {props.manualRendererState === null ? <></> : <ManualRenderer {...props.manualRendererState}/>}
-  </Modal>;
-}

+ 0 - 243
src/frontend/versioned_model/merge_view.tsx

@@ -1,243 +0,0 @@
-import * as React from "react";
-import * as Mantine from "@mantine/core";
-import * as Icons from "@tabler/icons";
-
-import {Version} from "onion/version";
-import {Delta} from "onion/delta";
-import {DeltaParser} from "onion/delta_parser";
-import {VersionParser} from "onion/version_parser";
-import {genericGraphVizLayout, GraphView} from "./graph_view";
-import {fullVersionId, HistoryGraphState, historyGraphReducer} from "../d3graph/reducers/history_graph";
-import {InfoHoverCardOverlay} from "../info_hover_card";
-import {OnionContext, OnionContextType} from "../onion_context";
-
-const inputColor = 'seashell';
-const outputColor = 'lightblue';
-
-export const historyGraphHelpText = <>
-  <Mantine.Divider label="Legend" labelPosition="center"/>
-  <Mantine.Text>
-    <b>Node</b>: Version<br/>
-    <b>Arrow</b>: Parent-version-link<br/>
-    Current version is <b>bold</b>.<br/>
-    Selected versions are <b>yellow</b>.
-  </Mantine.Text>
-  <Mantine.Divider label="Controls" labelPosition="center"/>
-  <Mantine.Text>
-    <b>Left-Drag</b>: Drag Node<br/>
-    <b>Right-Click</b>: Goto Version<br/>
-    <Mantine.Kbd>S</Mantine.Kbd> + <b>Right-Click</b>: Select Version<br/>
-  </Mantine.Text>
-</>;
-
-export const graphvizHelpText = <>
-  <Mantine.Divider label="Legend" labelPosition="center"/>
-  <Mantine.Text>
-    <b>Node</b>: Version<br/>
-    <b>Arrow</b>: Parent-version-link<br/>
-    Current version is <b>bold</b>.<br/>
-    Selected versions are <b>yellow</b>.
-  </Mantine.Text>
-  <Mantine.Divider label="Controls" labelPosition="center"/>
-  <Mantine.Text>
-    <b>Left-Click</b>: Goto Version<br/>
-    <Mantine.Kbd>S</Mantine.Kbd> + <b>Left-Click</b>: Select Version<br/>
-  </Mantine.Text>
-</>
-
-type EmbeddingTreeNode = {
-  visible: boolean;
-  children: Array<[string, EmbeddingTreeNode]>,
-};
-
-export function MergeView({history, forces, versionRegistry, onMerge, onGoto, deltaRegistry, appendVersions, appendDelta}) {
-  const [inputs, setInputs] = React.useState<Version[]>([]);
-  const [outputs, setOutputs] = React.useState<Version[]>([]);
-  const [showTooltip, setShowTooltip] = React.useState<any>(null);
-  const [selectMode, setSelectMode] = React.useState<boolean>(false);
-  const [visibleEmbeddings, setVisibleEmbeddings] = React.useState<Array<[string, boolean]>>([]);
-
-  // Figure out all the 'guestId's in the history graph:
-  const allEmbeddings = React.useMemo(() => {
-    const all = new Set<string>();
-    for (const n of history.nodes) {
-      for (const [guestId] of n.obj.reverseEmbeddings.entries()) {
-        // here we get every possible new 'guestId'
-        all.add(guestId);
-      }
-    }
-    return all;
-  }, [history]);
-
-  // Update our state of visible/hidden embeddings according to all the guestIds in the history graph:
-  React.useEffect(() => {
-    const toAdd: Array<string> = [];
-    for (const guestId of allEmbeddings) {
-      // here we get every possible new 'guestId'
-      if (!visibleEmbeddings.find(([gId]) => gId === guestId)) {
-        toAdd.push(guestId);
-      }
-    }
-    const filtered = visibleEmbeddings
-      .filter(([guestId]) => allEmbeddings.has(guestId))
-      .concat(toAdd.map(x => [x,true] as [string,boolean]));
-    setVisibleEmbeddings(filtered);
-  }, [allEmbeddings]);
-
-  // TODO: better to move the style of D3Graph nodes/links to a separate (React state) data structure.
-  // An update of the style will then not trigger an update of the layout.
-  const historyHighlightedInputs = inputs.reduce(
-    (history, version) =>
-      historyGraphReducer(history, {type: 'highlightVersion', version, overrideColor: inputColor}),
-    history);
-
-  const historyHighlighted = outputs.reduce(
-    (history, version) =>
-      historyGraphReducer(history, {type: 'highlightVersion', version, overrideColor: outputColor}),
-    historyHighlightedInputs);
-
-  const historyFiltered = visibleEmbeddings.reduce(
-    (history, [guestId], i) => {
-      if (visibleEmbeddings[i][1]) {
-        return history;
-      }
-      else {
-        const filteredNodes = history.nodes.filter(n => n.obj.reverseEmbeddings.get(guestId) === undefined);
-        return {
-          nodes: filteredNodes,
-          links: history.links.filter(l => filteredNodes.some(n => n.obj === l.source.obj)
-                                        && filteredNodes.some(n => n.obj === l.target.obj)),
-        };
-      }
-    },
-    historyHighlighted);
-
-  const removeButton = version => (
-    <Mantine.ActionIcon size="xs" color="dark" radius="xl" variant="transparent" onClick={() => {
-      setInputs(inputs.filter(v => v !== version));
-      setOutputs([]);
-    }}>
-      <Icons.IconX size={10} />
-    </Mantine.ActionIcon>
-  );
-
-  function onKeyEvent(e) {
-    if (e.key === "s") {
-      setSelectMode(e.type === "keydown");
-    }
-  }
-
-  React.useEffect(() => {
-    window.addEventListener("keydown", onKeyEvent);
-    window.addEventListener("keyup", onKeyEvent);
-    return () => {
-      window.removeEventListener("keydown", onKeyEvent);
-      window.removeEventListener("keyup", onKeyEvent);
-    }
-  }, [])
-
-  return <>
-    <GraphView<Version,Delta> graphData={historyFiltered}
-        graphvizLayout={genericGraphVizLayout}
-        help={historyGraphHelpText} 
-        graphvizHelp={graphvizHelpText}
-        mouseUpHandler={(e, {x, y}, node) => {
-          if (node !== undefined) {
-            if (selectMode) {
-              if (inputs.includes(node.obj)) {
-                // remove from inputs
-                setInputs(inputs => inputs.filter(v => v !== node.obj));
-              }
-              else {
-                // add to inputs
-                setInputs(inputs => inputs.concat(node.obj));
-              }
-              setOutputs([]);
-            }
-            else {
-              onGoto(node.obj);
-            }
-          }
-        }}
-        defaultRenderer="graphviz">
-      <Mantine.Switch checked={selectMode} onChange={e => setSelectMode(e.currentTarget.checked)} label={<><Mantine.Kbd>S</Mantine.Kbd>elect</>}/>
-      {visibleEmbeddings.map(([guestId, visible], i) => 
-        <Mantine.Checkbox key={guestId} label={guestId} checked={visible}
-          onChange={event => {
-            const vCloned = visibleEmbeddings.slice();
-            vCloned[i] = [guestId, event.currentTarget.checked];
-            setVisibleEmbeddings(vCloned);
-          }}
-        />
-      )}
-    </GraphView>
-
-    <Mantine.Group style={{minHeight: 30}}>
-      {inputs.map(version => <Mantine.Badge key={fullVersionId(version)} pr={3} variant="outline" color="dark" style={{backgroundColor: inputColor}} rightSection={removeButton(version)}>
-        {fullVersionId(version).slice(0,8)}
-        </Mantine.Badge>)}
-      { outputs.length === 0 ? <></> :
-        <>
-          <Icons.IconArrowNarrowRight/>
-          {outputs.map(version => <Mantine.Badge key={fullVersionId(version)} variant="outline" color="dark"
-            style={{backgroundColor: outputColor, cursor: "pointer"}} onClick={() => {
-              setInputs([version]);
-              setOutputs([]);
-            }}>
-              {fullVersionId(version).slice(0,8)}
-            </Mantine.Badge>)}
-        </> }
-    </Mantine.Group>
-    <Mantine.Group grow>
-      <Mantine.Button compact variant="outline" leftIcon={<Icons.IconX/>} disabled={inputs.length===0 && outputs.length===0} onClick={() => {
-        setInputs([]);
-        setOutputs([]);
-      }}>Clear Selection</Mantine.Button>
-
-      <Mantine.Button compact leftIcon={<Icons.IconArrowMerge/>} onClick={() => {
-        const outputs = versionRegistry.crazyMerge(inputs, d => d.description);
-        setOutputs(outputs);
-        onMerge(outputs);
-      }}>Merge</Mantine.Button>
-      <Mantine.Button compact leftIcon={<Icons.IconDatabaseImport/>} onClick={() => {
-        let parsed;
-        while (true) {
-          const toImport = prompt("Versions to import (JSON)", "[]");
-          if (toImport === null) {
-            return; // 'cancel'
-          }
-          try {
-            parsed = JSON.parse(toImport);
-            break;
-          } catch (e) {
-            alert("Invalid JSON");
-          }
-          // ask again ...
-        }
-
-        const deltaParser = new DeltaParser(deltaRegistry);
-        const versionParser = new VersionParser(deltaParser, versionRegistry);
-
-        versionParser.load(parsed, delta => {
-          appendDelta(delta);
-        }, version => {
-          appendVersions([version]);
-        });
-      }}
-      >Import</Mantine.Button>
-      <Mantine.Tooltip label="Copied to clipboard!" opened={showTooltip !== null} withArrow>
-        <Mantine.Button compact leftIcon={<Icons.IconDatabaseExport/>} disabled={inputs.length!==1} onClick={() => {
-          const type = "application/json";
-          const blob = new Blob([], {type});
-          console.log(inputs[0].serialize());
-          navigator.clipboard.writeText(
-
-            JSON.stringify(inputs[0].serialize(), null, 2)
-          );
-          if (showTooltip !== null) clearTimeout(showTooltip);
-          setShowTooltip(setTimeout(()=>setShowTooltip(null), 1500));
-        }}>Export</Mantine.Button>
-      </Mantine.Tooltip>
-    </Mantine.Group>
-  </>
-}

+ 0 - 373
src/frontend/versioned_model/single_model.tsx

@@ -1,373 +0,0 @@
-import * as React from "react";
-import * as Mantine from "@mantine/core";
-import * as Icons from "@tabler/icons";
-
-import {D3OnionGraphData, D3GraphUpdater} from "../d3graph/reducers/onion_graph";
-import {D3GraphEditable, UserEditCallback} from "../d3graph/d3graph_editable";
-
-import {
-  DeltaGraphState,
-  fullDeltaId,
-  deltaGraphReducer,
-} from "../d3graph/reducers/delta_graph";
-
-import {
-  HistoryGraphState,
-  initialHistoryGraph,
-  fullVersionId,
-  historyGraphReducer,
-} from "../d3graph/reducers/history_graph";
-
-import * as helpText from "./help_text";
-import {MergeView} from "./merge_view";
-import {GraphView, Renderer, genericGraphVizLayout, objectLikeGraphVizLayout} from "./graph_view";
-
-import {D3Graph, emptyGraph, defaultGraphForces} from "../d3graph/d3graph";
-import {RountangleEditor} from "../rountangleEditor/RountangleEditor";
-import {InfoHoverCardOverlay} from "../info_hover_card";
-import {OnionContext, OnionContextType} from "../onion_context";
-
-import {Version, VersionRegistry, Embeddings} from "onion/version";
-import {PrimitiveDelta, Transaction, findTxDependencies} from "onion/delta";
-import {DeltaRegistry} from "onion/delta_registry";
-import {PrimitiveValue, UUID} from "onion/types";
-import {GraphState} from "onion/graph_state"; 
-import {Delta} from "onion/delta";
-import {DeltaParser} from "onion/delta_parser";
-
-export const undoButtonHelpText = "Use the Undo/Redo buttons or the History panel to navigate to any version.";
-
-export interface VersionedModelState {
-  version: Version; // the 'current version'
-  graph: D3OnionGraphData; // the state what is displayed in the leftmost panel
-  historyGraph: HistoryGraphState; // the state of what is displayed in the middle panel
-  deltaGraphL1: DeltaGraphState; // the state of what is displayed in the rightmost panel
-  deltaGraphL0: DeltaGraphState; // the state of what is displayed in the rightmost panel
-}
-
-interface VersionedModelCallbacks {
-  onUserEdit?: UserEditCallback;
-  onUndoClicked?: (parentVersion: Version, deltaToUndo: Delta) => void;
-  onRedoClicked?: (childVersion: Version, deltaToRedo: Delta) => void;
-  onVersionClicked?: (Version) => void;
-  onMerge?: (outputs: Version[]) => void;
-}
-
-// Basically everything we need to construct the React components for:
-//  - Graph state (+ optionally, a Rountangle Editor)
-//  - History graph (+undo/redo buttons)
-//  - Delta graph
-// , their state, and callbacks for updating their state.
-export function newOnion({readonly, deltaRegistry, versionRegistry}) {
-  const graphState = new GraphState();
-
-  // SVG coordinates to be used when adding a new node
-  let x = 0;
-  let y = 0;
-
-  // The "current version" is both part of the React state (for rendering undo/redo buttons) and a local variable here, such that we can get the current version (synchronously), even outside of a setState-callback.
-  let currentVersion = versionRegistry.initialVersion;
-
-  function useOnion(overridenCallbacks: (any) => VersionedModelCallbacks) {
-    const [version, setVersion] = React.useState<Version>(versionRegistry.initialVersion);
-    const [graph, setGraph] = React.useState<D3OnionGraphData>(emptyGraph);
-    const [historyGraph, setHistoryGraph] = React.useState<HistoryGraphState>(initialHistoryGraph(versionRegistry.initialVersion));
-    const [deltaGraphL1, setDeltaGraphL1] = React.useState<DeltaGraphState>(emptyGraph);
-    const [deltaGraphL0, setDeltaGraphL0] = React.useState<DeltaGraphState>(emptyGraph);
-
-    // Reducer
-
-    // Create and add a new version, and its deltas, without changing the current version
-    const createVersion = (deltas: PrimitiveDelta[], description: string, parentHash: Buffer, embeddings: (Version) => Embeddings = () => new Map()) => {
-
-      const parentVersion = versionRegistry.lookupOptional(parentHash);
-      if (parentVersion !== undefined) {
-        // The following is not very efficient, and it looks weird and hacky, but it works.
-        // Cleaner looking solution would be to implement a function version.getGraphState() ...
-        let prevVersion = currentVersion;
-        gotoVersion(parentVersion);
-        const dependencies = findTxDependencies(deltas, graphState.deltas);
-        const tx = deltaRegistry.newTransaction(deltas, description, dependencies);
-        gotoVersion(prevVersion); // go back
-
-        const newVersion = versionRegistry.createVersion(parentVersion, tx, embeddings);
-
-        setHistoryGraph(historyGraph => historyGraphReducer(historyGraph, {type: 'addVersion', version: newVersion}));
-        setDeltaGraphL1(deltaGraphL1 => tx.deltas.length > 0 ? deltaGraphReducer(deltaGraphL1, {type: 'addDelta', delta: tx, active: false}) : deltaGraphL1);
-        setDeltaGraphL0(deltaGraphL0 => tx.deltas.reduce((graph, delta) => deltaGraphReducer(graph, {type: 'addDelta', delta, active: false}), deltaGraphL0));
-
-        return newVersion;
-      }
-    };
-    const createAndGotoNewVersion = (deltas: PrimitiveDelta[], description: string, parentVersion: Version = currentVersion, embeddings: (Version) => Embeddings = () => new Map()): Version => {
-      const newVersion = createVersion(deltas, description, parentVersion.hash, embeddings) as Version;
-      gotoVersion(newVersion);
-      return newVersion;
-    };
-    // Idempotent
-    const appendVersions = (versions: Version[]) => {
-      setHistoryGraph(historyGraph => {
-        const versionsToAdd: Version[] = [];
-        const addIfDontHaveYet = version => {
-          if (!historyGraph.nodes.some(n => n.obj === version) && !versionsToAdd.includes(version)) {
-            collectVersions(version);
-          }
-        }
-        const collectVersions = version => {
-          // first add parent, then child
-          // this prevents infinite recursion when a version explicitly embeds itself.
-          for (const [parent] of version.parents) {
-            addIfDontHaveYet(parent);
-          }
-          for (const {version: guest} of version.embeddings.values()) {
-            if (guest === version) {
-              continue; // special case: self-embedding...
-            }
-            addIfDontHaveYet(guest);
-          }
-          versionsToAdd.push(version);
-        }
-        for (const v of versions) {
-          collectVersions(v);
-        }
-        return versionsToAdd.reduce((historyGraph, version) => historyGraphReducer(historyGraph, {type: 'addVersion', version}), historyGraph);
-      })
-    }
-    // Idempotent
-    const appendDelta = (delta: Transaction) => {
-      setDeltaGraphL0(deltaGraphL0 => delta.deltas.reduce((graph, delta) => deltaGraphReducer(graph, {type: 'addDelta', delta, active: false}), deltaGraphL0));
-      setDeltaGraphL1(deltaGraphL1 => deltaGraphReducer(deltaGraphL1, {type: 'addDelta', delta, active: false}));
-    }
-
-    const undoWithoutUpdatingHistoryGraph = (deltaToUndo) => {
-      const d3Updater = new D3GraphUpdater(setGraph, x, y);
-      graphState.unexec(deltaToUndo, d3Updater);
-
-      setDeltaGraphL0(deltaGraphL0 => deltaToUndo.deltas.reduce((deltaGraphL0, delta) => deltaGraphReducer(deltaGraphL0, {type: 'setDeltaInactive', delta}), deltaGraphL0));
-      setDeltaGraphL1(deltaGraphL1 => deltaGraphReducer(deltaGraphL1, {type: 'setDeltaInactive', delta: deltaToUndo}));
-    };
-    const redoWithoutUpdatingHistoryGraph = (deltaToRedo) => {
-      const d3Updater = new D3GraphUpdater(setGraph, x, y);
-      graphState.exec(deltaToRedo, d3Updater);
-
-      setDeltaGraphL0(deltaGraphL0 => deltaToRedo.deltas.reduce((deltaGraphL0, delta) => deltaGraphReducer(deltaGraphL0, {type: 'setDeltaActive', delta}), deltaGraphL0));
-      setDeltaGraphL1(deltaGraphL1 => deltaGraphReducer(deltaGraphL1, {type: 'setDeltaActive', delta: deltaToRedo}));
-    };
-    const undo = (parentVersion, deltaToUndo) => {
-      undoWithoutUpdatingHistoryGraph(deltaToUndo);
-      currentVersion = parentVersion;
-
-      setVersion(prevVersion => {
-        setHistoryGraph(historyGraph => historyGraphReducer(historyGraphReducer(historyGraph,
-              {type: 'highlightVersion', version: prevVersion, bold: false}),
-              {type: 'highlightVersion', version: parentVersion, bold: true}));
-        return parentVersion;
-      });
-    };
-    const redo = (childVersion, deltaToRedo) => {
-      redoWithoutUpdatingHistoryGraph(deltaToRedo);
-      currentVersion = childVersion;
-
-      setVersion(prevVersion => {
-        setHistoryGraph(historyGraph => historyGraphReducer(historyGraphReducer(historyGraph,
-            {type: 'highlightVersion', version: prevVersion, bold: false}),
-            {type: 'highlightVersion', version: childVersion, bold: true}));
-        return childVersion;
-      });
-    };
-    const gotoVersion = (chosenVersion: Version) => {
-      const path = currentVersion.findPathTo(chosenVersion);
-      if (path === undefined) {
-        throw new Error("Could not find path to version!");
-      }
-      for (const [linkType, delta] of path) {
-        if (linkType === 'p') {
-          undoWithoutUpdatingHistoryGraph(delta);
-        }
-        else if (linkType === 'c') {
-          redoWithoutUpdatingHistoryGraph(delta);
-        }
-      }
-      currentVersion = chosenVersion;
-      setVersion(prevVersion => {
-        setHistoryGraph(historyGraph => historyGraphReducer(historyGraphReducer(historyGraph,
-            {type: 'highlightVersion', version: prevVersion, bold: false}),
-            {type: 'highlightVersion', version: chosenVersion, bold: true}));
-        return chosenVersion;
-      });
-    };
-
-    const reducer = {
-      createVersion,
-      gotoVersion,
-      createAndGotoNewVersion,
-      appendVersions,
-      appendDelta,
-      undo,
-      redo,
-    };
-
-    // Components
-
-    const defaultCallbacks = {
-      onUserEdit: createAndGotoNewVersion,
-      onUndoClicked: undo,
-      onRedoClicked: redo,
-      onVersionClicked: gotoVersion,
-      onMerge: appendVersions,
-    };
-
-    const callbacks = Object.assign({}, defaultCallbacks, overridenCallbacks(reducer));
-
-    const graphStateComponent = <GraphView
-      defaultRenderer={readonly ? "graphviz" : "d3"}
-      graphvizLayout={objectLikeGraphVizLayout}
-      graphData={graph}
-      help={readonly ? helpText.graphEditorReadonly: helpText.graphEditor}
-      mouseUpHandler={()=>{}}
-      editorCallbacks={readonly ? undefined : {
-        graphState,
-        setNextNodePosition: (newX,newY) => {x = newX; y = newY;},
-        onUserEdit: callbacks.onUserEdit,
-      }}
-    />;
-
-    const deltaComponentProps = {
-      graphvizLayout: genericGraphVizLayout,
-      help: helpText.deltaGraph,
-      defaultRenderer: ('graphviz' as Renderer),
-      mouseUpHandler: (e, {x,y}, node) => {
-        if (node) {
-          alert(JSON.stringify(node.obj.serialize(), null, 2));
-        }
-      },
-    };
-    const deltaGraphL0Component = <GraphView<Delta,null> graphData={deltaGraphL0} {...deltaComponentProps} />;
-    const deltaGraphL1Component = <GraphView<Delta,null> graphData={deltaGraphL1} {...deltaComponentProps} />;
-
-    const historyComponentWithMerge = <MergeView
-      history={historyGraph}
-      forces={defaultGraphForces}
-      versionRegistry={versionRegistry}
-      onMerge={outputs => callbacks.onMerge?.(outputs)}
-      onGoto={version => callbacks.onVersionClicked?.(version)}
-      appendVersions={reducer.appendVersions}
-      appendDelta={reducer.appendDelta}
-      {...{deltaRegistry, createVersion}}
-    />;
-
-    const rountangleEditor = <InfoHoverCardOverlay contents={helpText.rountangleEditor}>
-      <RountangleEditor
-        graph={graph}
-        graphState={graphState}
-        onUserEdit={callbacks.onUserEdit}
-      />
-    </InfoHoverCardOverlay>;
-
-    const makeUndoOrRedoButton = (parentsOrChildren, text, leftIcon?, rightIcon?, callback?) => {
-      if (parentsOrChildren.length === 0) {
-        return <Mantine.Button compact leftIcon={leftIcon} rightIcon={rightIcon} disabled>{text}</Mantine.Button>;
-      }
-      if (parentsOrChildren.length === 1) {
-        return <Mantine.Button compact leftIcon={leftIcon} rightIcon={rightIcon} onClick={callback?.bind(null, parentsOrChildren[0][0], parentsOrChildren[0][1])}>{text}</Mantine.Button>;
-      }
-      return <Mantine.Menu shadow="md" position="bottom-start" trigger="hover" offset={0} transitionProps={{duration:0}}>
-          <Mantine.Menu.Target>
-            <Mantine.Button compact leftIcon={leftIcon} rightIcon={rightIcon}>{text} ({parentsOrChildren.length.toString()})</Mantine.Button>
-          </Mantine.Menu.Target>
-          <Mantine.Menu.Dropdown>
-            {parentsOrChildren.map(([parentOrChildVersion,deltaToUndoOrRedo]) =>
-              <Mantine.Menu.Item key={fullDeltaId(deltaToUndoOrRedo)} onClick={callback?.bind(null, parentOrChildVersion, deltaToUndoOrRedo)}>{deltaToUndoOrRedo.description}</Mantine.Menu.Item>)}
-          </Mantine.Menu.Dropdown>
-        </Mantine.Menu>;
-
-    }
-    const undoButton = makeUndoOrRedoButton(version.parents, "Undo", <Icons.IconChevronLeft/>, null, callbacks.onUndoClicked);
-    const redoButton = makeUndoOrRedoButton(version.children, "Redo", null, <Icons.IconChevronRight/>, callbacks.onRedoClicked);
-
-    const undoRedoButtons = <>
-      {undoButton}
-      <Mantine.Space w="sm"/>
-      {redoButton}
-    </>;
-
-    const stackedUndoButtons = version.parents.map(([parentVersion,deltaToUndo]) => {
-      return (
-        <div key={fullVersionId(parentVersion)}>
-          <Mantine.Button fullWidth={true} compact={true} leftIcon={<Icons.IconChevronLeft size={18}/>} onClick={callbacks.onUndoClicked?.bind(null, parentVersion, deltaToUndo)}>
-            UNDO {deltaToUndo.description}
-          </Mantine.Button>
-          <Mantine.Space h="xs"/>
-        </div>
-      );
-    });
-    const stackedRedoButtons = version.children.map(([childVersion,deltaToRedo]) => {
-      return (
-        <div key={fullVersionId(childVersion)}>
-          <Mantine.Button style={{width: "100%"}} compact={true} rightIcon={<Icons.IconChevronRight size={18}/>} onClick={callbacks.onRedoClicked?.bind(null, childVersion, deltaToRedo)}>
-            REDO {deltaToRedo.description}
-          </Mantine.Button>
-          <Mantine.Space h="xs"/>
-        </div>
-      );
-    });
-    const stackedUndoRedoButtons = (
-      <Mantine.SimpleGrid cols={2}>
-        <div>{stackedUndoButtons}</div>
-        <div>{stackedRedoButtons}</div>
-      </Mantine.SimpleGrid>
-    );
-    const makeTabs = (defaultTab: string, tabs: string[]) => {
-      return <Mantine.Tabs defaultValue={defaultTab} keepMounted={false}>
-        <Mantine.Tabs.List grow>
-          {tabs.map(tab => ({
-            editor: <Mantine.Tabs.Tab key={tab} value={tab}>Editor</Mantine.Tabs.Tab>,
-            state:  <Mantine.Tabs.Tab key={tab} value={tab}>State</Mantine.Tabs.Tab>,
-            merge: <Mantine.Tabs.Tab key={tab} value={tab}>History</Mantine.Tabs.Tab>,
-            deltaL1: <Mantine.Tabs.Tab key={tab} value={tab}>Deltas (L1)</Mantine.Tabs.Tab>,
-            deltaL0: <Mantine.Tabs.Tab key={tab} value={tab}>Deltas (L0)</Mantine.Tabs.Tab>,
-          }[tab]))}
-        </Mantine.Tabs.List>
-        <Mantine.Tabs.Panel value="state">
-          {graphStateComponent}
-        </Mantine.Tabs.Panel>
-        <Mantine.Tabs.Panel value="editor">
-          {rountangleEditor}
-        </Mantine.Tabs.Panel>
-        <Mantine.Tabs.Panel value="deltaL1">
-          {deltaGraphL1Component}
-        </Mantine.Tabs.Panel>
-        <Mantine.Tabs.Panel value="deltaL0">
-          {deltaGraphL0Component}
-        </Mantine.Tabs.Panel>
-        <Mantine.Tabs.Panel value="merge">
-          {historyComponentWithMerge}
-        </Mantine.Tabs.Panel>
-      </Mantine.Tabs>;
-    }
-
-    return {
-      state: {
-        version,
-      },
-      reducer,
-      components: {
-        graphStateComponent,
-        rountangleEditor,
-        deltaGraphL1Component,
-        deltaGraphL0Component,
-        historyComponentWithMerge,
-        undoButton,
-        redoButton,
-        undoRedoButtons,
-        stackedUndoRedoButtons,
-        makeTabs,
-      },
-    };
-  }
-
-  return {
-    graphState,
-    useOnion,
-  }
-}

+ 0 - 206
src/onion/legacy/delta.ts.legacy

@@ -1,206 +0,0 @@
-import {inspect} from "util"; // NodeJS library
-
-export abstract class Delta {
-  // Get deltas that this delta depends on.
-  abstract getDependencies(): Array<Delta>;
-
-  // Get deltas that this delta depends on + the type of each dependency.
-  abstract getTypedDependencies(): Array<[Delta, string]>;
-
-  // Get deltas that are conflicting with this delta.
-  abstract getConflicts(): Array<Delta>;
-
-  // Get an ID that is unique to the VALUE (attributes and dependencies) of this Delta.
-  // Meaning: if two deltas are identical (they do exactly the same thing, and they have the exact same dependencies), they have the same ID.
-  // Returned value must be 8 bytes (256 bits). SHA-256 hashing is used internally.
-  abstract getHash(): Buffer;
-
-  // A short text string that summarizes the delta.
-  // For visualization purposes.
-  abstract getDescription(): string;
-
-  // Result must survive a JSON round-trip.
-  abstract serialize(): any;
-
-  // Get all primitive deltas that this delta consists of.
-  abstract iterPrimitiveDeltas(): Iterable<PrimitiveDelta>;
-
-  abstract getLevel(): number;
-
-  // Inverse dependency
-  partOf: Array<Transaction> = []; // append-only
-}
-
-// All non-Transaction types of Delta
-export abstract class PrimitiveDelta extends Delta {
-}
-
-export function isConflicting(a: Delta, b: Delta) {
-  return a.conflictsWith.includes(b);
-}
-
-// Yields all elements of 'it' that are conflicting with 'd'.
-export function* iterConflicts(d: Delta, it: Iterable<Delta>) {
-  for (const conflictsWith of d.conflictsWith) {
-    for (const otherDelta of it) {
-      if (conflictsWith === otherDelta) {
-        yield conflictsWith;
-      }
-    }
-  }
-}
-
-export function* iterMissingDependencies(d: Delta, it: Iterable<Delta>) {
-  for (const dep of d.getDependencies()) {
-    let found = false;
-    for (const otherDelta of it) {
-      if (dep === otherDelta) {
-        found = true;
-        break;
-      }
-    }
-    if (!found) {
-      yield dep;
-    }
-  }
-}
-
-
-export class Transaction extends Delta {
-  // Dependencies
-  deltas: Array<Delta>;
-  dependencies: Array<Transaction>;
-
-  conflicts: Array<Transaction>;
-
-  // Inverse dependencies
-  inverseDependencies: Array<Transaction> = []; // append-only
-
-  readonly hash: Buffer;
-  readonly description: string;
-  readonly lvl: number;
-
-  constructor(hash: Buffer, deltas: Array<Delta>, dependencies: Array<Transaction>, description: string) {
-    super();
-    this.hash = hash;
-    this.deltas = deltas;
-    this.dependencies = dependencies;
-    this.description = description;
-
-    // Figure out lvl
-    let maxLvl = 0;
-    for (const d of deltas) {
-      maxLvl = Math.max(maxLvl, d.getLevel());
-    }
-    this.lvl = maxLvl + 1;
-
-    // Figure out conflicts
-    this.conflicts = [];
-    for (const delta of deltas) {
-      for (const conflictingDelta of delta.conflictsWith) {
-        if (deltas.includes(conflictingDelta)) {
-          // console.log("Conflict between", conflictingDelta, "and", delta);
-          throw new Error("Cannot create a composite delta out of conflicting deltas");
-        }
-        const conflictingTxs = conflictingDelta.partOf;
-        for (const otherTx of conflictingTxs) {
-          if (!this.conflicts.includes(otherTx)) {
-            this.conflicts.push(otherTx);
-            otherTx.conflicts.push(this);
-          }
-        }
-      }
-    }
-
-    if (dependencies.some(dependency => this.conflicts.includes(dependency))) {
-      throw new Error("Assertion failed: Transaction depends on another conflicting transaction.");
-    }
-
-    // Inverse dependencies
-    for (const delta of deltas) {
-      delta.partOf.push(this);
-    }
-    for (const dep of dependencies) {
-      dep.inverseDependencies.push(this);
-    }
-  }
-
-  getDependencies(): Array<Delta> {
-    return this.dependencies;
-    // return this.deltas.concat(this.dependencies);
-  }
-
-  getTypedDependencies(): Array<[Delta, string]> {
-    return this.dependencies.map(d => [d, ""] as [Delta,string]);
-    // return this.deltas.map(d => [d, "TX"] as [Delta,string])
-    //   .concat(this.dependencies.map(d => [d, ""] as [Delta,string]));
-  }
-
-  getConflicts(): Array<Delta> {
-    return [];
-  }
-
-  getHash(): Buffer {
-    return this.hash;
-  }
-
-  getDescription(): string {
-    return this.description;
-  }
-
-  // pretty print to console under NodeJS
-  [inspect.custom](depth: number, options: object) {
-    return "Tx(" + this.deltas.map(d => d[inspect.custom](0, options)).join(',') + ")";
-  }
-
-  toString(): string {
-    return this[inspect.custom](0, {});
-  }
-
-  serialize(): any {
-    return {
-      hash: this.hash.toString('base64'),
-      type: "Transaction",
-      description: this.description,
-      deltas: this.deltas.map(d => d.getHash().toString('base64')),
-      dependencies: this.dependencies.map(d => d.getHash().toString('base64')),
-    }
-  }
-
-  *iterPrimitiveDeltas(): Iterable<Delta> {
-    for (const d of this.deltas) {
-      yield* d.iterPrimitiveDeltas();
-    }
-  }
-
-  getLevel() {
-    return this.lvl;
-  }
-}
-
-// Given a set of deltas that we are trying to glue together in a (new) transaction, what other transactions should this new transaction depend on?
-// Argument 'currentDeltas' is a set of deltas to be considered as possible dependencies. Typically you only want to consider the deltas that make up the current version. This is decide which transaction to depend on, if a delta is contained by multiple transactions. If this argument is left undefined, then an error will be thrown if one of the deltas is contained by multiple transactions.
-export function findTxDependencies(deltas: Array<Delta>, candidates?: Set<Delta>): Array<Transaction> {
-  const dependencies: Array<Transaction> = [];
-  for (const delta of deltas) {
-    for (const dependency of delta.getDependencies()) {
-      if (!deltas.includes(dependency)) {
-        const txs = dependency.partOf;
-        const filteredTxs = candidates !== undefined ?
-           txs.filter(d => candidates!.has(d)) : txs;
-        if (txs.length > 1) {
-          throw new Error("Error: One of the composite's dependencies is contained by multiple composites.");
-        }
-        if (txs.length === 0) {
-          // throw new Error("Assertion failed: delta " + delta.description + " depends on " + dependency.description + " but this dependency could not be found in a composite.");
-          continue;
-        }
-        const [tx] = filteredTxs;
-        if (!dependencies.includes(tx)) {
-          dependencies.push(tx);
-        }
-      }
-    }
-  }
-  return dependencies;
-}

+ 0 - 75
src/onion/legacy/delta_parser.ts.legacy

@@ -1,75 +0,0 @@
-import {Delta, PrimitiveDelta} from "./delta";
-
-import {
-  NodeCreation,
-  NodeDeletion,
-  EdgeCreation,
-  EdgeUpdate,
-  EdgeTargetType,
-} from "./delta";
-
-import {DeltaRegistry} from "./delta_registry";
-
-
-import {UUID} from "./types";
-
-export class DeltaParser {
-  readonly deltaRegistry: DeltaRegistry;
-
-  constructor(deltaRegistry) {
-    this.deltaRegistry = deltaRegistry;
-  }
-
-  private getDependency<T>(hash): T {
-    const result = this.deltaRegistry.deltas.get(hash);
-    if (result === undefined) throw new Error("Could not dependency: " + hash);
-    return result as T;
-  }
-
-  loadEdgeTarget({type, ...rest}): EdgeTargetType {
-    if (type === "value") {
-      const {value} = rest;
-      return value;
-    }
-    if (type === "node") {
-      const {creation} = rest;
-      return this.getDependency<NodeCreation>(creation);
-    }
-    throw new Error("Unknown edge target type: " + type);
-  }
-
-  loadDelta({type, ...rest}): Delta {
-    if (type === "NodeCreation") {
-      const {id} = rest;
-      return this.deltaRegistry.newNodeCreation(new UUID(id));
-    }
-    if (type === "EdgeCreation") {
-      const {source, label, target} = rest;
-      return this.deltaRegistry.newEdgeCreation(
-        this.getDependency<NodeCreation>(source),
-        label,
-        this.loadEdgeTarget(target));
-    }
-    if (type === "EdgeUpdate") {
-      const {overwrites, target} = rest;
-      return this.deltaRegistry.newEdgeUpdate(
-        this.getDependency<EdgeCreation|EdgeUpdate>(overwrites),
-        this.loadEdgeTarget(target));
-    }
-    if (type === "NodeDeletion") {
-      const {creation, deletedOutgoingEdges, afterIncomingEdges} = rest;
-      return this.deltaRegistry.newNodeDeletion(
-        this.getDependency<NodeCreation>(creation),
-        deletedOutgoingEdges.map(d => this.getDependency<EdgeCreation|EdgeUpdate>(d)),
-        afterIncomingEdges.map(d => this.getDependency<EdgeUpdate|NodeDeletion>(d)));
-    }
-    if (type === "Transaction") {
-      const {type, deltas, dependencies, description} = rest;
-      return this.deltaRegistry.newTransaction(
-        deltas.map(d => this.getDependency<Delta>(d)),
-        description,
-        dependencies.map(d => this.getDependency<Delta>(d)));
-    }
-    throw new Error("Unknown delta type: " + type);
-  }
-}

+ 0 - 82
src/onion/legacy/delta_registry.ts.legacy

@@ -1,82 +0,0 @@
-import {Delta, Transaction, findTxDependencies} from "./delta";
-import {NodeCreation, NodeDeletion, EdgeCreation, EdgeUpdate, EdgeTargetType} from "./delta";
-import {UUID} from "./types";
-import {createHash} from "crypto";
-import {Buffer} from "buffer";
-import {bufferXOR} from "./buffer_xor";
-
-// Ensures that deltas with the same hash are only created once.
-export class DeltaRegistry {
-  deltas: Map<string, Delta> = new Map();
-
-  // Given the expected hash 
-  private createIdempotent<T extends Delta>(hash: Buffer, callback: () => T): T {
-    const base64 = hash.toString('base64');
-    return this.deltas.get(base64) as T || (() => {
-      const delta = callback();
-      this.deltas.set(base64, delta);
-      return delta;
-    })();
-  }
-
-  newNodeCreation(id: UUID): NodeCreation {
-    const hash = createHash('sha256')
-      .update(JSON.stringify.id)) // prevent collisions between 'true' (actual boolean) and '"true"' (string "true"), or 42 (number) and "42" (string)
-      .digest();
-    return this.createIdempotent(hash, () => new NodeCreation(hash, id));
-  }
-
-  newNodeDeletion(creation: NodeCreation, deletedOutgoingEdges: Array<EdgeCreation|EdgeUpdate>, afterIncomingEdges: Array<EdgeUpdate|NodeDeletion>): NodeDeletion {
-    // hash will be calculated based on all EdgeCreations/EdgeUpdates/NodeDeletions that we depend on, by XOR (insensitive to array order).
-    let hash = createHash('sha256').update(creation.hash);
-    let union = Buffer.alloc(32); // all zeroes - neutral element for XOR
-    for (const deletedOutgoing of deletedOutgoingEdges) {
-      union = bufferXOR(union, deletedOutgoing.getHash());
-    }
-    for (const afterIncoming of afterIncomingEdges) {
-      union = bufferXOR(union, afterIncoming.getHash());
-    }
-    const buf = hash.update(union).digest();
-    return this.createIdempotent(buf, () => new NodeDeletion(buf, creation, deletedOutgoingEdges, afterIncomingEdges));
-  }
-
-  newEdgeCreation(source: NodeCreation, label: string, target: EdgeTargetType): EdgeCreation {
-    const hash = createHash('sha256')
-      .update(source.hash)
-      .update('create').update(label)
-      .update('target=').update(targetToHash(target))
-      .digest();
-    return this.createIdempotent(hash, () => new EdgeCreation(hash, source, label, target));
-  }
-
-  newEdgeUpdate(overwrites: EdgeCreation | EdgeUpdate, target: EdgeTargetType): EdgeUpdate {
-    const hash = createHash('sha256')
-      .update(overwrites.hash)
-      .update('target=').update(targetToHash(target))
-      .digest();
-    return this.createIdempotent(hash, () => new EdgeUpdate(hash, overwrites, target));
-  }
-
-  newTransaction(deltas: Array<Delta>, description: string, dependencies: Array<Transaction> = findTxDependencies(deltas)): Transaction {
-    // XOR of hashes of deltas
-    const xor = deltas.reduce((buf, delta) => bufferXOR(delta.getHash(), buf), Buffer.alloc(32));
-    let hash = createHash('sha256')
-      .update('tx')
-      .update(xor);
-    for (const d of dependencies) {
-      hash = hash.update(d.hash);
-    }
-    const buf = hash.digest();
-    return this.createIdempotent(buf, () => new Transaction(buf, deltas, dependencies, description));
-  }
-}
-
-function targetToHash(target: EdgeTargetType): any {
-  // just needs to return something "unique" to feed into the hash function
-  if (target instanceof NodeCreation) {
-    return target.hash;
-  }
-  else {
-    return "value" + JSON.stringify(target);
-  }
-}

+ 0 - 635
src/onion/legacy/primitive_delta.ts.legacy

@@ -1,635 +0,0 @@
-import * as _ from "lodash";
-import {inspect} from "util";
-import {UUID, PrimitiveValue} from "./types";
-import {Delta, PrimitiveDelta} from "./delta";
-
-export class NodeCreation extends Delta {
-  readonly id: UUID;
-  readonly hash: Buffer;
-  readonly description: string;
-
-  // Inverse dependency: Deletions of this node.
-  deletions: Array<NodeDeletion> = []; // append-only
-
-  // Inverse dependency: Creation outgoing edges.
-  outgoingEdges: Array<EdgeCreation> = []; // append-only
-
-  // Inverse dependency: All the times this node was the target of an edge update.
-  incomingEdges: Array<EdgeCreation | EdgeUpdate> = []; // append-only
-
-  constructor(hash: Buffer, id: UUID) {
-    super();
-    this.hash = hash;
-    this.id = id;
-    this.description = "NEW("+this.id.toString().slice(0,8)+")";
-  }
-
-  getDependencies(): [] {
-    return [];
-  }
-
-  getTypedDependencies(): [] {
-    return [];
-  }
-
-  getConflicts(): [] {
-    return [];
-  }
-
-  getHash(): Buffer {
-    return this.hash;
-  }
-
-  getDescription(): string {
-    return this.description;
-  }
-
-  // pretty print to console under NodeJS
-  [inspect.custom](depth: number, options: object) {
-    return "NodeCreation{" + inspect(this.id, options) + "}";
-  }
-
-  toString(): string {
-    return this[inspect.custom](0, {});
-  }
-
-  serialize(): any {
-    return {
-      hash: this.hash.toString('base64'),
-      type: "NodeCreation",
-      id: this.id,
-    }
-  }
-
-  *iterPrimitiveDeltas(): Iterable<NodeCreation> {
-    yield this;
-  }
-
-  getLevel() {
-    return 0;
-  }
-}
-
-export class NodeDeletion extends Delta {
-  readonly hash: Buffer;
-  readonly description: string;
-
-  // Dependency: The node being deleted.
-  readonly creation: NodeCreation;
-
-  // Dependency: All outgoing edges of the deleted node must be deleted also.
-  readonly deletedOutgoingEdges: Array<EdgeCreation | EdgeUpdate>;
-
-  // Dependency: For every time the deleted node was target of an edge, the deletion depends on the EdgeUpdate that sets this edge to have a different target.
-  readonly afterIncomingEdges: Array<EdgeUpdate | NodeDeletion>;
-
-  // Conflicts: Concurrent deletion of the same node.
-  deleteConflicts: Array<NodeDeletion> = [];
-
-  // Conflicts: Concurrent creation of an edge with as source the deleted node.
-  edgeSourceConflicts: Array<EdgeCreation> = [];
-
-  // Conflicts: Concurrent update and deletion of an edge (because its source node is deleted).
-  updateConflicts: Array<EdgeUpdate | NodeDeletion> = [];
-
-  // Conflicts: Concurrent creation/update of an edge with as target the deleted node.
-  edgeTargetConflicts: Array<EdgeCreation | EdgeUpdate> = [];
-
-  // Parameters:
-  //   deletedOutgoingEdges: For every outgoing edge of this node being deleted, must explicitly specify the most recent EdgeCreation/EdgeUpdate on this edge, to make it explicit that this deletion happens AFTER the EdgeCreation/EdgeUpdate (instead of concurrently, which is a conflict).
-  //   afterIncomingEdges: For every edge that is or was (once) incoming to this node, must explicitly specify an EdgeUpdate/NodeDeletion that makes this edge point somewhere else (no longer to this node).
-  constructor(hash: Buffer, creation: NodeCreation, deletedOutgoingEdges: Array<EdgeCreation|EdgeUpdate>, afterIncomingEdges: Array<EdgeUpdate|NodeDeletion>) {
-    super();
-    this.hash = hash;
-    this.creation = creation;
-    this.deletedOutgoingEdges = deletedOutgoingEdges;
-    this.afterIncomingEdges = afterIncomingEdges;
-
-    // Check some assertions
-    if (_.uniq(deletedOutgoingEdges).length !== deletedOutgoingEdges.length) {
-      throw new Error("Assertion failed: deletedOutgoingEdges contains duplicates.");
-    }
-    if (_.uniq(afterIncomingEdges).length !== afterIncomingEdges.length) {
-      throw new Error("Assertion failed: deletedOutgoingEdges contains duplicates.");
-    }
-    for (const supposedlyOutgoingEdge of this.deletedOutgoingEdges) {
-      if (supposedlyOutgoingEdge.getCreation().source !== this.creation) {
-        throw new Error("Assertion failed: Every element of delOutgoings must be an EdgeCreation or EdgeUpdate of an outgoing edge of the deleted node.")
-      }
-    }
-    for (const supposedlyIncomingEdge of this.afterIncomingEdges) {
-      if (supposedlyIncomingEdge instanceof NodeDeletion) {
-        // should check if the NodeDeletion deletes an edge that was *once* 
-        let isReallyIncomingEdge = false;
-        for (const deletedEdge of supposedlyIncomingEdge.deletedOutgoingEdges) {
-          let current: EdgeCreation|EdgeUpdate|undefined = deletedEdge;
-          while (current !== undefined) {
-            if (current.target.getTarget() === this.creation) {
-              isReallyIncomingEdge = true;
-              break;
-            }
-            if (current instanceof EdgeUpdate) current = current.overwrites
-            else break;
-          }
-          if (isReallyIncomingEdge) break;
-        }
-        if (!isReallyIncomingEdge) {
-          throw new Error("Assertion failed: NodeDeletion in afterIncomingEdges does not delete an incoming edge.");
-        }
-      }
-      else {      
-        if (supposedlyIncomingEdge.target.getTarget() === this.creation) {
-          throw new Error("Assertion failed: Every element in afterIncomingEdges MUST set the target of the edge to SOME PLACE ELSE.");
-        }
-        if (![...supposedlyIncomingEdge.iterUpdates()].some(e => e.target.getTarget() === this.creation)) {
-          throw new Error("Assertion failed: None of the EdgeUpdates of supposedlyIncomingEdge set the target to the node being deleted.");
-        }
-      }
-    }
-
-    this.description = "DEL("+this.creation.id.toString().slice(0,8)+")"
-
-    // Detect conflicts
-
-    // Delete/delete
-    for (const concurrentDeletion of this.creation.deletions) {
-      // Symmetric:
-      this.deleteConflicts.push(concurrentDeletion);
-      concurrentDeletion.deleteConflicts.push(this);
-    }
-
-    // Concurrently created outgoing edges of this node
-    for (const outgoingEdgeCreation of this.creation.outgoingEdges) {
-      if (!this.deletedOutgoingEdges.some(edge => edge.getCreation() === outgoingEdgeCreation)) {
-        // Conflict: The deleted node has an outgoing edge that this deletion does not depend on.
-        // Symmetric
-        this.edgeSourceConflicts.push(outgoingEdgeCreation);
-        outgoingEdgeCreation.deleteSourceConflicts.push(this);
-      }
-    }
-
-    // Related to previous conflict type: Concurrent edge updates
-    for (const deletedEdge of this.deletedOutgoingEdges) {
-      for (const concurrentEdgeUpdate of deletedEdge.overwrittenBy) {
-        if (this.afterIncomingEdges.includes(concurrentEdgeUpdate)) {
-          // This is a special case that can occur when a node with a self-edge is deleted.
-          // Not a conflict.
-        }
-        else {
-          // Conflict: Edge concurrently updated and deleted.
-          // Symmetric
-          this.updateConflicts.push(concurrentEdgeUpdate);
-          concurrentEdgeUpdate.updateConflicts.push(this);
-        }
-      }
-    }
-
-    function overwritesEdge(op: EdgeCreation | EdgeUpdate | NodeDeletion, edge: EdgeUpdate | EdgeCreation) {
-      if (op === edge) {
-        return true;
-      }
-      if (op instanceof EdgeUpdate) {
-        return overwritesEdge(op.overwrites, edge);
-      }
-      if (op instanceof NodeDeletion) {
-        return op.deletedOutgoingEdges.some(deletedEdge => overwritesEdge(deletedEdge, edge));
-      }
-      return false;
-    }
-
-    // Concurrently updated incoming edges of this node
-    for (const incomingEdge of this.creation.incomingEdges) {
-      // every incoming edge of deleted node must have been overwritten by an EdgeUpdate that is an explicit dependency:
-      if (!this.afterIncomingEdges.some(edge => overwritesEdge(edge, incomingEdge))) {
-        // Symmetric
-        this.edgeTargetConflicts.push(incomingEdge);
-        incomingEdge.target.addDeleteTargetConflict(this);
-      }
-    }
-
-
-    // Create inverse dependencies
-
-    this.creation.deletions.push(this);
-
-    for (const deletedEdge of this.deletedOutgoingEdges) {
-      // NodeDeletion acts a bit as an EdgeUpdate here
-      deletedEdge.overwrittenBy.push(this);
-    }
-  }
-
-  getDependencies(): Array<PrimitiveDelta> {
-    return Array<Delta>().concat(
-      [this.creation],
-      this.deletedOutgoingEdges,
-      this.afterIncomingEdges,
-    );
-  }
-
-  getTypedDependencies(): Array<[PrimitiveDelta, string]> {
-    return Array<[PrimitiveDelta, string]>().concat(
-      [[this.creation, "DEL"]],
-      this.deletedOutgoingEdges.map(edge => ([edge, "D"])),
-      this.afterIncomingEdges.map(edge => ([edge, "A"])),
-    );
-  }
-
-  getConflicts(): Array<PrimitiveDelta> {
-    return Array<PrimitiveDelta>().concat(
-      this.deleteConflicts,
-      this.edgeSourceConflicts,
-      this.edgeTargetConflicts,
-      this.updateConflicts,
-    );
-  }
-
-  getHash(): Buffer {
-    return this.hash;
-  }
-
-  getDescription(): string {
-    return this.description;
-  }
-
-  // pretty print to console under NodeJS
-  [inspect.custom](depth: number, options: object) {
-    return "NodeDeletion{" + inspect(this.creation.id, options) + ",delEdges=" + this.deletedOutgoingEdges.map(e => inspect(e, options)).join(",") + ",after=" + this.afterIncomingEdges.map(e => inspect(e, options)).join(",") + "}";
-  }
-
-  toString(): string {
-    return this[inspect.custom](0, {});
-  }
-
-  serialize(): any {
-    return {
-      hash: this.hash.toString('base64'),
-      type: "NodeDeletion",
-      creation: this.creation.hash.toString('base64'),
-      deletedOutgoingEdges: this.deletedOutgoingEdges.map(d => d.hash.toString('base64')),
-      afterIncomingEdges: this.afterIncomingEdges.map(d => d.hash.toString('base64')),
-    };
-  }
-
-  *iterPrimitiveDeltas(): Iterable<Delta> {
-    yield this;
-  }
-
-  getLevel() {
-    return 0;
-  }
-}
-
-// Target of an edge can be: another node, nothing (edge doesn't exist) or a value (i.e., string, number or boolean)
-export type EdgeTargetType = NodeCreation | null | PrimitiveValue;
-
-// Target of an edge can be either: (1) another node, (2) a value or (3) null (hides the edge - initially all edges are assumed to be null).
-export interface SetsTarget {
-  getTarget(): EdgeTargetType;
-  addDeleteTargetConflict(nodeDeletion: NodeDeletion);
-  getDeleteTargetConflicts(): ReadonlyArray<NodeDeletion>;
-  getDependencies(): ReadonlyArray<NodeCreation>;
-  getTypedDependencies(): ReadonlyArray<[NodeCreation, string]>;
-  getHash(): Buffer;
-  serialize(): any;
-}
-
-// Common functionality in EdgeCreation and EdgeUpdate: both set the target of an edge, and this can conflict with the deletion of the target.
-class SetsTargetToNode implements SetsTarget {
-  // Dependency
-  private readonly targetNode: NodeCreation;
-
-  // Conflict: Concurrent deletion of target node.
-  private deleteTargetConflicts: Array<NodeDeletion> = []; // append-only
-
-  constructor(targetNode: NodeCreation, edgeOperation: EdgeCreation|EdgeUpdate) {
-    this.targetNode = targetNode;
-
-    // Concurrent deletion of target node
-    if (this.targetNode instanceof NodeCreation) {
-      for (const targetDeletion of this.targetNode.deletions) {
-        if (targetDeletion.afterIncomingEdges.some(edge => {
-          while (true) {
-            if (edge === edgeOperation) return true;
-            if (edge instanceof EdgeUpdate && edge.overwrites instanceof EdgeUpdate) edge = edge.overwrites;
-            else return false;
-          }
-        })) {
-          // this can never happen - something is very wrong if you get this error:
-          throw new Error("Assertion failed - did not expect existing deletion to be aware of a new edge update");
-        }
-        // Symmetric
-        this.deleteTargetConflicts.push(targetDeletion);
-        targetDeletion.edgeTargetConflicts.push(edgeOperation);
-      }
-
-      // Create inverse dependency
-      this.targetNode.incomingEdges.push(edgeOperation);
-    }
-  }
-
-  getTarget(): EdgeTargetType {
-    return this.targetNode;
-  }
-  addDeleteTargetConflict(nodeDeletion: NodeDeletion) {
-    this.deleteTargetConflicts.push(nodeDeletion);
-  }
-  getDeleteTargetConflicts(): ReadonlyArray<NodeDeletion> {
-    return this.deleteTargetConflicts;
-  }
-  getDependencies(): ReadonlyArray<NodeCreation> {
-    return [this.targetNode];
-  }
-  getTypedDependencies(): ReadonlyArray<[NodeCreation, string]> {
-    return [[this.targetNode, "TGT"]];
-  }
-  getHash(): Buffer {
-    return this.targetNode.hash;
-  }
-  [inspect.custom](depth: number, options: object) {
-    return inspect(this.targetNode.id, options);
-  }
-  serialize(): any {
-    return {
-      type: "node",
-      creation: this.targetNode.hash.toString('base64'),
-    }
-  }
-}
-class SetsTargetToValue implements SetsTarget {
-  readonly value: PrimitiveValue | null;
-
-  constructor(value: PrimitiveValue | null) {
-    this.value = value;
-  }
-
-  getTarget(): EdgeTargetType {
-    return this.value;
-  }
-  addDeleteTargetConflict(nodeDeletion: NodeDeletion) {
-    throw new Error("Assertion error: SetsTargetToValue cannot be involved in conflict with NodeDeletion");
-  }
-  getDeleteTargetConflicts(): ReadonlyArray<NodeDeletion> {
-    return [];
-  }
-  getDependencies(): ReadonlyArray<NodeCreation> {
-    return [];
-  }
-  getTypedDependencies(): ReadonlyArray<[NodeCreation, string]> {
-    return [];
-  }
-  getHash(): Buffer {
-    return Buffer.from(JSON.stringify(this.value));
-  }
-  [inspect.custom](depth: number, options: object) {
-    return inspect(this.value, options);
-  }
-  serialize(): any {
-    return {
-      type: "value",
-      value: this.value,
-    }
-  }
-}
-
-function makeSetsTarget(target: EdgeTargetType, edgeOperation: EdgeCreation|EdgeUpdate) {
-  if (target instanceof NodeCreation) {
-    return new SetsTargetToNode(target, edgeOperation);
-  } else {
-    return new SetsTargetToValue(target);
-  }
-}
-
-class ReadDependencies {
-  // Dependencies
-  readonly deps: Array<EdgeCreation | EdgeUpdate>;
-
-  // Conflicts: Concurrent read-write.
-  updateConficts: Array<EdgeUpdate> = []; // append-only
-
-  constructor(deps: EdgeCreation | EdgeUpdate) {
-    this.deps = deps;
-
-    for (const d of deps) {
-      for (const o of d.overwrittenBy) {
-        if (d === o) {
-          continue; // a delta cannot conflict with itself
-        }
-        if (o.readDependencies)
-      }
-    }
-  }
-}
-
-export class EdgeCreation extends Delta {
-  // Dependencies
-  readonly source: NodeCreation;
-  readonly label: string;
-  readonly target: SetsTarget;
-
-  readonly hash: Buffer;
-  readonly description: string;
-
-  // Inverse dependency
-  // NodeDeletion if source of edge is deleted.
-  overwrittenBy: Array<EdgeUpdate | NodeDeletion> = []; // append-only
-
-  // Conflicts: Concurrent creations of the same edge.
-  createConflicts: Array<EdgeCreation> = []; // append-only
-
-  // Conflicts: Concurrent deletions of source node.
-  deleteSourceConflicts: Array<NodeDeletion> = []; // append-only
-
-  constructor(hash: Buffer, source: NodeCreation, label: string, target: EdgeTargetType, readDependencies: Array<EdgeCreation | EdgeUpdate>) {
-    super();
-    this.hash = hash;
-    this.source = source;
-    this.label = label;
-    this.target = makeSetsTarget(target, this);
-
-    this.description = "U("+this.label+")";
-
-    // Detect conflicts
-
-    // Create/create
-    for (const outgoingEdge of this.source.outgoingEdges) {
-      if (outgoingEdge.label === this.label) {
-        // Symmetric:
-        this.createConflicts.push(outgoingEdge);
-        outgoingEdge.createConflicts.push(this);
-      }
-    }
-
-    // Concurrent deletions of source node
-    for (const sourceDeletion of this.source.deletions) {
-      if (sourceDeletion.deletedOutgoingEdges.some(edge => edge.getCreation() === this)) {
-        // this can never happen - something is very wrong if you get this error:
-        throw new Error("Assertion failed - did not expect existing deletion to be aware of a new edge creation");
-      }
-      // Symmetric
-      this.deleteSourceConflicts.push(sourceDeletion);
-      sourceDeletion.edgeSourceConflicts.push(this);
-    }
-
-    // Create inverse dependency
-    this.source.outgoingEdges.push(this);
-  }
-
-  // Helper
-  getCreation(): EdgeCreation {
-    return this;
-  }
-
-  getDependencies(): Array<NodeCreation> {
-    return [this.source, ...this.target.getDependencies()];
-  }
-
-  getTypedDependencies(): Array<[NodeCreation, string]> {
-    return [[this.source, "SRC"], ...this.target.getTypedDependencies()];
-  }
-
-  getConflicts(): Array<PrimitiveDelta> {
-    return Array<Delta>().concat(
-      this.createConflicts,
-      this.deleteSourceConflicts,
-      this.target.getDeleteTargetConflicts(),
-    );
-  }
-
-  getHash(): Buffer {
-    return this.hash;
-  }
-
-  getDescription(): string {
-    return this.description;
-  }
-
-  // pretty print to console under NodeJS
-  [inspect.custom](depth: number, options: object) {
-    return "EdgeCreation{src=" + inspect(this.source.id, options) + ",tgt=" + inspect(this.target, options) + ",label=" + this.label + "}";
-  }
-
-  toString(): string {
-    return this[inspect.custom](0, {});
-  }
-
-  serialize(): any {
-    return {
-      hash: this.hash.toString('base64'),
-      type: "EdgeCreation",
-      source: this.source.hash.toString('base64'),
-      label: this.label,
-      target: this.target.serialize(),
-    };
-  }
-
-  *iterPrimitiveDeltas(): Iterable<PrimitiveDelta> {
-    yield this;
-  }
-
-  getLevel() {
-    return 0;
-  }
-}
-
-export class EdgeUpdate extends Delta {
-  // Dependencies
-  readonly overwrites: EdgeCreation | EdgeUpdate;
-  readonly target: SetsTarget;
-
-  readonly hash: Buffer;
-  readonly description: string;
-
-  // Inverse dependency
-  // NodeDeletion if source of edge is deleted.
-  overwrittenBy: Array<EdgeUpdate | NodeDeletion> = []; // append-only
-
-  // Conflicts: Concurrent updates
-  updateConflicts: Array<EdgeUpdate | NodeDeletion> = []; // append-only
-
-  constructor(hash: Buffer, overwrites: EdgeCreation | EdgeUpdate, newTarget: EdgeTargetType) {
-    super();
-    this.hash = hash;
-    this.overwrites = overwrites;
-    this.target = makeSetsTarget(newTarget, this);
-
-    this.description = this.getCreation().description;
-
-    // Detect conflicts
-
-    // Concurrent updates (by EdgeUpdate or NodeDeletion)
-    for (const concurrentUpdate of this.overwrites.overwrittenBy) {
-      // Symmetric
-      this.updateConflicts.push(concurrentUpdate);
-      concurrentUpdate.updateConflicts.push(this);
-    }
-
-    // Create inverse dependency
-    this.overwrites.overwrittenBy.push(this);
-  }
-
-  // Helper
-  getCreation(): EdgeCreation {
-    return this.overwrites.getCreation();
-  }
-
-  getDependencies(): Array<Delta> {
-    return [this.overwrites, ...this.target.getDependencies()];
-  }
-
-  getTypedDependencies(): Array<[Delta, string]> {
-    return [[this.overwrites, "U"], ...this.target.getTypedDependencies()];
-  }
-
-  getConflicts(): Array<Delta> {
-    return Array<Delta>().concat(
-      this.updateConflicts,
-      this.target.getDeleteTargetConflicts(),
-    );
-  }
-
-  getHash(): Buffer {
-    return this.hash;
-  }
-
-  getDescription(): string {
-    return this.description;
-  }
-
-  // pretty print to console under NodeJS
-  [inspect.custom](depth: number, options: object) {
-    return "EdgeUpdate{upd=" + inspect(this.overwrites, options) + ",tgt=" + inspect(this.target, options) + "}";
-  }
-
-  toString(): string {
-    return this[inspect.custom](0, {});
-  }
-
-  serialize(): any {
-    return {
-      hash: this.hash.toString('base64'),
-      type: "EdgeUpdate",
-      overwrites: this.overwrites.hash.toString('base64'),
-      target: this.target.serialize(),
-    };
-  }
-
-  *iterUpdates() {
-    let current: EdgeUpdate | EdgeCreation = this;
-    while (true) {
-      yield current;
-      if (current instanceof EdgeUpdate) {
-        current = current.overwrites;
-      } else {
-        return;
-      }
-    }
-  }
-
-  *iterPrimitiveDeltas(): Iterable<EdgeUpdate> {
-    yield this;
-  }
-
-  getLevel() {
-    return 0;
-  }
-}

+ 0 - 19
src/parser/parser.ts

@@ -1,19 +0,0 @@
-// import {Version} from "../onion/version";
-import {Delta, PrimitiveDelta} from "onion/delta";
-
-export interface ParseOrRenderResult {
-  corrDeltas: PrimitiveDelta[];
-  targetDeltas: PrimitiveDelta[];
-  csOverrides: Map<Delta,Delta>;
-  asOverrides: Map<Delta,Delta>;
-}
-
-// export interface Parser {
-//   // parse(csDeltas: PrimitiveDelta[], csParent: Version, corrParent: Version, asParent: Version): ParseOrRenderResult;
-//   parse(csDeltas: PrimitiveDelta[], csState: GraphState, corrState: GraphState, asState: GraphState): ParseOrRenderResult;
-// }
-
-// export interface Renderer {
-//   // render(asDelta: PrimitiveDelta[], csParent: Version, corrParent: Version, asParent: Version): ParseOrRenderResult;
-//   render(asDelta: PrimitiveDelta[], csState: GraphState, corrState: GraphState, asState: GraphState): ParseOrRenderResult;
-// }

+ 0 - 98
src/parser/rountangle_parser.test.ts

@@ -1,98 +0,0 @@
-import {RountangleParser} from "./rountangle_parser";
-
-import {
-  VersionRegistry,
-} from "../onion/version";
-
-import {
-  NodeCreation,
-  NodeDeletion,
-  EdgeUpdate,
-} from "../onion/delta";
-
-import {DeltaRegistry} from "../onion/delta_registry";
-
-import {assert} from "../util/assert";
-
-// describe("Trivial Parser", () => {
-//   it("Parse CS creation and deletion", () => {
-//     const deltaRegistry = new DeltaRegistry();
-//     const csRegistry = new VersionRegistry();
-//     const corrRegistry = new VersionRegistry();
-//     const asRegistry = new VersionRegistry();
-
-//     const csLvl = new CompositeLevel(); // L1
-//     const asLvl = new CompositeLevel(); // L1
-//     const corrLvl = new CompositeLevel(); // L1
-//     const getUuid = mockUuid();
-//     const parser = new TrivialParser(deltaRegistry, getUuid, /*{csLvl, asLvl, corrLvl}*/);
-
-//     const csCreation = deltaRegistry.newNodeCreation(getUuid());
-//     const csDeletion = deltaRegistry.newNodeDeletion(csCreation, [], []);
-
-//     const csV1Composite = csLvl.createComposite([csCreation]);
-//     const csV2Composite = csLvl.createComposite([csDeletion]);
-
-//     const csV0 = csRegistry.initialVersion;
-//     const corrV0 = corrRegistry.initialVersion;
-//     const asV0 = asRegistry.initialVersion;
-    
-//     const {corrComposite: corrV1Composite, targetComposite: asV1Composite} = parser.parse(csV1Composite, csV0, corrV0, asV0);
-
-//     const csV1 = csRegistry.createVersion(csV0, csV1Composite);
-//     const corrV1 = corrRegistry.createVersion(corrV0, corrV1Composite);
-//     const asV1 = asRegistry.createVersion(asV0, asV1Composite);
-
-//     const {corrComposite: corrV2Composite, targetComposite: asV2Composite} = parser.parse(csV2Composite, csV1, corrV1, asV1);
-
-//     const csV2 = csRegistry.createVersion(csV1, csV2Composite);
-//     const corrV2 = corrRegistry.createVersion(corrV1, corrV2Composite);
-//     const asV2 = asRegistry.createVersion(asV1, asV2Composite);
-
-//     const asV2Primitives = [...asV2.iterPrimitiveDeltas()];
-//     assert(asV2Primitives.length === 2, "Expected 2 primitive deltas on AS");
-//   });
-
-//   it("Parse CS creation and deletion (with CS edge)", () => {
-//     const deltaRegistry = new DeltaRegistry();
-//     const csRegistry = new VersionRegistry();
-//     const corrRegistry = new VersionRegistry();
-//     const asRegistry = new VersionRegistry();
-//     const csLvl = new CompositeLevel(); // L1
-//     const asLvl = new CompositeLevel(); // L1
-//     const corrLvl = new CompositeLevel(); // L1
-//     const getUuid = mockUuid();
-//     const parser = new TrivialParser(deltaRegistry, getUuid, {csLvl, asLvl, corrLvl});
-
-//     // Same as before, but this time, we also create an edge in CS:
-
-//     const csV0 = csRegistry.initialVersion;
-//     const corrV0 = corrRegistry.initialVersion;
-//     const asV0 = asRegistry.initialVersion;
-
-//     const csCreation = deltaRegistry.newNodeCreation(getUuid());
-//     const csCreateEdge = deltaRegistry.newEdgeUpdate(csCreation.createOutgoingEdge("x"), csCreation); // self-edge
-//     const csUnsetEdge = deltaRegistry.newEdgeUpdate(csCreateEdge, null);
-//     const csDeletion = deltaRegistry.newNodeDeletion(csCreation, [csUnsetEdge], [csUnsetEdge]);
-//     assert(csDeletion.conflictsWith.length === 0, "expected CS deletion to not have conflicts (yet).");
-
-//     const csTransactions = [
-//       csLvl.createComposite([csCreation]),
-//       csLvl.createComposite([csCreateEdge]),
-//       csLvl.createComposite([csUnsetEdge]),
-//       csLvl.createComposite([csDeletion]),
-//     ];
-
-//     // Parse all CS composites:
-//     const [csFinal, corrFinal, asFial] = csTransactions.reduce(([csParent, corrParent, asParent], csComposite) => {
-//       const {corrComposite, targetComposite: asComposite} = parser.parse(csComposite, csParent, corrParent, asParent);
-//       return [
-//         csRegistry.createVersion(csParent, csComposite),
-//         corrRegistry.createVersion(corrParent, corrComposite),
-//         asRegistry.createVersion(asParent, asComposite),
-//       ];
-//     }, [csV0, corrV0, asV0]);
-
-//     console.log("corrFinal primitives:", [...corrFinal.iterPrimitiveDeltas()])
-//   });
-// });

+ 0 - 370
src/parser/rountangle_parser.ts

@@ -1,370 +0,0 @@
-import {Delta, ExistingEdge, PrimitiveDelta} from "../onion/delta";
-import {PrimitiveValue, UUID} from "../onion/types";
-import {visitPartialOrdering} from "../util/partial_ordering";
-
-import {
-  NodeCreation,
-  NodeDeletion,
-  EdgeUpdate,
-} from "../onion/delta";
-
-import {DeltaRegistry} from "../onion/delta_registry";
-
-import {
-  GraphState,
-  INodeState,
-} from "../onion/graph_state";
-
-export class ParseError extends Error {}
-
-export interface Geometry2DRect {
-  x: number;
-  y: number;
-  w: number;
-  h: number;
-}
-
-const geometryLabels = ["x", "y", "width", "height"];
-
-// Whether a is inside of b
-export 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;
-
-export function getGeometry(sourceState: GraphState, nodeId: PrimitiveValue): Geometry2DRect | undefined {
-  const node = sourceState.nodes.get(nodeId);
-  if (node !== undefined) {
-    try {
-      return {
-        x: (node.outgoing.get("x")!.asTarget()) as number,
-        y: (node.outgoing.get("y")!.asTarget()) as number,
-        w: (node.outgoing.get("width")!.asTarget()) as number,
-        h: (node.outgoing.get("height")!.asTarget()) as number,
-      };
-    }
-    catch(e) {}
-  }
-}
-
-// A parser that creates an AS-node for every CS-node, with a Corr-node in between.
-export class RountangleParser  {
-  readonly getUuid: () => UUID;
-
-  readonly deltaRegistry: DeltaRegistry;
-
-  constructor(deltaRegistry: DeltaRegistry, getUuid: () => UUID,
-    /*compositeLvls: {csLvl: CompositeLevel, asLvl: CompositeLevel, corrLvl: CompositeLevel}*/) {
-    this.deltaRegistry = deltaRegistry;
-    this.getUuid = getUuid;
-  }
-
-  // We can use pretty much the same code for both parsing and rendering :)
-  propagate_change(parse: boolean, sourceDeltas: PrimitiveDelta[], sourceState: GraphState, corrState: GraphState, targetState: GraphState) {
-    const targetDeltas: Delta[] = []; // deltas that are only part of target model
-    const corrDeltas: Delta[] = []; // deltas that are part of correspondence model, but NOT target model
-
-    const sourceOverrides: Map<Delta,Delta> = new Map();
-    const targetOverrides: Map<Delta,Delta> = new Map();
-
-    const corr2SourceLabel = parse ? "cs" : "as";
-    const corr2TargetLabel = parse ? "as" : "cs";
-
-    // In order to parse, the CS/CORR/AS-state may be altered in-place.
-    // Whenever the state is altered, a callback must be pushed to this array that undoes the change.
-    // const revertState: (()=>void)[] = [];
-
-    const applyToState = (state: GraphState, delta: PrimitiveDelta | PrimitiveDelta[]) => {
-      if (Array.isArray(delta)) {
-        delta.forEach(d => applyToState(state, d));
-      }
-      else {
-        state.exec(delta);
-        // revertState.push(() => state.unexec(delta));
-      }
-    }
-
-    let complete = true; // is parsing/rendering complete, or do we need manual adjustment? (for missing geometry information)
-
-    try {
-      for (const sourceDelta of sourceDeltas) {
-        // We'll update the sourceState, in-place, for every delta that we are parsing/rendering
-        applyToState(sourceState, sourceDelta);
-
-        if (sourceDelta instanceof NodeCreation) {
-
-          // Creations are easy, and never cause conflicts:
-          const sourceCreation = sourceDelta; // alias for readability :)
-          const corrCreation = this.deltaRegistry.newNodeCreation(this.getUuid());
-          const corr2Source = this.deltaRegistry.newEdgeUpdate(corrCreation.createOutgoingEdge(corr2SourceLabel), sourceCreation);
-          const targetCreation = this.deltaRegistry.newNodeCreation(this.getUuid());
-          const corr2Target = this.deltaRegistry.newEdgeUpdate(corrCreation.createOutgoingEdge(corr2TargetLabel), targetCreation);
-
-          // We also update the corrState, in-place, for every change that is the result of parsing/rendering
-          applyToState(corrState, sourceCreation);
-          applyToState(corrState, targetCreation);
-          applyToState(corrState, [corrCreation, corr2Source, corr2Target]);
-
-          corrDeltas.push(corrCreation, corr2Source, corr2Target);
-          targetDeltas.push(targetCreation);
-
-          if (!parse) {
-            // generate a Rountangle with some default geometry:
-            const edges: [string,PrimitiveValue][] = [
-              ["type", "Rountangle"],
-              // ["label", ""],
-              ["x", 0],
-              ["y", 0],
-              ["z-index", 0],
-              ["width", 100],
-              ["height", 60],
-            ];
-            targetDeltas.push(...edges.map(([edgeLabel, value]) =>
-              this.deltaRegistry.newEdgeUpdate(targetCreation.createOutgoingEdge(edgeLabel), value)));
-
-            complete = false; // actual geometry of new rountangle to be determined by user
-          }
-        }
-        else if (sourceDelta instanceof NodeDeletion) {
-          const sourceDeletion = sourceDelta; // alias for readability :)
-          const sourceCreation = sourceDeletion.node; // the NodeCreation of the deleted cs node
-          const sourceId = sourceCreation.id;
-
-          // Follow 'cs'/'as' edges in correspondence model to find the corresponding node:
-          const sourceNodeState = corrState.nodes.get(sourceId)!;
-          const [_, corrNodeState] = sourceNodeState.getIncomingEdges().find(([label, nodeState]) => label === corr2SourceLabel)!;
-          const targetNodeState = corrNodeState.outgoing.get(corr2TargetLabel) as INodeState;
-
-          // Deletion of correspondence node, and its outgoing ('cs', 'as') edges:
-          const corrDeletion = corrNodeState.getDeltasForDelete(this.deltaRegistry);
-
-          // The following operations on corrState depend on 'corrDeletion'. That's why update corrState now. We'll undo these changes later.
-          applyToState(corrState, corrDeletion);
-
-          // We create 2 asDeletions:
-          // one that is to be used only in the AS model, unaware of any CORR-stuff, and one that is to be used in the CORR model.
-          const targetDeletion = targetState.nodes.get(targetNodeState.creation.id)!.getDeltasForDelete(this.deltaRegistry);
-          const targetDeletion1 = targetNodeState.getDeltasForDelete(this.deltaRegistry);
-
-          applyToState(corrState, targetDeletion1);
-
-          // We already have the deletion in the CS model, so we only need to create another one to be used in the CORR model:
-          const sourceDeletion1 = sourceNodeState.getDeltasForDelete(this.deltaRegistry);
-
-          applyToState(corrState, sourceDeletion1);
-
-          sourceOverrides.set(sourceDeletion, sourceDeletion1[sourceDeletion1.length-1]);
-          targetOverrides.set(targetDeletion[targetDeletion.length-1], targetDeletion1[targetDeletion1.length-1]);
-
-          targetDeltas.push(...targetDeletion);
-          corrDeltas.push(...corrDeletion);
-        }
-        else if (sourceDelta instanceof EdgeUpdate) { // sourceDelta is EdgeCreation or EdgeUpdate
-          if (!parse) {
-            // Special case: an edge is being deleted (i.e., its target is set to null) and the previous target of the edge is a node that's being deleted - in this case, we *can* render the result (and we don't even have to do anything).
-            if (sourceDelta instanceof EdgeUpdate) {
-              const target = sourceDelta.target.value;
-              if (target === null) {
-                if (sourceDelta.overwrites instanceof ExistingEdge) {
-                  const prevTarget = sourceDelta.overwrites.delta.target.value;
-                  if (sourceDeltas.some(d => d instanceof NodeDeletion && d.node === prevTarget)) {
-                    continue;
-                  }
-                }
-              }
-            }
-
-            // parent edge updated: need to update geometry: (manually)
-            complete = false;
-            continue;
-          }
-
-          // const edgeCreation = (sourceDelta as (EdgeCreation | EdgeUpdate)).getCreation();
-          // const label = edgeCreation.label;
-          const label = sourceDelta.overwrites.label;
-
-          function findCorrespondingAsNode(csNode: INodeState, reverse: boolean = false) {
-            const pair = csNode.getIncomingEdges().find(([label]) => label===(reverse?"as":"cs"));
-            if (pair === undefined) {
-              throw new Error("No incoming 'cs' edge.");
-            }
-            const [_, corrNodeState] = pair;
-            const asState = corrNodeState.outgoing.get(reverse?"cs":"as");
-            if (asState === undefined) {
-              throw new Error("Found correspondence node, but it has no outgoing 'as' edge.")
-            }
-            return asState as INodeState;
-          }
-
-          if (geometryLabels.includes(label)) {
-            const updatedNodeId = sourceDelta.overwrites.source.id;
-            const updatedGeometry = getGeometry(sourceState, updatedNodeId);
-            if (updatedGeometry !== undefined) {
-              const updatedSurface = updatedGeometry.w * updatedGeometry.h;
-              const updatedAsNode = findCorrespondingAsNode(corrState.nodes.get(updatedNodeId) as INodeState);
-
-              const findAndSetNewParent = (geometry: Geometry2DRect, asNodeState: INodeState) => {
-                const surface = geometry.w * geometry.h;
-                // Of all other geometries that we are inside of, find the one with the smallest surface area.
-                // This will be our new parent.
-                let smallestParent: INodeState | null = null;
-                let smallestSurface = Infinity;
-                for (const [otherNodeId,otherNodeState] of sourceState.nodes.entries()) {
-                  if (otherNodeState.isDeleted) {
-                    continue; // skip deleted rountangles
-                  }
-                  if (otherNodeState.creation === sourceDelta.overwrites.source) {
-                    continue; // don't compare with ourselves
-                  }
-                  const otherGeometry = getGeometry(sourceState, otherNodeId);
-                  if (otherGeometry !== undefined) {
-                    const inside = isInside(geometry, otherGeometry);
-                    if (inside) {
-                      const surface = otherGeometry.w * otherGeometry.h;
-                      if (surface < smallestSurface) {
-                        smallestSurface = surface;
-                        smallestParent = findCorrespondingAsNode(corrState.nodes.get(otherNodeId) as INodeState);
-                      }
-                    }
-                  }
-                }
-                if (smallestParent !== null) {
-                  const existingLink = asNodeState.outgoing.get("hasParent");
-                  if (existingLink !== smallestParent) {
-                    // console.log("updated geometry is on inside...");
-                    const asParentLink = asNodeState.getDeltaForSetEdge(this.deltaRegistry, "hasParent", smallestParent.asTarget());
-                    applyToState(corrState, asParentLink);
-                    targetDeltas.push(asParentLink);
-                  }
-                }
-              }
-
-              // 1. Check if existing parent links still hold
-              // 1.a. outgoing parent links
-              const otherAsNodeState = updatedAsNode.outgoing.get("hasParent");
-              if (otherAsNodeState !== undefined && otherAsNodeState.type === "node") {
-                const otherCsNodeState = findCorrespondingAsNode(otherAsNodeState, true);
-                const otherNodeId = otherCsNodeState.creation.id;
-                const otherGeometry = getGeometry(sourceState, otherNodeId);
-                if (otherGeometry === undefined || !isInside(updatedGeometry, otherGeometry)) {
-                  // parent relation no longer holds
-                  // console.log("deleting outgoing link...")
-                  // CORRECT: we'll find updatedAsNode's new parent in step 2.
-                  const deleteLink = updatedAsNode.getDeltaForSetEdge(this.deltaRegistry, "hasParent", null); // deletes the edge
-                  applyToState(corrState, deleteLink);
-                  targetDeltas.push(deleteLink);
-                }
-              }
-              // 1.b. incoming parent links
-              for (const [_, otherAsNodeState] of updatedAsNode.getIncomingEdges().filter(([label, ns]) => label === "hasParent")) {
-                const otherCsNodeState = findCorrespondingAsNode(otherAsNodeState, true);
-                const otherNodeId = otherCsNodeState.creation.id;
-                const otherGeometry = getGeometry(sourceState, otherNodeId);
-                if (otherGeometry === undefined) {
-                  throw new Error("Assertion failed: The Corresponding CS node of an AS node that is target of 'hasParent' has no geometry.");
-                }
-                if (!isInside(otherGeometry, updatedGeometry)) {
-                  // parent relation no longer holds
-                  const otherAsNode = findCorrespondingAsNode(corrState.nodes.get(otherNodeId) as INodeState);
-                  console.log("deleting incoming link...")
-                  // WRONG (TODO): must also find otherAsNode's new parent.
-                  const deleteLink = otherAsNode.getDeltaForSetEdge(this.deltaRegistry, "hasParent", null); // deletes the edge
-                  applyToState(corrState, deleteLink);
-                  targetDeltas.push(deleteLink);
-                  // Find new parent of otherAsNode:
-                  findAndSetNewParent(otherGeometry, otherAsNode);
-                  // The above function call leads to time complexity O(n^2) for parsing.
-                  // There should be ways to make it more efficient (e.g., keeping all rountangles sorted by their surface area, in order to find the new parent much quicker), but for our demonstrator, the bottleneck is React/d3, not the parsing/rendering process.
-                }
-              }
-
-              // 2. Compare the new geometry to every other rountangle
-              for (const [otherNodeId,otherNodeState] of sourceState.nodes.entries()) {
-                if (otherNodeState.isDeleted) {
-                  continue; // no point comparing ourselves with deleted rountangles
-                }
-                if (otherNodeState.creation === sourceDelta.overwrites.source) {
-                  continue; // don't compare with ourselves
-                }
-                const otherGeometry = getGeometry(sourceState, otherNodeId);
-                if (otherGeometry !== undefined) {
-                  findAndSetNewParent(updatedGeometry, updatedAsNode);
-                  const outside = isInside(otherGeometry, updatedGeometry);
-                  // CORRECT: For geometries that are inside of our updated geometry, we should only "steal" their outgoing parent link if our updated geometry is smaller than their current parent.
-                  if (outside) {
-                    const otherAsNode = findCorrespondingAsNode(corrState.nodes.get(otherNodeId) as INodeState);
-                    const otherCurrentParent = otherAsNode.outgoing.get("hasParent");
-                    const otherCurrentParentSurface = (() => {
-                      // find surface area of existing parent of other geometry...
-                      if (otherCurrentParent === undefined || otherCurrentParent.type !== "node") {
-                        return Infinity;
-                      }
-                      const otherCurrentParentCs = findCorrespondingAsNode(otherCurrentParent as INodeState, true);
-                      const otherCurrentParentGeometry = getGeometry(sourceState, otherCurrentParentCs.creation.id);
-                      if (otherCurrentParentGeometry === undefined) {
-                        return Infinity;
-                      }
-                      return otherCurrentParentGeometry.w * otherCurrentParentGeometry.h;
-                    })();
-                    if (updatedSurface < otherCurrentParentSurface) {
-                      // console.log("updated geometry is on outside...");
-                      const asParentLink = otherAsNode.getDeltaForSetEdge(this.deltaRegistry, "hasParent", updatedAsNode.asTarget());
-                      applyToState(corrState, asParentLink);
-                      targetDeltas.push(asParentLink);
-                    }
-                  }
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-    finally {
-      // Rollback all changes that were made (in-place) to the CS/CORR/AS-state
-      // 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().map(([d]) => d),
-      (d: Delta) => corrDeltasOrderedByDependency.push(d)
-    );
-
-
-    const result = {
-      corrDeltas: corrDeltasOrderedByDependency,
-      targetDeltas,
-      sourceOverrides,
-      targetOverrides,
-      complete,
-    };
-    // console.log(result);
-    return result;
-  }
-
-  parse(csDeltas: PrimitiveDelta[], csState: GraphState, corrState: GraphState, asState: GraphState) {
-    const {
-      corrDeltas,
-      targetDeltas,
-      sourceOverrides: csOverrides,
-      targetOverrides: asOverrides,
-      complete,
-    } = this.propagate_change(true, csDeltas, csState, corrState, asState);
-
-    return { corrDeltas, asDeltas: targetDeltas, csOverrides, asOverrides, complete };
-  }
-
-  render(asDeltas: PrimitiveDelta[], csState: GraphState, corrState: GraphState, asState: GraphState) {
-    const {
-      corrDeltas,
-      targetDeltas,
-      sourceOverrides: asOverrides,
-      targetOverrides: csOverrides,
-      complete,
-    } = this.propagate_change(false, asDeltas, asState, corrState, csState);
-
-    return { corrDeltas, csDeltas: targetDeltas, csOverrides, asOverrides, complete };
-  }
-}

+ 0 - 203
src/util/names.ts

@@ -1,203 +0,0 @@
-// Source: https://www.ssa.gov/OACT/babynames/decades/century.html
-export const names = [
-  "James",
-  "Mary",
-  "Robert",
-  "Patricia",
-  "John",
-  "Jennifer",
-  "Michael",
-  "Linda",
-  "David",
-  "Elizabeth",
-  "William",
-  "Barbara",
-  "Richard",
-  "Susan",
-  "Joseph",
-  "Jessica",
-  "Thomas",
-  "Sarah",
-  "Christopher",
-  "Karen",
-  "Charles",
-  "Lisa",
-  "Daniel",
-  "Nancy",
-  "Matthew",
-  "Betty",
-  "Anthony",
-  "Sandra",
-  "Mark",
-  "Margaret",
-  "Donald",
-  "Ashley",
-  "Steven",
-  "Kimberly",
-  "Andrew",
-  "Emily",
-  "Paul",
-  "Donna",
-  "Joshua",
-  "Michelle",
-  "Kenneth",
-  "Carol",
-  "Kevin",
-  "Amanda",
-  "Brian",
-  "Melissa",
-  "George",
-  "Deborah",
-  "Timothy",
-  "Stephanie",
-  "Ronald",
-  "Dorothy",
-  "Jason",
-  "Rebecca",
-  "Edward",
-  "Sharon",
-  "Jeffrey",
-  "Laura",
-  "Ryan",
-  "Cynthia",
-  "Jacob",
-  "Amy",
-  "Gary",
-  "Kathleen",
-  "Nicholas",
-  "Angela",
-  "Eric",
-  "Shirley",
-  "Jonathan",
-  "Brenda",
-  "Stephen",
-  "Emma",
-  "Larry",
-  "Anna",
-  "Justin",
-  "Pamela",
-  "Scott",
-  "Nicole",
-  "Brandon",
-  "Samantha",
-  "Benjamin",
-  "Katherine",
-  "Samuel",
-  "Christine",
-  "Gregory",
-  "Helen",
-  "Alexander",
-  "Debra",
-  "Patrick",
-  "Rachel",
-  "Frank",
-  "Carolyn",
-  "Raymond",
-  "Janet",
-  "Jack",
-  "Maria",
-  "Dennis",
-  "Catherine",
-  "Jerry",
-  "Heather",
-  "Tyler",
-  "Diane",
-  "Aaron",
-  "Olivia",
-  "Jose",
-  "Julie",
-  "Adam",
-  "Joyce",
-  "Nathan",
-  "Victoria",
-  "Henry",
-  "Ruth",
-  "Zachary",
-  "Virginia",
-  "Douglas",
-  "Lauren",
-  "Peter",
-  "Kelly",
-  "Kyle",
-  "Christina",
-  "Noah",
-  "Joan",
-  "Ethan",
-  "Evelyn",
-  "Jeremy",
-  "Judith",
-  "Walter",
-  "Andrea",
-  "Christian",
-  "Hannah",
-  "Keith",
-  "Megan",
-  "Roger",
-  "Cheryl",
-  "Terry",
-  "Jacqueline",
-  "Austin",
-  "Martha",
-  "Sean",
-  "Madison",
-  "Gerald",
-  "Teresa",
-  "Carl",
-  "Gloria",
-  "Harold",
-  "Sara",
-  "Dylan",
-  "Janice",
-  "Arthur",
-  "Ann",
-  "Lawrence",
-  "Kathryn",
-  "Jordan",
-  "Abigail",
-  "Jesse",
-  "Sophia",
-  "Bryan",
-  "Frances",
-  "Billy",
-  "Jean",
-  "Bruce",
-  "Alice",
-  "Gabriel",
-  "Judy",
-  "Joe",
-  "Isabella",
-  "Logan",
-  "Julia",
-  "Alan",
-  "Grace",
-  "Juan",
-  "Amber",
-  "Albert",
-  "Denise",
-  "Willie",
-  "Danielle",
-  "Elijah",
-  "Marilyn",
-  "Wayne",
-  "Beverly",
-  "Randy",
-  "Charlotte",
-  "Vincent",
-  "Natalie",
-  "Mason",
-  "Theresa",
-  "Roy",
-  "Diana",
-  "Ralph",
-  "Brittany",
-  "Bobby",
-  "Doris",
-  "Russell",
-  "Kayla",
-  "Bradley",
-  "Alexis",
-  "Philip",
-  "Lori",
-  "Eugene",
-  "Marie",
-];

+ 0 - 63
webpack.config.cjs

@@ -1,63 +0,0 @@
-const path = require('path');
-const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
-const child_process = require('child_process');
-const webpack = require('webpack');
-
-let revision;
-try {
-  revision = child_process.execSync('git rev-parse HEAD').toString();
-}
-catch (e) {
-  revision = "unknown";
-}
-
-// export default { 
-module.exports = {
-  entry: path.resolve(__dirname, 'src', 'frontend', 'index.tsx'),
-  module: {
-    rules: [
-      {
-        test: /\.tsx?$/,
-        use: 'ts-loader',
-        exclude: /node_modules/,
-      },
-      {
-        test: /\.css$/i,
-        use: ["style-loader", "css-loader"],
-      },
-      {
-        test: /\.svg$/,
-        use: 'url-loader'
-      },
-    ],
-  },
-  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:
-      // Should probably migrate to the SubtleCrypto Web API
-      crypto: require.resolve('crypto-browserify'),
-      stream: require.resolve('stream-browserify'),
-      buffer: require.resolve('buffer'),
-    },
-  },
-  output: {
-    filename: 'bundle.js',
-    path: path.resolve(__dirname, 'dist'),
-  },
-  devServer: {
-    static: path.resolve(__dirname, 'dist'),
-    port: 9000,
-  },
-  plugins: [
-    new ForkTsCheckerWebpackPlugin(),
-    new webpack.DefinePlugin({
-      REVISION: JSON.stringify(revision),
-    }),
-  ]
-};