diff --git a/css/application.css b/css/application.css
index 651d646..5160c74 100755
--- a/css/application.css
+++ b/css/application.css
@@ -86,12 +86,15 @@ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cecece', end
padding:10px;
cursor:move;
width:90px;
- margin: 30px auto 30px auto;
+ margin: 15px auto 15px auto;
text-align:center;
- align:center;
border-radius: 5px;
}
+.tutorial-btn {
+ margin-bottom: 15px;
+}
+
.shadow {
-moz-box-shadow: 3px 3px 4px #000;
-webkit-box-shadow: 3px 3px 4px #000;
@@ -158,6 +161,13 @@ rect.draw2d_ResizeHandle {
border-bottom: 1px solid #CCC;
}
+.custom-menu.label .title {
+ padding: 4px 6px;
+ font-size: 11px;
+ color: rgb(61, 113, 130);
+ border-bottom: 1px solid #CCC;
+}
+
.custom-menu ul {
margin: 0;
padding: 0;
@@ -172,6 +182,11 @@ rect.draw2d_ResizeHandle {
user-select: none;
}
+.custom-menu.label li {
+ padding: 4px 6px;
+ font-size: 12px;
+}
+
.custom-menu li.clear {
border-top: 1px solid #CCC;
color: red;
@@ -180,4 +195,14 @@ rect.draw2d_ResizeHandle {
.custom-menu li:hover {
background-color: #DEF;
+}
+
+/******************************************************************
+ * CSS for Tutorial
+ ******************************************************************/
+.pointer-none,
+.pointer-none *,
+.to-be-deleted,
+.to-be-deleted * {
+ pointer-events: none !important;
}
\ No newline at end of file
diff --git a/gui/HoverConnection.js b/gui/HoverConnection.js
index 2fa6caf..209f3ac 100644
--- a/gui/HoverConnection.js
+++ b/gui/HoverConnection.js
@@ -76,12 +76,6 @@ function showCustomConfigs(x, y, connection) {
connectBasedOnConfig([node], "one-by-one", eth_switch)
}
- break;
- case "second":
- alert("second");
- break;
- case "third":
- alert("Third");
break;
}
diff --git a/gui/Label.js b/gui/Label.js
new file mode 100644
index 0000000..6449543
--- /dev/null
+++ b/gui/Label.js
@@ -0,0 +1,63 @@
+Label = draw2d.shape.basic.Label.extend({
+ NAME: "Label",
+
+ ORIENTATION_PROPERTIES: Object.freeze({
+ [OrientationEnum.north]: {
+ "rotationAngle": 0,
+ "padding": { left: 10, top: 5, right: 10, bottom: 5 },
+ "locator": new draw2d.layout.locator.BottomLocator(),
+ },
+ [OrientationEnum.east]: {
+ "rotationAngle": -90,
+ "padding": { left: 10, top: 5, right: 10, bottom: 5 },
+ "locator": new draw2d.layout.locator.RightLocator(),
+ },
+ [OrientationEnum.south]: {
+ "rotationAngle": 0,
+ "padding": { left: 10, top: 5, right: 10, bottom: 5 },
+ "locator": new draw2d.layout.locator.TopLocator(),
+ },
+ [OrientationEnum.west]: {
+ "rotationAngle": -270,
+ "padding": { left: 10, top: 5, right: 10, bottom: 5 },
+ "locator": new draw2d.layout.locator.LeftLocator(),
+ },
+ }),
+
+ init: function (attr) {
+ this._super($.extend({
+ text: "Label text",
+ radius: 4,
+ bgColor: "#5b5b5b",
+ color: "#5b5b5b",
+ fontColor: "white",
+ resizeable: true,
+ }, attr));
+
+
+ this.setOrientation(attr.orientation, false);
+ this.installEditPolicy(new SelectionMenuPolicy());
+ this.installEditor(new draw2d.ui.LabelInplaceEditor());
+ },
+
+ setLinksToChannel: function (partner) {
+ this.linksToChannel.push(partner);
+ },
+
+ getOrientation: function () {
+ return this.orientation;
+ },
+
+
+ setOrientation: function (orientation, repaint = true) {
+ this.orientation = orientation;
+ let prop = this.ORIENTATION_PROPERTIES[orientation];
+ this.rotationAngle = prop.rotationAngle;
+ this.height = 0;
+ this.setPadding(prop.padding);
+ if (repaint) {
+ this.repaint();
+ }
+ }
+});
+
diff --git a/gui/NodeShape.js b/gui/NodeShape.js
index c50ad3a..dd0a50d 100755
--- a/gui/NodeShape.js
+++ b/gui/NodeShape.js
@@ -76,8 +76,6 @@ ChannelShape = draw2d.shape.basic.Label.extend({
port.on("connect", function () {
this.setVisible(false);
- console.log("Firing");
-
if (siblingChannel) siblingChannel.setVisible(false)
}, port)
port.on("disconnect", function () {
diff --git a/gui/SelectionMenuPolicy.js b/gui/SelectionMenuPolicy.js
index a8453f6..86139d3 100755
--- a/gui/SelectionMenuPolicy.js
+++ b/gui/SelectionMenuPolicy.js
@@ -65,28 +65,29 @@ var SelectionMenuPolicy = draw2d.policy.figure.SelectionPolicy.extend({
let deleteBtn = $("
");
let rotateLeftBtn = $("");
let rotateRightBtn = $("");
- let options = $("");
+ let nodeOptions = $("");
+ let labelOptions = $("");
this.overlay.append(deleteBtn);
this.overlay.append(rotateLeftBtn);
this.overlay.append(rotateRightBtn);
if (figure instanceof NodeShape) {
- this.overlay.append(options);
+ this.overlay.append(nodeOptions);
+ }
+
+ if (figure instanceof Label) {
+ this.overlay.append(labelOptions);
}
$("body").append(this.overlay);
rotateLeftBtn.on("click", function () {
- if (figure instanceof NodeShape || figure instanceof SwitchShape) {
- figure.setOrientation(OrientationEnum.next(figure.getOrientation()));
- }
+ figure.setOrientation(OrientationEnum.next(figure.getOrientation()));
});
rotateRightBtn.on("click", function () {
- if (figure instanceof NodeShape || figure instanceof SwitchShape) {
- figure.setOrientation(OrientationEnum.prev(figure.getOrientation()));
- }
+ figure.setOrientation(OrientationEnum.prev(figure.getOrientation()));
});
deleteBtn.on("click", function () {
@@ -165,7 +166,7 @@ var SelectionMenuPolicy = draw2d.policy.figure.SelectionPolicy.extend({
// canvas.getCommandStack().execute(command);
})
- options.on("click", function(ev) {
+ nodeOptions.on("click", function(ev) {
// If the document is clicked somewhere
$(document).bind("mousedown", function (e) {
@@ -226,11 +227,55 @@ var SelectionMenuPolicy = draw2d.policy.figure.SelectionPolicy.extend({
$(".custom-menu.single li").unbind("click");
});
+ let { x, y } = ev.target.getBoundingClientRect();
+ $(".custom-menu.single").finish().toggle(100).css({
+ top: (y + 30) + "px",
+ left: x + "px"
+ });
+ });
+
+ labelOptions.on("click", function(ev) {
+
+ // If the document is clicked somewhere
+ $(document).bind("mousedown", function (e) {
+ // If the clicked element is not the menu, hide the menu
+ if (!$(e.target).parents(".custom-menu.label").length > 0) {
+ $(".custom-menu.label").hide(100);
+
+ $(document).unbind("mousedown");
+ $(".custom-menu.label li").unbind("click");
+ }
+ });
+ // If the menu element is clicked
+ $(".custom-menu.label li").one("click", function () {
+ // This is the triggered action name
+ let action = $(this).attr("data-action");
+ let color = action.split("-")[1];
+
+ // Change background color
+ if (action.startsWith("bg")) {
+ figure.setBackgroundColor(color);
+ figure.setColor(color);
+ } else {
+ figure.setFontColor(color);
+ }
+
+
+
+
+
- $(".custom-menu.single").finish().toggle(100).css({
- top: ev.clientY + "px",
- left: ev.clientX + "px"
+ // Hide it AFTER the action was triggered
+ $(".custom-menu.label").hide(100);
+ $(document).unbind("mousedown");
+ $(".custom-menu.label li").unbind("click");
+ });
+
+ let { x, y } = ev.target.getBoundingClientRect();
+ $(".custom-menu.label").finish().toggle(100).css({
+ top: (y + 30) + "px",
+ left: x + "px"
});
});
}
diff --git a/gui/Toolbar.js b/gui/Toolbar.js
index 1d06d8f..84dfbcc 100755
--- a/gui/Toolbar.js
+++ b/gui/Toolbar.js
@@ -16,10 +16,13 @@ example.Toolbar = Class.extend({
//
view.on("select", $.proxy(this.onSelectionChanged, this));
+ let undoRedoContainer = $("");
+ this.html.append(undoRedoContainer);
+
// Inject the UNDO Button and the callbacks
//
this.undoButton = $("");
- this.html.append(this.undoButton);
+ undoRedoContainer.append(this.undoButton);
this.undoButton.button().click($.proxy(function () {
this.view.getCommandStack().undo();
// console.log(this.view);
@@ -28,7 +31,7 @@ example.Toolbar = Class.extend({
// Inject the REDO Button and the callback
//
this.redoButton = $("");
- this.html.append(this.redoButton);
+ undoRedoContainer.append(this.redoButton);
this.redoButton.button().click($.proxy(function () {
// This is a workaround to fix a bug in the redo
@@ -43,7 +46,7 @@ example.Toolbar = Class.extend({
connection.setSource(connection.sourcePort);
connection.setTarget(connection.targetPort);
}
-
+
this.view.getCommandStack().redo();
}, this)).button("option", "disabled", true);
@@ -98,8 +101,12 @@ example.Toolbar = Class.extend({
// Inject the SRUN Button
//
+
+ let importContainer = $("");
+ this.html.append(importContainer);
+
this.srunImportIntelButton = $("");
- this.html.append(this.srunImportIntelButton);
+ importContainer.append(this.srunImportIntelButton);
this.srunImportIntelButton.button().click($.proxy(function () {
var srun_raw = prompt("Enter command, f.e. srun", "FPGALINK0=n2fpga03:acl1:ch0-n2fpga03:acl1:ch1 FPGALINK1=n2fpga02:acl0:ch0-n2fpga02:acl0:ch1 FPGALINK2=n2fpga02:acl0:ch2-n2fpga02:acl0:ch3 FPGALINK3=n2fpga03:acl1:ch2-n2fpga03:acl1:ch3");
@@ -108,7 +115,7 @@ example.Toolbar = Class.extend({
}, this)).button("option", "enabled", true);
this.srunImportXilinxButton = $("");
- this.html.append(this.srunImportXilinxButton);
+ importContainer.append(this.srunImportXilinxButton);
this.srunImportXilinxButton.button().click($.proxy(function () {
var srun_raw = prompt("Enter command, f.e. srun", " -N 2 --fpgalink=n01:acl1:ch1-n00:acl2:ch1 --fpgalink=n01:acl2:ch0-n01:acl2:ch1 --fpgalink=n00:acl1:ch0-n00:acl1:ch1 --fpgalink=n01:acl0:ch0-n01:acl0:ch1 --fpgalink=n00:acl0:ch0-n01:acl1:ch0 --fpgalink=n00:acl0:ch1-n00:acl2:ch0");
@@ -122,9 +129,9 @@ example.Toolbar = Class.extend({
// Inject the srun EXPORT Button
//
- this.srunExportInput = $("");
+ this.srunExportInput = $("");
this.html.append(this.srunExportInput);
- this.srunExportButton = $("");
+ this.srunExportButton = $("");
this.html.append(this.srunExportButton);
this.srunExportButton.button().click($.proxy(function () {
@@ -139,9 +146,9 @@ example.Toolbar = Class.extend({
// Inject the url EXPORT Button
//
- this.urlExportInput = $("");
+ this.urlExportInput = $("");
this.html.append(this.urlExportInput);
- this.urlExportButton = $("");
+ this.urlExportButton = $("");
this.html.append(this.urlExportButton);
this.urlExportButton.button().click($.proxy(function () {
@@ -155,24 +162,59 @@ example.Toolbar = Class.extend({
this.html.append(this.delimiter);
// Export SVG.
- this.svgExportButton = $("");
+ this.svgExportButton = $("");
this.html.append(this.svgExportButton);
this.svgExportButton.button().click($.proxy(function () {
+ // Turn off the grid if it's turned on
+ // First, save previous state
+ let gridIsOn = app.toolbar.installedPolicy != null;
+ if (gridIsOn) {
+ // Simulate click on the button
+ $("#toggleGridButton").click();
+ }
+ // Save current dimensions
var tDimension = new draw2d.geo.Rectangle(0, 0, view.getWidth(), view.getHeight());
+ // Set the dimenstion to fit current nodes
view.setDimension();
+ // Get the SVG text
+ let svgText = "";
var writer = new draw2d.io.svg.Writer();
writer.marshal(view, function (svg) {
- $("#svg_output").text(svg);
+ svgText = svg;
});
+ // Set the view back to its old dimension
view.setDimension(tDimension);
- $('#svg_output_div').show();
+ // Turn on the grid again, if it was turned on
+ if (gridIsOn) {
+ // Simulate click on the button
+ $("#toggleGridButton").click();
+ }
+
+ // Download the SVG
+ // Convert SVG string to a Blob
+ const blob = new Blob([svgText], { type: 'image/svg+xml' });
+
+ // Create a temporary object URL
+ const url = URL.createObjectURL(blob);
+
+ // Create a temporary element
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'output.svg';
+ document.body.appendChild(a);
+
+ // Trigger the download
+ a.click();
+ // Clean up
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
}, this)).button("option", "enabled", true);
},
@@ -384,7 +426,7 @@ example.Toolbar = Class.extend({
// If there is no --fpgalink, just add them so that the following
// while loop will work normally
- if(srun_raw_copy.indexOf(srun_fpgalinks_needle) == -1) {
+ if (srun_raw_copy.indexOf(srun_fpgalinks_needle) == -1) {
srun_raw_copy = srun_raw_copy.trim().split(" ").map((x => {
x = "--fpgalink=" + x;
return x;
diff --git a/gui/Tutorial.js b/gui/Tutorial.js
new file mode 100644
index 0000000..02b71c5
--- /dev/null
+++ b/gui/Tutorial.js
@@ -0,0 +1,640 @@
+const steps = {
+ "advanced": {
+ "createNode": {
+ title: 'Create a Node',
+ description: 'You can create a node by drag and drop this label into the grid',
+ },
+ "nodeStructure": {
+ title: 'Node Structure',
+ description: 'The node is divided into different FPGAs, each FPGA into different channels.
You can find more information about the nodes here',
+ side: "right",
+ align: 'start',
+ },
+ "setConnection": {
+ title: 'Set a Connection',
+ description: 'You can use these grey ports to set up connections by dragging the port to another port.',
+ side: "right",
+ align: 'start',
+ },
+ "selfConnections": {
+ title: 'Self Connections',
+ description: 'You can connect channels within the some node',
+ side: "right",
+ align: 'start',
+ },
+ "nodeOptions": {
+ title: 'Node Options',
+ description: 'Once you select a node, a list of options will appear. You can rotate, delete or configure a node.',
+ side: "right",
+ align: 'start',
+ },
+ "nodeConfigurations": {
+ title: 'Node Configurations',
+ description: 'When you select the 3 dots, you will find the following menu.
There are a set of pre-defined connections which you choose from. Lets try the Pair configuration.',
+ side: "right",
+ align: 'start',
+ },
+ "pairConfiguration": {
+ title: 'Pair Configuration',
+ description: 'You can see now that the node is configured as Pair connections, where each channel from the first FPGA is connceted to relative channel in the second FPGA.',
+ side: "right",
+ align: 'start',
+ },
+ "connection2Nodes": {
+ title: 'Connection between 2 nodes',
+ description: 'You can also connect the channels of 2 different nodes.',
+ side: "bottom",
+ align: 'center',
+ },
+ "greenPorts": {
+ title: 'Green Ports',
+ description: 'When you connect 2 nodes with the green port, a popup will appear with different pre-defined configurations. You can select one of them and the nodes will be connected autmoatically. For example, lets choose the 1:1 Mapping.',
+ side: "bottom",
+ align: 'center',
+ },
+ "mapping1:1": {
+ title: '1:1 Mapping',
+ description: 'You can see now that each channel from the first node is connected to its relative channel in the second node.',
+ side: "right",
+ align: 'center',
+ },
+ "copyConfiguration": {
+ title: 'Copy Configuration',
+ description: 'Once you have implemented your design, you can click here to copy the --fpgalink configuration. This configuration can be used later with the changeFPGALinks tool to configure the connections of the FPGA. You can learn more about the changeFPGALinks tool here',
+ side: "bottom",
+ align: 'center',
+ }
+ },
+ "switch": {
+ "createSwitch": {
+ title: 'Create a Switch',
+ description: 'You can create a switch by drag and drop this label into the grid. Lets create one!',
+ },
+ "createNode": {
+ title: 'Create a Node',
+ description: 'Ethernet switches are only allowed to be connected to Xilinx nodes. Lets create one!',
+ },
+ "setConnections": {
+ title: 'Set Connections',
+ description: 'You can connect the nodes by drag&drop the grey ports. Lets create a 1:1 connection!',
+ side: "right",
+ align: "center"
+ },
+ "showConnections": {
+ title: 'View Connections',
+ description: "Now that we have successfully connected the nodes, it's time to copy the configuration!",
+ side: "right",
+ align: "center"
+ }
+ // copy --fpgalink step is being added in the JS from the advanced tutorial
+ },
+ "actions": {
+ "undoRedo": {
+ title: 'Undo / Redo',
+ description: 'At any moment, you can undo/redo your actions.',
+ side: "bottom",
+ align: 'center',
+ },
+ "importConfiguration": {
+ title: 'Import Configuration',
+ description: 'If you already have a conifguration, you can use these buttons to import it. You can find more information about the syntax of these configurations here',
+ side: "bottom",
+ align: 'center',
+ },
+ // copy --fpgalink step is being added in the JS from the advanced tutorial
+ "copyURL": {
+ title: 'Copy URL',
+ description: 'If you want to share the current design that you have implemented, you can copy the following link and share it with others!',
+ side: "bottom",
+ align: 'center',
+ },
+ "exportSVG": {
+ title: 'Export SVG',
+ description: 'You can export your design and download it as a SVG.',
+ side: "bottom",
+ align: 'center',
+ }
+ }
+}
+
+
+// Basic is same as advanced but without some steos
+const basicSteps = structuredClone(steps.advanced);
+['nodeConfigurations', 'pairConfiguration', 'connection2Nodes', 'greenPorts', 'mapping1:1'].forEach(k => delete basicSteps[k]);
+steps["basic"] = basicSteps;
+
+// Add copy --fpgalink step to the actions tutorial and to the switch tutorial
+steps["actions"]["copyConfiguration"] = steps["advanced"]["copyConfiguration"];
+steps["switch"]["copyConfiguration"] = steps["advanced"]["copyConfiguration"];
+
+
+// Get tutorial type from the URL
+const url = new URL(window.location.href);
+let tutorialType = url.searchParams.get("tutorial");
+let activeSteps = steps[tutorialType];
+
+
+// Prepare driver steps
+let driverSteps = [];
+
+if (tutorialType == "advanced" || tutorialType == "basic") {
+ driverSteps = [
+ {
+ element: '#intel-node',
+ popover: {
+ ...activeSteps["createNode"],
+ onNextClick: () => {
+ // Create a node
+ createNodes("node-intel", "east", 90, 60);
+ driverObj.moveNext();
+ },
+ }
+ },
+ {
+ element: '.NodeShape',
+ popover: {
+ ...activeSteps["nodeStructure"],
+ onNextClick: () => {
+ // Create a invisible div to highlight it
+ let nodeElement = $(".NodeShape");
+ let { left, top } = nodeElement.offset();
+ let outerHeight = nodeElement.outerHeight();
+ let outerWidth = nodeElement.outerWidth();
+
+ let div = document.createElement("div");
+ div.style.width = 20;
+ div.style.height = outerHeight - 45;
+ div.style.top = top + 45;
+ div.style.left = left + outerWidth - 10;
+ div.style.position = "absolute";
+ div.classList.add(...["to-be-deleted", "tutorial-step3"]);
+ $("body").append(div);
+
+ driverObj.moveNext();
+ },
+ onPrevClick: () => {
+ // Delete the node
+ // Simulate the click on the deleteBtn of the node
+ app.view.figures.data[0].select();
+ $(".overlayMenuDeleteItem").click();
+
+ driverObj.movePrevious();
+ },
+ }
+ },
+ {
+ element: '.tutorial-step3',
+ popover: {
+ ...activeSteps["setConnection"],
+ onNextClick: () => {
+ // Create a self connection
+ let node = app.view.figures.data[0];
+ let nodeChannels = node.getFPGAs().data[0].getChannels().data;
+ app.toolbar.connectChannels(nodeChannels[1], nodeChannels[3]);
+
+ driverObj.moveNext();
+ },
+ },
+ },
+ {
+ element: '.draw2d_connection',
+ popover: {
+ ...activeSteps["selfConnections"],
+ onNextClick: () => {
+ // Remove the old connection
+ let nodeConnections = app.view.figures.data[0].getAllConnections();
+ delete_connections(nodeConnections, app.view);
+
+ let node = app.view.figures.data[0];
+ node.select();
+
+ driverObj.moveNext();
+ },
+ onPrevClick: () => {
+ // Remove the old connection
+ let nodeConnections = app.view.figures.data[0].getAllConnections();
+ delete_connections(nodeConnections, app.view);
+
+ driverObj.movePrevious();
+ }
+ }
+ },
+ {
+ element: '.overlayMenu',
+ popover: {
+ ...activeSteps["nodeOptions"],
+ onNextClick: () => {
+ if (tutorialType == "advanced") {
+ // Open the options menu
+ $(".overlayMenuItem.options").click();
+ $(".custom-menu.single").addClass("pointer-none");
+ }
+
+ driverObj.moveNext();
+ },
+ onPrevClick: () => {
+ // Recreate the slef connection
+ let node = app.view.figures.data[0];
+ let nodeChannels = node.getFPGAs().data[0].getChannels().data;
+ app.toolbar.connectChannels(nodeChannels[1], nodeChannels[3]);
+
+ // Unselect the node
+ node.unselect();
+
+ driverObj.movePrevious();
+ }
+ }
+ },
+ {
+ element: '.custom-menu.single',
+ popover: {
+ ...activeSteps["nodeConfigurations"],
+ onNextClick: () => {
+ // Create a pair connection
+ let node = app.view.figures.data[0];
+ app.toolbar.createNodesAndConnections("pair", 1, [node], []);
+
+ // Unselect the node
+ node.unselect();
+
+ // click anywhere to remove the overlay menu
+ $("body").trigger("mousedown");
+
+ // Create a invisible div to highlight it
+ let nodeElement = $(".NodeShape");
+ let { left, top } = nodeElement.offset();
+ let outerWidth = nodeElement.outerWidth();
+ let outerHeight = nodeElement.outerHeight();
+
+ let div = document.createElement("div");
+ div.style.width = outerWidth + 50;
+ div.style.height = outerHeight;
+ div.style.top = top;
+ div.style.left = left;
+ div.style.position = "absolute";
+ div.classList.add(...["to-be-deleted", "tutorial-step7"]);
+ $("body").append(div);
+
+ driverObj.moveNext();
+ },
+ onPrevClick: () => {
+ // Remove the overlay menu by simulating a click anywhere
+ $("body").trigger("mousedown");
+
+ // Select the node again
+ let node = app.view.figures.data[0];
+ node.select();
+
+ driverObj.movePrevious();
+ }
+ }
+ },
+ {
+ element: '.tutorial-step7',
+ popover: {
+ ...activeSteps["pairConfiguration"],
+ onNextClick: () => {
+ // Remove the old connection
+ let nodeConnections = app.view.figures.data[0].getAllConnections();
+ delete_connections(nodeConnections, app.view);
+
+ // Create a new node
+ createNodes("node-intel", "west", 400, 60);
+
+ // Create a connection between the 2 nodes
+ let node1 = app.view.figures.data[0];
+ let node2 = app.view.figures.data[1];
+ let node1Channels = node1.getFPGAs().data[0].getChannels().data;
+ let node2Channels = node2.getFPGAs().data[0].getChannels().data;
+ app.toolbar.connectChannels(node1Channels[1], node2Channels[1]);
+
+ driverObj.moveNext();
+ },
+ onPrevClick: () => {
+ // Remove the pair connections
+ let node = app.view.figures.data[0];
+ delete_connections(node.getAllConnections(), app.view);
+
+
+ // Select the node
+ node.select();
+
+ // Open the options menu
+ $(".overlayMenuItem.options").click();
+ $(".custom-menu.single").addClass("pointer-none");
+
+ driverObj.movePrevious();
+ }
+ }
+ },
+ {
+ element: '.draw2d_connection',
+ popover: {
+ ...activeSteps["connection2Nodes"],
+ onNextClick: () => {
+ // Remove the old connection
+ let nodeConnections = app.view.figures.data[0].getAllConnections();
+ delete_connections(nodeConnections, app.view);
+
+ // Connect the 2 config (green) ports
+ let node1ConfigPort = app.view.figures.data[0].getPorts().data[0];
+ let node2ConfigPort = app.view.figures.data[1].getPorts().data[0];
+ var c = new HoverConnection(node1ConfigPort, node2ConfigPort);
+ var command = new draw2d.command.CommandAdd(app.view, c, 0, 0);
+ app.view.getCommandStack().execute(command);
+
+ // Create a invisible div to highlight it
+ let nodeElement = $(".NodeShape");
+ let { left, top } = nodeElement.offset();
+ let outerWidth = nodeElement.outerWidth();
+
+ let div = document.createElement("div");
+ div.style.width = 400 - 90 - outerWidth + 20;
+ div.style.height = 45;
+ div.style.top = top;
+ div.style.left = left + outerWidth - 10;
+ div.style.position = "absolute";
+ div.classList.add(...["to-be-deleted", "tutorial-step9"]);
+ $("body").append(div);
+
+ driverObj.moveNext();
+ },
+ onPrevClick: () => {
+ // Remove the connection between the 2 nodes
+ let node1 = app.view.figures.data[0];
+ let node2 = app.view.figures.data[1];
+ delete_connections(node2.getAllConnections(), app.view);
+
+ // Remove the second node
+ // Simulate the click on the deleteBtn of the node
+ node2.select();
+ $(".overlayMenuDeleteItem").click();
+
+ // Recreate the pair connections
+ app.toolbar.createNodesAndConnections("pair", 1, [node1], []);
+
+ driverObj.movePrevious();
+ }
+ }
+ },
+ {
+ element: '.tutorial-step9',
+ popover: {
+ ...activeSteps["greenPorts"],
+ onNextClick: () => {
+ // This is to remove the config port popup
+ $(".tutorial-step9").trigger("mousedown");
+
+ // Create 1:1 Mapping connections
+ let node1 = app.view.figures.data[0];
+ let node2 = app.view.figures.data[1];
+ connectBasedOnConfig([node1, node2], "one-by-one");
+
+ // Create a invisible div to highlight it
+ let nodeElement = $(".NodeShape");
+ let { left, top } = nodeElement.offset();
+ let outerWidth = nodeElement.outerWidth();
+ let outerHeight = nodeElement.outerHeight();
+
+ let div = document.createElement("div");
+ div.style.width = 400 - 90 + outerWidth;
+ div.style.height = outerHeight;
+ div.style.top = top;
+ div.style.left = left;
+ div.style.position = "absolute";
+ div.classList.add(...["to-be-deleted", "tutorial-step10"]);
+ $("body").append(div);
+
+
+ driverObj.moveNext();
+ },
+ onPrevClick: () => {
+ // Remove the connection between the 2 config ports
+ $(".tutorial-step9").trigger("mousedown");
+
+ // Recreate the connection between the 2 nodes
+ let node1 = app.view.figures.data[0];
+ let node2 = app.view.figures.data[1];
+ let node1Channels = node1.getFPGAs().data[0].getChannels().data;
+ let node2Channels = node2.getFPGAs().data[0].getChannels().data;
+ app.toolbar.connectChannels(node1Channels[1], node2Channels[1]);
+
+
+ driverObj.movePrevious();
+ }
+ }
+ },
+ {
+ element: '.tutorial-step10',
+ popover: {
+ ...activeSteps["mapping1:1"],
+ onPrevClick: () => {
+ let node1 = app.view.figures.data[0];
+ let node2 = app.view.figures.data[1];
+
+ // Remove the pair connections
+ delete_connections(node1.getAllConnections(), app.view);
+
+ // Recreate the connection between the 2 config ports
+ let node1ConfigPort = node1.getPorts().data[0];
+ let node2ConfigPort = node2.getPorts().data[0];
+ var c = new HoverConnection(node1ConfigPort, node2ConfigPort);
+ var command = new draw2d.command.CommandAdd(app.view, c, 0, 0);
+ app.view.getCommandStack().execute(command);
+
+ driverObj.movePrevious();
+ }
+ }
+ },
+ {
+ element: '.copy-fpgalink',
+ popover: {
+ ...activeSteps["copyConfiguration"]
+ }
+ },
+ ];
+
+ if (tutorialType == "basic") {
+ // Remove 5 elements starting from index 5
+ driverSteps.splice(5, 5);
+ }
+} else if (tutorialType == "actions") {
+ driverSteps = [
+ {
+ element: '.undo-redo-container',
+ popover: {
+ ...activeSteps["undoRedo"]
+ }
+ },
+ {
+ element: '.import-container',
+ popover: {
+ ...activeSteps["importConfiguration"]
+ }
+ },
+ {
+ element: '.copy-fpgalink',
+ popover: {
+ ...activeSteps["copyConfiguration"]
+ }
+ },
+ {
+ element: '.copy-url',
+ popover: {
+ ...activeSteps["copyURL"]
+ }
+ },
+ {
+ element: '.export-svg',
+ popover: {
+ ...activeSteps["exportSVG"]
+ }
+ }
+ ]
+} else if (tutorialType == "switch") {
+ driverSteps = [
+ {
+ element: '#ethernet-switch',
+ popover: {
+ ...activeSteps["createSwitch"],
+ onNextClick: () => {
+ // Create a switch
+ createNodes("node-ethernet-switch", "north", 400, 60);
+ driverObj.moveNext();
+ },
+ }
+ },
+ {
+ element: '#xilinx-node',
+ popover: {
+ ...activeSteps["createNode"],
+ onNextClick: () => {
+ // Create a node
+ createNodes("node-xilinx", "east", 90, 60);
+
+ // Create a invisible div to highlight it
+ let nodeElement = $(".NodeShape");
+ let { left, top } = nodeElement.offset();
+ let nodeOuterHeight = nodeElement.outerHeight();
+ let nodeOuterWidth = nodeElement.outerWidth();
+
+ let switchElement = $(".SwitchShape");
+ let switchOuterWidth = switchElement.outerWidth();
+
+ let div = document.createElement("div");
+ div.style.width = (400 - 90) + switchOuterWidth;
+ div.style.height = nodeOuterHeight;
+ div.style.top = top;
+ div.style.left = left;
+ div.style.position = "absolute";
+ div.classList.add(...["to-be-deleted", "tutorial-step2"]);
+ $("body").append(div);
+
+ driverObj.moveNext();
+ },
+ onPrevClick: () => {
+ // Delete the switch
+ // Simulate the click on the deleteBtn of the switch
+ app.view.figures.data[0].select();
+ $(".overlayMenuDeleteItem").click();
+
+ driverObj.movePrevious();
+ },
+ }
+ },
+ {
+ element: '.tutorial-step2',
+ popover: {
+ ...activeSteps["setConnections"],
+ onNextClick: () => {
+ // Create a 1:1 connection between the node and the switch
+ let eth_switch = app.view.figures.data[0];
+ let node = app.view.figures.data[1];
+ connectBasedOnConfig([node], "one-by-one", eth_switch)
+
+ driverObj.moveNext();
+ },
+ onPrevClick: () => {
+ // Delete the node
+ // Simulate the click on the deleteBtn of the node
+ app.view.figures.data[1].select();
+ $(".overlayMenuDeleteItem").click();
+
+ driverObj.movePrevious();
+ },
+ }
+ },
+ {
+ element: '.tutorial-step2',
+ popover: {
+ ...activeSteps["showConnections"],
+ onPrevClick: () => {
+ // Remove the connections between the node and the switch
+ let nodeConnections = app.view.figures.data[0].getAllConnections();
+ delete_connections(nodeConnections, app.view);
+
+ driverObj.movePrevious();
+ }
+ },
+ },
+ {
+ element: '.copy-fpgalink',
+ popover: {
+ ...activeSteps["copyConfiguration"]
+ }
+ },
+ ]
+}
+
+const driver = window.driver.js.driver;
+const driverObj = driver({
+ showProgress: true,
+ steps: driverSteps,
+ onDestroyStarted: () => {
+ // Delete all intermediate divs
+ $(".to-be-deleted").remove();
+ $(".pointer-none").removeClass("pointer-none");
+
+ driverObj.destroy();
+
+ // Remove the "tutorial=*" from the URL
+ const url = new URL(window.location.href);
+ url.search = "";
+ // window.history.replaceState({}, document.title, url);
+ }
+});
+
+driverObj.drive();
+
+
+
+
+
+
+// {
+// element: '#xilinx-node',
+// popover: {
+// title: 'Different Node',
+// description: 'You can also create a different type of nodes using the same drag & drop method.
You can learn more about the hardware structure of this node here',
+// side: "right",
+// align: 'center',
+// }
+// },
+// {
+// element: '#ethernet-switch',
+// popover: {
+// title: 'Ethernet Switch',
+// description: 'You can also create an ethernet switch.',
+// side: "right",
+// align: 'center',
+// }
+// },
+// {
+// element: '#label',
+// popover: {
+// title: 'Labels',
+// description: 'You can drag and drop this to add labels wherever you want.',
+// side: "right",
+// align: 'center',
+// }
+// },
\ No newline at end of file
diff --git a/gui/Util.js b/gui/Util.js
index 49c9829..6d0a35d 100644
--- a/gui/Util.js
+++ b/gui/Util.js
@@ -191,4 +191,68 @@ function connectBasedOnConfig(nodes, config, eth_switch) {
}
}
+}
+
+function createNodes(type, shape, x, y) {
+ let view = app.view;
+
+ switch (type) {
+ case "node-intel":
+ var node = new NodeShape({ "orientation": shape })
+
+ // node.setOrientation(orientation);
+ node.setName(view.getNodeNameNew());
+ node.addFPGA("acl0", 4);
+ node.addFPGA("acl1", 4);
+
+ // create a command for the undo/redo support
+ var command = new draw2d.command.CommandAdd(view, node, x, y);
+ view.getCommandStack().execute(command);
+
+ // Workaround to fix size problem
+ node.setOrientation(node.orientation);
+
+ break;
+ case "node-xilinx":
+ var node = new NodeShape({ "orientation": shape })
+
+ // node.setOrientation(orientation);
+ node.setName(view.getNodeNameNew());
+ node.addFPGA("acl0", 2);
+ node.addFPGA("acl1", 2);
+ node.addFPGA("acl2", 2);
+
+ // create a command for the undo/redo support
+ var command = new draw2d.command.CommandAdd(view, node, x, y);
+ view.getCommandStack().execute(command);
+
+ break;
+ case "label":
+ // Add decoration by type.
+ switch (shape) {
+ case "label":
+ var label = new Label({"orientation": "north"});
+
+ // create a command for the undo/redo support
+ var command = new draw2d.command.CommandAdd(view, label, x, y);
+ view.getCommandStack().execute(command);
+
+ break;
+ default:
+ console.log("unknown shape.");
+ }
+
+ break;
+ case "node-ethernet-switch":
+ var eth_switch = new SwitchShape({ "orientation": shape })
+ eth_switch.setText(view.getSwitchNameNew())
+
+ // create a command for the undo/redo support
+ var command = new draw2d.command.CommandAdd(view, eth_switch, x, y);
+ view.getCommandStack().execute(command);
+
+ break;
+ default:
+ console.log("unknown type: \"" + $(droppedDomNode).data("type") + "\".");
+ }
}
\ No newline at end of file
diff --git a/gui/View.js b/gui/View.js
index 3c10ed7..84f0b34 100755
--- a/gui/View.js
+++ b/gui/View.js
@@ -67,72 +67,8 @@ example.View = draw2d.Canvas.extend({
// Switch between types that can be added.
// - type "node": n2fpga nodes (with orientation)
// - type "label": different labels for decoration
-
- switch($(droppedDomNode).data("type")) {
- case "node-intel" :
- var node = new NodeShape({ "orientation": $(droppedDomNode).data("shape") })
-
- // node.setOrientation(orientation);
- node.setName(this.getNodeNameNew());
- node.addFPGA("acl0", 4);
- node.addFPGA("acl1", 4);
-
- // create a command for the undo/redo support
- var command = new draw2d.command.CommandAdd(this, node, x, y);
- this.getCommandStack().execute(command);
-
- // Workaround to fix size problem
- node.setOrientation(node.orientation);
-
- break;
- case "node-xilinx":
- var node = new NodeShape({ "orientation": $(droppedDomNode).data("shape") })
-
- // node.setOrientation(orientation);
- node.setName(this.getNodeNameNew());
- node.addFPGA("acl0", 2);
- node.addFPGA("acl1", 2);
- node.addFPGA("acl2", 2);
-
- // create a command for the undo/redo support
- var command = new draw2d.command.CommandAdd(this, node, x, y);
- this.getCommandStack().execute(command);
-
- break;
- case "label" :
- // Add decoration by type.
- switch ($(droppedDomNode).data("shape")) {
- case "label" :
- var figure = new draw2d.shape.note.PostIt({
- text: "label text",
- color: "#000000",
- padding: 20
- });
-
- figure.installEditor(new draw2d.ui.LabelInplaceEditor());
-
- // create a command for the undo/redo support
- var command = new draw2d.command.CommandAdd(this, figure, x, y);
- this.getCommandStack().execute(command);
-
- break;
- default :
- console.log("unknown shape.");
- }
-
- break;
- case "node-ethernet-switch":
- var eth_switch = new SwitchShape({ "orientation": $(droppedDomNode).data("shape") })
- eth_switch.setText(this.getSwitchNameNew())
-
- // create a command for the undo/redo support
- var command = new draw2d.command.CommandAdd(this, eth_switch, x, y);
- this.getCommandStack().execute(command);
-
- break;
- default :
- console.log("unknown type: \"" + $(droppedDomNode).data("type") + "\".");
- }
+ // This function is available in the Utils.js
+ createNodes($(droppedDomNode).data("type"), $(droppedDomNode).data("shape"), x, y);
}
});
diff --git a/index.html b/index.html
index e2955f9..a4a1398 100755
--- a/index.html
+++ b/index.html
@@ -7,10 +7,18 @@
+
+
+
+
+
+
+
+
@@ -26,8 +34,8 @@
-
-
+
+
@@ -220,17 +246,52 @@
+
+
+
FPGALink-GUI
-
Add FPGA Nodes by
Drag&Drop
-
Intel Stratix 10 Node
-
Xilinx Alveo U280 Nodes
-
Add Ethernet Switch by
Drag&Drop
-
Ethernet Switch
-
Add Annotations
-
Label
+
Add Elements by Drag&Drop
+
+ Intel Stratix 10
+ Node
+
+ Xilinx Alveo U280
+ Nodes
+
Ethernet
+ Switch
+
Label
+
+
Tutorials
+
+
+
+
+
Settings
Change Router
@@ -257,12 +318,6 @@
-->
-
- SVG output
-
-
-