From 327b496b1dffe5adc81fb588e71b51f79dff0082 Mon Sep 17 00:00:00 2001 From: Jake <jake.read@cba.mit.edu> Date: Sat, 10 Nov 2018 21:29:21 -0500 Subject: [PATCH] working through state management --- README.md | 14 +++-- client/client.js | 122 +++++++++++++++++++++++++++----------- client/divtools.js | 10 ++-- client/style.css | 9 ++- lib/jsunit.js | 6 +- programs/hookup.json | 136 +++++++++++++++++++++++++++++++++++++++++++ views.js | 89 +++++++++++++++++++++++++--- 7 files changed, 330 insertions(+), 56 deletions(-) create mode 100644 programs/hookup.json diff --git a/README.md b/README.md index 7234f83..a039fb3 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,10 @@ This project serves the developement environment / api we use to write and repre - change settings - right now - - add module - - l for load - - s for save + - push state down & up + - note below : + - not actually setting, do node main, change ms, watch console + - some wholistic test ... - *don't forget* - onload, load state... @@ -84,15 +85,18 @@ Modules-that-represent-remote-computing also have To assemble a representation of these, we want to have a kind of 'netlist' that, for convenience, we'll treat like a JSON object. We want heirarchy, so consider the representation having 'top-level' outputs / inputs / state as well ? +## Programming Notes +### 15 Minute Tasks + - @ views.js, uiRequestModuleMenu and uiRequestProgramMenu don't properly build trees from folder structure. similarly, reciprical fn's in client.js do the same + - @ these load / save functions could also reach into the modules' source to retrieve their proper names, as spec'd in description ... -## Programming Notes + - 's' for save program uses hack-asf DOM alert to ask for path title bar L for load prgmem M for add module - - once we can plan gcode sequence, work towards more - better hardware abstraction, i.e. stepper.port = bridge.port('0,1') diff --git a/client/client.js b/client/client.js index e17fda1..69c0c79 100644 --- a/client/client.js +++ b/client/client.js @@ -101,9 +101,13 @@ function socketRecv(evt) { console.log('RECV CONSOLE:', data) break case 'put module menu': - console.log('RECV MENU') + console.log('RECV MODULE MENU') heapSendsModuleMenu(data) break + case 'put program menu': + console.log('RECV PRG MENU') + heapSendsProgramMenu(data) + break case 'put program': console.log('RECV PROGRAM') heapSendsNewProgram(data) @@ -112,10 +116,16 @@ function socketRecv(evt) { console.log('RECV NEW MODULE') heapSendsNewModule(data) break - case 'put state': + case 'put module change': + console.log('RECV MODULE CHANGE') + heapSendsModuleChange(data) + break + case 'put state change': console.log('RECV STATE CHANGE') - heapSendsNewState(data) + heapSendsStateChange(data) break + case 'restart': + location.reload() default: console.log('ERR recv with non recognized type', recv) break @@ -129,9 +139,10 @@ MISC --------------------------------------------------- */ // 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 = 'menu' + menuDom.id = 'moduleMenu' menuDom.style.left = lastPos.x + 'px' menuDom.style.top = lastPos.y + 'px' for (key in tree) { @@ -145,7 +156,7 @@ function heapSendsModuleMenu(tree) { li.addEventListener('click', function(evt) { var data = this.id socketSend('put module', data) - wrapper.removeChild(document.getElementById('menu')) + wrapper.removeChild(document.getElementById('moduleMenu')) }) ul.appendChild(li) } @@ -154,10 +165,42 @@ function heapSendsModuleMenu(tree) { wrapper.append(menuDom) function rmListener(evt) { - var findMenu = document.getElementById('menu') - if (findMenu !== null && findMenu.id == 'menu') { + 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' + 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) @@ -201,7 +244,6 @@ function heapSendsNewModule(mdl) { redrawLinks() } - // writes DOM elements to represent the module, appends to the wrapper // and appends to the rep object a .ui object // containing references to those DOM objects @@ -220,23 +262,19 @@ function addRepToView(rep) { // more html: the title var title = document.createElement('div') title.className = 'modname' - title.innerHTML = rep.description.name + ' id: ' + rep.id + title.innerHTML = rep.description.id title.alt = rep.description.alt domElem.appendChild(title) var uiSetFlag // place in pos if info present // the rep.ui object will store references to the module's related DOM elements - console.log(rep.description.position) if (rep.description.position != null) { - console.log("FOUND POS") uiSetFlag = false if (rep.description.position.left != null) { - console.log("FOUND LEFT") domElem.style.left = rep.description.position.left + 'px' } if (rep.description.position.top != null) { - console.log("FOUND TOP") domElem.style.top = rep.description.position.top + 'px' } } else { @@ -317,24 +355,35 @@ function addRepToView(rep) { } } -// update state from server to UI -function heapSendsNewState(data) { - console.log('HEAP SENDS CHANGE STATE IN MODULE', data) - var rep = reps[data.id] - for (var key in data.state) { - if (data.state[key].type == 'button') { - console.log('BUTTON UPDATE') - } else if (data.state[key].type == 'multiline') { - console.log('MULTILINE UPDATE') - rep.ui.state[key].value = data.state[key].value - } else { - // two ? - rep.state[key] = data.state[key] - if (rep.ui.state[key] != null) { - rep.ui.state[key].value = rep.state[key] - } +function heapSendsModuleChange(data){ + console.log(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.ui.state[key].value = data.state[key] } } + // wreckless or wonderful? + //clear(rep) + redrawLinks() +} + +// update state from server to UI +function heapSendsStateChange(data) { + console.log('HEAP SENDS CHANGE STATE IN MODULE', data) + heapSendsModuleChange(data) } /* @@ -344,11 +393,16 @@ UI -> HEAP --------------------------------------------------- */ // push new state from UI to server -function putState(rep) { +function putState(rep, key) { // ship it all home: not perfect, but hey var data = { - id: rep.id, - state: rep.state + description: { + id: rep.description.id + }, + state: { + key: key, + val: rep.state[key] + } } socketSend('put state', data) } @@ -419,7 +473,7 @@ var svg = {} /* -UI EVENTS --------------------------------------------------- +UI EVENTS --------------------------------------------------------------------- */ @@ -455,7 +509,7 @@ document.onkeydown = function(evt){ socketSend('save program', path) break case 'l': - console.log('LOADID') + socketSend('get program menu', '') break case 'm': socketSend('get module menu', '') diff --git a/client/divtools.js b/client/divtools.js index fb99fc9..ed2866c 100644 --- a/client/divtools.js +++ b/client/divtools.js @@ -14,7 +14,7 @@ function writeStateRep(container, rep, key) { } else { rep.state[key].isPressed = true } - putState(rep) + putState(rep, key) }) container.appendChild(li) return li @@ -30,7 +30,7 @@ function writeStateRep(container, rep, key) { txtArea.value = variable.value txtArea.addEventListener('change', function() { rep.state[key].value = txtArea.value - putState(rep) + putState(rep, key) }) li.appendChild(txtArea) container.appendChild(li) @@ -46,7 +46,7 @@ function writeStateRep(container, rep, key) { input.value = variable input.addEventListener('change', function() { rep.state[key] = input.value - putState(rep) + putState(rep, key) }) li.appendChild(input) container.appendChild(li) @@ -60,7 +60,7 @@ function writeStateRep(container, rep, key) { input.value = variable.toString() input.addEventListener('change', function() { rep.state[key] = parseFloat(input.value) - putState(rep) + putState(rep, key) }) li.appendChild(input) container.appendChild(li) @@ -79,7 +79,7 @@ function writeStateRep(container, rep, key) { array[index] = parseFloat(element) }) rep.state[key] = arr - putState(rep) + putState(rep, key) }) li.appendChild(input) container.appendChild(li) diff --git a/client/style.css b/client/style.css index da2e456..eaa1412 100644 --- a/client/style.css +++ b/client/style.css @@ -98,9 +98,16 @@ li:active{ background-color: #d1d1d1; } -#menu { +#moduleMenu { position: absolute; width: 245px; padding: 10px; background-color: #303030; } + +#programMenu { + position: absolute; + width: 245px; + padding: 10px; + background-color: #303030; +} \ No newline at end of file diff --git a/lib/jsunit.js b/lib/jsunit.js index 7145053..071b68d 100644 --- a/lib/jsunit.js +++ b/lib/jsunit.js @@ -79,11 +79,11 @@ function isStateKey(key) { } // a coupl'a fancy UI state hooks -function Button(label) { +function Button(label, onClick) { var button = { type: 'button', - isPressed: false, - label: label + label: label, + onClick: onClick } return button diff --git a/programs/hookup.json b/programs/hookup.json new file mode 100644 index 0000000..b73809a --- /dev/null +++ b/programs/hookup.json @@ -0,0 +1,136 @@ +{ + "description": { + "name": "tstprgmem", + "counter": 4 + }, + "modules": { + "gate-1": { + "description": { + "id": "gate-1", + "name": "gate", + "alt": "in ... out", + "path": "./src/util/gate.js", + "position": { + "left": 10, + "top": 10 + } + }, + "inputs": { + "thru": { + "accepts": "any" + } + }, + "outputs": { + "out": { + "emits": "any", + "calls": [ + { + "parentId": "delay-2", + "key": "thru" + }, + { + "parentId": "logger-3", + "key": "thru" + }, + { + "parentId": "delay-4", + "key": "thru" + } + ] + } + }, + "state": { + "toggle": { + "type": "button", + "isPressed": false, + "label": "Open / Close" + }, + "message": "closed" + } + }, + "delay-2": { + "description": { + "id": "delay-2", + "name": "delay", + "alt": "in ... out", + "path": "./src/util/delay.js", + "position": { + "left": 132, + "top": 225 + } + }, + "inputs": { + "thru": { + "accepts": "any" + } + }, + "outputs": { + "out": { + "emits": "any", + "calls": [ + { + "parentId": "logger-3", + "key": "thru" + } + ] + } + }, + "state": { + "ms": 100 + } + }, + "logger-3": { + "description": { + "id": "logger-3", + "name": "logger", + "alt": "in ... out to console", + "path": "./src/util/log.js", + "position": { + "left": 123, + "top": 367 + } + }, + "inputs": { + "thru": { + "accepts": "any" + } + }, + "outputs": { + "throughput": { + "emits": "any", + "calls": [] + } + }, + "state": { + "prefix": "LOGGER:", + "message": "---" + } + }, + "delay-4": { + "description": { + "id": "delay-4", + "name": "delay", + "alt": "in ... out", + "path": "./src/util/delay.js", + "position": { + "left": 721, + "top": 309 + } + }, + "inputs": { + "thru": { + "accepts": "any" + } + }, + "outputs": { + "out": { + "emits": "any", + "calls": [] + } + }, + "state": { + "ms": 100 + } + } + } +} \ No newline at end of file diff --git a/views.js b/views.js index 951bf74..44a6514 100644 --- a/views.js +++ b/views.js @@ -87,12 +87,15 @@ function socketRecv(evt) { case 'get module menu': uiRequestModuleMenu() break + case 'get program menu': + uiRequestProgramMenu() + break case 'load program': uiRequestLoadProgram(data) break case 'save program': uiRequestSaveProgram(data) - break + break case 'put module': uiRequestNewModule(data) break @@ -148,6 +151,9 @@ function uiRequestCurrentProgram() { socketSend('put program', prgRep) } + +// TODO: proper heirarchy, with both of these ... + function uiRequestModuleMenu() { var availableSourceRep = {} var dir = fs.readdirSync('./src') @@ -166,24 +172,48 @@ function uiRequestModuleMenu() { socketSend('put module menu', availableSourceRep) } +function uiRequestProgramMenu() { + var availableProgramRep = {} + var paths = fs.readdirSync('./programs') + for (i in paths) { + if (paths[i].slice(-5) === '.json') { + var name = paths[i].slice(0, -5) + var path = './programs/' + paths[i] + availableProgramRep[paths[i]] = { + name: name, + path: path + } + } + } + socketSend('put program menu', availableProgramRep) +} + /* UI -> HEAP HANDLES --------------------------------------------------- */ -function uiRequestLoadProgram(data){ +function uiRequestLoadProgram(data) { console.log('UI REQUEST TO OPEN NEW PROGRAM', data) + // gonna tear it doooown, maybe just kick page afterwards to restart it? + program = null + program = Programs.open(data) + + socketSend('restart', '') } -function uiRequestSaveProgram(data){ +function uiRequestSaveProgram(data) { console.log('UI REQUEST TO SAVE PROGRAM', data) // is data a path? add .json ? - if(!data.includes('.json')){ - data = data + '.json' + if (data) { + if (!data.includes('.json')) { + data = data + '.json' + } + path = 'programs/' + data + Programs.save(program, path) + socketSend('console', ('saved program at' + path)) } - path = 'programs/' + data - Programs.save(program, path) } function uiRequestNewModule(data) { @@ -196,11 +226,54 @@ function uiRequestNewModule(data) { function uiRequestStateChange(data) { console.log('UI REQUEST CHANGE STATE IN MODULE', data) - // and don't forget state.obj.emit + // there should only be one ! + var id = data.description.id + var key = data.state.key + var val = data.state.val + + var mdlState = program.modules[id].state + console.log('mdlState', mdlState) + var mdlStateItem = program.modules[id].state[key] + console.log('mdlStateItem', mdlStateItem) + + if (mdlStateItem) { + switch (mdlState.type) { + case 'button': + mdlStateItem.onClick() + break + case 'multiline': + mdlStateItem.value = val + mdlState.emitChange(key) + break + default: + mdlStateItem = val + mdlState.emitChange(key) + break + } + } else { + console.log("ERR no state key,", key, "found here", data) + } + + console.log('mdlStateItem', mdlStateItem) + console.log(program.modules[id]) } function uiRequestLinkChange(data) { console.log('UI REQUEST ADD EVENT LINK', data) + var fromId = data.from.id + var outputName = data.from.output + var toId = data.to.id + var inputName = data.to.input + + // HERE: check if hooked already + + var fromMdl = program.modules[fromId] + var toMdl = program.modules[toId] + fromMdl.outputs[outputName].attach(toMdl.inputs[inputName]) + + var nRep = Reps.makeFromModule(fromMdl) + + socketSend('put module change', nRep) } function uiRequestUiChange(data) { -- GitLab