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; +}