Skip to content
Snippets Groups Projects
mods.js 63.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • //
    // 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,
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       link_color:'rgb(0,0,128)',
    
       link_highlight:'rgb(255,0,0)',
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       header:50,
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       mousedown:null,
    
       menu:null,
       top:null,
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       left:null,
       xstart:null,
       ystart:null,
       xtrans:null,
       ytrans:null
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    // 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))
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       return({s:s,tx:tx,ty:ty,ox:ox,oy:oy})
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    document.body.style.transform = 'scale(1) translate(0px,0px)'
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    document.body.style.transformOrigin = '0px 0px'
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    //
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    // scroll wheel event
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    //
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    window.addEventListener('wheel',function(evt) {
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       /*
       (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")) {
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          set_prompt('scroll to zoom')
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          evt.preventDefault()
          evt.stopPropagation()
    
          var t = mods_transform()
    
          if (evt.deltaY > 0)
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             var scale = t.s*1.1
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             var scale = t.s*0.9
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          var tx = t.tx+(evt.pageX-t.ox)*(1-1/t.s)
          var ty = t.ty+(evt.pageY-t.oy)*(1-1/t.s)
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          document.body.style.transform = `scale(${scale}) translate(${tx}px,${ty}px)`
    
          document.body.style.transformOrigin = `${evt.pageX}px ${evt.pageY}px`
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          }
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       })
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    //
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    // body mouse events
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    //
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    window.addEventListener('mousedown',function(evt) {
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       // get element mouse is over
    
       var el = document.elementFromPoint(evt.pageX,evt.pageY)
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       //
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       // check if on body
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       //
    
       if ((el.tagName == "HTML") || (el.tagName == "BODY")) {
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          //
          // remember button
          //
          mods.ui.mousedown = evt.button
          if (mods.ui.mousedown == 0) {
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             set_prompt('left-drag to pan, right-drag to select')
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             if (mods.ui.menu != null) {
                document.body.removeChild(mods.ui.menu)
                mods.ui.menu = null
                }
             }
          else if (mods.ui.mousedown == 2) {
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             set_prompt('menu; left-drag to pan, right-drag to select')
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             }
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          //
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          // 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
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          }
       })
    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) {
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             //
             // pan on left drag
             //
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             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) {
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             //
             // select on right drag
             //
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             var rect = document.getElementById('svgrect')
             if (rect == undefined) {
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
                //
                // start dragging
                //
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
                if (mods.ui.menu != null) {
                   document.body.removeChild(mods.ui.menu)
                   mods.ui.menu = null
                   }
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
                set_prompt('right-drag to select')
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
                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 {
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
                //
                // continue dragging
                //
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
                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)
                }
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       })
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    window.addEventListener('mouseup',function(evt) {
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       // mouse up
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       mods.ui.mousedown = null
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       //
       // check for selection rectangle
       //
    
       var rect = document.getElementById('svgrect')
    
       if (rect != null) {
          //
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          // 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')
          //
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          // loop to find selected modules
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          mods.ui.selected = {}
    
          for (var module in modules.childNodes) {
             var container = modules.childNodes[module]
             var id = container.id
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             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"
                   }
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       })
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    //
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    // context menu
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
    window.addEventListener('contextmenu',function(evt){
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       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"
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          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()
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          evt.stopPropagation()
    
          document.body.removeChild(evt.target.parentNode)
          var div = document.createElement('div')
          make_menu(div)
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          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){
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             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()
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          evt.stopPropagation()
    
          document.body.removeChild(evt.target.parentNode)
          var div = document.createElement('div')
          make_menu(div)
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          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()
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          evt.stopPropagation()
    
          document.body.removeChild(evt.target.parentNode)
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          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
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             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 = {}
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
                   }
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
                }
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             })
          //
          // 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()
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          evt.stopPropagation()
    
          document.body.removeChild(evt.target.parentNode)
    
          mods.ui.menu = null
    
          var div = document.createElement('div')
          make_menu(div)
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          set_prompt('options')
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          // view files
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          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)
          //
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          // view project
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          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
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          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
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          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
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
             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
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       args.filename = ""
    
       var div = add_module(args)
       return(div)
       }
    function add_module(args) {
       var idnumber = args.id
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
       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
    
    Neil Gershenfeld's avatar
    Neil Gershenfeld committed
          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'