From c61d0d4475e9639814795b4eec2adc2d15fc9d83 Mon Sep 17 00:00:00 2001
From: Jake <jake.read@cba.mit.edu>
Date: Thu, 2 May 2019 16:59:10 -0400
Subject: [PATCH] program save!

---
 README.md                 |  11 +++-
 hunks/interface/number.js |   9 +--
 hunks/view.js             | 118 ++++++++++++++++++++++++++---------
 style.css                 |  28 +++++++++
 typeset.js                |  28 ++++++++-
 view/vdom.js              |   4 +-
 view/vptch.js             | 127 ++++++++++++++++++++++++++++++++++++++
 7 files changed, 288 insertions(+), 37 deletions(-)
 create mode 100644 view/vptch.js

diff --git a/README.md b/README.md
index 60f97bf..f4e98b0 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,15 @@ Native Cuttlefish 'Hunks' are given access to a DOM/div element, making them nic
 
 # Scratch
 
+'aye, but as the legends state: software doth make the hardware singeth'
+
+ -> typeset
+  - do name -> names: ['float64', 'number'], for same byte-cast for colloquial type cast ... i.e. uint8 and byte, ? what else ? is it worth it, or just copy pasta ? should also be lists of things that can be hooked up !
+
+ -> with programs, prototype a bootstrap that loads cuttlefish and waits ... then loads a program there ? 
+
+maybe this is something of a JAMTOC moment ... the view stores and manipulates represenations, we only need a minimum set of messages to load that representation remotely
+
 I'm very close, though, and want to focus. I should be talking to a nautilus manager tonight and prototyping how I want remote resources to open up from cuttlefish.
 
 OK. I'm just through serializing things. I'd really like to be into ponyo early tomorrow.
@@ -30,7 +39,7 @@ questions
  - why does a new view init with the same 'ok' in the titlebox / msgbox ?
  - because  #ids are not really unique and you were jquerying to the (0th) instance
 
-... 
+...
 then, go to nautilus, write cobserial, consider how to quickload program for ponyo test
 
 The next meaningful work on ponyo happens when I can:
