// // mods.js // // Neil Gershenfeld // (c) Massachusetts Institute of Technology 2018 // // This work may be reproduced, modified, distributed, performed, and // displayed for any purpose, but must acknowledge the mods // project. Copyright is retained and must be preserved. The work is // provided as is; no warranty is provided, and users accept all // liability. // // closure // (function(){ // // globals // var mods = {} mods.mod = {} mods.globals = {} mods.ui = {source:null, progname:'', padding:7, bezier:100, canvas:250, rows:5, cols:20, link_color:'rgb(0,0,128)', link_highlight:'rgb(255,0,0)', header:50, selected:{}, mousedown:null, menu:null, top:null, left:null, xstart:null, ystart:null, xtrans:null, ytrans:null } // // UI // document.body.style.overflow = "hidden" function mods_transform() { var transform = document.body.style.transform var m = new DOMMatrix(getComputedStyle(document.body).transform) var s = m.m11 var tx = m.m41/s var ty = m.m42/s var origin = document.body.style.transformOrigin var pxx = origin.indexOf('px') var ox = parseFloat(origin.slice(0,pxx)) var pxy = origin.indexOf('px',pxx+2) var oy = parseFloat(origin.slice(pxx+2,pxy)) return({s:s,tx:tx,ty:ty,ox:ox,oy:oy}) } document.body.style.transform = 'scale(1) translate(0px,0px)' document.body.style.transformOrigin = '0px 0px' // // scroll wheel event // window.addEventListener('wheel',function(evt) { /* (xw+tx-ox)*s+ox = xs xw = ox-tx+(xs-ox)/s (xw+tx0-ox0)*s+ox0 = (xw+tx1-ox1)*s+ox1 (tx0-ox0)*s+ox0 = (tx1-ox1)*s+ox1 tx0+(ox1-ox0)+(ox0-ox1)/s = tx1 tx0+(ox1-ox0)*(1-1/s) = tx1 */ var el = document.elementFromPoint(evt.pageX,evt.pageY) if ((el.tagName == "HTML") || (el.tagName == "BODY")) { set_prompt('scroll to zoom') evt.preventDefault() evt.stopPropagation() var t = mods_transform() if (evt.deltaY > 0) var scale = t.s*1.1 else var scale = t.s*0.9 var tx = t.tx+(evt.pageX-t.ox)*(1-1/t.s) var ty = t.ty+(evt.pageY-t.oy)*(1-1/t.s) document.body.style.transform = `scale(${scale}) translate(${tx}px,${ty}px)` document.body.style.transformOrigin = `${evt.pageX}px ${evt.pageY}px` } }) // // body mouse events // window.addEventListener('mousedown',function(evt) { // // get element mouse is over // var el = document.elementFromPoint(evt.pageX,evt.pageY) // // check if on body // if ((el.tagName == "HTML") || (el.tagName == "BODY")) { // // remember button // mods.ui.mousedown = evt.button if (mods.ui.mousedown == 0) { set_prompt('left-drag to pan, right-drag to select') if (mods.ui.menu != null) { document.body.removeChild(mods.ui.menu) mods.ui.menu = null } } else if (mods.ui.mousedown == 2) { set_prompt('menu; left-drag to pan, right-drag to select') } // // remember position // var t = mods_transform() mods.ui.xstart = evt.pageX mods.ui.ystart = evt.pageY mods.ui.xtrans = t.tx mods.ui.ytrans = t.ty } }) window.addEventListener('mousemove',function(evt) { // // mouse move // if (mods.ui.mousedown != null) { evt.preventDefault() evt.stopPropagation() var t = mods_transform() if (mods.ui.mousedown == 0) { // // pan on left drag // xtrans = mods.ui.xtrans+(evt.pageX-mods.ui.xstart)/t.s ytrans = mods.ui.ytrans+(evt.pageY-mods.ui.ystart)/t.s document.body.style.transform = `scale(${t.s}) translate(${xtrans}px,${ytrans}px)` } else if (mods.ui.mousedown == 2) { // // select on right drag // var rect = document.getElementById('svgrect') if (rect == undefined) { // // start dragging // if (mods.ui.menu != null) { document.body.removeChild(mods.ui.menu) mods.ui.menu = null } set_prompt('right-drag to select') var t = mods_transform() var rect = document.createElementNS('http://www.w3.org/2000/svg','rect') rect.setAttribute('id','svgrect') rect.setAttribute('x',t.ox-t.tx+(evt.pageX-t.ox)/t.s) rect.setAttribute('y',t.oy-t.ty+(evt.pageY-t.oy)/t.s-mods.ui.header) rect.setAttribute('width',0) rect.setAttribute('height',0) rect.setAttribute('fill','rgb(200,200,200)') rect.setAttribute('stroke','none') var svg = document.getElementById('svg') svg.insertBefore(rect,svg.firstChild) } else { // // continue dragging // var rect = document.getElementById('svgrect') var xp = t.ox-t.tx+(mods.ui.xstart-t.ox)/t.s var yp = t.oy-t.ty+(mods.ui.ystart-t.oy)/t.s-mods.ui.header var xw = t.ox-t.tx+(evt.pageX-t.ox)/t.s var yw = t.oy-t.ty+(evt.pageY-t.oy)/t.s-mods.ui.header if (xw < xp) { rect.setAttribute('x',xw) rect.setAttribute('width',xp-xw) } else rect.setAttribute('width',xw-xp) if (yw < yp) { rect.setAttribute('y',yw) rect.setAttribute('height',yp-yw) } else rect.setAttribute('height',yw-yp) } } } }) window.addEventListener('mouseup',function(evt) { // // mouse up // mods.ui.mousedown = null // // check for selection rectangle // var rect = document.getElementById('svgrect') if (rect != null) { // // rectangle exists, selecting modules // var x = parseFloat(rect.getAttribute('x')) var y = parseFloat(rect.getAttribute('y')) var width = parseFloat(rect.getAttribute('width')) var height = parseFloat(rect.getAttribute('height')) svg.removeChild(rect) var modules = document.getElementById('modules') // // loop to find selected modules // mods.ui.selected = {} for (var module in modules.childNodes) { var container = modules.childNodes[module] var id = container.id if (id != undefined) { var name = container.firstChild var left = parseFloat(container.dataset.left) var top = parseFloat(container.dataset.top) if ((x <= left) && (left <= x+width) && (y <= top) && (top <= y+height)) { // // module is in selection rectangle // name.style.fontWeight = "bold" mods.ui.selected[id] = true } else { // // module is not in selection rectangle // name.style.fontWeight = "normal" } } } } }) // // context menu // window.addEventListener('contextmenu',function(evt){ evt.stopPropagation() evt.preventDefault() if (mods.ui.menu != null) { document.body.removeChild(mods.ui.menu) mods.ui.menu = null } var div = document.createElement('div') make_menu(div) add_menu(div,'modules',modules) add_menu(div,'programs',programs) add_menu(div,'edit',edit) add_menu(div,'options',options) document.body.appendChild(div) function make_menu(div) { mods.ui.menu = div div.style.position = "absolute" var t = mods_transform() div.style.top = t.oy-t.ty+(evt.pageY-t.oy)/t.s div.style.left = t.ox-t.tx+(evt.pageX-t.ox)/t.s div.style.zIndex = 0 div.style.cursor = 'default' div.style.backgroundColor = "rgb(220,255,255)" div.style.padding = 1.5*mods.ui.padding div.style.textAlign = 'left' div.style.border = '2px solid' div.style.borderRadius = '10px' } function add_menu(div,text,click) { var textdiv = document.createElement('div') textdiv.appendChild(document.createTextNode(text)) textdiv.appendChild(document.createElement('br')) textdiv.addEventListener('mouseover',function(evt){ evt.target.style.fontWeight = 'bold'}) textdiv.addEventListener('mouseout',function(evt){ evt.target.style.fontWeight = 'normal'}) textdiv.addEventListener('mousedown',click) textdiv.addEventListener('touchend',click) div.appendChild(textdiv) } // // modules menu // function modules(evt) { evt.preventDefault() evt.stopPropagation() document.body.removeChild(evt.target.parentNode) var div = document.createElement('div') make_menu(div) set_prompt('modules') // // open server module // add_menu(div,'open server module',function(evt){ function module_label(label) { var div = document.createElement('div') var i = document.createElement('i') i.appendChild(document.createTextNode(label)) div.appendChild(i) div.appendChild(document.createElement('br')) menu.appendChild(div) } function module_menu(label,module) { var div = document.createElement('div') div.appendChild( document.createTextNode('\u00A0\u00A0\u00A0'+label)) div.addEventListener('mouseover',function(evt){ evt.target.style.fontWeight = 'bold'}) div.addEventListener('mouseout',function(evt){ evt.target.style.fontWeight = 'normal'}) div.addEventListener('mousedown',function(evt){ evt.preventDefault() evt.stopPropagation() document.body.removeChild(evt.target.parentNode) mods.ui.menu = null var t = mods_transform() mod_message_handler(module, t.oy-t.ty+(evt.pageY-t.oy)/t.s, t.ox-t.tx+(evt.pageX-t.ox)/t.s)}) div.appendChild(document.createElement('br')) menu.appendChild(div) } document.body.removeChild(evt.target.parentNode) var menu = document.createElement('div') make_menu(menu) document.body.appendChild(menu) menu.style.width = mods.ui.canvas menu.style.height = mods.ui.canvas menu.style.overflow = 'auto' var req = new XMLHttpRequest() req.responseType = 'text' req.onreadystatechange = function() { if (req.readyState == XMLHttpRequest.DONE) { var str = req.response eval(str) } } req.open('GET','modules/index.js'+'?rnd='+Math.random()) req.send() }) // // open local module // add_menu(div,'open local module',function(evt){ var t = mods_transform() mods.ui.top = t.oy-t.ty+(evt.pageY-t.oy)/t.s mods.ui.left = t.ox-t.tx+(evt.pageX-t.ox)/t.s document.body.removeChild(evt.target.parentNode) mods.ui.menu = null var file = document.getElementById('mod_input') file.value = null file.click() }) // // open remote module // add_menu(div,'open remote module',function(evt){ document.body.removeChild(evt.target.parentNode) mods.ui.menu = null set_prompt('remotes not yet implemented') }) document.body.appendChild(div) } // // programs menu // function programs(evt) { evt.preventDefault() evt.stopPropagation() document.body.removeChild(evt.target.parentNode) var div = document.createElement('div') make_menu(div) set_prompt('programs') // // open local program // add_menu(div,'open local program',function(evt){ document.body.removeChild(evt.target.parentNode) mods.ui.menu = null var file = document.getElementById('prog_input') file.value = null file.click() }) // // open server program // add_menu(div,'open server program',function(evt){ function program_label(label) { var div = document.createElement('div') var i = document.createElement('i') i.appendChild(document.createTextNode(label)) div.appendChild(i) div.appendChild(document.createElement('br')) menu.appendChild(div) } function program_menu(label,program) { var div = document.createElement('div') div.appendChild( document.createTextNode('\u00A0\u00A0\u00A0'+label)) div.addEventListener('mouseover',function(evt){ evt.target.style.fontWeight = 'bold'}) div.addEventListener('mouseout',function(evt){ evt.target.style.fontWeight = 'normal'}) div.addEventListener('mousedown',function(evt){ evt.preventDefault() evt.stopPropagation() if (location.port == 80) var uri = 'http://'+location.hostname +'?program='+program else var uri = 'http://'+location.hostname+':' +location.port+'?program='+program set_prompt('<a href='+uri+'>program link</a>') prog_message_handler(program) document.body.removeChild(evt.target.parentNode) mods.ui.menu = null }) div.appendChild(document.createElement('br')) menu.appendChild(div) } document.body.removeChild(evt.target.parentNode) var menu = document.createElement('div') make_menu(menu) document.body.appendChild(menu) menu.style.width = mods.ui.canvas menu.style.height = mods.ui.canvas menu.style.overflow = 'auto' var req = new XMLHttpRequest() req.responseType = 'text' req.onreadystatechange = function() { if (req.readyState == XMLHttpRequest.DONE) { var str = req.response eval(str) } } req.open('GET','programs/index.js'+'?rnd='+Math.random()) req.send() }) // // open remote program // add_menu(div,'open remote program',function(evt){ document.body.removeChild(evt.target.parentNode) mods.ui.menu = null set_prompt('remotes not yet implemented') }) // // save local program // add_menu(div,'save local program',function(evt){ document.body.removeChild(evt.target.parentNode) mods.ui.menu = null save_program() }) // // save local page // add_menu(div,'save local page',function(evt){ document.body.removeChild(evt.target.parentNode) mods.ui.menu = null save_page() }) document.body.appendChild(div) } // // edit menu // function edit(evt) { evt.preventDefault() evt.stopPropagation() document.body.removeChild(evt.target.parentNode) var div = document.createElement('div') make_menu(div) set_prompt('edit') // // cut // add_menu(div,'cut',function(evt){ evt.preventDefault() evt.stopPropagation() document.body.removeChild(evt.target.parentNode) mods.ui.menu = null if ((Object.keys(mods.ui.selected).length) == 0) { set_prompt("nothing selected") } else { for (var id in mods.ui.selected) { var div = document.getElementById(id) delete_module(id) mods.ui.selected = {} } } }) // // copy // add_menu(div,'copy',function(evt){ evt.preventDefault() evt.stopPropagation() document.body.removeChild(evt.target.parentNode) mods.ui.menu = null set_prompt('copy not yet implemented') }) // // paste // add_menu(div,'paste',function(evt){ evt.preventDefault() evt.stopPropagation() document.body.removeChild(evt.target.parentNode) mods.ui.menu = null set_prompt('paste not yet implemented') }) document.body.appendChild(div) } // // options menu // function options(evt) { evt.preventDefault() evt.stopPropagation() document.body.removeChild(evt.target.parentNode) mods.ui.menu = null var div = document.createElement('div') make_menu(div) set_prompt('options') // // view files // add_menu(div,'view files',function(evt){ document.body.removeChild(evt.target.parentNode) mods.ui.menu = null var win = window.open('files.html') }) document.body.appendChild(div) // // view project // add_menu(div,'view project',function(evt){ document.body.removeChild(evt.target.parentNode) mods.ui.menu = null var win = window.open('https://gitlab.cba.mit.edu/pub/mods') }) document.body.appendChild(div) } }) // // prompt // document.body.appendChild(document.createTextNode(' ')) var span = document.createElement('span') span.setAttribute('id','logo') span.style.display = 'inline-block' span.style.verticalAlign = 'middle' span.style.width = 20 span.style.height = 20 span.style.padding = mods.ui.padding span.appendChild(logo(1)) document.body.appendChild(span) document.body.appendChild(document.createTextNode(' ')) var span = document.createElement('span') span.setAttribute('id','prompt') span.style.display = 'inline-block' span.style.verticalAlign = 'middle' var innerspan = document.createElement('span') span.appendChild(innerspan) document.body.appendChild(span) function logo(size) { var x = 0 var y = 2.8*size/3.8 var svgNS = "http://www.w3.org/2000/svg" var logo = document.createElementNS(svgNS,"svg") logo.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink","http://www.w3.org/1999/xlink") logo.setAttributeNS(null,'viewBox',"0 0 "+size+" "+size) var new_rect = document.createElementNS(svgNS,"rect"); new_rect.setAttribute("width",size/3.8) new_rect.setAttribute("height",size/3.8) new_rect.setAttribute("x",x) new_rect.setAttribute("y",y) new_rect.setAttribute("fill","blue") logo.appendChild(new_rect) var new_rect = document.createElementNS(svgNS,"rect"); new_rect.setAttribute("width",size/3.8) new_rect.setAttribute("height",size/3.8) new_rect.setAttribute("x",x+1.4*size/3.8) new_rect.setAttribute("y",y) new_rect.setAttribute("fill","blue") logo.appendChild(new_rect) var new_rect = document.createElementNS(svgNS,"rect"); new_rect.setAttribute("width",size/3.8) new_rect.setAttribute("height",size/3.8) new_rect.setAttribute("x",x+2.8*size/3.8) new_rect.setAttribute("y",y) new_rect.setAttribute("fill","blue") logo.appendChild(new_rect) var new_rect = document.createElementNS(svgNS, "rect"); new_rect.setAttribute("width",size/3.8) new_rect.setAttribute("height",size/3.8) new_rect.setAttribute("x",x) new_rect.setAttribute("y",y-1.4*size/3.8) new_rect.setAttribute("fill","blue") logo.appendChild(new_rect) var new_rect = document.createElementNS(svgNS, "rect"); new_rect.setAttribute("width", size / 3.8) new_rect.setAttribute("height", size / 3.8) new_rect.setAttribute("x", x + 2.8 * size / 3.8) new_rect.setAttribute("y", y - 1.4 * size / 3.8) new_rect.setAttribute("fill", "blue") logo.appendChild(new_rect) var new_rect = document.createElementNS(svgNS, "rect"); new_rect.setAttribute("width", size / 3.8) new_rect.setAttribute("height", size / 3.8) new_rect.setAttribute("x", x + 1.4 * size / 3.8) new_rect.setAttribute("y", y - 2.8 * size / 3.8) new_rect.setAttribute("fill", "blue") logo.appendChild(new_rect) var new_rect = document.createElementNS(svgNS, "rect"); new_rect.setAttribute("width", size / 3.8) new_rect.setAttribute("height", size / 3.8) new_rect.setAttribute("x", x + 2.8 * size / 3.8) new_rect.setAttribute("y", y - 2.8 * size / 3.8) new_rect.setAttribute("fill", "blue") logo.appendChild(new_rect) var new_circ = document.createElementNS(svgNS, "circle"); new_circ.setAttribute("r", size / (2 * 3.8)) new_circ.setAttribute("cx", x + size / (2 * 3.8)) new_circ.setAttribute("cy", y + size / (2 * 3.8) - 2.8 * size / 3.8) new_circ.setAttribute("fill", "red") logo.appendChild(new_circ) var new_circ = document.createElementNS(svgNS, "circle"); new_circ.setAttribute("r", size / (2 * 3.8)) new_circ.setAttribute("cx", x + size / (2 * 3.8) + 1.4 * size / 3.8) new_circ.setAttribute("cy", y + size / (2 * 3.8) - 1.4 * size / 3.8) new_circ.setAttribute("fill", "red") logo.appendChild(new_circ) return logo } set_prompt('right click/two finger/long press for menu; scroll for zoom, drag for pan') // // SVG canvas for drawing // var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg") svg.style.position = 'absolute' svg.style.backgroundColor = 'rgb(255,255,255)' svg.style.top = mods.ui.header svg.style.left = 0 svg.style.zIndex = 0 svg.style.overflow = 'visible' svg.setAttribute('width',40) svg.setAttribute('height',40) svg.setAttribute('id','svg') svg.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink") document.body.appendChild(svg) // // link container // var svg = document.getElementById('svg') var g = document.createElementNS('http://www.w3.org/2000/svg','g') g.setAttribute('id','links') svg.appendChild(g) // // file reading controls // var file = document.createElement('input') file.setAttribute('type','file') file.setAttribute('id','mod_input') file.style.position = 'absolute' file.style.left = 0 file.style.top = 0 file.style.width = 0 file.style.height = 0 file.style.opacity = 0 file.addEventListener('change',function() { mod_read_handler() }) document.body.appendChild(file) var file = document.createElement('input') file.setAttribute('type','file') file.setAttribute('id','prog_input') file.style.position = 'absolute' file.style.left = 0 file.style.top = 0 file.style.width = 0 file.style.height = 0 file.style.opacity = 0 file.addEventListener('change',function() { prog_read_handler() }) document.body.appendChild(file) // // module container // var div = document.createElement('div') div.setAttribute('id','modules') document.body.appendChild(div) // // check for program load query // if (location.search.length > 0) { var args = location.search.slice(1).split('&') for (var a in args) { var arg = args[a].split('=') if (arg[0] == 'program') prog_message_handler(arg[1]) } } // // program routines // function prog_read_handler(event) { var file = document.getElementById('prog_input') var file_reader = new FileReader() file_reader.onload = prog_load_handler file_reader.readAsText(file.files[0]) mods.ui.progname = file.files[0].name } function prog_message_handler(filename) { var req = new XMLHttpRequest() req.responseType = 'text' req.onreadystatechange = function() { if (req.readyState == XMLHttpRequest.DONE) { prog = JSON.parse(req.response) prog_load(prog) } } var index = filename.lastIndexOf('/') if (index != -1) { mods.ui.progname = filename.slice(index+1) } else mods.ui.progname = filename // // send request, with random query to prevent caching // req.open('GET',filename+'?rnd='+Math.random()) req.send() } function prog_load_handler(event) { prog = JSON.parse(event.target.result) prog_load(prog) } function prog_load(prog) { // // load modules // for (var idnumber in prog.modules) { var module = prog.modules[idnumber] var str = module.definition try { eval('var args = '+str) } catch (err) { console.log(err.message) return } args.definition = str args.id = idnumber var t = mods_transform() var xw = t.ox-t.tx+(mods.ui.xstart-t.ox)/t.s var yw = t.oy-t.ty+(mods.ui.ystart-t.oy)/t.s-mods.ui.header args.top = parseFloat(module.top)+yw args.left = parseFloat(module.left)+xw args.filename = module.filename add_module(args) } // // load links // for (var linkid in prog.links) { var str = prog.links[linkid] eval('var link = '+str) eval('var linksrc = '+link.source) eval('var linkdst = '+link.dest) var src = document.getElementById( JSON.stringify({id:linksrc.id,type:linksrc.type,name:linksrc.name})) var dst = document.getElementById( JSON.stringify({id:linkdst.id,type:linkdst.type,name:linkdst.name})) add_link(src,dst) } } function save_program() { set_prompt('program name? ') get_prompt(mods.ui.progname,function(filename){ mods.ui.progname = filename var prog = {modules:{},links:[]} var modules = document.getElementById('modules') // // save modules // for (var c = 0; c < modules.childNodes.length; ++c) { var module = modules.childNodes[c] var idnumber = module.id prog.modules[idnumber] = { definition:update_module_definition( idnumber,module.dataset.definition), top:module.dataset.top, left:module.dataset.left, filename: module.dataset.filename, inputs:{}, outputs:{} } } // // save links // var svg = document.getElementById('svg') var links = svg.getElementById('links') for (var l = 0; l < links.childNodes.length; ++l) { var link = links.childNodes[l] var linkid = link.id prog.links.push(linkid) } // // download // var text = JSON.stringify(prog) var a = document.createElement('a') a.setAttribute('href','data:text/plain;charset=utf-8,'+ encodeURIComponent(text)) a.setAttribute('download',filename) a.style.display = 'none' document.body.appendChild(a) a.click() document.body.removeChild(a) }) } function save_page() { set_prompt('page name? ') get_prompt(mods.ui.progname+".html",function(filename){ mods.ui.progname = filename var prog = {modules:{},links:[]} var modules = document.getElementById('modules') // // save modules // for (var c = 0; c < modules.childNodes.length; ++c) { var module = modules.childNodes[c] var idnumber = module.id prog.modules[idnumber] = { definition:update_module_definition( idnumber,module.dataset.definition), top:module.dataset.top, left:module.dataset.left, filename: module.dataset.filename, inputs:{}, outputs:{} } } // // save links // var svg = document.getElementById('svg') var links = svg.getElementById('links') for (var l = 0; l < links.childNodes.length; ++l) { var link = links.childNodes[l] var linkid = link.id prog.links.push(linkid) } // // read mods.js // var req = new XMLHttpRequest() req.responseType = 'text' req.onreadystatechange = function() { if (req.readyState == XMLHttpRequest.DONE) { // // construct page // var str = req.response var text ="<html>\n" text += "<head><meta charset='utf-8'>\n" text += "<title>mods</title>\n" text += "</head>\n" text += "<body link='black' alink='black' vlink='black'>\n" text += "<"+"script"+">\n" text += str text += "var prog = JSON.parse(JSON.stringify("+JSON.stringify(prog)+"))\n" text += "window.mods_prog_load(prog)\n" text += "</"+"script"+">\n" text += "</body>\n" text += "</html>\n" // // download page // var a = document.createElement('a') a.setAttribute('href','data:text/plain;charset=utf-8,'+ encodeURIComponent(text)) a.setAttribute('download',filename) a.style.display = 'none' document.body.appendChild(a) a.click() document.body.removeChild(a) } } // // send request, with random query to prevent caching // req.open('GET','js/mods.js'+'?rnd='+Math.random()) req.send() }) } // // add program load to window // window.mods_prog_load = function(prog) { prog_load(prog) } // // module routines // function mod_read_handler(event) { var file = document.getElementById('mod_input') var file_reader = new FileReader() file_reader.onload = mod_load_handler file_reader.readAsText(file.files[0]) } function mod_message_handler(filename,top,left) { var req = new XMLHttpRequest() req.responseType = 'text' req.onreadystatechange = function() { if (req.readyState == XMLHttpRequest.DONE) { var str = req.response try { eval('var args = '+str) } catch (err) { console.log(err.message) return } args.definition = str args.id = String(Math.random()) args.top = top args.left = left args.filename = filename add_module(args) } } // // send request, with random query to prevent caching // req.open('GET',filename+'?rnd='+Math.random()) req.send() } function mod_load_handler(event) { str = event.target.result try { eval('var args = '+str) } catch (err) { console.log(err.message) return } args.definition = str args.id = String(Math.random()) args.top = mods.ui.top args.left = mods.ui.left args.filename = "" var div = add_module(args) return(div) } function add_module(args) { var idnumber = args.id mods.mod[idnumber] = args.mod var modules = document.getElementById('modules') // // container // var container = document.createElement('div') container.setAttribute("id",idnumber) container.style.position = "absolute" container.style.top = args.top container.style.left = args.left container.dataset.top = args.top container.dataset.left = args.left container.dataset.filename = args.filename container.dataset.name = args.name container.style.zIndex = 0 container.style.width = window.innerWidth container.dataset.definition = args.definition modules.appendChild(container) // // name // var divname = document.createElement('div') divname.appendChild(document.createTextNode(args.name)) divname.addEventListener('mouseover',name_over) divname.addEventListener('mouseout',name_out) divname.addEventListener('mousedown',name_mousedown) divname.addEventListener('touchstart',name_touchdown) divname.style.backgroundColor = "rgb(210,240,210)" divname.style.padding = 1.5*mods.ui.padding divname.style.position = "absolute" divname.style.cursor = 'default' divname.style.top = 0 divname.style.left = 0 divname.style.textAlign = 'center' divname.style.border = '2px solid' divname.style.borderRadius = '10px' container.appendChild(divname) // // controls // var divctrl = document.createElement('div') var editspan = document.createElement('span') editspan.innerHTML = 'edit' editspan.style.fontWeight = 'normal' editspan.addEventListener('mouseover',function(event){ set_prompt('click to edit') editspan.style.fontWeight = 'bold'}) editspan.addEventListener('mouseout',function(event){ set_prompt('') editspan.style.fontWeight = 'normal'}) editspan.addEventListener('mousedown',edit_module) divctrl.appendChild(editspan) var delspan = document.createElement('span') delspan.innerHTML = ' delete ' delspan.addEventListener('mouseover',function(event){ set_prompt('click to delete') delspan.style.fontWeight = 'bold'}) delspan.addEventListener('mouseout',function(event){ set_prompt('') delspan.style.fontWeight = 'normal'}) delspan.addEventListener('mousedown',function(event){ delete_module(event.target.parentNode.parentNode.id)}) divctrl.appendChild(delspan) divctrl.style.backgroundColor = "rgb(240,220,220)" divctrl.style.padding = mods.ui.padding divctrl.style.position = "absolute" divctrl.style.cursor = 'default' divctrl.style.top = divname.clientHeight divctrl.style.left = 0 divctrl.style.textAlign = 'center' divctrl.style.border = '2px solid' divctrl.style.borderRadius = '10px' container.appendChild(divctrl) divctrl.style.left = divname.clientWidth/2-divctrl.clientWidth/2 // // interface // var divint = document.createElement('div') divint.style.backgroundColor = "rgb(240,240,240)" divint.style.padding = mods.ui.padding divint.style.position = "absolute" divint.style.top = divname.clientHeight+divctrl.clientHeight divint.style.textAlign = 'center' divint.style.border = '2px solid' divint.style.borderRadius = '10px' divint.setAttribute('id',JSON.stringify({id:idnumber,type:'interface'})) divint.dataset.id = idnumber divint.dataset.divNameSize = divname.clientWidth args.interface(divint) container.appendChild(divint) divint.style.left = divname.clientWidth/2-divint.clientWidth/2 // // inputs // var divin = document.createElement('div') divin.setAttribute('id',JSON.stringify({id:idnumber,type:'inputs'})) var b = document.createElement('b') b.appendChild(document.createTextNode('inputs')) divin.appendChild(b) divin.style.backgroundColor = "rgb(240,240,210)" divin.style.padding = mods.ui.padding divin.style.paddingLeft = '2px' divin.style.position= "absolute" divin.style.top = divname.clientHeight+divctrl.clientHeight divin.style.textAlign = 'left' divin.style.border = '2px solid' divin.style.borderRadius = '10px' divin.setAttribute('id',JSON.stringify({id:idnumber,type:'inputs'})) divin.style.cursor = 'default' divin.addEventListener('mousedown',nothing) divin.addEventListener('touchstart',nothing) divin.addEventListener('mouseup',nothing) divin.addEventListener('touchend',nothing) for (var v in args.inputs) { var div = document.createElement('div') if (args.inputs[v].label != undefined) div.innerHTML += args.inputs[v].label else div.innerHTML += v if (args.inputs[v].type != '') div.innerHTML += ' ('+args.inputs[v].type+')' div.setAttribute('id',JSON.stringify({id:idnumber,type:'inputs',name:v})) div.addEventListener('mouseover',input_over) div.addEventListener('mouseout',input_out) div.addEventListener('mousedown',input_mousedown) div.addEventListener('touchstart',input_touchdown) div.dataset.links = JSON.stringify([]) div.dataset.name = v divin.appendChild(div) div.dataset.id = JSON.stringify( {id:idnumber,type:'input',name:v, rnd:Math.random()}) // randomize for unique events window.addEventListener(div.dataset.id,args.inputs[v].event) } container.appendChild(divin) if ((Object.keys(args.inputs).length) == 0) divin.style.visibility = 'hidden' divin.style.left = divname.clientWidth/2 -divint.clientWidth/2-divin.clientWidth for (var i = 1; i < divin.childNodes.length; ++i) { divin.childNodes[i].dataset.dx = divin.offsetLeft +divin.childNodes[i].offsetLeft divin.childNodes[i].dataset.dy = divin.offsetTop +divin.childNodes[i].offsetTop +divin.childNodes[i].offsetHeight/2 } // // outputs // var divout = document.createElement('div') divout.setAttribute('id',JSON.stringify({id:idnumber,type:'outputs'})) var b = document.createElement('b') b.appendChild(document.createTextNode('outputs')) divout.appendChild(b) divout.style.backgroundColor = "rgb(240,240,210)" divout.style.padding = mods.ui.padding divout.style.paddingRight = '2px' divout.style.position = "absolute" divout.style.top = divname.clientHeight+divctrl.clientHeight divout.style.textAlign = 'right' divout.addEventListener('mousedown',nothing) divout.style.border = '2px solid' divout.style.borderRadius = '10px' divout.setAttribute('id',JSON.stringify({id:idnumber,type:'outputs'})) divout.style.cursor = 'default' divout.addEventListener('touchstart',nothing) divout.addEventListener('mouseup',nothing) divout.addEventListener('touchend',nothing) for (var v in args.outputs) { var div = document.createElement('div') if (args.outputs[v].label != undefined) div.innerHTML += args.outputs[v].label else div.innerHTML += v if (args.outputs[v].type != '') div.innerHTML += ' ('+args.outputs[v].type+')' div.setAttribute('id',JSON.stringify({id:idnumber,type:'outputs',name:v})) div.addEventListener('mouseover',output_over) div.addEventListener('mouseout',output_out) div.addEventListener('mousedown',output_mousedown) div.addEventListener('touchstart',output_touchdown) div.dataset.links = JSON.stringify([]) div.dataset.name = v divout.appendChild(div) div.dataset.id = JSON.stringify( {id:idnumber,type:'output',name:v, rnd:Math.random()}) // randomize for unique events window.addEventListener(div.dataset.id,args.outputs[v].event) } container.appendChild(divout) if ((Object.keys(args.outputs).length) == 0) divout.style.visibility = 'hidden' divout.style.left = divname.clientWidth/2 +divint.clientWidth/2 for (var i = 1; i < divout.childNodes.length; ++i) { divout.childNodes[i].dataset.dx = divout.offsetLeft +divout.childNodes[i].offsetLeft +divout.childNodes[i].offsetWidth divout.childNodes[i].dataset.dy = divout.offsetTop +divout.childNodes[i].offsetTop +divout.childNodes[i].offsetHeight/2 } // // initialization // args.init() // // resize to contents // container.style.width = divint.clientWidth+divin.clientWidth+divout.clientWidth mods.fit(divint) // // return container // return(container) } function delete_module(idnumber) { // // delete links // var ins = document.getElementById( JSON.stringify({id:idnumber,type:'inputs'})) var outs = document.getElementById( JSON.stringify({id:idnumber,type:'outputs'})) for (var i = 1; i < ins.childNodes.length; ++i) { var links = JSON.parse(ins.childNodes[i].dataset.links) for (var l in links) delete_link(links[l]) } for (var i = 1; i < outs.childNodes.length; ++i) { var links = JSON.parse(outs.childNodes[i].dataset.links) for (var l in links) delete_link(links[l]) } // // delete container // var modules = document.getElementById('modules') var container = document.getElementById(idnumber) modules.removeChild(container) // // clear prompt // set_prompt('') } function update_module_definition(id) { // // get definition // var module = document.getElementById(id) var def = module.dataset.definition // // check for mod // if (mods.mod[id] == undefined) return def // // split definition // var lines = def.split('\n') // // find init function // var line = 0 while (line < lines.length) { if (lines[line].indexOf("var init") == 0) break line += 1 } // // read initializations up to inputs function // while (line < lines.length) { if (lines[line].indexOf(".value =") != -1) { var start = 4+lines[line].indexOf("mod.") var end = lines[line].indexOf(".value") var key = lines[line].slice(start,end) var value = mods.mod[id][key]['value'] if (value.indexOf('\n') != -1) value = value.replace(/\n/g,"\\n") lines[line] = " mod."+key+".value = '"+value+"'" } else if (lines[line].indexOf(".checked =") != -1) { var start = 4+lines[line].indexOf("mod.") var end = lines[line].indexOf(".checked") var key = lines[line].slice(start,end) var value = mods.mod[id][key]['checked'] lines[line] = " mod."+key+".checked = "+value } if (lines[line].indexOf("var inputs") == 0) break line += 1 } return(lines.join('\n')) } function edit_module(evt) { var mod = evt.target.parentNode.parentNode var idnumber = mod.id var def = update_module_definition(idnumber) // // open edit window // var top = mod.dataset.top var left = mod.dataset.left var name = mod.dataset.name var filename = mod.dataset.filename var fontsize = 100 var win = window.open('') var file = document.createElement('input') file.setAttribute('type','file') file.setAttribute('id','edit_module_file') file.style.position = 'absolute' file.style.left = 0 file.style.top = 0 file.style.width = 0 file.style.height = 0 file.style.opacity = 0 file.addEventListener('change',function() { edit_module_read_handler() }) win.document.body.appendChild(file) function edit_module_read_handler() { var file = win.document.getElementById('edit_module_file') var file_reader = new FileReader() file_reader.onload = edit_module_load_handler file_reader.readAsText(file.files[0]) } function edit_module_load_handler(event) { str = event.target.result var text = win.document.getElementById('edit_module_text') text.value = str update_module(idnumber) win.close() } if (filename != "undefined" && filename != "") { var btn = document.createElement('button') btn.appendChild(document.createTextNode('reload from server')) btn.style.padding = mods.ui.padding btn.style.margin = 1 btn.addEventListener('click',function(){ var req = new XMLHttpRequest() req.responseType = 'text' req.onreadystatechange = function() { if (req.readyState == XMLHttpRequest.DONE) { text.value = req.response update_module(idnumber) } } req.open('GET',filename+'?rnd='+Math.random()) req.send() win.close() }) win.document.body.appendChild(btn) } var btn = document.createElement('button') btn.appendChild(document.createTextNode('load from file')) btn.style.padding = mods.ui.padding btn.style.margin = 1 btn.addEventListener('click',function(){ var file = win.document.getElementById('edit_module_file') file.value = null file.click() }) win.document.body.appendChild(btn) var btn = document.createElement('button') btn.appendChild(document.createTextNode('update and close')) btn.style.padding = mods.ui.padding btn.style.margin = 1 btn.addEventListener('click',function(){ update_module(idnumber) win.close() }) win.document.body.appendChild(btn) var btn = document.createElement('button') btn.appendChild(document.createTextNode('update')) btn.style.padding = mods.ui.padding btn.style.margin = 1 btn.addEventListener('click',function(){ update_module(idnumber) }) win.document.body.appendChild(btn) var btn = document.createElement('button') btn.appendChild(document.createTextNode('close')) btn.style.padding = mods.ui.padding btn.style.margin = 1 btn.addEventListener('click',function(){ win.close() }) win.document.body.appendChild(btn) var btn = document.createElement('button') btn.appendChild(document.createTextNode('save')) btn.style.padding = mods.ui.padding btn.style.margin = 1 btn.addEventListener('click',function(){ var a = document.createElement('a') a.setAttribute('href','data:text/plain;charset=utf-8,'+ encodeURIComponent(text.value)) a.setAttribute('download',name) a.style.display = 'none' document.body.appendChild(a) a.click() document.body.removeChild(a) }) win.document.body.appendChild(btn) var btn = document.createElement('button') btn.appendChild(document.createTextNode('increase font')) btn.style.padding = mods.ui.padding btn.style.margin = 1 btn.addEventListener('click',function(){ fontsize *= 1.2 text.style.fontSize = fontsize+'%' }) win.document.body.appendChild(btn) var btn = document.createElement('button') btn.appendChild(document.createTextNode('decrease font')) btn.style.padding = mods.ui.padding btn.style.margin = 1 btn.addEventListener('click',function(){ fontsize /= 1.2 text.style.fontSize = fontsize+'%' }) win.document.body.appendChild(btn) win.document.body.appendChild(document.createElement('br')) var text = document.createElement('textarea') text.setAttribute('id','edit_module_text') text.style.width = '100%' text.style.height= '100%' text.value = def win.document.body.appendChild(text) function reload_module(idnumber) { } function update_module(idnumber) { // // save links // var ins = document.getElementById( JSON.stringify({id:idnumber,type:'inputs'})) var inlinks = [] for (var i = 1; i < ins.childNodes.length; ++i) { var links = JSON.parse(ins.childNodes[i].dataset.links) for (var l in links) inlinks.push(links[l]) } var outs = document.getElementById( JSON.stringify({id:idnumber,type:'outputs'})) var outlinks = [] for (var i = 1; i < outs.childNodes.length; ++i) { var links = JSON.parse(outs.childNodes[i].dataset.links) for (var l in links) outlinks.push(links[l]) } // // delete module // delete_module(idnumber) // // add module // var def = text.value try { eval('var args = '+def) } catch (err) { console.log(err.message) return } args.definition = def args.id = idnumber args.top = top args.left = left args.filename = filename add_module(args) // // add links // for (var l in inlinks) { eval('var link = '+inlinks[l]) eval('var linksrc = '+link.source) eval('var linkdst = '+link.dest) var src = document.getElementById( JSON.stringify( {id:linksrc.id,type:linksrc.type,name:linksrc.name})) var dst = document.getElementById( JSON.stringify( {id:linkdst.id,type:linkdst.type,name:linkdst.name})) add_link(src,dst) } for (var l in outlinks) { eval('var link = '+outlinks[l]) eval('var linksrc = '+link.source) eval('var linkdst = '+link.dest) var src = document.getElementById( JSON.stringify( {id:linksrc.id,type:linksrc.type,name:linksrc.name})) var dst = document.getElementById( JSON.stringify( {id:linkdst.id,type:linkdst.type,name:linkdst.name})) add_link(src,dst) } } } // // UI routines // function set_prompt(txt) { var span = document.getElementById('prompt') span.childNodes[0].innerHTML = ' '+txt } function get_prompt(txt,fn) { var div = document.getElementById('prompt') if (div.childNodes.length > 2) // // already getting a prompt // return var text = document.createElement('input') text.type = 'text' text.size = 20 text.value = txt text.addEventListener('keydown',function(evt){ if (evt.key == 'Enter') { var div = document.getElementById('prompt') div.removeChild(div.childNodes[1]) div.removeChild(div.childNodes[1]) set_prompt('') fn(text.value) } }) div.appendChild(text) div.appendChild(document.createTextNode(' (enter)')) text.focus() } function nothing(evt) { evt.preventDefault() evt.stopPropagation() } // // link routines // mods.add_link = function(src,dst) { add_link(src,dst) } function add_link(src,dst) { // // link order from out to in // if (src.id.indexOf('outputs') == -1) { var tmp = src src = dst dst = tmp } // // check if link exists // var id = JSON.stringify({source:src.id,dest:dst.id}) var link = document.getElementById(id) if (link != null) { // // yes, remove it and return // delete_link(id) return } // // no, add link // var links = JSON.parse(src.dataset.links) links.push(id) src.dataset.links = JSON.stringify(links) var links = JSON.parse(dst.dataset.links) links.push(id) dst.dataset.links = JSON.stringify(links) // // draw link // xsrc = src.parentNode.parentNode.offsetLeft +parseFloat(src.dataset.dx) ysrc = src.parentNode.parentNode.offsetTop +parseFloat(src.dataset.dy) -mods.ui.header xdst = dst.parentNode.parentNode.offsetLeft +parseFloat(dst.dataset.dx) ydst = dst.parentNode.parentNode.offsetTop +parseFloat(dst.dataset.dy) -mods.ui.header var links = document.getElementById('links') var path = document.createElementNS('http://www.w3.org/2000/svg','path') path.setAttribute('id',id) path.setAttribute('d','M'+xsrc+','+ysrc+' C'+(xsrc+mods.ui.bezier)+',' +ysrc+' '+(xdst-mods.ui.bezier)+','+ydst+' '+xdst+','+ydst) path.setAttribute('fill','none') path.setAttribute('stroke','rgb(0,0,128)') path.setAttribute('stroke-width',3) links.appendChild(path) /* // // don't trigger link // eval('var evtid = '+src.id) evtid.type = 'output' var evtstr = JSON.stringify(evtid) var evt = new CustomEvent(evtstr) window.dispatchEvent(evt) */ } function delete_link(linkid) { // // delete a link // var links = document.getElementById('links') links.removeChild(document.getElementById(linkid)) link = JSON.parse(linkid) src = document.getElementById(link.source) dst = document.getElementById(link.dest) var links = JSON.parse(src.dataset.links) var index = links.indexOf(linkid) links.splice(index,1) src.dataset.links = JSON.stringify(links) var links = JSON.parse(dst.dataset.links) var index = links.indexOf(linkid) links.splice(index,1) dst.dataset.links = JSON.stringify(links) } function draw_links(idnumber,color) { // // draw a module's links // var ins = document.getElementById( JSON.stringify({id:idnumber,type:'inputs'})) var outs = document.getElementById( JSON.stringify({id:idnumber,type:'outputs'})) for (var i = 1; i < ins.childNodes.length; ++i) { var links = JSON.parse(ins.childNodes[i].dataset.links) for (var l in links) draw_link(links[l],color) } for (var i = 1; i < outs.childNodes.length; ++i) { var links = JSON.parse(outs.childNodes[i].dataset.links) for (var l in links) draw_link(links[l],color) } } function draw_link(id,color) { // // draw a link // var link = JSON.parse(id) src = document.getElementById(link.source) dst = document.getElementById(link.dest) var path = document.getElementById(id) xsrc = src.parentNode.parentNode.offsetLeft +parseFloat(src.dataset.dx) ysrc = src.parentNode.parentNode.offsetTop +parseFloat(src.dataset.dy) -mods.ui.header xdst = dst.parentNode.parentNode.offsetLeft +parseFloat(dst.dataset.dx) ydst = dst.parentNode.parentNode.offsetTop +parseFloat(dst.dataset.dy) -mods.ui.header path.setAttribute('d','M'+xsrc+','+ysrc+' C'+(xsrc+mods.ui.bezier)+',' +ysrc+' '+(xdst-mods.ui.bezier)+','+ydst+' '+xdst+','+ydst) path.setAttribute('stroke',color) } // // mods routines to be called from modules // mods.fit = function(div) { // // fit a module // div.style.left = div.dataset.divNameSize/2-div.clientWidth/2 var divin = document.getElementById( JSON.stringify( {id:div.dataset.id,type:'inputs'})) divin.style.left = div.dataset.divNameSize/2-div.clientWidth/2-divin.clientWidth var divout = document.getElementById( JSON.stringify( {id:div.dataset.id,type:'outputs'})) divout.style.left = div.dataset.divNameSize/2+div.clientWidth/2 } mods.output = function(mod,varname,val) { // // send module outputs // var div = mod.div var key = JSON.parse(div.id) var idnumber = key.id var out = document.getElementById( JSON.stringify( {id:idnumber,type:'outputs',name:varname})) var links = JSON.parse(out.dataset.links) for (var l in links) { var link = JSON.parse(links[l]) var dest = JSON.parse(link.dest) var divin = document.getElementById(JSON.stringify( {id:dest.id,type:'inputs',name:dest.name})) var evt = new CustomEvent(divin.dataset.id,{detail:val}) window.dispatchEvent(evt) } } // // module mod-ification calls // mods.module_create = function(args) { var event = {target:{result:args}} var div = mod_load_handler(event) return(div) } mods.module_delete = function(id) { delete_module(id) } mods.module_id = function(div) { return div.parentNode.id } mods.module_left = function(id) { var module = document.getElementById(id) return (parseInt(module.style.left)) } mods.module_move = function(id,dx,dy) { var module = document.getElementById(id) var top = parseInt(module.style.top) module.style.top = top+dy module.dataset.top = top+dy var left = parseInt(module.style.left) module.style.left = left+dx module.dataset.left = left+dx draw_links(id,mods.ui.link_color) } mods.module_inputs = function(id,index) { var module = document.getElementById(id) var inputs = document.getElementById( JSON.stringify({id:id,type:'inputs'})) console.log(inputs.childNodes[index]) } mods.module_outputs = function(id,index) { var module = document.getElementById(id) var outputs = document.getElementById( JSON.stringify({id:id,type:'outputs'})) console.log(outputs.childNodes[index]) } mods.module_position = function(id,x,y) { var module = document.getElementById(id) var top = parseInt(module.style.top) module.style.top = y module.dataset.top = y var left = parseInt(module.style.left) module.style.left = x module.dataset.left = x draw_links(id,mods.ui.link_color) } mods.module_top = function(id) { var module = document.getElementById(id) return (parseInt(module.style.top)) } mods.module_width = function(id) { var module = document.getElementById(id) return (parseInt(module.clientWidth)) } // // input event handlers // function input_over(evt) { evt.target.style.fontWeight = 'bold' var links = JSON.parse(evt.target.dataset.links) for (var l in links) draw_link(links[l],mods.ui.link_highlight) if (mods.ui.source == null) set_prompt('click to link') } function input_out(evt) { evt.target.style.fontWeight = 'normal' var links = JSON.parse(evt.target.dataset.links) for (var l in links) draw_link(links[l],mods.ui.link_color) if (mods.ui.source == null) set_prompt('') } function input_mousedown(evt) { if (mods.ui.source == null) { mods.ui.source = evt.target set_prompt('variable to link/unlink to?') } else { add_link(mods.ui.source,evt.target) set_prompt('') mods.ui.source = null } } function input_touchdown(evt) { if (mods.ui.source == null) { mods.ui.source = evt.target set_prompt('variable to link/unlink to?') } else { add_link(mods.ui.source,evt.target) set_prompt('') mods.ui.source = null } } // // output event handlers // function output_over(evt) { evt.target.style.fontWeight = 'bold' var links = JSON.parse(evt.target.dataset.links) for (var l in links) draw_link(links[l],mods.ui.link_highlight) if (mods.ui.source == null) set_prompt('click to link') } function output_out(evt) { evt.target.style.fontWeight = 'normal' var links = JSON.parse(evt.target.dataset.links) for (var l in links) draw_link(links[l],mods.ui.link_color) if (mods.ui.source == null) set_prompt('') } function output_mousedown(evt) { if (mods.ui.source == null) { mods.ui.source = evt.target set_prompt('variable to link/unlink to?') } else { add_link(mods.ui.source,evt.target) set_prompt('') mods.ui.source = null } } function output_touchdown(evt) { if (mods.ui.source == null) { mods.ui.source = evt.target set_prompt('variable to link/unlink to?') } else { add_link(mods.ui.source,evt.target) set_prompt('') mods.ui.source = null } } // // name event handlers // function name_over(evt) { evt.target.style.fontWeight = 'bold' if (mods.ui.source == null) set_prompt('click and drag to move') } function name_out(evt) { evt.target.style.fontWeight = 'normal' if (mods.ui.source == null) set_prompt('') } function name_mousedown(evt) { evt.preventDefault() evt.stopPropagation() var div = document.getElementById(evt.target.parentNode.id) div.style.zIndex = 1 mods.ui.xstart = evt.clientX mods.ui.ystart = evt.clientY mods.ui.selected[evt.target.parentNode.id] = true window.addEventListener('mousemove',name_mousemove) window.addEventListener('mouseup',name_mouseup) } function name_mousemove(evt) { evt.preventDefault() evt.stopPropagation() var t = mods_transform() for (var id in mods.ui.selected) { var div = document.getElementById(id) var dx = (evt.clientX-mods.ui.xstart)/t.s var dy = (evt.clientY-mods.ui.ystart)/t.s var newleft = parseFloat(div.dataset.left)+dx var newtop = parseFloat(div.dataset.top)+dy div.style.left = newleft+'px' div.style.top = newtop+'px' draw_links(id,mods.ui.link_color) } } function name_mouseup(evt) { evt.preventDefault() evt.stopPropagation() var t = mods_transform() for (var id in mods.ui.selected) { var div = document.getElementById(id) div.style.zIndex = 0 div.childNodes[0].style.fontWeight = 'normal' var dx = (evt.clientX-mods.ui.xstart)/t.s var dy = (evt.clientY-mods.ui.ystart)/t.s div.dataset.left = parseFloat(div.dataset.left)+dx div.dataset.top = parseFloat(div.dataset.top)+dy window.removeEventListener('mousemove',name_mousemove) window.removeEventListener('mouseup',name_mouseup) } mods.ui.selected = {} } function name_touchdown(evt) { evt.preventDefault() evt.stopPropagation() var div = document.getElementById(evt.target.parentNode.id) div.style.zIndex = 1 mods.ui.xstart = evt.changedTouches[0].pageX mods.ui.ystart = evt.changedTouches[0].pageY mods.ui.selected[evt.target.parentNode.id] = true window.addEventListener('touchmove',name_touchmove) window.addEventListener('touchend',name_touchup) } function name_touchmove(evt) { evt.preventDefault() evt.stopPropagation() var t = mods_transform() for (var id in mods.ui.selected) { var div = document.getElementById(id) var dx = (evt.changedTouches[0].pageX-mods.ui.xstart)/t.s var dy = (evt.changedTouches[0].pageY-mods.ui.ystart)/t.s var newleft = parseFloat(div.dataset.left)+dx var newtop = parseFloat(div.dataset.top)+dy div.style.left = newleft+'px' div.style.top = newtop+'px' draw_links(id,mods.ui.link_color) } } function name_touchup(evt) { evt.preventDefault() evt.stopPropagation() var t = mods_transform() for (var id in mods.ui.selected) { var div = document.getElementById(id) div.style.zIndex = 0 var dx = (evt.changedTouches[0].pageX-mods.ui.xstart)/t.s var dy = (evt.changedTouches[0].pageY-mods.ui.ystart)/t.s div.dataset.left = parseFloat(div.dataset.left)+dx div.dataset.top = parseFloat(div.dataset.top)+dy window.removeEventListener('touchmove',name_touchmove) window.removeEventListener('touchend',name_touchup) } mods.ui.selected = {} } })()