diff --git a/front-end/src/components/CustomNodeUpload.js b/front-end/src/components/CustomNodeUpload.js
new file mode 100644
index 0000000..f9a744b
--- /dev/null
+++ b/front-end/src/components/CustomNodeUpload.js
@@ -0,0 +1,48 @@
+import React, {useRef, useState} from "react";
+import * as API from "../API";
+import {Button} from "react-bootstrap";
+
+
+export default function CustomNodeUpload({ onUpload }) {
+
+ const input = useRef(null);
+ const [status, setStatus] = useState("ready");
+
+ const uploadFile = async file => {
+ setStatus("loading");
+ const fd = new FormData();
+ fd.append("file", file);
+ API.uploadDataFile(fd)
+ .then(resp => {
+ onUpload();
+ setStatus("ready");
+ }).catch(() => {
+ setStatus("failed");
+ });
+ input.current.value = null;
+ };
+ const onFileSelect = e => {
+ e.preventDefault();
+ if (!input.current.files) return;
+ uploadFile(input.current.files[0]);
+ };
+
+ let content;
+ if (status === "loading") {
+ content =
Uploading file...
;
+ } else if (status === "failed") {
+ content = (Upload failed. Try a new file.
);
+ }
+ return (
+ <>
+
+
+ {content}
+ >
+ )
+}
diff --git a/front-end/src/components/NodeMenu.js b/front-end/src/components/NodeMenu.js
index b84d9c0..ac13fa6 100644
--- a/front-end/src/components/NodeMenu.js
+++ b/front-end/src/components/NodeMenu.js
@@ -1,6 +1,7 @@
import React from 'react';
import * as _ from 'lodash';
-import { Col } from 'react-bootstrap';
+import { Col, OverlayTrigger, Tooltip } from 'react-bootstrap';
+import CustomNodeUpload from "./CustomNodeUpload";
export default function NodeMenu(props) {
@@ -18,28 +19,80 @@ export default function NodeMenu(props) {
const config = data.options;
delete data.options;
return (
-
+
)}
)}
)}
+
);
}
+/**
+ * Format docstring with newlines into tooltip content
+ * @param string - node docstring
+ * @returns {array} - array of strings and HTML elements
+ */
+function formatTooltip(string) {
+ const split = string.split("\n");
+ const out = [];
+ split.forEach((line, i) => {
+ out.push(line);
+ out.push(
);
+ });
+ out.pop();
+ return out;
+}
+
+
function NodeMenuItem(props) {
+ if (!props.nodeInfo.missing_packages) {
+ const tooltip = props.nodeInfo.doc ? formatTooltip(props.nodeInfo.doc) : "This node has no documentation."
+ return (
+ }>
+ {
+ event.dataTransfer.setData(
+ 'storm-diagram-node',
+ JSON.stringify(props));
+ }}
+ style={{color: props.nodeInfo.color}}>
+ {props.nodeInfo.name}
+
+
+ )
+ } else {
+ let tooltip = "These Python modules could not be imported:\n\n"
+ + props.nodeInfo.missing_packages.join("\n");
+ tooltip = formatTooltip(tooltip);
+ return (
+ }>
+ {props.nodeInfo.filename}
+
+ )
+ }
+}
+
+
+// Overlay with props has to use ref forwarding
+const NodeTooltip = React.forwardRef((props, ref) => {
return (
- {
- event.dataTransfer.setData(
- 'storm-diagram-node',
- JSON.stringify(props));
- }}
- style={{ color: props.nodeInfo.color }}>
- {props.nodeInfo.name}
-
+
+
+ {props.message}
+
+
)
-}
+});
+
diff --git a/front-end/src/components/Workspace.js b/front-end/src/components/Workspace.js
index 57c0f32..32e580a 100644
--- a/front-end/src/components/Workspace.js
+++ b/front-end/src/components/Workspace.js
@@ -22,6 +22,7 @@ class Workspace extends React.Component {
this.engine.setModel(this.model);
this.engine.setMaxNumberPointsPerLink(0);
this.state = {nodes: []};
+ this.getAvailableNodes = this.getAvailableNodes.bind(this);
this.load = this.load.bind(this);
this.clear = this.clear.bind(this);
this.handleNodeCreation = this.handleNodeCreation.bind(this);
@@ -29,10 +30,17 @@ class Workspace extends React.Component {
}
componentDidMount() {
+ this.getAvailableNodes();
+ API.initWorkflow(this.model).catch(err => console.log(err));
+ }
+
+ /**
+ * Retrieve available nodes from server to display in menu
+ */
+ getAvailableNodes() {
API.getNodes()
.then(nodes => this.setState({nodes: nodes}))
.catch(err => console.log(err));
- API.initWorkflow(this.model).catch(err => console.log(err));
}
/**
@@ -107,7 +115,7 @@ class Workspace extends React.Component {
-
+
this.handleNodeCreation(event)}
diff --git a/front-end/src/styles/Workspace.css b/front-end/src/styles/Workspace.css
index ee60a27..d448cec 100644
--- a/front-end/src/styles/Workspace.css
+++ b/front-end/src/styles/Workspace.css
@@ -16,14 +16,22 @@
linear-gradient(to bottom, rgba(54, 169, 231, 0.1) 1px, transparent 1px);
}
.NodeMenuItem {
- cursor: pointer;
background-color: white;
- border-radius: 5px;
margin-bottom: 3px;
+ box-shadow: 0px 2px 4px gray;
+}
+.NodeMenuItem[draggable] {
+ cursor: pointer;
}
-.NodeMenuItem::before {
+.NodeMenuItem[draggable]::before {
content: "+";
padding-right: 10px;
padding-left: 5px;
color: black;
}
+.NodeMenuItem.invalid::before {
+ content: "\26a0";
+ padding-right: 10px;
+ padding-left: 5px;
+ color: red;
+}