Ver código fonte

Move CSS from HTML to CSS file. Graph nodes display drag cursor.

Joeri Exelmans 2 anos atrás
pai
commit
f4e4b73a16

+ 0 - 33
dist/index.html

@@ -3,39 +3,6 @@
   <head>
     <meta charset="UTF-8"/>
     <title>Onion VCS Demo</title>
-    <style>
-      body {
-         -webkit-user-select: none;
-         -moz-user-select: none;
-         -ms-user-select: none;
-         user-select: none;
-      }
-
-      /* SVG style: */
-
-      .container {
-        background-color: #ddd;
-      }
-
-      .node {
-        stroke: #000;
-        stroke-width: 1.5px;
-      }
-
-      .link {
-        stroke: #000;
-        stroke-opacity: 1;
-      }
-
-      .arrowHead {
-        fill: #000;
-      }
-
-      .label {
-        font-size: 10px;
-        fill: #000;
-      }
-    </style>
   </head>
   <body>
     <div id="root"></div>

+ 5 - 0
src/frontend/app.css

@@ -1,3 +1,8 @@
 html,body {
     min-height: 100vh;
+
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
 }

+ 49 - 42
src/frontend/editable_graph.tsx

@@ -49,49 +49,57 @@ export class EditableGraph extends React.Component<EditableGraphProps, EditableG
   readonly compositeLvl: CompositeLevel = new CompositeLevel();
   readonly versionRegistry: VersionRegistry = new VersionRegistry();
 
-  mouseDownHandler = (event, {x,y}, node: NodeType | undefined) => {
+  constructor(props) {
+    super(props);
+    this.state = {showCrossHair: false};
+  }
+
+  mouseDownHandler = (event, {x,y}, mouseDownNode: NodeType | undefined) => {
     event.stopPropagation();
-    if (node) {
-      this.mouseDownNode = node;
+    if (mouseDownNode) {
+      this.mouseDownNode = mouseDownNode;
     }
   }
 
-  mouseUpHandler = (event, {x,y}, node: NodeType | undefined) => {
+  mouseUpHandler = (event, {x,y}, mouseUpNode: NodeType | undefined) => {
     event.stopPropagation();
 
     // Construct the delta(s) that capture the user's change:
     const deltas: PrimitiveDelta[] = (() => {
       if (event.button === 2) { // right mouse button
-        if (this.mouseDownNode !== null && node !== undefined) {
-          // 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 this.mouseDownNode.obj.getDeltasForSetEdge(label, node.obj);
+        if (this.mouseDownNode !== null && this.mouseDownNode.obj instanceof NodeState) {
+          // create outgoing edge...
+          if (mouseUpNode !== undefined) {
+            // 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 this.mouseDownNode.obj.getDeltasForSetEdge(label, mouseUpNode.obj);
+            }
           }
-        }
-        else if (this.mouseDownNode !== null && node === undefined) {
-          // 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 or boolean):\n(ESC to cancel)", enteredValue);
-              if (enteredValue === null) {
-                break;
+          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 or boolean):\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 (typeofParsedValue !== "string" && typeofParsedValue !== "number" && typeofParsedValue !== "boolean") {
+                  alert("Expected string, number or boolean. Got: " + typeofParsedValue);
+                  continue;
+                }
+                return this.mouseDownNode.obj.getDeltasForSetEdge(label, this.props.graphDeltaExecutor.getValueState(parsedValue));
               }
-              let parsedValue;
-              try {
-                parsedValue = JSON.parse(enteredValue);
-              } catch (err) {
-                alert("Invalid JSON!");
-                continue;
-              }
-              const typeofParsedValue = typeof parsedValue;
-              if (typeofParsedValue !== "string" && typeofParsedValue !== "number" && typeofParsedValue !== "boolean") {
-                alert("Expected string, number or boolean. Got: " + typeofParsedValue);
-                continue;
-              }
-              return this.mouseDownNode.obj.getDeltasForSetEdge(label, this.props.graphDeltaExecutor.getValueState(parsedValue));
             }
           }
         }
@@ -103,9 +111,9 @@ export class EditableGraph extends React.Component<EditableGraphProps, EditableG
         }
       }
       else if (event.button === 1) { // middle mouse button
-        if (node !== undefined) {
+        if (mouseUpNode !== undefined) {
           // middle mouse button click on node -> delete node (and incoming/outgoing edges)
-          return node.obj.getDeltasForDelete();
+          return mouseUpNode.obj.getDeltasForDelete();
         }
       }
       return [];
@@ -128,14 +136,13 @@ export class EditableGraph extends React.Component<EditableGraphProps, EditableG
 
   render() {
     return (
-      <>
-        <Graph
-          graph={this.props.graph}
-          forces={this.props.forces}
-          mouseDownHandler={this.mouseDownHandler}
-          mouseUpHandler={this.mouseUpHandler}
-        />
-      </>
+      <Graph
+        graph={this.props.graph}
+        forces={this.props.forces}
+
+        mouseDownHandler={this.mouseDownHandler}
+        mouseUpHandler={this.mouseUpHandler}
+      />
     );
   }
 

+ 35 - 0
src/frontend/graph.css

@@ -0,0 +1,35 @@
+.graph {
+  background-color: #ddd;
+}
+
+.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;
+
+}

+ 25 - 9
src/frontend/graph.tsx

@@ -53,6 +53,11 @@ class Link<LinkType> extends React.Component<{ link: d3Types.d3Link<LinkType> },
     ;
   }
 
+  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,
@@ -62,8 +67,8 @@ class Link<LinkType> extends React.Component<{ link: d3Types.d3Link<LinkType> },
     }
     return (
       <g>
-        <line className="link" ref={this.ref} style={arrowStyle} markerEnd={this.props.link.bidirectional ? "" : "url(#arrowEnd)"}/>
-        <text className="label" ref={this.refLabel} style={textStyle}>{this.props.link.label}</text>
+        <line className="graphLink" ref={this.ref} style={arrowStyle} markerEnd={this.props.link.bidirectional ? "" : "url(#arrowEnd)"}/>
+        <text className="graphLinkLabel" ref={this.refLabel} style={textStyle}>{this.props.link.label}</text>
       </g>);
   }
 }
