Skip to content
Snippets Groups Projects
Select Git revision
  • ba905fc61c84ad4fd183e9ee99543fa52a81a96f
  • master default protected
2 results

forkspi.js

Blame
  • client.js 19.56 KiB
    //
    //
    // new node controller / VIEW 
    // reconfigurable unviersal numeric dataflow machine controller
    // 'RUN DMC'
    // dataflow numeric controller
    // OLD Depricated: used for !REF
    // DNC 
    //
    // client.js
    //
    //
    // Jake Read at the Center for Bits and Atoms
    // (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.
    
    /*
    
    CLIENT GLOBALS ---------------------------------------------------
    
    */
    
    var sckt = {}
    var lastPos = { x: 10, y: 30 }
    
    // drawing / div-ing
    var wrapper = {}
    var nav = {}
    
    var verbose = false
    var verboseComs = false
    
    /*
    
    STARTUP ---------------------------------------------------
    
    */
    
    window.onload = function() {
        svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
        svg.style.position = 'absolute'
        svg.style.left = 0
        svg.style.top = 0
        svg.style.zIndex = 0
        svg.style.overflow = 'visible'
        svg.setAttribute('width', 2)
        svg.setAttribute('height', 2)
        svg.setAttribute('id', 'svg')
        svg.setAttribute('width', '100%')
        svg.setAttribute('height', '100%')
        document.body.appendChild(svg)
    
        wrapper = document.createElement('div')
        wrapper.id = 'wrapper'
        document.body.append(wrapper)
    
        nav = document.getElementById('nav')
    
        const socket = new WebSocket('ws://localhost:8082')
    
        socket.onopen = function(evt) {
            // pass to global ref 
            sckt = this
            // say hello 
            socketSend('console', 'hello server')
            console.log('SCKT: socket open')
            // ask for the current program 
            socketSend('get current program', '')
            // main socket entry point
            this.onmessage = (evt) => {
                socketRecv(evt)
            }
            // others
            this.onerror = (err) => {
                alert('link to server is broken')
                location.reload()
                console.log('SCKT: socket error', err)
            }
            this.onclose = (evt) => {
                console.log('SCKT: socket closed', evt)
                sckt = null
            }
        }
    }
    
    /*
    
    RECV / SEND PORTALS ---------------------------------------------------
    
    */
    
    function socketSend(type, data) {
        var msg = {
            type: type,
            data: data
        }
        if (verboseComs) console.log('SEND', msg)
        sckt.send(JSON.stringify(msg))
    }
    
    function socketRecv(evt) {
        var recv = JSON.parse(evt.data)
        var type = recv.type
        var data = recv.data
        if (verboseComs) console.log('RECV', recv)
        // tree banger
        switch (type) {
            case 'console':
                console.log('RECV CONSOLE:', data)
                break
            case 'put module menu':
                if (verbose) console.log('RECV MODULE MENU')
                heapSendsModuleMenu(data)
                break
            case 'put program menu':
                if (verbose) console.log('RECV PRG MENU')
                heapSendsProgramMenu(data)
                break
            case 'put program':
                if (verbose) console.log('RECV PROGRAM')
                heapSendsNewProgram(data)
                break
            case 'put module':
                if (verbose) console.log('RECV NEW MODULE')
                heapSendsNewModule(data)
                break
            case 'put module change':
                if (verbose) console.log('RECV MODULE CHANGE')
                heapSendsModuleChange(data)
                break
            case 'put state change':
                if (verbose) console.log('RECV STATE CHANGE')
                heapSendsStateChange(data)
                break
            case 'put ui change':
                if (verbose) console.log('RECV UI CHANGE')
                heapSendsUiChange(data)
                break
            case 'restart':
                location.reload()
            default:
                console.log('ERR: recv with non recognized type', recv)
                break
        }
    }
    
    /*
    
    MISC ---------------------------------------------------
    
    */
    
    
    /*
    
    HEAP -> SERVER ---------------------------------------------------
    
    */
    
    // always a rep, tho
    var program = {}
    
    // re-writes t program, adds a description,
    // and loads multiple representations of modules to the view 
    
    function heapSendsNewProgram(prgm) {
        // whole hearted replace
        // hello for bugs when we lay this on top of something else 
        program = prgm
        if (program.description.view != null) {
            writeTransformToPage(program.description.view)
        }
        // 1st we want to git rm old files ... 
        // when adding links, we'll have to add all and then draw links
        if (verbose) console.log('LOAD PROGRAM', program)
        for (mdlName in program.modules) {
            addRepToView(program.modules[mdlName])
        }
        redrawLinks()
    }
    
    function heapSendsNewModule(mdl) {
        if (program.description == null) {
            program.description.name = 'unnamed program'
        }
        if (program.modules == null) {
            program.modules = {}
        }
        addRepToView(mdl)
        program.modules[mdl.description.id] = mdl
        redrawLinks()
    }
    
    // writes DOM elements to represent the module, appends to the wrapper
    // and appends to the rep object a .dom object 
    // containing references to those DOM objects 
    
    function heapSendsModuleChange(data) {
        if (verbose) console.log('HEAP SENDS MODULE CHANGE', data)
        // data should be rep of changed module 
        var rep = program.modules[data.description.id]
        // we want a general case, but for now we know we're looking for 
        // new event hookups or new state items 
        for (key in rep.outputs) {
            var output = rep.outputs[key]
            if (output.calls.length !== data.outputs[key].calls.length) {
                rep.outputs = data.outputs
            }
        }
        // ok
        for (key in rep.state) {
            var stateItem = rep.state[key]
            if (stateItem != data.state[key]) {
                stateItem = data.state[key]
                rep.dom.state[key].value = data.state[key]
            }
        }
        // wreckless or wonderful?
        //clear(rep)
        redrawLinks()
    }
    
    // update state from server to UI
    function heapSendsStateChange(data) {
        if (verbose) console.log('HEAP SENDS CHANGE STATE IN MODULE', data)
        var rep = program.modules[data.id]
        rep.state[data.key] = data.val
        if (typeof data.val == 'boolean') {
            rep.dom.state[data.key].innerHTML = data.key + ':\t\t' + rep.state[data.key].toString()
        } else {
            rep.dom.state[data.key].value = data.val
        }
    }
    
    function heapSendsUiChange(data) {
        if (verbose) console.log('HEAP SENDS MSG TO UI ELEMENT IN MDL', data)
        program.modules[data.id].ui[data.key].lump.onMessage(data.msg)
    }
    
    /*
    
    UI -> HEAP ---------------------------------------------------
    
    */
    
    // push new state from UI to server 
    function putState(rep, key) {
        var data = {
            id: rep.description.id,
            key: key,
            val: rep.state[key]
        }
        socketSend('put state change', data)
    }
    
    // save ui position to server for reload
    function putPosition(rep) {
        var data = {
            description: {
                id: rep.description.id,
                position: {
                    left: rep.description.position.left,
                    top: rep.description.position.top
                }
            }
        }
    
        socketSend('put position change', data)
    }
    
    // input / output click handling 
    var clkState = false
    var oClk = {}
    var tmpBz = {}
    
    function evtConnectHandler(clk) {
        if (!clkState) {
            // first click
            oClk = clk
            clkState = true
        } else {
            // second click
            var tClk = clk
            //console.log(oClk, tClk)
            var x1 = parseInt(oClk.evt.target.offsetParent.style.left, 10) + oClk.evt.target.offsetLeft + oClk.evt.target.clientWidth
            var y1 = parseInt(oClk.evt.target.offsetParent.style.top, 10) + oClk.evt.target.offsetTop + oClk.evt.target.clientHeight / 2
            var x2 = parseInt(tClk.evt.target.offsetParent.style.left, 10) + tClk.evt.target.offsetLeft
            var y2 = parseInt(tClk.evt.target.offsetParent.style.top, 10) + tClk.evt.target.offsetTop + tClk.evt.target.clientHeight / 2
            //var bz = newBezier(x1, y1, x2, y2)
            clkState = false
            //console.log('connect', oClk.rep.description.id, oClk.name, 'to', tClk.rep.description.id, tClk.name)
            var data = {
                from: {
                    id: oClk.rep.description.id,
                    output: oClk.name
                },
                to: {
                    id: tClk.rep.description.id,
                    input: tClk.name
                }
            }
            socketSend('put link change', data)
        }
    }
    
    /*
    
    UTILITIES ---------------------------------------------------
    
    */
    
    function redrawLinks() {
        // probably not a great way to do this, we're removing everything
        // svg -rm -r 
        while (svg.firstChild) {
            svg.removeChild(svg.firstChild)
        }
        // draw origin 
        var og1 = newLine(-20, 0, 20, 0, 3, false)
        var og2 = newLine(0, -20, 0, 20, 3, false)
        // find that link
        var lnkPt
        var nLnk = 0
        for (mdlName in program.modules) {
            if (program.modules[mdlName].description.isLink) {
                lnkPt = getLeftWall(program.modules[mdlName].dom.domElem)
            }
        }
        // redraw thru all links, just look at reps
        for (mdlName in program.modules) {
            var mdlRep = program.modules[mdlName]
            for (key in mdlRep.outputs) {
                var output = mdlRep.outputs[key]
                var outputUi = mdlRep.dom.outputs[key]
                for (input in output.calls) {
                    var toId = output.calls[input].parentId
                    var toKey = output.calls[input].key
                    var inputUi = program.modules[toId].dom.inputs[toKey]
                    var outPos = getOutputArrow(outputUi)
                    var inPos = getInputArrow(inputUi)
                    if (inputUi.isHovering || outputUi.isHovering) {
                        var bz = newBezier(outPos.x, outPos.y, inPos.x, inPos.y, true)
                    } else {
                        var bz = newBezier(outPos.x, outPos.y, inPos.x, inPos.y, false)
                    }
                }
            }
            if (mdlRep.description.isHardware && !mdlRep.description.isLink) {
                nLnk++
                var hwPt = getRightWall(mdlRep.dom.domElem)
                lnkPt.y += 5 * nLnk
                var ln = newLine(hwPt.x, hwPt.y, lnkPt.x, lnkPt.y, 7, true)
            }
        }
    }
    
    /*
    
    UI EVENTS ---------------------------------------------------------------------
    
    */
    
    // drag / pan etc 
    
    document.body.style.overflow = 'hidden'
    document.body.style.transform = 'scale(1) translate(0px, 0px)'
    document.body.style.transformOrigin = '0px 0px'
    // s/o @ Neil 
    function getCurrentTransform() {
        // a string 
        var transform = document.body.style.transform
    
        var index = transform.indexOf('scale')
        var left = transform.indexOf('(', index)
        var right = transform.indexOf(')', index)
        var s = parseFloat(transform.slice(left + 1, right))
        var index = transform.indexOf('translate')
        var left = transform.indexOf('(', index)
        var right = transform.indexOf('px', left)
        var tx = parseFloat(transform.slice(left + 1, right))
        var left = transform.indexOf(',', right)
        var right = transform.indexOf('px', left)
        var ty = parseFloat(transform.slice(left + 1, right))
        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
        })
    }
    
    function writeTransformToPage(trns) {
        // console.log('writing transform', trns)
        /* transform is like {
            scale: number,
            translate: [x, y],
            origin: [x, y]
        }
        */
        document.body.style.transform = `scale(${trns.scale}) translate(${trns.translate[0]}px, ${trns.translate[1]}px)`
        document.body.style.transformOrigin = `${trns.origin[0]}px ${trns.origin[1]}px`
        // opposite for nav 
        nav.style.transformOrigin = `${trns.origin[0]}px ${trns.origin[1]}px`
        nav.style.transform = `scale(${1/trns.scale}) translate(${-trns.translate[0]*trns.scale}px,${-trns.translate[1]*trns.scale}px)`
    }
    
    function elementIsNotModule(element) {
        if ((element.tagName == 'HTML') || (element.tagName == 'BODY') || (element.tagName == 'svg')) {
            return true
        } else {
            return false
        }
    }
    
    onwheel = function(evt) {
        var el = document.elementFromPoint(evt.pageX, evt.pageY)
        if (elementIsNotModule(el)) {
            var cT = getCurrentTransform()
            evt.preventDefault()
            evt.stopPropagation()
            if (evt.deltaY > 0) {
                var scale = 1.05 * cT.s
            } else {
                var scale = 0.95 * cT.s
            }
            var tx = cT.tx + (evt.pageX - cT.ox) * (1 - 1 / cT.s)
            var ty = cT.ty + (evt.pageY - cT.oy) * (1 - 1 / cT.s)
    
            // body
            writeTransformToPage({
                scale: scale,
                translate: [tx, ty],
                origin: [evt.pageX, evt.pageY]
            })
            document.body.style.transform = `scale(${scale}) translate(${tx}px,${ty}px)`
            document.body.style.transformOrigin = `${evt.pageX}px ${evt.pageY}px`
    
            // opposite for nav 
            nav.style.transformOrigin = `${evt.pageX}px ${evt.pageY}px`
            nav.style.transform = `scale(${1/scale}) translate(${-tx*scale}px,${-ty*scale}px)`
        }
    }
    
    onmousedown = function(evt) {
        var qr = document.querySelector(':focus')
        if (qr) {
            qr.blur()
        }
        var el = document.elementFromPoint(evt.pageX, evt.pageY)
        if (elementIsNotModule(el)) {
            evt.preventDefault()
            evt.stopPropagation()
            window.addEventListener('mousemove', mouseMoveDragListener)
            window.addEventListener('mouseup', mouseUpDragListener)
        }
    }
    
    function mouseMoveDragListener(evt) {
        evt.preventDefault()
        evt.stopPropagation()
        var cT = getCurrentTransform()
        var dx = evt.movementX
        var dy = evt.movementY
        var tx = cT.tx + dx / cT.s
        var ty = cT.ty + dy / cT.s
    
        // for body 
        document.body.style.transform = `scale(${cT.s}) translate(${tx}px,${ty}px)`
    
        // opposite for nav 
        nav.style.transform = `scale(${1/cT.s}) translate(${-tx*cT.s}px,${-ty*cT.s}px)`
    }
    
    function mouseUpDragListener(evt) {
        window.removeEventListener('mousemove', mouseMoveDragListener)
        window.removeEventListener('mouseup', mouseUpDragListener)
    }
    
    // get json menu item and render
    // and ask for module at /obj/key
    oncontextmenu = function(evt) {
        if (evt.target.className == 'modname') {
            var modRep = program.modules[evt.target.innerHTML]
            if (modRep) {
                writeModuleOptionMenu(modRep)
            }
        } else if (evt.target.tagName != 'HTML') {
            // clicked on something else 
        } else {
            if (sckt) {
                socketSend('get module menu', '')
            } else {
                // socket brkn, reload page 
                location.reload()
            }
            // prevents event bubbling 
        }
        return false
    }
    
    onmousemove = function(evt) {
        var cT = getCurrentTransform()
        lastPos.x = cT.ox - cT.tx + (evt.pageX - cT.ox) / cT.s
        lastPos.y = cT.oy - cT.ty + (evt.pageY - cT.oy) / cT.s
    }
    
    document.onkeydown = function(evt) {
        switch (evt.key) {
            case 'Escape':
                location.reload()
                break
            case 's':
                // get path ? 
                var path = prompt("path? starting at atkapi/programs/")
                socketSend('save program', path)
                break
            case 'l':
                socketSend('get program menu', '')
                break
            case 'm':
                socketSend('get module menu', '')
                break
            case 'd':
                console.log(program)
                break
            case 'k':
                socketSend('save program', 'temp')
            default:
                break
        }
    }
    
    function writeModuleOptionMenu(modRep) {
        var menuDom = document.createElement('div')
        menuDom.id = 'perModuleMenu'
        menuDom.style.left = 10 + modRep.dom.domElem.offsetLeft + modRep.dom.domElem.offsetWidth + 'px'
        menuDom.style.top = modRep.dom.domElem.offsetTop + 'px'
        // future: rm all inputs, rm all outputs, rename, open (heirarchy)
        var opts = ['delete', 'copy']
        for (i in opts) {
            var li = document.createElement('li')
            li.innerHTML = opts[i]
            li.id = opts[i]
            if (opts[i] == 'delete') {
                li.addEventListener('click', function(evt) {
                    var data = {
                        id: modRep.description.id
                    }
                    socketSend('remove module', data)
                    wrapper.removeChild(document.getElementById('perModuleMenu'))
                })
            } else if (opts[i] == 'copy') {
                li.addEventListener('click', function(evt) {
                    var data = modRep.description.path
                    socketSend('put module', data)
                    wrapper.removeChild(document.getElementById('perModuleMenu'))
                })
            }
            menuDom.appendChild(li)
        }
        wrapper.append(menuDom)
    
        function rmListener(evt) {
            var findMenu = document.getElementById('perModuleMenu')
            if (findMenu != null && findMenu.id == 'perModuleMenu') {
                wrapper.removeChild(findMenu)
            }
            evt.target.removeEventListener(evt.type, arguments.callee)
        }
    
        document.addEventListener('click', rmListener)
    }
    
    // return ul element with name and alt and link? 
    // TODO: not properly a tree, see note @ reciprocal fn in views.js
    function heapSendsModuleMenu(tree) {
        var menuDom = document.createElement('div')
        menuDom.id = 'moduleMenu'
        menuDom.style.left = lastPos.x + 'px'
        menuDom.style.top = lastPos.y + 'px'
        var title = document.createElement('div')
        title.className = 'title'
        title.innerHTML = 'module menu'
        menuDom.appendChild(title)
        for (key in tree) {
            var ul = document.createElement('ul')
            ul.innerHTML = key.toString()
            for (subkey in tree[key]) {
                var li = document.createElement('li')
                var path = tree[key][subkey].path
                li.innerHTML = subkey.toString()
                li.id = path
                li.addEventListener('click', function(evt) {
                    var data = this.id
                    socketSend('put module', data)
                    wrapper.removeChild(document.getElementById('moduleMenu'))
                })
                ul.appendChild(li)
            }
            menuDom.appendChild(ul)
        }
        wrapper.append(menuDom)
    
        function rmListener(evt) {
            var findMenu = document.getElementById('moduleMenu')
            if (findMenu !== null && findMenu.id == 'moduleMenu') {
                wrapper.removeChild(findMenu)
            }
            evt.target.removeEventListener(evt.type, arguments.callee)
        }
    
        document.addEventListener('click', rmListener)
    }
    
    function heapSendsProgramMenu(tree) {
        var menuDom = document.createElement('div')
        menuDom.id = 'programMenu'
        menuDom.style.left = lastPos.x + 'px'
        menuDom.style.top = lastPos.y + 'px'
        var title = document.createElement('div')
        title.className = 'title'
        title.innerHTML = 'program menu'
        menuDom.appendChild(title)
        for (key in tree) {
            var li = document.createElement('li')
            var path = tree[key].path
            li.innerHTML = key.toString()
            li.id = path
            li.addEventListener('click', function(evt) {
                var data = this.id
                socketSend('load program', data)
                wrapper.removeChild(document.getElementById('programMenu'))
            })
            menuDom.appendChild(li)
        }
        wrapper.append(menuDom)
    
        function rmListener(evt) {
            var findMenu = document.getElementById('programMenu')
            if (findMenu !== null && findMenu.id == 'programMenu') {
                wrapper.removeChild(findMenu)
            }
            // rm this listner... 
            evt.target.removeEventListener(evt.type, arguments.callee)
        }
    
        document.addEventListener('click', rmListener)
    }