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 @@ + +
+
Change background color:
+ +
Change text color:
+ +
+
--> - - Node type (intel or xilinx) unclear. Select