diff --git a/README.md b/README.md index ceb924783971193323fe7e75a1abbabd5aea2db3..8f5252e1c1436815c8d426f7e7a53202fcdcb39f 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,41 @@ Messages: In my dreams, this is a way of key-parsing messages as well. I.E. I don't really want to write down a list of keys / values for manager messages - these should be locally defined by the unit that will respond to them. I am fond of that 'replyHello' implementation, and would like to extend it to all aspects of manager-ness, and think that this may be an especially helpful idea in the world of purely-serialized data and micros. +# Serialization, Links + +I'm up to this, now. To test, debug, run faster, I'm going to focus on bringing about serialization truths to cuttlefish / nautilus, before deep diving into ponyo. + +Basically, I want to get rid of message-passing-by-json because string-parsing everything would be a bigtime slowdown. Quizically, I'm going to keep string-parsing inside of managers, but not inside of links. + +To wake up, I've a small list: + - type-name inputs, especially at the link + - same for comm/ + - rebuild / use scrape.js (with //HEADER and //ENDHEADER flags) + +Once I'm through that, I should be back at happily opening up nautilus-via-cuttlefish. I'll polish my UI, particularly wire views. + +I'll explicate the list of messages that I need to send to managers, and to links. + +My links, and state-loading, aren't really well enough developed yet to do what I would like to. A generic 'link' can't be limited to some # / type of inputs / outputs. The interface needs to be developable. + +I know I'm going to implement some state-object loading on startup, and have init functions. I could use two state objects (inputList and outputList), those being inputs / outputs typed: like "charArray, uint32, float, floatArray" - on init, or on change, we can match this array to internal operation. And can put messages on them via first-byte drop-in (with type checking!). + +First order would be re-building links to have non-static wakeup states. + +Messages to 254 port are debug, should just be printed. Messages to 253 are to the link itself? Otherwise 0-ordered, and 0th port go to the manager, by convention. + +What's stopping me from implementing this? Am I burnt out? + +I think this also asks me to reconsider how programs are loaded. Does it? No. It's actually a good test of the existing system. + +## A List of Types + +Since serialization is pretty bound up with types, and I'm going through standardizing that now, here's a list: + +Type Name | Length | Byte Structure +--- | --- | --- +byteArray | n | 250,n,b1,...,bn + # Videos to Take / Demos - loading with view relaxation diff --git a/bztools.js b/bztools.js index 7e9f24ca82ae87863e86daa13374bb8bad077d42..9272c80647486b0bbe76ee0beda55e881e4726c1 100644 --- a/bztools.js +++ b/bztools.js @@ -73,4 +73,4 @@ export { getLeftHandle, getFloaterHandle, writeBezier -} \ No newline at end of file +} diff --git a/hunks/comm/websocketclient.js b/hunks/comm/websocketclient.js index 1fafe9c799d78ceeed77c799c332e9d797f04d10..ba9e87cf0f5a0c5b734168fc24b5ffbca7d6b425 100644 --- a/hunks/comm/websocketclient.js +++ b/hunks/comm/websocketclient.js @@ -4,114 +4,136 @@ line input */ -import { Hunkify, Input, Output, State } from '../hunks.js' +import { + Hunkify, + Input, + Output, + State +} from '../hunks.js' function WebSocketClient() { - Hunkify(this, 'WebSocketClient') - - this.inputs.bytes = new Input('type', 'bytes') - this.outputs.bytes = new Output('type', 'bytes') - - // TODO is tackling state sets / updates / onupdate fn's - // this is hunk -> manager commune ... - this.state.status = new State('string', 'status', 'closed') - this.state.address = new State('string', 'address', '127.0.0.1') - this.state.port = new State('string', 'port', '2042') - - // this ws is a client, - let ws = {} - let url = 'ws://127.0.0.1:2020' - this.outbuffer = new Array() - - this.init = () => { - startWs() + Hunkify(this, 'WebSocketClient') + + this.inputs.data = new Input('byteArray', 'data') + this.outputs.data = new Output('byteArray', 'data') + + // TODO is tackling state sets / updates / onupdate fn's + // this is hunk -> manager commune ... + this.state.status = new State('string', 'status', 'closed') + this.state.retrycount = new State('number', 'retrycount', 3) + this.state.address = new State('string', 'address', '127.0.0.1') + this.state.port = new State('string', 'port', '2042') + + // this ws is a client, + let ws = {} + let url = 'ws://127.0.0.1:2020' + this.outbuffer = new Array() + + this.init = () => { + startWs() + } + + let startWs = () => { + // manager calls this once + // it is loaded and state is updated (from program) + url = 'ws://' + this.state.address.value + ':' + this.state.port.value + console.log('INIT WS', url) + ws = new WebSocket(url) + ws.onopen = (evt) => { + this.log('ws opened') + this.log('the ws object', ws) + this.log(evt) + this.state.status.set('opened') } - - let startWs = () => { - // manager calls this once - // it is loaded and state is updated (from program) - url = 'ws://' + this.state.address.value + ':' + this.state.port.value - console.log('INIT WS', url) - ws = new WebSocket(url) - ws.onopen = (evt) => { - this.log('ws opened') - this.log('the ws object', ws) - this.log(evt) - this.state.status.set('opened') - } - ws.onerror = (evt) => { - this.log('ws error, will reset to check') - console.log(evt) - this.state.status.set('error') - setCheck(500) - } - ws.onclose = (evt) => { - this.log('ws close') - setCheck(500) - } - ws.onmessage = (msg) => { - console.log("WS RX:", msg.data) - if (this.outputs.bytes.ie && this.outbuffer.length === 0) { - this.outputs.bytes.put(JSON.parse(msg.data)) - } else { - this.outbuffer.push(msg.data) - } - } - this.state.status.set('connecting ...') + ws.onerror = (evt) => { + this.log('ws error, will reset to check') + console.log(evt) + this.state.status.set('error') + setCheck(500) + } + ws.onclose = (evt) => { + this.log('ws close') + setCheck(500) } + ws.onmessage = (msg) => { + console.log("WS RX:", msg.data) + if (this.outputs.data.ie && this.outbuffer.length === 0) { + this.outputs.data.put(JSON.parse(msg.data)) + } else { + this.outbuffer.push(msg.data) + } + } + this.state.status.set('connecting ...') + } - let checking = false + let checking = false - let setCheck = (ms) => { - if(checking){ - // noop - } else { - setTimeout(checkWsStatus, ms) - checking = true - } + let setCheck = (ms) => { + if (checking) { + // noop + } else { + setTimeout(checkWsStatus, ms) + checking = true } - - let checkWsStatus = () => { - checking = false - this.log('CHECKING STATUS') - switch (ws.readyState) { - case WebSocket.CONNECTING: - this.log('is connecting') - break - case WebSocket.OPEN: - this.log('is open') - break - case WebSocket.CLOSING: - this.log('is closing') - break - case WebSocket.CLOSED: - this.log('is closed, retrying ...') - startWs() - break - default: - throw new Error('nonsensical result at ws readystate check for ws') - break - } + } + + let checkWsStatus = () => { + let retrycount = this.state.retrycount.value - 1 + if (retrycount < 1) { + // give up + this.state.status.set('not connected') + this.state.retrycount.set(retrycount) + checking = false + } else { + this.state.retrycount.set(retrycount) + checking = false + this.log('CHECKING STATUS') + switch (ws.readyState) { + case WebSocket.CONNECTING: + this.log('is connecting') + break + case WebSocket.OPEN: + this.log('is open') + break + case WebSocket.CLOSING: + this.log('is closing') + break + case WebSocket.CLOSED: + this.log('is closed, retrying ...') + startWs() + break + default: + throw new Error('nonsensical result at ws readystate check for ws') + break + } + } + } + + // override default change f'n + this.state.retrycount.change = (value) => { + this.log('retrycount reset') + this.state.retrycount.set(value) + setCheck(10) + } + + this.loop = () => { + // something like if(ws !== null && ws.isopen) + // if we have an open port, and have bytes to send downstream, + if (ws !== null && ws.readyState === 1) { + // no buffering + if (this.inputs.data.io) { + let message = this.inputs.data.get() + console.log("WS SENDING", message) + ws.send(JSON.stringify(message)) + } } - this.loop = () => { - // something like if(ws !== null && ws.isopen) - // if we have an open port, and have bytes to send downstream, - if (ws !== null && ws.readyState === 1) { - // no buffering - if (this.inputs.bytes.io) { - let message = this.inputs.bytes.get() - console.log("WS SENDING", message) - ws.send(JSON.stringify(message)) - } - } - - // check if we have outgoing to pass along - if (this.outbuffer.length > 0 && this.outputs.bytes.ie) { - this.outputs.bytes.put(this.outbuffer.shift()) - } - + // check if we have outgoing to pass along + if (this.outbuffer.length > 0 && this.outputs.data.ie) { + this.outputs.data.put(this.outbuffer.shift()) } + + } } -export default WebSocketClient \ No newline at end of file +export default WebSocketClient diff --git a/hunks/hunks.js b/hunks/hunks.js index dc6a3096d150bf93745e757a462e203136a45a31..9134a88ad982f74b2910e03876cac7ec4d11e8a4 100644 --- a/hunks/hunks.js +++ b/hunks/hunks.js @@ -1,144 +1,156 @@ /* -tha hunks +tha hunks */ function Hunkify(hunk, name) { - // scripting languages should name hunks by their script location and filename - // compiled languages will assign a string - hunk.id = null - hunk.name = null - - hunk.log = function(msg) { - for (let i = 0; i < arguments.length; i++) { - console.log('LG:', hunk.id, ':', arguments[i]) - } + // scripting languages should name hunks by their script location and filename + // compiled languages will assign a string + hunk.id = null + hunk.name = null + + hunk.log = function(msg) { + for (let i = 0; i < arguments.length; i++) { + console.log('LG:', hunk.id, ':', arguments[i]) } + } - hunk.inputs = {} - hunk.outputs = {} - hunk.state = {} + hunk.inputs = {} + hunk.outputs = {} + hunk.state = {} } function Input(type, name) { - this.name = name - this.type = type - this.data = null - this.io = false - this.ie = true - - this.put = (data) => { - // used when MGR does transport - if (data === undefined || data === null) { - console.log("THROWING PUT OF UNDEF OR NULL") - console.log('to', this) - throw new Error('UNDEF OR NULL PUT TO LINK') - } else { - this.data = data - this.io = true - this.ie = false - } + this.name = name + this.type = type + this.data = null + this.io = false + this.ie = true + + this.put = (data) => { + // used when MGR does transport + if (data === undefined || data === null) { + console.log("THROWING PUT OF UNDEF OR NULL") + console.log('to', this) + throw new Error('UNDEF OR NULL PUT TO LINK') + } else { + this.data = data + this.io = true + this.ie = false } - - this.get = () => { - // used internal to hunk to pull data from link - if (this.io) { - // TODO: BIG: deep copy here - let dc = JSON.parse(JSON.stringify(this.data)) - this.data = null - this.io = false - this.ie = true - return dc - } else { - return null - } + } + + this.get = () => { + // used internal to hunk to pull data from link + if (this.io) { + // TODO: BIG: deep copy here + let dc = JSON.parse(JSON.stringify(this.data)) + this.data = null + this.io = false + this.ie = true + return dc + } else { + return null } + } } function Output(type, name) { - this.name = name - this.type = type + this.name = name + this.type = type + this.data = null + this.posted = false + this.io = false + this.ie = true + this.connections = new Array() + + this.put = (data) => { + if (this.ie) { + this.data = data + this.io = true + this.ie = false + this.posted = false + return true + } else { + // TODO: throw error here? these should not be allowed + return false + } + } + + this.clear = () => { this.data = null - this.posted = false this.io = false this.ie = true - this.connections = new Array() - - this.put = (data) => { - if (this.ie) { - this.data = data - this.io = true - this.ie = false - this.posted = false - return true - } else { - // TODO: throw error here? these should not be allowed - return false - } - } - - this.clear = () => { - this.data = null - this.io = false - this.ie = true + } + + this.attach = (input, pid) => { + this.connections.push({ + input: input, + parentid: pid + }) + } + + this.remove = (input, pid) => { + // find by names pls + let recip = { + input: input, + parentid: pid } - - this.attach = (input, pid) => { - this.connections.push({ input: input, parentid: pid }) - } - - this.remove = (input, pid) => { - // find by names pls - let recip = { input: input, parentid: pid } - let pos = -1 - let conn = this.connections.find((cn, i) => { - pos = i - return ((cn.parentid === recip.parentid) && (cn.input.name === recip.input.name)) - }) - if (conn !== undefined | conn !== null) { - this.connections.splice(pos, 1) - return true - } else { - console.log('was looking for ', recip, 'in', this.connections) - return false - } + let pos = -1 + let conn = this.connections.find((cn, i) => { + pos = i + return ((cn.parentid === recip.parentid) && (cn.input.name === recip.input.name)) + }) + if (conn !== undefined | conn !== null) { + this.connections.splice(pos, 1) + return true + } else { + console.log('was looking for ', recip, 'in', this.connections) + return false } + } - this.unhook = (pid) => { - for (let conn of this.connections) { - if (conn.parentid === pid) { - this.remove(conn) - } - } + this.unhook = (pid) => { + for (let conn of this.connections) { + if (conn.parentid === pid) { + this.remove(conn) + } } + } } function State(type, name, startup) { - this.name = name - this.type = type - this.value = startup - // this is still something of a hack - // during load, the manager gets in here and dishes a function 2 us - this.hookup = (newval) => { - console.log('@ state change request, no manager hookup') - } - // could be getters / setters ? worth it? - - // from within a hunk, we typically call this - // to update the value and ship it out to any views - this.set = (value) => { - this.value = value - this.hookup(this.value) - } - - // a view change calls this function, which we can - // also manually overwrite (say, to set a change handler / checker) - this.change = (value) => { - // correct, this will by default ring back to - // the view, to confirm - this.set(value) - } + this.name = name + this.type = type + this.value = startup + // this is still something of a hack + // during load, the manager gets in here and dishes a function 2 us + this.hookup = (newval) => { + console.log('@ state change request, no manager hookup') + } + // could be getters / setters ? worth it? + + // from within a hunk, we typically call this + // to update the value and ship it out to any views + this.set = (value) => { + this.value = value + this.hookup(this.value) + } + + // a view change calls this function, which we can + // also manually overwrite (say, to set a change handler / checker) + this.change = (value) => { + // correct, this will by default ring back to + // the view, to confirm + console.log('default state change') + this.set(value) + } } -export { Hunkify, Input, Output, State } \ No newline at end of file +export { + Hunkify, + Input, + Output, + State +} diff --git a/hunks/link.js b/hunks/link.js index 61f196c43f9d94281a5b3cff284643a8c0bae53a..c17f88e24305dbb91919cb0eb80566c11ccf07e2 100644 --- a/hunks/link.js +++ b/hunks/link.js @@ -4,164 +4,173 @@ line input */ -import { Hunkify, Input, Output, State } from './hunks.js' +// HEADER +import { + Hunkify, + Input, + Output, + State +} from './hunks.js' +// END HEADER function Link() { - Hunkify(this, 'Link') - - // data in/out - // assumed that whatever is on the other end of bytes - // does byte / packet level flowcontrol - this.inputs.bytes = new Input('bytes', 'bytes') - this.outputs.bytes = new Output('bytes', 'bytes') - - // these are *special link inputs* keeping track of downstream status - this.inputs.zero = new Input('any', 'zero') - this.inputs.zero.dss = 'open' - - this.inputs.one = new Input('any', 'one') - this.inputs.zero.dss = 'open' - - this.inputs.two = new Input('any', 'two') - this.inputs.two.dss = 'open' - - - // special outputs having upstream buffers - this.outputs.zero = new Output('any', 'zero') - this.outputs.zero.hold = {} - this.outputs.zero.hold.status = 'open' - this.outputs.zero.hold.msg = {} - - this.outputs.one = new Output('any', 'one') - this.outputs.one.hold = {} - this.outputs.one.hold.status = 'open' - this.outputs.one.hold.msg = {} - - this.init = () => { - // manager calls this once - // it is loaded and state is updated (from program) - this.log('hello Link') + Hunkify(this, 'Link') + + // data in/out + // assumed that whatever is on the other end of bytes + // does byte / packet level flowcontrol + this.inputs.data = new Input('byteArray', 'data') + this.outputs.data = new Output('byteArray', 'data') + + // these are *special link inputs* keeping track of downstream status + this.inputs.zero = new Input('any', 'zero') + this.inputs.zero.dss = 'open' + + this.inputs.one = new Input('any', 'one') + this.inputs.zero.dss = 'open' + + this.inputs.two = new Input('any', 'two') + this.inputs.two.dss = 'open' + + + // special outputs having upstream buffers + this.outputs.zero = new Output('any', 'zero') + this.outputs.zero.hold = {} + this.outputs.zero.hold.status = 'open' + this.outputs.zero.hold.msg = {} + + this.outputs.one = new Output('any', 'one') + this.outputs.one.hold = {} + this.outputs.one.hold.status = 'open' + this.outputs.one.hold.msg = {} + + this.init = () => { + // manager calls this once + // it is loaded and state is updated (from program) + this.log('hello Link') + } + + // so far we won't ack at the link layer, + // i.e. we will assume that we can send all ports across, + // without getting an ack back from the link + // but we will need to do this for the dmarippers + // perhaps that should live in the layer that 'websocket' is at now + + let inports = [this.inputs.zero, this.inputs.zero, this.inputs.one] + let outports = [this.outputs.zero, this.outputs.zero] + + let outbuffer = new Array() + + // I think we just need to buffer + // the outputs that we pull but can't send + + /* + let msg = { + msg: content, + port: portnum, + isAck: false + } + // or + let msg = { + port: portnum, + isAck: true + } + */ + + // the link level will flow control across bytes, + // so we just flow control across ports + + this.loop = () => { + + // for everything we're holding, check our outputs + for (let i in outports) { + if (outports[i].hold.status === 'occupied' && outports[i].ie) { + // gr8 news, we can ship it + outports[i].put(outports.hold.msg) + outports[i].hold.status = 'clear' + let ack = { + isAck: true, + port: i + } + outbuffer.push(ack) + } else { + // we can't do anything, waiting for outside world + } } - // so far we won't ack at the link layer, - // i.e. we will assume that we can send all ports across, - // without getting an ack back from the link - // but we will need to do this for the dmarippers - // perhaps that should live in the layer that 'websocket' is at now - - let inports = [this.inputs.zero, this.inputs.zero, this.inputs.one] - let outports = [this.outputs.zero, this.outputs.zero] - - let outbuffer = new Array() - - // I think we just need to buffer - // the outputs that we pull but can't send - - /* - let msg = { - msg: content, - port: portnum, - isAck: false - } - // or - let msg = { - port: portnum, - isAck: true - } - */ - - // the link level will flow control across bytes, - // so we just flow control across ports - - this.loop = () => { - - // for everything we're holding, check our outputs - for (let i in outports) { - if (outports[i].hold.status === 'occupied' && outports[i].ie) { - // gr8 news, we can ship it - outports[i].put(outports.hold.msg) - outports[i].hold.status = 'clear' - let ack = { - isAck: true, - port: i - } - outbuffer.push(ack) - } else { - // we can't do anything, waiting for outside world - } + // then check for messages from the data link + if (this.inputs.data.io) { + // we pull every time + let msg = this.inputs.data.get() + // if it's an ack, we can clear an input + if (msg.isAck) { + if (inports[msg.port].dss !== 'await ack') { + console.log("LINK ERROR: ACK FROM NON WAIT") + throw new Error('link panic', msg) + } else { + inports[msg.port].dss = 'open' } - - // then check for messages from the data link - if (this.inputs.bytes.io) { - // we pull every time - let msg = this.inputs.bytes.get() - // if it's an ack, we can clear an input - if (msg.isAck) { - if (inports[msg.port].dss !== 'await ack') { - console.log("LINK ERROR: ACK FROM NON WAIT") - throw new Error('link panic', msg) - } else { - inports[msg.port].dss = 'open' - } - } else { - // otherwise we have a message for one of our outputs - let dsport = outports[msg.port] - // if we have one - if (dsport !== null && dsport !== undefined) { - if (dsport.hold.status === 'occupied') { - // bad news, we are already waiting to send - // but we have this new thing, so - console.log('LINK ERROR: 2ND MSG TO NON ACKED PORT') - throw new Error('link panic', msg) - } else if (dsport.ie){ - // this is easy, we can just ship it - dsport.put(msg.msg) - let ack = { - isAck: true, - port: msg.port - } - outbuffer.push(ack) - } else { - // well, we already pulled it off stream, so - dsport.hold.status = 'occupied' - dsport.hold.msg = msg.msg - // store it locally, but don't ack - // so we should not get another message on this port until we ack ... - // if we do, the 1st if statement in this block will be triggered - } - } else { - console.log('LINK ERROR: RECEIVES MESSAGE FOR PORT IT DOTH NOT HAVE') - console.log(msg.port, typeof msg.port) - // TODO: make one, and report to manager that we have done so - } - } // end message-not-ack - } // end if input has bytes - - // now let's run over our inputs, - for (let i in inports) { - // if there's a message on the input, and the downstream is clear, - if (inports[i].io && inports[i].dss === 'open') { - // we can send it, and reset to await an ack - this.log('i', typeof i) - let dsmsg = { - msg: inports[i].get(), - port: parseInt(i), - isAck: false, - } - this.log('LNK MSG OUT', dsmsg) - inports[i].dss = 'await ack' - outbuffer.push(dsmsg) - } else { - // otherwise, there's nothing we can do but let it sit there + } else { + // otherwise we have a message for one of our outputs + let dsport = outports[msg.port] + // if we have one + if (dsport !== null && dsport !== undefined) { + if (dsport.hold.status === 'occupied') { + // bad news, we are already waiting to send + // but we have this new thing, so + console.log('LINK ERROR: 2ND MSG TO NON ACKED PORT') + throw new Error('link panic', msg) + } else if (dsport.ie) { + // this is easy, we can just ship it + dsport.put(msg.msg) + let ack = { + isAck: true, + port: msg.port } + outbuffer.push(ack) + } else { + // well, we already pulled it off stream, so + dsport.hold.status = 'occupied' + dsport.hold.msg = msg.msg + // store it locally, but don't ack + // so we should not get another message on this port until we ack ... + // if we do, the 1st if statement in this block will be triggered + } + } else { + console.log('LINK ERROR: RECEIVES MESSAGE FOR PORT IT DOTH NOT HAVE') + console.log(msg.port, typeof msg.port) + // TODO: make one, and report to manager that we have done so } - - // flow control outgoing messages - // one per loop ! - if (this.outputs.bytes.ie && outbuffer.length > 0) { - this.outputs.bytes.put(outbuffer.shift()) + } // end message-not-ack + } // end if input has bytes + + // now let's run over our inputs, + for (let i in inports) { + // if there's a message on the input, and the downstream is clear, + if (inports[i].io && inports[i].dss === 'open') { + // we can send it, and reset to await an ack + this.log('i', typeof i) + let dsmsg = { + msg: inports[i].get(), + port: parseInt(i), + isAck: false, } - } // end loop + this.log('LNK MSG OUT', dsmsg) + inports[i].dss = 'await ack' + outbuffer.push(dsmsg) + } else { + // otherwise, there's nothing we can do but let it sit there + } + } + + // flow control outgoing messages + // one per loop ! + if (this.outputs.data.ie && outbuffer.length > 0) { + this.outputs.data.put(outbuffer.shift()) + } + } // end loop } -export default Link \ No newline at end of file +// FOOTER +export default Link +// END FOOTER diff --git a/hunks/view.js b/hunks/view.js index e9af913c72b9c188d206b714cfd3cacd049cb487..de7e6b7e0f1dc455535c26a677e1f46aed340ab8 100644 --- a/hunks/view.js +++ b/hunks/view.js @@ -1,795 +1,840 @@ -/* +/* VIEW - - scrape to manager, - - manager gogetter ... and then, hello click - - and then, nicely: - - also - link for flowcontrol when downstream non-conn ? the init-over-link question? a few states ... - - div structure ... ? open something up just barely, to test node and scraper - - beer 2 celly - - div structure: the div(div) scaling unfuckery + - scrape to manager, + - manager gogetter ... and then, hello click + - and then, nicely: + - also - link for flowcontrol when downstream non-conn ? the init-over-link question? a few states ... + - div structure ... ? open something up just barely, to test node and scraper + - beer 2 celly + - div structure: the div(div) scaling unfuckery */ -import { Hunkify, Input, Output, State } from './hunks.js' +import { + Hunkify, + Input, + Output, + State +} from './hunks.js' + import * as BZ from '../bztools.js' function View() { - Hunkify(this, 'View') - - // view of a manager, has route to that manager (via a link?) - - this.inputs.msgs = new Input('message', 'msgs') - this.outputs.msgs = new Output('message', 'msgs') - - // das UI globals - // our dom is the space we're allotted, - this.dom = {} - // the plane, one layer beneath, is where divs live - this.plane = {} - - /* --------------------------- ---------------------------- */ - /* -------------------- INIT, LISTENERS ---------------------- */ - /* --------------------------- ---------------------------- */ - - this.init = () => { - // in the case of UIs, we have the dom before init runs, - // so this is kind of like the 'window.onload' function - this.log('hello ui') - this.dom = $('<div>').addClass('view').get(0) - // for nested dom elements, - this.plane = $('<div>').addClass('plane').get(0) - - // init transform - let dft = { s: 1, x: 0, y: 0, ox: 0, oy: 0 } - writeTransform(this.plane, dft) - writeBackgroundTransform(this.dom, dft) - - // zoom the context - this.dom.addEventListener('wheel', mouseWheelListener) - - // pan the context - this.dom.addEventListener('mousedown', (evt) => { - if ($(evt.target).is('.view')) { - evt.preventDefault() - evt.stopPropagation() - window.addEventListener('mousemove', canvasDragListener) - window.addEventListener('mouseup', canvasUpListener) - } else if ($(evt.target).is('input')) { - // no op - } else { - // prevents bubbling up outside of the view, potentially a bug farm ? - // evt.preventDefault() - // evt.stopPropagation() - } - }) - - // get the context menu on righ click - this.dom.addEventListener('contextmenu', onContextMenu) - this.plane.subscale = 1 - - // append - $(this.dom).append(this.plane) + Hunkify(this, 'View') + + // view of a manager, has route to that manager (via a link?) + + this.inputs.msgs = new Input('message', 'msgs') + this.outputs.msgs = new Output('message', 'msgs') + + // das UI globals + // our dom is the space we're allotted, + this.dom = {} + // the plane, one layer beneath, is where divs live + this.plane = {} + + /* --------------------------- ---------------------------- */ + /* -------------------- INIT, LISTENERS ---------------------- */ + /* --------------------------- ---------------------------- */ + + this.init = () => { + // in the case of UIs, we have the dom before init runs, + // so this is kind of like the 'window.onload' function + this.log('hello ui') + this.dom = $('<div>').addClass('view').get(0) + // for nested dom elements, + this.plane = $('<div>').addClass('plane').get(0) + + // init transform + let dft = { + s: 1, + x: 0, + y: 0, + ox: 0, + oy: 0 } + writeTransform(this.plane, dft) + writeBackgroundTransform(this.dom, dft) - let mouseWheelListener = (evt) => { - if (!$(evt.target).is('.view')) { - return false - } + // zoom the context + this.dom.addEventListener('wheel', mouseWheelListener) + + // pan the context + this.dom.addEventListener('mousedown', (evt) => { + if ($(evt.target).is('.view')) { evt.preventDefault() evt.stopPropagation() + window.addEventListener('mousemove', canvasDragListener) + window.addEventListener('mouseup', canvasUpListener) + } else if ($(evt.target).is('input')) { + // no op + } else { + // prevents bubbling up outside of the view, potentially a bug farm ? + // evt.preventDefault() + // evt.stopPropagation() + } + }) + + // get the context menu on righ click + this.dom.addEventListener('contextmenu', onContextMenu) + this.plane.subscale = 1 + + // append + $(this.dom).append(this.plane) + } + + let mouseWheelListener = (evt) => { + if (!$(evt.target).is('.view')) { + return false + } + evt.preventDefault() + evt.stopPropagation() - let ox = evt.layerX - let oy = evt.layerY + let ox = evt.layerX + let oy = evt.layerY - let ds - if (evt.deltaY > 0) { - ds = 0.025 - } else { - ds = -0.025 - } + let ds + if (evt.deltaY > 0) { + ds = 0.025 + } else { + ds = -0.025 + } - let ct = readTransform(this.plane) + let ct = readTransform(this.plane) - ct.s *= 1 + ds + ct.s *= 1 + ds - ct.x += (ct.x - ox) * ds - ct.y += (ct.y - oy) * ds + ct.x += (ct.x - ox) * ds + ct.y += (ct.y - oy) * ds - writeTransform(this.plane, ct) - writeBackgroundTransform(this.dom, ct) + writeTransform(this.plane, ct) + writeBackgroundTransform(this.dom, ct) - // now redraw links - } + // now redraw links + } - let canvasDragListener = (evt) => { - evt.preventDefault() - evt.stopPropagation() - let ct = readTransform(this.plane) - ct.x += evt.movementX - ct.y += evt.movementY - writeTransform(this.plane, ct) - writeBackgroundTransform(this.dom, ct) - - /* - this.pan.x += evt.movementX - this.pan.y += evt.movementY - this.plane.style.backgroundPosition = `${this.pan.x}px ${this.pan.y}px` - $(this.plane).children().each((index, div) => { - let tf = readTransform(div) - tf.x += evt.movementX - tf.y += evt.movementY - writeTransform(div, tf) + let canvasDragListener = (evt) => { + evt.preventDefault() + evt.stopPropagation() + let ct = readTransform(this.plane) + ct.x += evt.movementX + ct.y += evt.movementY + writeTransform(this.plane, ct) + writeBackgroundTransform(this.dom, ct) + + /* + this.pan.x += evt.movementX + this.pan.y += evt.movementY + this.plane.style.backgroundPosition = `${this.pan.x}px ${this.pan.y}px` + $(this.plane).children().each((index, div) => { + let tf = readTransform(div) + tf.x += evt.movementX + tf.y += evt.movementY + writeTransform(div, tf) + }) + */ + } + + let canvasUpListener = (evt) => { + window.removeEventListener('mousemove', canvasDragListener) + window.removeEventListener('mouseup', canvasUpListener) + } + + // CONTEXT MENU + let onContextMenu = (evt) => { + //console.log(evt) + evt.preventDefault() + evt.stopPropagation() + // TRANSFORM here to write menu in the right spot on click + let menu = $('<div>').addClass('contextmenu') + .append('<ul> hello -> </ul>').get(0) + let ct = readTransform(this.plane) + console.log('write to ', ct) + writeTransform(menu, { + s: 1, + x: evt.layerX - ct.x * ct.s, + y: evt.layerY - ct.y * ct.s + }) + $(this.plane).append(menu) + // an output + this.writemessage('hello', ' ') + } + + let menuNextItem = (text) => { + $(this.plane).children('.contextmenu').children().not('ul').remove() + $(this.plane).children('.contextmenu').append('<ul>' + text + '</ul>') + } + + let addContextOptions = (list) => { + let menu = $(this.plane).children('.contextmenu').get(0) + for (let index in list) { + $(menu).append(writeContextOption(list[index].text, list[index].header, list[index].content)) + } + } + + let writeContextOption = (text, header, content) => { + let item = $('<li>' + text + '</li>').get(0) + item.addEventListener('click', (evt) => { + this.writemessage(header, content) + menuNextItem(header + ' ' + content + ' ->') + }) + return item + } + + + /* --------------------------- ---------------------------- */ + /* ---------------------- FORCE LAYOUT ----------------------- */ + /* --------------------------- ---------------------------- */ + + // ok, my thoughts on this + /* + + I obviously need to think carefully about it for a minute + I'm going to make it sortof-work before I go forwards with the link + + - should restart on adding a new hunk with existing positions + - should be able to stop it when we mousedown + + */ + + let blocks = new Array() + let flsimrun = false + let flsim = {} + let flnodes = [] + + let finAlpha = 0.5 + + let updateForceLoop = () => { + // init and/or update + if (!flsimrun && blocks.length > 1) { + // Case for starting sim + console.log('starting force sim') + flsimrun = true + // start with two nodes + let positions = this.getAllHunkPositions() + for (let i in positions) { + flnodes.push({ + index: i, + x: positions[i].x, + y: positions[i].y, + vx: 0, + vy: 0 }) - */ + } + flsim = d3.forceSimulation(flnodes) + .force('charge', d3.forceManyBody().strength(-20)) + .force('center', d3.forceCenter(600, 600)) + .force('collide', d3.forceCollide(150)) + .alphaMin(finAlpha) + .on('tick', flTick) + .on('end', flEnd) + } else if (blocks.length <= 1) { + // donot + } else { + // case for adding / rming from sim + this.log('UPD8 Force Sim') + let positions = this.getAllHunkPositions() + if (positions.length > flnodes.length) { + let last = positions.length - 1 + //console.log('to add new node like', positions[last]) + let newNode = { + index: last, + x: positions[last].x, + y: positions[last].y, + vx: 0, + vy: 0 + } + flnodes.push(newNode) + // console.log('SIM adds now this', newNode.x, newNode.y) + } else { + console.log("SIM DELETE CASE NOT WRITTEN") + } + flsim.nodes(flnodes) + flsim.alpha(1) + .alphaMin(finAlpha) + .restart() } - - let canvasUpListener = (evt) => { - window.removeEventListener('mousemove', canvasDragListener) - window.removeEventListener('mouseup', canvasUpListener) + } + + let flTick = () => { + // called on sim update + let blks = $(this.plane).children('.block') + if (blks.length !== flnodes.length) { + console.log('FLOOP NODES MISMATCH', blks.length, flnodes.length) + } else { + for (let i = 0; i < blks.length; i++) { + blks[i].style.left = flnodes[i].x + 'px' + blks[i].style.top = flnodes[i].y + 'px' + } } - - // CONTEXT MENU - let onContextMenu = (evt) => { - //console.log(evt) - evt.preventDefault() - evt.stopPropagation() - // TRANSFORM here to write menu in the right spot on click - let menu = $('<div>').addClass('contextmenu') - .append('<ul> hello -> </ul>').get(0) - let ct = readTransform(this.plane) - console.log('write to ', ct) - writeTransform(menu, { s: 1, x: evt.layerX - ct.x * ct.s, y: evt.layerY - ct.y * ct.s }) - $(this.plane).append(menu) - // an output - this.writemessage('hello', ' ') + drawLinks() + } + + let flEnd = () => { + console.log('FIN DU SIM') + } + + this.getAllHunkPositions = () => { + let nds = $(this.plane).children('.block') + let positions = new Array() + for (let nd of nds) { + let ps = readXY(nd) + ps.id = nd.id + positions.push(ps) } - - let menuNextItem = (text) => { - $(this.plane).children('.contextmenu').children().not('ul').remove() - $(this.plane).children('.contextmenu').append('<ul>' + text + '</ul>') + return positions + // should do transform here ? + } + + /* --------------------------- ---------------------------- */ + /* -------------------- DRAWING & MOVING --------------------- */ + /* --------------------------- ---------------------------- */ + + let writeTransform = (div, tf) => { + div.style.transform = `scale(${tf.s})` + //div.style.transformOrigin = `${tf.ox}px ${tf.oy}px` + div.style.left = `${tf.x}px` + div.style.top = `${tf.y}px` + } + + let writeBackgroundTransform = (div, tf) => { + div.style.backgroundSize = `${tf.s * 100}px ${tf.s * 100}px` + div.style.backgroundPosition = `${tf.x}px ${tf.y}px ` + } + + let readTransform = (div) => { + // transform, for scale + let transform = div.style.transform + let index = transform.indexOf('scale') + let left = transform.indexOf('(', index) + let right = transform.indexOf(')', index) + let s = parseFloat(transform.slice(left + 1, right)) + + // left and right, position + let x = parseFloat(div.style.left) + let y = parseFloat(div.style.top) + + return ({ + s: s, + x: x, + y: y + }) + } + + let reposition = (div) => { + div.style.left = div.x + 'px' + div.style.top = div.y + 'px' + } + + let readXY = (div) => { + //console.log('READXY left', div.style.left, 'top', div.style.top) + return { + x: div.style.left, + y: div.style.top } - - let addContextOptions = (list) => { - let menu = $(this.plane).children('.contextmenu').get(0) - for (let index in list) { - $(menu).append(writeContextOption(list[index].text, list[index].header, list[index].content)) + } + + let drawLinks = () => { + // from within the div + let outputs = $(this.plane).children('.block').children('.outputs').children('.output') + // clear all links + BZ.clear(this.plane) + // and draw new ones + for (let output of outputs) { + // finding the children to hookup to + for (let conn of output.connectedTo) { + // find looks down *all* branches of the dom tree + // with children().children() we can reduce that tree + let hookup = $(this.plane).find(conn) + if (hookup.length !== 1) { + // this can happen when a dependent is not loaded yet + console.log('mismatched connection', hookup.length, conn) + } else { + let hk = hookup.get(0) + let head = BZ.getRightHandle(output, this.plane.subscale) + let tail + if (hk.id === 'floater') { + tail = BZ.getFloaterHandle(hk, this.plane.subscale) + } else { + tail = BZ.getLeftHandle(hk, this.plane.subscale) + } + let bz = BZ.writeBezier(head, tail, output.id + hk.id, this.plane) + bz.addEventListener('mouseenter', (evt) => { + bz.style.stroke = '#2889af' + }) + bz.addEventListener('mouseout', (evt) => { + bz.style.stroke = '#1a1a1a' + }) + bz.addEventListener('contextmenu', (evt) => { + evt.preventDefault() + evt.stopPropagation() + // nice + let outId = output.id.substring(0, output.id.indexOf('_output_')) + let outName = output.id.substring(output.id.indexOf('_output_') + 8) + let inId = conn.substring(1, conn.indexOf('_input_')) + let inName = conn.substring(conn.indexOf('_input_') + 7) + this.writemessage('removelink', { + outId: outId, + outName: outName, + inId: inId, + inName: inName + }) + console.log('cntextlink', '#' + output.id, conn) + // write those up as a message ... + }) } + } } - - let writeContextOption = (text, header, content) => { - let item = $('<li>' + text + '</li>').get(0) - item.addEventListener('click', (evt) => { - this.writemessage(header, content) - menuNextItem(header + ' ' + content + ' ->') - }) - return item + } + + /* --------------------------- ---------------------------- */ + /* -------------------- DEFS in the DOM ---------------------- */ + /* --------------------------- ---------------------------- */ + + // ADD A DEF + // TODO HERE big def walk and update ... + // this will make updating things more straightforward? + // until we have a def that has a view ... ... + let putDef = (def) => { + if (false) console.log('ready write', def) + // get a position + // TODO if/ for case where no menu before load + let menu = $(this.plane).children('.contextmenu').get(0) + // TRANSFORM for scale here, position to where-added ? + let mt + if (menu === undefined) { + // we need a position, let's pick random + mt = { + s: 1, + x: Math.random() * 1000, + y: Math.random() * 800 + } + } else { + mt = readTransform(menu) } - - /* --------------------------- ---------------------------- */ - /* ---------------------- FORCE LAYOUT ----------------------- */ - /* --------------------------- ---------------------------- */ - - // ok, my thoughts on this - /* - - I obviously need to think carefully about it for a minute - I'm going to make it sortof-work before I go forwards with the link - - - should restart on adding a new hunk with existing positions - - should be able to stop it when we mousedown - - */ - - let blocks = new Array() - let flsimrun = false - let flsim = {} - let flnodes = [] - - let finAlpha = 0.5 - - let updateForceLoop = () => { - // init and/or update - if (!flsimrun && blocks.length > 1) { - // Case for starting sim - console.log('starting force sim') - flsimrun = true - // start with two nodes - let positions = this.getAllHunkPositions() - for (let i in positions) { - flnodes.push({ - index: i, - x: positions[i].x, - y: positions[i].y, - vx: 0, - vy: 0 - }) - } - flsim = d3.forceSimulation(flnodes) - .force('charge', d3.forceManyBody().strength(-20)) - .force('center', d3.forceCenter(600, 600)) - .force('collide', d3.forceCollide(150)) - .alphaMin(finAlpha) - .on('tick', flTick) - .on('end', flEnd) - } else if (blocks.length <= 1) { - // donot - } else { - // case for adding / rming from sim - this.log('UPD8 Force Sim') - let positions = this.getAllHunkPositions() - if (positions.length > flnodes.length) { - let last = positions.length - 1 - //console.log('to add new node like', positions[last]) - let newNode = { - index: last, - x: positions[last].x, - y: positions[last].y, - vx: 0, - vy: 0 - } - flnodes.push(newNode) - // console.log('SIM adds now this', newNode.x, newNode.y) - } else { - console.log("SIM DELETE CASE NOT WRITTEN") - } - flsim.nodes(flnodes) - flsim.alpha(1) - .alphaMin(finAlpha) - .restart() + // write the def dom + let de = writeDefDom(def, false) + // not interested in adding the top level's view to itself + // TODO probably this is a bugfarm when we go to nest views + if (def.dom !== null && def.dom !== undefined && def.id !== 'TLView') { + this.log(`${def.id} UI DOM appears...`, def.dom) + try { + $(de).append($(def.dom).addClass('cuttlefishhunkdom')) + if ($(def.dom).is('.view')) { + de.style.height = '800px' + de.style.width = '1200px' + // start of a handle attempt ... + $(de).append($('<div>').addClass('rsHandle').on('mousedown', (evt) => { + console.log('rshandle down') + })) } + } catch (err) { + console.log('error while appending cfhnk to dom', err) + } } - - let flTick = () => { - // called on sim update - let blks = $(this.plane).children('.block') - if (blks.length !== flnodes.length) { - console.log('FLOOP NODES MISMATCH', blks.length, flnodes.length) - } else { - for (let i = 0; i < blks.length; i++) { - blks[i].style.left = flnodes[i].x + 'px' - blks[i].style.top = flnodes[i].y + 'px' - } + writeTransform(de, mt) + // rm menu if it's around + if (menu !== undefined) $(menu).remove() + + // add the def to the view + $(this.plane).append(de) + + // for the sim, add an x and y + // and we keep a list, but maybe don't need to? + blocks.push(de) + + // AS Policy, since this thing *is* a stateful view, + // we keep state in the view. no mirror, def is a throwaway + // i.e. we won't return it: it contains everything we need + drawLinks() + + // here is an OK place to do the sorting ... + // TODO: add to D3 + updateForceLoop() + } + + let removeDef = (id) => { + // get the inputs that we'll have to watch removal for + let inpts = $(this.plane).children('#' + id).children('.inputs').children('.input') + // remove the def / block + $(this.plane).children('#' + id).remove() + // we have to walk over the other outputs and disconnect them, + let otps = $(this.plane).find('.output') + for (let otp of otps) { + for (let inp of inpts) { + if (otp.connectedTo.includes('#' + inp.id)) { + otp.connectedTo.splice(otp.connectedTo.indexOf('#' + inp.id)) } - drawLinks() + } } - - let flEnd = () => { - console.log('FIN DU SIM') + drawLinks() + } + + let putLink = (outId, outName, inId, inName) => { + try { + let outp = $(this.plane).children('.block').children('.outputs').children('#' + outId + '_output_' + outName).get(0) + outp.connectedTo.push('#' + inId + '_input_' + inName) + drawLinks() + } catch (err) { + console.log('ERR at putlink', err) + return false } - - this.getAllHunkPositions = () => { - let nds = $(this.plane).children('.block') - let positions = new Array() - for (let nd of nds) { - let ps = readXY(nd) - ps.id = nd.id - positions.push(ps) - } - return positions - // should do transform here ? + return true + } + + let removeLink = (outId, outName, inId, inName) => { + try { + let outp = $(this.plane).children('.block').children('.outputs').children('#' + outId + '_output_' + outName).get(0) + outp.connectedTo.splice(outp.connectedTo.indexOf('#' + inId + '_input_' + inName), 1) + drawLinks() + } catch (err) { + console.log('ERR at rmlink', err) + return true + } + return false + } + + /* --------------------------- ---------------------------- */ + /* ---------------------- WRITING DEFS ----------------------- */ + /* --------------------------- ---------------------------- */ + + let writeDefDom = (def, debug) => { + // debug + if (debug) console.log('writing for def', def) + // a div to locate it + let de = document.createElement('div') + $(de).addClass('block').attr('id', def.id) + + // more html: the title + $(de).append($('<div>' + de.id + '</div>').addClass('blockid').append('<span style="float:right;"> (' + def.name + ')</span>')) + + let title = $(de).children('.blockid').get(0) + + title.oncontextmenu = (evt) => { + evt.preventDefault() + evt.stopPropagation() + // write menu for requesting delete and copy + let menu = $('<div>').addClass('contextmenu').get(0) + // title.offsetWidth is 400 + // de.style.left, de.style.top, de.clientWidth, + let ct = readTransform(de) + let x = ct.x + 400 + 10 + 'px' + writeTransform(menu, { + s: 1, + x: ct.x + ((400 + 10)), + y: ct.y + }) + $(this.plane).append(menu) + $(menu).append($('<li>remove hunk</li>').on('click', (evt) => { + this.writemessage('removehunk', { + id: def.id, + name: def.name + }) + $(menu).remove() + })) + $(menu).append($('<li>copy hunk</li>').on('click', (evt) => { + this.writemessage('addhunk', def.name) + $(menu).remove() + })) } - /* --------------------------- ---------------------------- */ - /* -------------------- DRAWING & MOVING --------------------- */ - /* --------------------------- ---------------------------- */ + title.onmousedown = function(evt) { + evt.preventDefault() + evt.stopPropagation() - let writeTransform = (div, tf) => { - div.style.transform = `scale(${tf.s})` - //div.style.transformOrigin = `${tf.ox}px ${tf.oy}px` - div.style.left = `${tf.x}px` - div.style.top = `${tf.y}px` - } + function domElemMouseMove(evt) { + // TRANSFORMS here to move div about on drag + evt.preventDefault() + evt.stopPropagation() + let ct = readTransform(de) + let pt = readTransform(de.parentElement) + ct.x += evt.movementX / pt.s + ct.y += evt.movementY / pt.s + writeTransform(de, ct) + drawLinks() + } - let writeBackgroundTransform = (div, tf) => { - div.style.backgroundSize = `${tf.s * 100}px ${tf.s * 100}px` - div.style.backgroundPosition = `${tf.x}px ${tf.y}px ` - } + function rmOnMouseUp(evt) { + // would do save of position state here + // TODO /\ + document.removeEventListener('mousemove', domElemMouseMove) + document.removeEventListener('mouseup', rmOnMouseUp) + } - let readTransform = (div) => { - // transform, for scale - let transform = div.style.transform - let index = transform.indexOf('scale') - let left = transform.indexOf('(', index) - let right = transform.indexOf(')', index) - let s = parseFloat(transform.slice(left + 1, right)) - - // left and right, position - let x = parseFloat(div.style.left) - let y = parseFloat(div.style.top) - - return ({ - s: s, - x: x, - y: y - }) + document.addEventListener('mousemove', domElemMouseMove) + document.addEventListener('mouseup', rmOnMouseUp) } - let reposition = (div) => { - div.style.left = div.x + 'px' - div.style.top = div.y + 'px' + if (Object.keys(def.inputs).length > 0) { + let idom = $('<div>').addClass('inputs') + for (let key in def.inputs) { + if (debug) console.log('def.inputs[key]', def.inputs[key]) + $(idom).append(writePortDom(def.inputs[key], def, 'input', debug)) + } + $(de).append(idom) } - let readXY = (div) => { - //console.log('READXY left', div.style.left, 'top', div.style.top) - return { x: div.style.left, y: div.style.top } + if (Object.keys(def.inputs).length > 0) { + let odom = $('<div>').addClass('outputs') + for (let key in def.outputs) { + if (debug) console.log('def.outputs[key]', def.outputs[key]) + $(odom).append(writePortDom(def.outputs[key], def, 'output', debug)) + } + $(de).append(odom) } - let drawLinks = () => { - // from within the div - let outputs = $(this.plane).children('.block').children('.outputs').children('.output') - // clear all links - BZ.clear(this.plane) - // and draw new ones - for (let output of outputs) { - // finding the children to hookup to - for (let conn of output.connectedTo) { - // find looks down *all* branches of the dom tree - // with children().children() we can reduce that tree - let hookup = $(this.plane).find(conn) - if (hookup.length !== 1) { - // this can happen when a dependent is not loaded yet - console.log('mismatched connection', hookup.length, conn) - } else { - let hk = hookup.get(0) - let head = BZ.getRightHandle(output, this.plane.subscale) - let tail - if (hk.id === 'floater') { - tail = BZ.getFloaterHandle(hk, this.plane.subscale) - } else { - tail = BZ.getLeftHandle(hk, this.plane.subscale) - } - let bz = BZ.writeBezier(head, tail, output.id + hk.id, this.plane) - bz.addEventListener('mouseenter', (evt) => { - bz.style.stroke = '#2889af' - }) - bz.addEventListener('mouseout', (evt) => { - bz.style.stroke = '#1a1a1a' - }) - bz.addEventListener('contextmenu', (evt) => { - evt.preventDefault() - evt.stopPropagation() - // nice - let outId = output.id.substring(0, output.id.indexOf('_output_')) - let outName = output.id.substring(output.id.indexOf('_output_') + 8) - let inId = conn.substring(1, conn.indexOf('_input_')) - let inName = conn.substring(conn.indexOf('_input_') + 7) - this.writemessage('removelink', { outId: outId, outName: outName, inId: inId, inName: inName }) - console.log('cntextlink', '#' + output.id, conn) - // write those up as a message ... - }) - } - } - } + if (Object.keys(def.inputs).length > 0) { + let sdom = $('<div>').addClass('state') + for (let key in def.state) { + if (debug) console.log('state dom', sdom) + if (debug) console.log('def.state[key]', def.state[key]) + $(sdom).append(writeStateDom(def.state[key], def, debug)) + } + $(de).append(sdom) } - - /* --------------------------- ---------------------------- */ - /* -------------------- DEFS in the DOM ---------------------- */ - /* --------------------------- ---------------------------- */ - - // ADD A DEF - // TODO HERE big def walk and update ... - // this will make updating things more straightforward? - // until we have a def that has a view ... ... - let putDef = (def) => { - if (false) console.log('ready write', def) - // get a position - // TODO if/ for case where no menu before load - let menu = $(this.plane).children('.contextmenu').get(0) - // TRANSFORM for scale here, position to where-added ? - let mt - if (menu === undefined) { - // we need a position, let's pick random - mt = { s: 1, x: Math.random() * 1000, y: Math.random() * 800 } - } else { - mt = readTransform(menu) - } - - // write the def dom - let de = writeDefDom(def, false) - // not interested in adding the top level's view to itself - // TODO probably this is a bugfarm when we go to nest views - if (def.dom !== null && def.dom !== undefined && def.id !== 'TLView') { - this.log(`${def.id} UI DOM appears...`, def.dom) - try { - $(de).append($(def.dom).addClass('cuttlefishhunkdom')) - if ($(def.dom).is('.view')) { - de.style.height = '800px' - de.style.width = '1200px' - // start of a handle attempt ... - $(de).append($('<div>').addClass('rsHandle').on('mousedown', (evt) =>{ - console.log('rshandle down') - })) - } - } catch (err) { - console.log('error while appending cfhnk to dom', err) - } - } - writeTransform(de, mt) - // rm menu if it's around - if (menu !== undefined) $(menu).remove() - - // add the def to the view - $(this.plane).append(de) - - // for the sim, add an x and y - // and we keep a list, but maybe don't need to? - blocks.push(de) - - // AS Policy, since this thing *is* a stateful view, - // we keep state in the view. no mirror, def is a throwaway - // i.e. we won't return it: it contains everything we need - drawLinks() - - // here is an OK place to do the sorting ... - // TODO: add to D3 - updateForceLoop() + return de + } + + let writePortDom = (port, def, inout, debug) => { + if (debug) console.log('port dom', port, def.id, inout) + let id = def.id + '_' + inout + '_' + port.name + if ($(this.plane).children(id).length > 0) { + console.log("VIEW UNTESTED RECEIPT OF DEF FOR EXISTING DEF") + $(this.plane).children(id).remove() } - - let removeDef = (id) => { - // get the inputs that we'll have to watch removal for - let inpts = $(this.plane).children('#' + id).children('.inputs').children('.input') - // remove the def / block - $(this.plane).children('#' + id).remove() - // we have to walk over the other outputs and disconnect them, - let otps = $(this.plane).find('.output') - for (let otp of otps) { - for (let inp of inpts) { - if (otp.connectedTo.includes('#' + inp.id)) { - otp.connectedTo.splice(otp.connectedTo.indexOf('#' + inp.id)) - } - } - } - drawLinks() + let dom = {} + if(inout === "input"){ + dom = $('<li>' + port.name + " (" + port.type + ")" + '</li>').addClass(inout).get(0) + } else { + dom = $('<li>' + "(" + port.type + ") " + port.name + '</li>').addClass(inout).get(0) } - - let putLink = (outId, outName, inId, inName) => { - try { - let outp = $(this.plane).children('.block').children('.outputs').children('#' + outId + '_output_' + outName).get(0) - outp.connectedTo.push('#' + inId + '_input_' + inName) - drawLinks() - } catch (err) { - console.log('ERR at putlink', err) - return false - } - return true + dom.id = id + // this goes in so that we can handily draw links + dom.connectedTo = new Array() + // let ... in is OK for zero-length arrays, but for ... of throws errors + for (let conn in port.connections) { + let cn = port.connections[conn] + // HERE is the missing link (haha) ... defid , what is + // has undefined in title ... + dom.connectedTo.push('#' + cn.parentid + '_input_' + cn.input) } - - let removeLink = (outId, outName, inId, inName) => { - try { - let outp = $(this.plane).children('.block').children('.outputs').children('#' + outId + '_output_' + outName).get(0) - outp.connectedTo.splice(outp.connectedTo.indexOf('#' + inId + '_input_' + inName), 1) - drawLinks() - } catch (err) { - console.log('ERR at rmlink', err) - return true - } - return false + // messy global for the potential floater + let floater = {} + + // the events + let evtDrag = (drag) => { + let cp = readTransform(floater) + cp.x += drag.movementX + cp.y += drag.movementY + writeTransform(floater, cp) + drawLinks() } - /* --------------------------- ---------------------------- */ - /* ---------------------- WRITING DEFS ----------------------- */ - /* --------------------------- ---------------------------- */ - - let writeDefDom = (def, debug) => { - // debug - if (debug) console.log('writing for def', def) - // a div to locate it - let de = document.createElement('div') - $(de).addClass('block').attr('id', def.id) - - // more html: the title - $(de).append($('<div>' + de.id + '</div>').addClass('blockid').append('<span style="float:right;"> (' + def.name + ')</span>')) - - let title = $(de).children('.blockid').get(0) - - title.oncontextmenu = (evt) => { - evt.preventDefault() - evt.stopPropagation() - // write menu for requesting delete and copy - let menu = $('<div>').addClass('contextmenu').get(0) - // title.offsetWidth is 400 - // de.style.left, de.style.top, de.clientWidth, - let ct = readTransform(de) - let x = ct.x + 400 + 10 + 'px' - writeTransform(menu, { s: 1, x: ct.x + ((400 + 10)), y: ct.y }) - $(this.plane).append(menu) - $(menu).append($('<li>remove hunk</li>').on('click', (evt) => { - this.writemessage('removehunk', { id: def.id, name: def.name }) - $(menu).remove() - })) - $(menu).append($('<li>copy hunk</li>').on('click', (evt) => { - this.writemessage('addhunk', def.name) - $(menu).remove() - })) - } - - title.onmousedown = function(evt) { - evt.preventDefault() - evt.stopPropagation() - - function domElemMouseMove(evt) { - // TRANSFORMS here to move div about on drag - evt.preventDefault() - evt.stopPropagation() - let ct = readTransform(de) - let pt = readTransform(de.parentElement) - ct.x += evt.movementX / pt.s - ct.y += evt.movementY / pt.s - writeTransform(de, ct) - drawLinks() - } - - function rmOnMouseUp(evt) { - // would do save of position state here - // TODO /\ - document.removeEventListener('mousemove', domElemMouseMove) - document.removeEventListener('mouseup', rmOnMouseUp) - } - - document.addEventListener('mousemove', domElemMouseMove) - document.addEventListener('mouseup', rmOnMouseUp) - } - - if (Object.keys(def.inputs).length > 0) { - let idom = $('<div>').addClass('inputs') - for (let key in def.inputs) { - if (debug) console.log('def.inputs[key]', def.inputs[key]) - $(idom).append(writePortDom(def.inputs[key], def, 'input', debug)) - } - $(de).append(idom) - } - - if (Object.keys(def.inputs).length > 0) { - let odom = $('<div>').addClass('outputs') - for (let key in def.outputs) { - if (debug) console.log('def.outputs[key]', def.outputs[key]) - $(odom).append(writePortDom(def.outputs[key], def, 'output', debug)) - } - $(de).append(odom) - } - - if (Object.keys(def.inputs).length > 0) { - let sdom = $('<div>').addClass('state') - for (let key in def.state) { - if (debug) console.log('state dom', sdom) - if (debug) console.log('def.state[key]', def.state[key]) - $(sdom).append(writeStateDom(def.state[key], def, debug)) - } - $(de).append(sdom) - } - return de + let dragMouseUp = (evt) => { + console.log('MOUSEUP ON', evt.target.id) + // recall name and def id from id + let str = evt.target.id + let last = str.lastIndexOf('_') + let name = str.substring(last + 1) + console.log('with name', name) + let first = str.indexOf('_') + let next = str.indexOf('_', first + 1) + let id = str.substring(0, next) + console.log('and id', id) + // HERE this is current stifle, an awkward message, I think ? + this.writemessage('addlink', { + outId: def.id, + outName: port.name, + inId: id, + inName: name + }) + // do things to conn, then + // cleanup + this.plane.removeEventListener('mouseup', dragMouseUp) + this.plane.removeEventListener('mousemove', evtDrag) + dom.connectedTo.splice(dom.connectedTo.indexOf('#floater'), 1) + $('#floater').remove() + drawLinks() } - let writePortDom = (port, def, inout, debug) => { - if (debug) console.log('port dom', port, def.id, inout) - let id = def.id + '_' + inout + '_' + port.name - if ($(this.plane).children(id).length > 0) { - console.log("VIEW UNTESTED RECEIPT OF DEF FOR EXISTING DEF") - $(this.plane).children(id).remove() - } - let dom = $('<li>' + port.name + '</li>').addClass(inout).get(0) - dom.id = id - // this goes in so that we can handily draw links - dom.connectedTo = new Array() - // let ... in is OK for zero-length arrays, but for ... of throws errors - for (let conn in port.connections) { - let cn = port.connections[conn] - // HERE is the missing link (haha) ... defid , what is - // has undefined in title ... - dom.connectedTo.push('#' + cn.parentid + '_input_' + cn.input) - } - // messy global for the potential floater - let floater = {} - - // the events - let evtDrag = (drag) => { - let cp = readTransform(floater) - cp.x += drag.movementX - cp.y += drag.movementY - writeTransform(floater, cp) - drawLinks() - } - - let dragMouseUp = (evt) => { - console.log('MOUSEUP ON', evt.target.id) - // recall name and def id from id - let str = evt.target.id - let last = str.lastIndexOf('_') - let name = str.substring(last + 1) - console.log('with name', name) - let first = str.indexOf('_') - let next = str.indexOf('_', first + 1) - let id = str.substring(0, next) - console.log('and id', id) - // HERE this is current stifle, an awkward message, I think ? - this.writemessage('addlink', { outId: def.id, outName: port.name, inId: id, inName: name }) - // do things to conn, then - // cleanup - this.plane.removeEventListener('mouseup', dragMouseUp) - this.plane.removeEventListener('mousemove', evtDrag) - dom.connectedTo.splice(dom.connectedTo.indexOf('#floater'), 1) - $('#floater').remove() - drawLinks() - } - - // startup the floater - dom.addEventListener('mousedown', (down) => { - console.log('MOUSEDOWN for', port.type, port.name, down) - // get the location to make the floater, - let cp = BZ.getRightHandle(dom, this.plane.subscale) - floater = $('<div>').attr('id', 'floater').append(port.type).get(0) - floater.style.zIndex = '1' - this.plane.appendChild(floater) - // 'hookup' our floater and its berth - dom.connectedTo.push('#floater') - // init out floater position, and put it in the dom - writeTransform(floater, { - s: this.plane.subscale, - x: cp.x - 80 * this.plane.subscale, - y: cp.y - (floater.clientHeight / 2) * this.plane.subscale - }) - // do relative moves - this.plane.addEventListener('mousemove', evtDrag) - // and delete / act when mouse comes up - this.plane.addEventListener('mouseup', dragMouseUp) + // startup the floater + dom.addEventListener('mousedown', (down) => { + console.log('MOUSEDOWN for', port.type, port.name, down) + // get the location to make the floater, + let cp = BZ.getRightHandle(dom, this.plane.subscale) + floater = $('<div>').attr('id', 'floater').append(port.type).get(0) + floater.style.zIndex = '1' + this.plane.appendChild(floater) + // 'hookup' our floater and its berth + dom.connectedTo.push('#floater') + // init out floater position, and put it in the dom + writeTransform(floater, { + s: this.plane.subscale, + x: cp.x - 80 * this.plane.subscale, + y: cp.y - (floater.clientHeight / 2) * this.plane.subscale + }) + // do relative moves + this.plane.addEventListener('mousemove', evtDrag) + // and delete / act when mouse comes up + this.plane.addEventListener('mouseup', dragMouseUp) + }) + + if (debug) console.log('port', dom) + return dom + } + + let writeStateDom = (state, def) => { + let dom = $('<li>' + state.name + '</li>').get(0) + dom.id = def.id + '_state_' + state.name + switch (typeof state.value) { + case 'string': + let strinput = $('<input>').attr('type', 'text').attr('size', 24).attr('value', state.value).get(0) + strinput.addEventListener('change', (evt) => { + // ask for a change, + // TODO HERE NOW: this is the state change request you want to write + // do it like writemessage() instead + // requestStateChange(def.id, state, strinput.value) + // but assert that we don't change the definition unless + requestStateChange(def.id, state, strinput.value) + strinput.value = state.value }) + state.dom = strinput + $(dom).append(strinput) + break + + case 'number': + let ninput = $('<input>').attr('type', 'text').attr('size', 24).attr('value', state.value.toString()).get(0) + ninput.addEventListener('change', (evt) => { + // ask for a change, + requestStateChange(def.id, state, parseFloat(ninput.value)) + // but assert that we don't change the definition unless + ninput.value = state.value + }) + state.dom = ninput + $(dom).append(ninput) + break + + case 'boolean': + $(dom).append('<span style="float:right;">' + state.value.toString() + '</span>') + dom.addEventListener('click', (evt) => { + // read the current 'state' (as written) and send the opposite + let txt = $(dom).children('span').text() + if (txt === 'true') { + requestStateChange(def.id, state, false) + } else { + requestStateChange(def.id, state, true) + } + }) + break - if (debug) console.log('port', dom) - return dom - } - - let writeStateDom = (state, def) => { - let dom = $('<li>' + state.name + '</li>').get(0) - dom.id = def.id + '_state_' + state.name - switch (typeof state.value) { - case 'string': - let strinput = $('<input>').attr('type', 'text').attr('size', 24).attr('value', state.value).get(0) - strinput.addEventListener('change', (evt) => { - // ask for a change, - // TODO HERE NOW: this is the state change request you want to write - // do it like writemessage() instead - // requestStateChange(def.id, state, strinput.value) - // but assert that we don't change the definition unless - requestStateChange(def.id, state, strinput.value) - strinput.value = state.value - }) - state.dom = strinput - $(dom).append(strinput) - break - - case 'number': - let ninput = $('<input>').attr('type', 'text').attr('size', 24).attr('value', state.value.toString()).get(0) - ninput.addEventListener('change', (evt) => { - // ask for a change, - requestStateChange(def.id, state, parseFloat(ninput.value)) - // but assert that we don't change the definition unless - ninput.value = state.value - }) - state.dom = ninput - $(dom).append(ninput) - break - - case 'boolean': - $(dom).append('<span style="float:right;">' + state.value.toString() + '</span>') - dom.addEventListener('click', (evt) => { - // read the current 'state' (as written) and send the opposite - let txt = $(dom).children('span').text() - if (txt === 'true') { - requestStateChange(def.id, state, false) - } else { - requestStateChange(def.id, state, true) - } - }) - break - - default: - - // + note on nonrec type - break - } - return dom - } + default: - let requestStateChange = (parentid, state, value) => { - this.writemessage('statechange', { - id: parentid, - name: state.name, - value: value - }) + // + note on nonrec type + break } - - // responses (change state / add link) - - let receiveStateChange = (parentid, name, value) => { - let blkstate = $(this.plane).find('#' + parentid + '_state_' + name) - if (blkstate !== null && blkstate !== undefined) { - // find value / etc - let inp = $(blkstate).children('input').get(0) - if (inp !== null && inp !== undefined) { - inp.value = value - } else { - if (typeof value !== 'boolean') { - console.log('ERR State Change for non Boolean on Boolean Type') - } else { - $(blkstate).children('span').text(value) - } - } + return dom + } + + let requestStateChange = (parentid, state, value) => { + this.writemessage('statechange', { + id: parentid, + name: state.name, + value: value + }) + } + + // responses (change state / add link) + + let receiveStateChange = (parentid, name, value) => { + let blkstate = $(this.plane).find('#' + parentid + '_state_' + name) + if (blkstate !== null && blkstate !== undefined) { + // find value / etc + let inp = $(blkstate).children('input').get(0) + if (inp !== null && inp !== undefined) { + inp.value = value + } else { + if (typeof value !== 'boolean') { + console.log('ERR State Change for non Boolean on Boolean Type') + } else { + $(blkstate).children('span').text(value) } + } } + } - /* --------------------------- ---------------------------- */ - /* --------------------- MESSAGES OUTPUT --------------------- */ - /* --------------------------- ---------------------------- */ + /* --------------------------- ---------------------------- */ + /* --------------------- MESSAGES OUTPUT --------------------- */ + /* --------------------------- ---------------------------- */ - this.outmsgbuffer = new Array() + this.outmsgbuffer = new Array() - this.writemessage = (header, content) => { - // guaranteed consumption here - let msg = { - header: header, - content: content - } - if (this.outputs.msgs.ie && this.outmsgbuffer.length < 1) { - // str8 shooters - this.log('msg out', msg.header) - console.log(msg.content) - this.outputs.msgs.put(msg) - } else { - // gotta buffer - this.outmsgbuffer.push(msg) - this.log('MGR OUTBUFFER LEN', this.outmsgbuffer.length) - } + this.writemessage = (header, content) => { + // guaranteed consumption here + let msg = { + header: header, + content: content } - - /* --------------------------- ---------------------------- */ - /* ------------------ MESSAGES INPUT, LOOP ------------------- */ - /* --------------------------- ---------------------------- */ - - // still looking for clear function naming / message naming - - this.loop = () => { - // THE Q: is it trees or is it inputs / outputs ? ports ... - if (this.inputs.msgs.io) { - // HERE: this hack gets the data without a deep copy, so we want this - // for bringing the dom element over *which is specific to a native manager* - // so TODO: add switch for native / nonative managers - let data = this.inputs.msgs.data - let pult = this.inputs.msgs.get() - let header = data.header - let content = data.content - this.log(`gets msg ${header}`) - switch (header) { - case 'putcontextoptions': - addContextOptions(content) - break - case 'putdef': - putDef(content) - break - case 'removedef': - removeDef(content) - break - case 'putlink': - putLink(content.outId, content.outName, content.inId, content.inName) - break - case 'removelink': - removeLink(content.outId, content.outName, content.inId, content.inName) - break - case 'putstate': - receiveStateChange(content.id, content.name, content.value) - break - case 'error': - console.log('RECV MANAGER ERROR') - console.log(content) - break - default: - console.log('VIEW: msg with no switch') - break - } - } + if (this.outputs.msgs.ie && this.outmsgbuffer.length < 1) { + // str8 shooters + this.log('msg out', msg.header) + console.log(msg.content) + this.outputs.msgs.put(msg) + } else { + // gotta buffer + this.outmsgbuffer.push(msg) + this.log('MGR OUTBUFFER LEN', this.outmsgbuffer.length) + } + } + + /* --------------------------- ---------------------------- */ + /* ------------------ MESSAGES INPUT, LOOP ------------------- */ + /* --------------------------- ---------------------------- */ + + // still looking for clear function naming / message naming + + this.loop = () => { + // THE Q: is it trees or is it inputs / outputs ? ports ... + if (this.inputs.msgs.io) { + // HERE: this hack gets the data without a deep copy, so we want this + // for bringing the dom element over *which is specific to a native manager* + // so TODO: add switch for native / nonative managers + let data = this.inputs.msgs.data + let pult = this.inputs.msgs.get() + let header = data.header + let content = data.content + this.log(`gets msg ${header}`) + switch (header) { + case 'putcontextoptions': + addContextOptions(content) + break + case 'putdef': + putDef(content) + break + case 'removedef': + removeDef(content) + break + case 'putlink': + putLink(content.outId, content.outName, content.inId, content.inName) + break + case 'removelink': + removeLink(content.outId, content.outName, content.inId, content.inName) + break + case 'putstate': + receiveStateChange(content.id, content.name, content.value) + break + case 'error': + console.log('RECV MANAGER ERROR') + console.log(content) + break + default: + console.log('VIEW: msg with no switch') + break + } } + } } -export default View \ No newline at end of file +export default View diff --git a/programs/mvnv.json b/programs/mvnv.json index 7d04021fdf7d9679bb834b6d4973aaf9c221cbb7..e9ed8a26233761ada04dd9994a1f398484447461 100644 --- a/programs/mvnv.json +++ b/programs/mvnv.json @@ -34,15 +34,15 @@ }, { "outhunk": "lnkone", - "outname": "bytes", + "outname": "data", "inhunk": "wsclient", - "inname": "bytes" + "inname": "data" }, { "outhunk": "wsclient", - "outname": "bytes", + "outname": "data", "inhunk": "lnkone", - "inname": "bytes" + "inname": "data" }, { "outhunk": "nautilusview", @@ -68,4 +68,4 @@ "inhunk": "Logger_Duo", "inname": "tolog" }] -} \ No newline at end of file +} diff --git a/style.css b/style.css index f191ce416e8d7c80430a44b5d9c817b65f790332..ab603aae98261fe1b01fb647f072219443dfa1ab 100644 --- a/style.css +++ b/style.css @@ -48,8 +48,8 @@ body { background: #d6d6d6; /*background-image:url("background.png");*/ /*background-origin: content-box;*/ - background-image: - linear-gradient(rgba(255, 255, 255, .2) 2px, transparent 2px), + background-image: + linear-gradient(rgba(255, 255, 255, .2) 2px, transparent 2px), linear-gradient(90deg, rgba(255, 255, 255, .2) 2px, transparent 2px); background-size: 100px 100px; } @@ -58,8 +58,8 @@ body { position: absolute; width: 100px; height: 100px; - /*background-image: - linear-gradient(rgba(255, 255, 255, .2) 2px, transparent 2px), + /*background-image: + linear-gradient(rgba(255, 255, 255, .2) 2px, transparent 2px), linear-gradient(90deg, rgba(255, 255, 255, .2) 2px, transparent 2px); background-size: 100px 100px;*/ background: url("bg.png"); @@ -113,7 +113,7 @@ body { } .block { - width: 400px; + width: 500px; overflow: hidden; position: absolute; padding: 0px; @@ -141,7 +141,7 @@ body { } .inputs { - width: 77px; + width: 117px; float: left; margin-left: 2px; font-size: 11px; @@ -154,7 +154,7 @@ body { } .outputs { - width: 77px; + width: 117px; float: right; margin-right: 2px; text-align: right; @@ -164,11 +164,11 @@ body { } .output { - /* TODO OUTPUT */ + /* TODO OUTPUT */ } .state { - padding: 0 83px 0 83px; + padding: 0 123px 0 123px; color: #eee; } @@ -267,4 +267,4 @@ li:active{ padding: 10px; width: 100%; border: none; -} \ No newline at end of file +}