@@ -83,7 +88,7 @@ class Links<LinkType> extends React.Component<{ links: d3Types.d3Link<LinkType>[
     });
 
     return (
-      <g className="links">
+      <g>
         {links}
       </g>
     );
@@ -96,11 +101,19 @@ interface NodeProps<NodeType> {
   mouseDownHandler: (event) => void;
   mouseUpHandler: (event) => void;
 }
+interface NodeState {
+  dragging: boolean;
+}
 
 
-class Node<NodeType> extends React.Component<NodeProps<NodeType>, {}> {
+class Node<NodeType> extends React.Component<NodeProps<NodeType>, NodeState> {
   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]);
 
@@ -110,6 +123,7 @@ class Node<NodeType> extends React.Component<NodeProps<NodeType>, {}> {
       }
       d.fx = d.x;
       d.fy = d.y;
+      this.setState({dragging: true});
     }
     const onDrag = (event, d: any) => {
       d.fx = event.x;
@@ -121,6 +135,7 @@ class Node<NodeType> extends React.Component<NodeProps<NodeType>, {}> {
       }
       d.fx = null;
       d.fy = null;
+      this.setState({dragging: false});
     }
     const dragBehavior = d3.drag()
       .on("start", onDragStart)
@@ -148,7 +163,7 @@ class Node<NodeType> extends React.Component<NodeProps<NodeType>, {}> {
     return (
       <circle
         ref={this.ref}
-        className="node"
+        className={"graphNode" + (this.state.dragging ? " dragging" : "")}
         onMouseDown={this.props.mouseDownHandler}
         onMouseUp={this.props.mouseUpHandler}
         r={5}
@@ -188,7 +203,7 @@ class Nodes<NodeType> extends React.Component<NodesProps<NodeType>, {}> {
     });
 
     return (
-      <g ref={this.ref} className="nodes">
+      <g ref={this.ref}>
         {nodes}
       </g>
     );
@@ -218,7 +233,7 @@ class Label<NodeType> extends React.Component<{ node: d3Types.d3Node<NodeType> }
   }
 
   render() {
-    return <text className="label" ref={this.ref} fontWeight={this.props.node.highlight?"bold":"normal"}>
+    return <text className="graphNodeLabel" ref={this.ref} fontWeight={this.props.node.highlight?"bold":"normal"}>
       {this.props.node.label}
     </text>;
   }
@@ -237,7 +252,7 @@ class Labels<NodeType> extends React.Component<{ nodes: d3Types.d3Node<NodeType>
     });
 
     return (
-      <g className="labels">
+      <g>
         {labels}
       </g>
     );
@@ -303,7 +318,8 @@ export class Graph<NodeType,LinkType> extends React.Component<GraphProps<NodeTyp
     }
 
     return (
-      <svg className="container"
+      <svg
+        className="graph"
         ref={this.refSVG}
         style={{width: "100%", height}}
         viewBox={`${-height/2/this.state.zoom} ${-height/2/this.state.zoom} ${height/this.state.zoom} ${height/this.state.zoom}`}

+ 1 - 0
src/frontend/index.tsx

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

+ 1 - 1
src/frontend/rountangleEditor/RountangleEditor.css

@@ -22,7 +22,7 @@
 .re-rountangle:hover.dragging {
     cursor: grabbing;
 }
-.re-ruontangle-name {
+.re-rountangle-name {
 
 }
 .re-rountangle-resize-handle {

+ 1 - 1
src/onion/delta_executor.ts

@@ -200,7 +200,7 @@ export class ValueState extends NodeOrValueState {
 
   getDeltasForSetEdge(label: string, targetState: NodeOrValueState): (EdgeCreation|EdgeUpdate)[] {
     // A value cannot be the source of an edge, so we return no deltas.
-    return [];
+    throw new Error("Assertion failed: A value cannot be the source of an edge.")
   }
   getDeltasForDelete(): (EdgeUpdate|NodeDeletion)[] {
     const [edgeUnsettings, _] = this.getIncomingEdgeDependenciesForDelete();