[
  {
    "path": "NEditor.css",
    "content": "*{font-family: arial; }\n\nbody{background:url(bg_grid.png);}\nsvg{position:absolute; z-index:-100; top:0px; left:0px; width:100%; height:100%;}\n\n/*---------------------------------------*/\n.NodeContainer{ position:absolute; background-color:rgba(63,63,63,.7); display:inline-block; border-radius:5px; box-shadow: 0px 5px 10px #000000;}\n\n.NodeContainer > header{ display:block; background-color:#297286; color:white; cursor:pointer; border-radius:5px 5px 0px 0px;\n\ttext-align:center; padding:4px 12px;\n\t-webkit-touch-callout: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;           \n}\n\n.NodeContainer > ul { margin:0px; padding:0px; list-style:none; }\n.NodeContainer > ul > li { position:relative; margin-top:8px; margin-bottom:8px; }\n.NodeContainer > ul > li > span{padding-left:1em; padding-right:1em; color:white;}\n\n.NodeContainer > ul > li > i{ position:absolute; width:0.6em; height:0.6em; background-color:gray; border-radius:1em; cursor:pointer; }\n.NodeContainer > ul > li > i:hover{ background-color:red; }\n\n.NodeContainer > ul > li.Input > i { left:-0.3em; top:0.4em; }\n.NodeContainer > ul > li.Output { text-align:right;}\n.NodeContainer > ul > li.Output > i { right:-0.25em; top:0.4em; }\n.NodeContainer > ul > li.Active > i { background-color:#72a836;}\t"
  },
  {
    "path": "NEditor.js",
    "content": "//Copyright 2016 Sketchpunk Labs\n\n//###########################################################################\n//Main Static Object\n//###########################################################################\nvar NEditor = {};\nNEditor.dragMode = 0;\nNEditor.dragItem = null;    //reference to the dragging item\nNEditor.startPos = null;    //Used for starting position of dragging lines\nNEditor.offsetX = 0;        //OffsetX for dragging nodes\nNEditor.offsetY = 0;        //OffsetY for dragging nodes\nNEditor.svg = null;         //SVG where the line paths are drawn.\n\nNEditor.pathColor = \"#999999\";\nNEditor.pathColorA = \"#86d530\";\nNEditor.pathWidth = 2;\nNEditor.pathDashArray = \"20,5,5,5,5,5\";\n\nNEditor.init = function(){\n\tNEditor.svg = document.getElementById(\"connsvg\");\n\tNEditor.svg.ns = NEditor.svg.namespaceURI;\n};\n\n/*--------------------------------------------------------\nGlobal Function */\n\n//Trail up the parent nodes to get the X,Y position of an element\nNEditor.getOffset = function(elm){\n\tvar pos = {x:0,y:0};\n\twhile(elm){\n\t  pos.x += elm.offsetLeft;\n\t  pos.y += elm.offsetTop;\n\t  elm = elm.offsetParent;\n\t}\n\treturn pos;\n};\n\n//Gets the position of one of the connection points\nNEditor.getConnPos = function(elm){\n\tvar pos = NEditor.getOffset(elm);\n\tpos.x += (elm.offsetWidth / 2) + 1.5; //Add some offset so its centers on the element\n\tpos.y += (elm.offsetHeight / 2) + 0.5;\n\treturn pos;\n};\n\n//Used to reset the svg path between two nodes\nNEditor.updateConnPath = function(o){\n\tvar pos1 = o.output.getPos(),\n\t\tpos2 = o.input.getPos();\n\tNEditor.setQCurveD(o.path,pos1.x,pos1.y,pos2.x,pos2.y);\n};\n\n//Creates an Quadratic Curve path in SVG\nNEditor.createQCurve = function (x1, y1, x2, y2) {\n\tvar elm = document.createElementNS(NEditor.svg.ns,\"path\");\n\telm.setAttribute(\"fill\", \"none\");\n\telm.setAttribute(\"stroke\", NEditor.pathColor);\n\telm.setAttribute(\"stroke-width\", NEditor.pathWidth);\n\telm.setAttribute(\"stroke-dasharray\", NEditor.pathDashArray);\n\n\tNEditor.setQCurveD(elm,x1,y1,x2,y2);\n\treturn elm;\n}\n\n//This is seperated from the create so it can be reused as a way to update an existing path without duplicating code.\nNEditor.setQCurveD = function(elm,x1,y1,x2,y2){\n\tvar dif = Math.abs(x1-x2) / 1.5,\n\t\tstr = \"M\" + x1 + \",\" + y1 + \" C\" +\t//MoveTo\n\t\t\t(x1 + dif) + \",\" + y1 + \" \" +\t//First Control Point\n\t\t\t(x2 - dif) + \",\" + y2 + \" \" +\t//Second Control Point\n\t\t\t(x2) + \",\" + y2;\t\t\t\t//End Point\n\n\telm.setAttribute('d', str);\n}\n\nNEditor.setCurveColor = function(elm,isActive){ elm.setAttribute('stroke', (isActive)? NEditor.pathColorA : NEditor.pathColor); }\n\n/*Unused function at the moment, it creates a straight line\nNEditor.createline = function (x1, y1, x2, y2, color, w) {\n\tvar line = document.createElementNS(NEditor.svg.ns, 'line');\n\tline.setAttribute('x1', x1);\n\tline.setAttribute('y1', y1);\n\tline.setAttribute('x2', x2);\n\tline.setAttribute('y2', y2);\n\tline.setAttribute('stroke', color);\n\tline.setAttribute('stroke-width', w);\n\treturn line;\n}*/\n\n\n/*--------------------------------------------------------\nDragging Nodes */\nNEditor.beginNodeDrag = function(n,x,y){\n\tif(NEditor.dragMode != 0) return;\n\n\tNEditor.dragMode = 1;\n\tNEditor.dragItem = n;\n\tthis.offsetX = n.offsetLeft - x;\n\tthis.offsetY = n.offsetTop - y;\n\n\twindow.addEventListener(\"mousemove\",NEditor.onNodeDragMouseMove);\n\twindow.addEventListener(\"mouseup\",NEditor.onNodeDragMouseUp);\n};\n\nNEditor.onNodeDragMouseUp = function(e){\n\te.stopPropagation(); e.preventDefault();\n\tNEditor.dragItem = null;\n\tNEditor.dragMode = 0;\n\n\twindow.removeEventListener(\"mousemove\",NEditor.onNodeDragMouseMove);\n\twindow.removeEventListener(\"mouseup\",NEditor.onNodeDragMouseUp);\n};\n\nNEditor.onNodeDragMouseMove = function(e){\n\te.stopPropagation(); e.preventDefault();\n\tif(NEditor.dragItem){\n\t  NEditor.dragItem.style.left = e.pageX + NEditor.offsetX + \"px\";\n\t  NEditor.dragItem.style.top = e.pageY + NEditor.offsetY + \"px\";\n\t  NEditor.dragItem.ref.updatePaths();\n\t}\n};\n\n/*--------------------------------------------------------\nDragging Paths */\nNEditor.beginConnDrag = function(path){\n\tif(NEditor.dragMode != 0) return;\n\n\tNEditor.dragMode = 2;\n\tNEditor.dragItem = path;\n\tNEditor.startPos = path.output.getPos();\n\n\tNEditor.setCurveColor(path.path,false);\n\twindow.addEventListener(\"click\",NEditor.onConnDragClick);\n\twindow.addEventListener(\"mousemove\",NEditor.onConnDragMouseMove);\n};\n\nNEditor.endConnDrag = function(){\n\tNEditor.dragMode = 0;\n\tNEditor.dragItem = null;\n\n\twindow.removeEventListener(\"click\",NEditor.onConnDragClick);\n\twindow.removeEventListener(\"mousemove\",NEditor.onConnDragMouseMove);\n}\n\nNEditor.onConnDragClick = function(e){\n\te.stopPropagation(); e.preventDefault();\n\tNEditor.dragItem.output.removePath(NEditor.dragItem);\n\tNEditor.endConnDrag();\n};\n\nNEditor.onConnDragMouseMove = function(e){\n\te.stopPropagation(); e.preventDefault();\n\tif(NEditor.dragItem) NEditor.setQCurveD(NEditor.dragItem.path,NEditor.startPos.x,NEditor.startPos.y,e.pageX,e.pageY);\n};\n\n/*--------------------------------------------------------\nConnection Event Handling */\nNEditor.onOutputClick = function(e){\n\te.stopPropagation(); e.preventDefault();\n\tvar path = e.target.parentNode.ref.addPath();\n\n\tNEditor.beginConnDrag(path);\n}\n\nNEditor.onInputClick = function(e){\n\te.stopPropagation(); e.preventDefault();\n\tvar o = this.parentNode.ref;\n\n\tswitch(NEditor.dragMode){\n\t\tcase 2: //Path Drag\n\t\t  o.applyPath(NEditor.dragItem);\n\t\t  NEditor.endConnDrag();\n\t\t  break;\n\t\tcase 0: //Not in drag mode\n\t\t  var path = o.clearPath();\n\t\t  if(path != null) NEditor.beginConnDrag(path);\n\t\t  break;\n\t}\n}\n\n\n//###########################################################################\n// Connector Object\n//###########################################################################\n\n//Connector UI Object. Ideally this should be an abstract class as a base for an output and input class, but save time\n//I wrote this object to handle both types. Its a bit hokey but if it becomes a problem I'll rewrite it in a better OOP way.\nNEditor.Connector = function(pElm,isInput,name){\n\tthis.name   = name;\n\tthis.root   = document.createElement(\"li\");\n\tthis.dot    = document.createElement(\"i\");\n\tthis.label  = document.createElement(\"span\");\n\n\t//Input/Output Specific values\n\tif(isInput) this.OutputConn = null;\t\t//Input can only handle a single connection.\n\telse this.paths = [];    \t\t\t\t//Outputs can connect to as many inputs is needed\n\n\t//Create Elements\n\tpElm.appendChild(this.root);\n\tthis.root.appendChild(this.dot);\n\tthis.root.appendChild(this.label);\n\n\t//Define the Elements\n\tthis.root.className = (isInput)?\"Input\":\"Output\";\n\tthis.root.ref = this;\n\tthis.label.innerHTML = name;\n\tthis.dot.innerHTML = \"&nbsp;\";\n\n\tthis.dot.addEventListener(\"click\", (isInput)?NEditor.onInputClick:NEditor.onOutputClick );\n};\n\n/*--------------------------------------------------------\nCommon Methods */\n\n//Get the position of the connection ui element\nNEditor.Connector.prototype.getPos = function(){ return NEditor.getConnPos(this.dot); }\n\n//Just updates the UI if the connection is currently active\nNEditor.Connector.prototype.resetState = function(){\n\tvar isActive = (this.paths && this.paths.length > 0) || (this.OutputConn != null);\n\n\tif(isActive) this.root.classList.add(\"Active\");\n\telse this.root.classList.remove(\"Active\");\n}\n\n//Used mostly for dragging nodes, so this allows the paths to be redrawn\nNEditor.Connector.prototype.updatePaths = function(){\n\tif(this.paths && this.paths.length > 0) for(var i=0; i < this.paths.length; i++) NEditor.updateConnPath(this.paths[i]);\n\telse if( this.OutputConn ) NEditor.updateConnPath(this.OutputConn);\n}\n\n\n/*--------------------------------------------------------\nOutput Methods */\n\n//This creates a new path between nodes\nNEditor.Connector.prototype.addPath = function(){\n\tvar pos = NEditor.getConnPos(this.dot),\n\t\tdat = {\n\t\t\tpath: NEditor.createQCurve(pos.x,pos.y,pos.x,pos.y),\n\t\t\tinput:null,\n\t\t\toutput:this\n\t\t};\n\n\tNEditor.svg.appendChild(dat.path);\n\tthis.paths.push(dat);\n\treturn dat;\n}\n\n//Remove Path\nNEditor.Connector.prototype.removePath = function(o){\n\tvar i = this.paths.indexOf(o);\n\n\tif(i > -1){\n\t\tNEditor.svg.removeChild(o.path);\n\t\tthis.paths.splice(i,1);\n\t\tthis.resetState();\n\t}\n}\n\nNEditor.Connector.prototype.connectTo = function(o){\n\tif(o.OutputConn === undefined){\n\t\tconsole.log(\"connectTo - not an input\");\n\t\treturn;\n\t}\n\n\tvar conn = this.addPath();\n\to.applyPath(conn);\n}\n\n/*--------------------------------------------------------\nInput Methods */\n\n//Applying a connection from an output\nNEditor.Connector.prototype.applyPath = function(o){\n\t//If a connection exists, disconnect it.\n\tif(this.OutputConn != null) this.OutputConn.output.removePath(this.OutputConn);\n\n\t//If moving a connection to here, tell previous input to clear itself.\n\tif(o.input != null) o.input.clearPath();\n\n\to.input = this;\t\t\t//Saving this connection as the input reference\n\tthis.OutputConn = o;\t//Saving the path reference to this object\n\tthis.resetState();\t\t//Update the state on both sides of the connection, TODO some kind of event handling scheme would work better maybe\n\to.output.resetState();\n\n\tNEditor.updateConnPath(o);\n\tNEditor.setCurveColor(o.path,true);\n}\n\n//clearing the connection from an output\nNEditor.Connector.prototype.clearPath = function(){\n\tif(this.OutputConn != null){\n\t\tvar tmp = this.OutputConn;\n\t\ttmp.input = null;\n\n\t\tthis.OutputConn = null;\n\t\tthis.resetState();\n\t\treturn tmp;\n\t}\n}\n\n\n//###########################################################################\n// Node Object\n//###########################################################################\nNEditor.Node = function(sTitle){\n\tthis.Title = sTitle;\n\tthis.Inputs = [];\n\tthis.Outputs = [];\n\n\t//.........................\n\tthis.eRoot = document.createElement(\"div\");\n\tdocument.body.appendChild(this.eRoot);\n\tthis.eRoot.className = \"NodeContainer\";\n\tthis.eRoot.ref = this;\n\n\t//.........................\n\tthis.eHeader = document.createElement(\"header\");\n\tthis.eRoot.appendChild(this.eHeader);\n\tthis.eHeader.innerHTML = this.Title;\n\tthis.eHeader.addEventListener(\"mousedown\",this.onHeaderDown);\n\n\t//.........................\n\tthis.eList = document.createElement(\"ul\");\n\tthis.eRoot.appendChild(this.eList);\n};\n\n\nNEditor.Node.prototype.addInput = function(name){\n\tvar o = new NEditor.Connector(this.eList,true,name) ;\n\tthis.Inputs.push(o);\n\treturn o;\n}\n\nNEditor.Node.prototype.addOutput = function(name){\n\tvar o = new NEditor.Connector(this.eList,false,name);\n\tthis.Outputs.push(o);\n\treturn o;\n}\n\nNEditor.Node.prototype.getInputPos = function(i){ return NEditor.getConnPos(this.Inputs[i].dot); }\nNEditor.Node.prototype.getOutputPos = function(i){ return NEditor.getConnPos(this.Outputs[i].dot); }\n\nNEditor.Node.prototype.updatePaths = function(){\n\tvar i;\n\tfor(i=0; i < this.Inputs.length; i++) this.Inputs[i].updatePaths();\n\tfor(i=0; i < this.Outputs.length; i++) this.Outputs[i].updatePaths();\n}\n\n//Handle the start node dragging functionality\nNEditor.Node.prototype.onHeaderDown = function(e){\n\te.stopPropagation();\n\tNEditor.beginNodeDrag(e.target.parentNode,e.pageX,e.pageY);\n};\n\nNEditor.Node.prototype.setPosition = function(x,y){\n\tthis.eRoot.style.left = x + \"px\";\n\tthis.eRoot.style.top = y + \"px\";\n};\n\nNEditor.Node.prototype.setWidth = function(w){ this.eRoot.style.width = w+\"px\"; }\n\n\n//###########################################################################\n// SETUP\n//###########################################################################\nwindow.addEventListener(\"load\",function(e){\n\tNEditor.init();\n});\n"
  },
  {
    "path": "README.md",
    "content": "# NEditorJS\n\n**Youtube Demo** : \nhttps://www.youtube.com/watch?v=HpyD2hrySd0\n\n**Live Demo** :\nhttp://sketchpunk.com/NEditorJS/ui.html\n\n**Purpose**:\nThese are one of those projects you do just for the experience and fun of it, I really have no need for it but who knows, maybe I'll\nmake my own automation software and this will make for a good UI for creating the process flow. I'll probably add on to this from time to\ntime.\n\nNote, if you have a need for this sort of interface for some application but you need it to do more, give me a ring maybe we can work\ntogether. I'm always open to working on interesting and worthwhile projects.\n\n### Links\n* [SketchPunk Labs Blog](http://sketchpunklabs.tumblr.com/)\n* [Support SketchPunk Labs @ Patreon](https://www.patreon.com/sketchpunk)\n\n### License\nThis application is released as in open source application, following the Creative Common - Attribution-NonCommercial Licence.\n\nhttps://creativecommons.org/licenses/by-nc/3.0/us/\n"
  },
  {
    "path": "ui.html",
    "content": "<html>\n\t<head>\n\t\t<link rel=\"stylesheet\" type=\"text/css\" href=\"NEditor.css\">\n\t\t<script src=\"NEditor.js\"></script>\n\t\t<script>\n\t\t\twindow.addEventListener(\"load\",function(e){\n\t\t\t\tvar n4 = new NEditor.Node(\"Compile Results\");\n\t\t\t\tvar n4i1 = n4.addInput(\"Input A\");\n\t\t\t\tvar n4i2 = n4.addInput(\"Input B\");\n\t\t\t\tn4.setPosition(700,180);\n\n\n\t\t\t\tvar n3 = new NEditor.Node(\"Some Process\");\n\t\t\t\tvar n3i1 = n3.addInput(\"Input\");\n\t\t\t\tvar n3o1 = n3.addOutput(\"Output\");\n\t\t\t\tn3.setPosition(300,50);\n\t\t\t\tn3.setWidth(200);\n\n\t\t\t\tn3o1.connectTo(n4i2);\n\n\n\t\t\t\tvar n2 = new NEditor.Node(\"Some Other Process\");\n\t\t\t\tvar n2i1 = n2.addInput(\"Input\");\n\t\t\t\tvar n2o1 = n2.addOutput(\"Output\");\n\t\t\t\tn2.setPosition(300,300);\n\t\t\t\tn2.setWidth(200);\n\n\t\t\t\tn2o1.connectTo(n4i1);\n\n\n\t\t\t\tvar n1 = new NEditor.Node(\"Starting Node\");\n\t\t\t\tvar n1o1 = n1.addOutput(\"Output\");\n\t\t\t\tn1.setPosition(50,200);\n\n\t\t\t\tn1o1.connectTo(n2i1);\n\t\t\t\tn1o1.connectTo(n3i1);\n\n\t\t\t\tvar n5 = new NEditor.Node(\"SketchPunk Labs\");\n\t\t\t\tn5.addInput(\"Donate :)\");\n\t\t\t\tn5.setPosition(700,500);\n\n\t\t\t\tvar n6 = new NEditor.Node(\"The User\");\n\t\t\t\tn6.addOutput(\"1 dollar\");\n\t\t\t\tn6.setPosition(500,500);\n\n\t\t\t});\n\t\t</script>\n\t</head>\n<body>\n\n<svg id=\"connsvg\"></svg>\n\n<p style=\"color:#a0a0a0; bottom:1px; position:absolute\">\n\t<i style=\"color:lime;\">Instructions</i><br>\n\t- Click on an output connecting dots to start a new connection.<br>\n\t- When new connection is made, click on an input connecting dot to complete connection.<br>\n\t- Click and hold node title bars to drag them around.\n\n\t<br><br>&copy; 2016 Sketchpunk Labs;\n</p>\n\n\n</body>\n</html>"
  }
]