diff --git a/hunks/interface/number.js b/hunks/interface/number.js
index 15f38d2..a531fda 100644
--- a/hunks/interface/number.js
+++ b/hunks/interface/number.js
@@ -14,14 +14,15 @@ import {
 function Number() {
     Hunkify(this, 'Number Input')
 
-    let numout = new Output('Number', 'num')
+    let numout = new Output('number', 'num')
     this.outputs.push(numout)
 
+    let numrep = new State('number', 'numrep', 275074)
+    this.state.push(numrep)
+
     // as is tradition,
     this.dom = {}
 
-    let num = 250112
-
     this.init = () => {
         // manager calls this once
         // it is loaded and state is updated (from program)
@@ -30,7 +31,7 @@ function Number() {
         let contact = $('<div>').addClass('btn').append('! contact !').get(0)
         $(this.dom).append(contact)
         contact.addEventListener('click', (evt) => {
-            numout.put(num += 1)
+            numout.put(numrep.value)
         })
     }
 
diff --git a/hunks/view.js b/hunks/view.js
index e6944ea..7789920 100644
--- a/hunks/view.js
+++ b/hunks/view.js
@@ -30,6 +30,7 @@ import {
 import DomTools from '../view/vdom.js'
 import BezierTools from '../view/vbzt.js'
 import MessageBox from '../view/vmsg.js'
+import PatchSet from '../view/vptch.js'
 
 function View() {
   Hunkify(this, 'View')
@@ -55,6 +56,8 @@ function View() {
   let msgbox = new MessageBox(this)
   // and tools for the links,
   let bzt = new BezierTools(this)
+  // and for program (patch) management
+  let patchset = new PatchSet(this, msgbox)
 
   /* ---------------------------    ---------------------------- */
   /* -------------------- INIT, LISTENERS ---------------------- */
@@ -69,6 +72,8 @@ function View() {
     this.plane = $('<div>').addClass('plane').get(0)
     // to log, type, etc
     msgbox.init()
+    // for programs
+    //patchset.init()
     // init transform of the plane,
     let dft = {
       s: 1,
@@ -203,44 +208,91 @@ function View() {
      -> save this program
      -> reload this view
      -> heartbeat checkin
-
     */
-
-    // TRANSFORM here to write menu in the right spot on click
-    let menu = $('<div>').addClass('contextmenu')
-      .append('<ul>requesting available hunks ... </ul>').get(0)
+    console.log('context...', evt)
+    // make the menu,
+    let menu = $('<div>').addClass('contextmenu').get(0)
+    // and place,
     let pt = dt.readTransform(this.plane)
-    console.log(pt)
-    ///console.log('write to ', ct)
+    console.log('plane transform', pt)
+    // HERE: there's still some mess, not landing in the right place,
+    // (and there's compounding scale issues on dragging the canvas)
     dt.writeTransform(menu, {
       s: 1,
       x: ((evt.clientX - pt.x) / pt.s), // (pt.s + -0.1 * (pt.s-1))),
       y: ((evt.clientY - pt.y) / pt.s) // + -0.1 * (pt.s-1)))
     })
+    //
     $(this.plane).append(menu)
-    writeMessage([MK.REQLISTAVAIL])
+    // hmmm
+    this.changeContextTitle('you can... ')
+    // on of the options will ...
+    // add a hunk menu
+    this.addContextOption('add a hunk', (evt) => {
+      msgbox.write('requested a list of hunks...')
+      $(evt.target).text('requested a list of hunks...')
+      writeMessage([MK.REQLISTAVAIL])
+    })
+
+    this.addContextOption('load a patch', (evt) => {
+      $(evt.target).append(' loading a list...')
+      patchset.findPatches()
+    })
+
+    this.addContextOption('save this patch', (evt) => {
+      $(evt.target).append(' > input for the name > ')
+      patchset.saveCurrent(evt, true)
+    })
+    // writeMessage([MK.REQLISTAVAIL])
   }
 
-  let addContextOptions = (list) => {
-    let menu = $(this.plane).children('.contextmenu').get(0)
-    for (let option of list) {
-      $(menu).append(writeContextOption(option))
+  // takes 'under' argument
+  this.addContextOption = (text, click) => {
+    $(this.plane).find('.contextmenu').get(0).append($('<li>' + text + '</li>').click((click)).get(0))
+  }
+
+  this.changeContextTitle = (text) => {
+    // clear,
+    $(this.plane).find('.contextmenu').children().remove()
+    // overkill, but fun
+    let menu = $(this.plane).find('.contextmenu').get(0)
+    let title = $(`<div>${text}</div>`).addClass('contextTitle').get(0)
+    title.onmousedown = (evt) => {
+      evt.preventDefault()
+      evt.stopPropagation()
+      let domElemMouseMove = (evt) => {
+        // TRANSFORMS here to move div about on drag
+        evt.preventDefault()
+        evt.stopPropagation()
+        let ct = dt.readTransform(menu)
+        let pt = dt.readTransform(menu.parentElement) // think that's just this.plane ?
+        ct.x += evt.movementX / pt.s
+        ct.y += evt.movementY / pt.s
+        dt.writeTransform(menu, ct)
+        this.drawLinks()
+      }
+
+      function rmOnMouseUp(evt) {
+        document.removeEventListener('mousemove', domElemMouseMove)
+        document.removeEventListener('mouseup', rmOnMouseUp)
+      }
+      document.addEventListener('mousemove', domElemMouseMove)
+      document.addEventListener('mouseup', rmOnMouseUp)
     }
+    $(this.plane).find('.contextmenu').append(title)
   }
 
-  let writeContextOption = (text) => {
-    let item = $('<li>' + text + '</li>').get(0)
-    item.addEventListener('click', (evt) => {
-      // SIP: serialize in place ... haha
-      let msg = new Array()
-      msg.push(MK.REQADDHUNK)
-      MSGS.writeTo(msg, text, 'string')
+  /*
+  this.changeContextTitle('list of available:')
+  for(let item of list){
+    this.addContextOption(item, (evt) => {
+      let msg = [MK.REQADDHUNK]
+      MSGS.writeTo(msg, item, 'string')
       writeMessage(msg)
-      $(item).append('   > requested ...')
+      $(evt.target).append(' > requested ... ')
     })
-    return item
   }
-
+  */
 
   /* ---------------------------    ---------------------------- */
   /* ---------------------- FORCE LAYOUT ----------------------- */
@@ -424,9 +476,9 @@ function View() {
       if ($(nd).attr('id') === "NROL39_0" || $(nd).attr('id') === "TLView") {
         //console.log('skip')
       } else {
-        let ps = dt.readXY(nd)
-        ps.id = nd.id
-        positions.push(ps)
+        let pos = dt.readXY(nd)
+        pos.id = nd.id
+        positions.push(pos)
       }
     }
     return positions
@@ -440,9 +492,9 @@ function View() {
       if ($(nd).attr('id') === "NROL39_0" || $(nd).attr('id') === "TLView") {
         //console.log('skip')
       } else {
-        let ps = dt.readSize(nd)
-        ps.id = nd.id
-        sizes.push(ps)
+        let sz = dt.readSize(nd)
+        sz.id = nd.id
+        sizes.push(sz)
       }
     }
     return sizes
@@ -854,7 +906,15 @@ function View() {
           break
         case MK.LISTOFAVAIL:
           let stringlist = MSGS.readListFrom(msg, 1, 'string')
-          addContextOptions(stringlist)
+          this.changeContextTitle('list of available:')
+          for (let item of stringlist) {
+            this.addContextOption(item, (evt) => {
+              let msg = [MK.REQADDHUNK]
+              MSGS.writeTo(msg, item, 'string')
+              writeMessage(msg)
+              $(evt.target).append(' > requested ... ')
+            })
+          }
           break
         case MK.HUNKALIVE:
           console.log('hunk alive, going to deserialize')
diff --git a/style.css b/style.css
index d5c0c1a..10a89cc 100644
--- a/style.css
+++ b/style.css
@@ -165,6 +165,10 @@ body {
 	cursor: grab;
 }
 
+.blockid:active{
+	cursor: grabbing;
+}
+
 .inputs {
 	width: 117px;
 	float: left;
@@ -197,6 +201,11 @@ body {
 	color: #eee;
 }
 
+.stateItem {
+	font-size: 11px;
+	padding: 3px;
+}
+
 .uidiv {
 	width: 396px;
 	padding-top: 5px;
@@ -254,12 +263,31 @@ li {
 
 li:hover{
 	background-color: #969696;
+	cursor: pointer;
 }
 
 li:active{
 	background-color: #d1d1d1;
 }
 
+.contextTitle {
+	font-size: 14px;
+	font-weight: bold;
+	font-style: italic;
+	color: #eee;
+	padding: 7px 5px 6px 5px;
+}
+
+.contextTitle:hover {
+	background-color: #969696;
+	cursor: grab;
+}
+
+.contextTitle:active {
+	background-color: #d1d1d1;
+	cursor: grabbing;
+}
+
 /* USING THESE WITHIN BOYOS */
 
 .cuttlefishhunkdom {
diff --git a/typeset.js b/typeset.js
index 783b17a..d82b473 100644
--- a/typeset.js
+++ b/typeset.js
@@ -122,7 +122,7 @@ const TSET = [{
       if(arr[start] !== this.key) throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`)
       let rdarr = arr.slice(start + 1, start + 5).reverse()
       let btarr = Uint8Array.from(rdarr)
-      console.log('bts on read', btarr)
+      console.log('bts on read of uint32', btarr)
       // now make uint32 view on this ...
       let vlarr = new Uint32Array(btarr.buffer)
       console.log('vlarr', vlarr)
@@ -131,6 +131,32 @@ const TSET = [{
         increment: 5
       }
     }
+  },
+  {
+    name: 'number',
+    key: 40,
+    write: function(value){
+      if (typeof value !== 'number') throw new Error('cannot cast non-number into physical world')
+      let tparr = new Float64Array(1)
+      tparr[0] = value
+      let btarr = new Uint8Array(tparr.buffer)
+      // place
+      let rtarr = Array.from(btarr).reverse()
+      rtarr.unshift(this.key)
+      return rtarr
+    },
+    read: function(arr, start){
+      if(arr[start] !== this.key) throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`)
+      let rdarr = arr.slice(start + 1, start + 9).reverse()
+      let btarr = Uint8Array.from(rdarr)
+      console.log('bts on read of float64', btarr)
+      let vlarr = new Float64Array(btarr.buffer)
+      console.log('vlarr', vlarr)
+      return {
+        item: vlarr[0],
+        increment: 9
+      }
+    }
   }
   // etc
 ]
diff --git a/view/vdom.js b/view/vdom.js
index 7ed1c0a..0fa53fd 100644
--- a/view/vdom.js
+++ b/view/vdom.js
@@ -72,7 +72,7 @@ function DomTools(View) {
     $(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>'))
+    $(de).append($('<div>' + de.id + '</div>').addClass('blockid').append('<span style="float:right;">(' + def.name + ')</span>'))
 
     let title = $(de).children('.blockid').get(0)
 
@@ -267,7 +267,7 @@ function DomTools(View) {
 
   // write those messy state objects
   let writeStateDom = (state, def) => {
-    let dom = $('<li><p>' + state.name + " (" + state.type + ")" + '</p></li>').get(0)
+    let dom = $('<div><p>' + state.name + " (" + state.type + ")" + '</p></div>').addClass('stateItem').get(0)
     dom.id = def.id + '_state_' + state.name
     switch (typeof state.value) {
       case 'string':
diff --git a/view/vptch.js b/view/vptch.js
new file mode 100644
index 0000000..a711a9a
--- /dev/null
+++ b/view/vptch.js
@@ -0,0 +1,127 @@
+// patches are programs that are *incomplete without you*
+
+function PatchSet(View, MsgBox){
+  let view = View
+  let msgbox = MsgBox
+
+  this.init = () => {
+    msgbox.write('hello from patchset')
+  }
+
+  // the issue you're worrying about is:
+  /*
+  what of these hunks that will autoload each time, the bootstraps?
+  ... np, fam, when we send a request to the manager to add them,
+  managers should just reply w/ an error - we have paths for that
+  this is a bug we can iron out later and ignore
+  */
+  this.saveCurrent = (evt, debug) => {
+    if(debug) console.log('saving ...')
+    // riperoni ok
+    let patch = {
+      hunks: [],
+      links: []
+    }
+    // js, u wyld
+    for(let hnk of $(view.plane).children('.block')){
+      // dom is truth
+      if(debug) console.log('hnk', hnk)
+      let name = $(hnk).children('.blockid').find('span').text()
+      name = name.substring(1, name.length - 1)
+      let phnk = {
+        id: hnk.id,
+        name: name
+      }
+      let state = []
+      for(let st of $(hnk).children('.state').children('.stateItem')){
+        // text is faithful,
+        let text = $(st).find('p').text()
+        let name = text.substring(0, text.indexOf(' '))
+        let type = text.substring(text.indexOf('(') + 1, text.indexOf(')'))
+        if(debug) console.log('state: name', `"${name}"`, 'type', `"${type}"`)
+        // the value... (typing on program saves? not if we JSON ...)
+        let value
+        if(type === 'boolean'){
+          let vstring = $(st).find('span').text()
+          vstring = vstring.substring(vstring.indexOf('(') + 1, vstring.indexOf(')'))
+          if(vstring === 'true'){
+            value = true
+          } else if (vstring === 'false'){
+            value = false
+          } else {
+            console.error('error saving boolean value, setting to false')
+            value = false
+          }
+        } else if (type === 'string') {
+          value = $(st).find('input').val()
+        } else {
+          // must be some kind of number,
+          value = parseFloat($(st).find('input').val())
+        }// end if-on-types sequence
+        state.push({
+          name: name,
+          type: type,
+          value: value
+        })
+      } // end roll over states,
+      if(state.length > 0){
+        phnk.state = state
+      }
+      patch.hunks.push(phnk)
+      // now roll over links in the hunk, to write outputs
+      for(let otp of $(hnk).children('.outputs').children('.output')){
+        if(debug) console.log('and output', otp)
+        let outId = otp.id.substring(0, otp.id.indexOf('_output_'))
+        let outName = otp.id.substring(otp.id.indexOf('_output_') + 8)
+        for(let conn of otp.connectedTo){
+          let inId = conn.substring(1, conn.indexOf('_input_'))
+          let inName = conn.substring(conn.indexOf('_input_') + 7)
+          patch.links.push({
+            outhunk: outId,
+            outname: outName,
+            inhunk: inId,
+            inname: inName
+          })
+        }
+      } // end loop over outputs, for links ...
+    } // end loop over hunks
+    // we have this now,
+    if(debug) console.log('a patch', patch)
+    // prompt for name? ... via the (evt) on the callback ??
+    $(evt.target).text('')
+    let tinput = $('<input>').attr('type', 'text').attr('size', 24).attr('value', 'patch').get(0)
+    $(evt.target).append(tinput) // etc
+    $(tinput).focus()
+    $(tinput).select()
+    $(tinput).on('keyup', (evt) => {
+      if(evt.keyCode == 13){
+        console.log('yar, ', tinput.value + '.json')
+        let bleb = JSON.stringify(patch)
+        console.log('tryit...')
+        let url = URL.createObjectURL(new Blob([JSON.stringify(patch)], {type: "application/json"}))
+        //window.location.href = url
+        console.log('url', url)
+        let anchor = $('<a>ok</a>').attr('href', url).attr('download', tinput.value+ '.json').get(0)
+        console.log(anchor)
+        $(evt.target).append(anchor)
+        anchor.click()
+        // finally, rip
+        $(view.plane).find('.contextmenu').remove()
+        //saveAs(bleb, 'file.json')
+      }
+    })
+    // should name it, and ID it for the interpeter ?
+  }
+
+  // load from server should be assumed, just rip that *baybie*
+  this.findPatches = () => {
+    msgbox.write('would look for list')
+  }
+
+  this.loadPatch = (name) => {
+
+  }
+
+}
+
+export default PatchSet
-- 
GitLab