diff --git a/.gitignore b/.gitignore index 3c3629e647f5ddf82548912e337bea9826b434af..9108365fc5839a9d46bf30d8dba75b606ba8d1a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ node_modules +.DS_Store +*.swp +*.swo +*.bak +*~ diff --git a/fabmo/Makefile b/fabmo/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..db6b4f66c71519b47cff3959c16c54fd301f0784 --- /dev/null +++ b/fabmo/Makefile @@ -0,0 +1,10 @@ +build/fabmo-mods-app.fma : clean + mkdir -p build + zip -r build/fabmo-mods-app.fma index.html package.json icon.png css js + cd ..; \ + zip -r fabmo/build/fabmo-mods-app.fma package.json files.html js modules programs + +clean: + rm -f build/fabmo-mods-app.fma + +.PHONY: clean diff --git a/fabmo/build/icon.png b/fabmo/build/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..42d5057b48fa09f8991abd0d886c9af9d7fb7c83 Binary files /dev/null and b/fabmo/build/icon.png differ diff --git a/fabmo/css/style.css b/fabmo/css/style.css new file mode 100644 index 0000000000000000000000000000000000000000..be5688c6ee10cde5f64122fd60462511d115e972 --- /dev/null +++ b/fabmo/css/style.css @@ -0,0 +1,45 @@ +body { + background: #fff; + font-family: monospace; + font-size: 11px; +} +.modal { + display: none; /* Hidden by default */ + position: fixed; /* Stay in place */ + z-index: 1; /* Sit on top */ + left: 0; + top: 0; + width: 100%; /* Full width */ + height: 100%; /* Full height */ + overflow: auto; /* Enable scroll if needed */ + background-color: rgb(0,0,0); /* Fallback color */ + background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ +} + +/* Modal Content/Box */ +.modal-content { + background-color: #fefefe; + margin: 5% auto; /* 15% from the top and centered */ + padding: 20px; + border: 1px solid #888; + width: 80%; /* Could be more or less, depending on screen size */ + min-height: 200px; + max-height: 70%; + border-radius: 10px; + overflow: scroll; +} + +/* The Close Button */ +.close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; +} + +.close:hover, +.close:focus { + color: black; + text-decoration: none; + cursor: pointer; +} diff --git a/fabmo/icon.png b/fabmo/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..42d5057b48fa09f8991abd0d886c9af9d7fb7c83 Binary files /dev/null and b/fabmo/icon.png differ diff --git a/fabmo/index.html b/fabmo/index.html new file mode 100644 index 0000000000000000000000000000000000000000..e4ec3a15714c79d1f745a6573f9a83487846a3bb --- /dev/null +++ b/fabmo/index.html @@ -0,0 +1,31 @@ +<html> +<head><meta charset="utf-8"> +<title>mods</title> +<link rel="stylesheet" href="css/style.css"> +</head> +<body link="black" alink="black" vlink="black"> +<script src="js/fabmo.js"></script> +<script type="text/javascript"> + var fabmo = new FabMoDashboard(); +</script> +<script src="js/fabmo-mods.js"></script> +<script src="js/mods.js"></script> + +<!-- The Modal --> +<div id="myModal" class="modal"> + <!-- Modal content --> + <div class="modal-content"> + <div id="modal-text"> + + </div> + </div> +</div> + +</body> +<script type="text/javascript"> +/* + showModal({ + html : "X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>X<br/>" + });*/ +</script> +</html> \ No newline at end of file diff --git a/fabmo/js/fabmo-mods.js b/fabmo/js/fabmo-mods.js new file mode 100644 index 0000000000000000000000000000000000000000..f4a8f1b28991892b87a1d70f3276145245806154 --- /dev/null +++ b/fabmo/js/fabmo-mods.js @@ -0,0 +1,150 @@ +window._open = window.open +window._close = window.close +window._alert = window.alert + +var fakeWindow = { + document : { + createTextNode : function(args) {return window.document.createTextNode(...arguments);}, + createElement : function(args) {return window.document.createElement(...arguments);}, + getElementById : function(args) {return window.document.createElement(...arguments);} + }, + close : function() { + hideModal(); + } +}; + +window.alert = function(msg) { + showModal({html : '<h2>Alert!</h2><p>' + msg + '</p>'}) +} + +function showModal(options) { + var modalContent = document.getElementById('modal-text'); + if('html' in options) { + modalContent.innerHTML = options['html'] + } + fakeWindow.document.body = modalContent; + var modal = document.getElementById('myModal'); + + modal.style.display = "block"; +} + +function hideModal() { + var modal = document.getElementById('myModal') + modal.style.display = "none" +} + +function httpGet(theUrl, callback) +{ + var xmlHttp = new XMLHttpRequest(); + xmlHttp.onreadystatechange = function() { + if (xmlHttp.readyState == 4 && xmlHttp.status == 200) + callback(xmlHttp.responseText); + } + xmlHttp.open("GET", theUrl, true); // true for asynchronous + xmlHttp.send(null); +} + +var handler = function(uri) { + console.log("Handler called"); + console.log(callback) + window.callback(uri) + hideModal(); + //window.close() +} + + +window.open = function(url, callback) { + console.info('Got a call to window.open for : ' + url) + console.info(callback) + console.trace() + if(url) { + httpGet(url, function(responseText) { + showModal({ + html : responseText + }) + }) + } else { + showModal({ + html : '' + }); + } + return fakeWindow; + /* Open the prescribed window, but in a modal dialog */ +} + +window.addEventListener('click', function(event) { + var modal = document.getElementById('myModal'); + if (event.target == modal) { + modal.style.display = "none"; + } +}); + +window.addEventListener('keypress', function(evt){ + evt = evt || window.event; + if (evt.keyCode == 27) { + hideModal(); + } +}); + +function get_current_program() { + //mods.ui.progname = filename + var prog = {modules:{},links:[]} + var modules = document.getElementById('modules') + // + // save modules + // + for (var c = 0; c < modules.childNodes.length; ++c) { + var module = modules.childNodes[c] + var idnumber = module.id + prog.modules[idnumber] = { + definition:module.dataset.definition, + top:module.dataset.top, + left:module.dataset.left, + inputs:{}, + outputs:{} + } + } + // + // save links + // + var svg = document.getElementById('svg') + var links = svg.getElementById('links') + for (var l = 0; l < links.childNodes.length; ++l) { + var link = links.childNodes[l] + var linkid = link.id + prog.links.push(linkid) + } + // + // download + // + var text = JSON.stringify(prog) + return text + } + + function load_program() { + fabmo.getAppConfig(function(err, cfg) { + if(err) { + return log.error(err); + } + console.log(cfg) + try { + var p = JSON.parse(cfg.current_program); + fabModules.prog_load(p); + } catch(e) { + console.warn('No program to load.') + } + }) + } + + function init() { + var btn = document.createElement('button') + btn.appendChild(document.createTextNode('Save...')) + document.body.appendChild(btn) + + btn.addEventListener('click', function(evt) { + fabmo.setAppConfig({'current_program' : get_current_program()}); + }); + } + + init(); + load_program(); diff --git a/fabmo/js/fabmo.js b/fabmo/js/fabmo.js new file mode 100644 index 0000000000000000000000000000000000000000..6e9e3a34f6bdf7b7ae347dc4ee1b7fbb6e915b3a --- /dev/null +++ b/fabmo/js/fabmo.js @@ -0,0 +1,1023 @@ +/** + * @module fabmo.js + */ +(function (root, factory) { + var body = document.getElementsByTagName('body'); + if (typeof define === 'function' && define.amd) { + // AMD + define([], factory); + } else if (typeof exports === 'object') { + // Node, CommonJS-like + module.exports = factory(); + } else { + // Browser globals (root is window) + root.FabMoDashboard = factory(); + } +}(this, function () { + +/** + * The top-level object representing the dashboard. + * + * @class FabMoDashboard + */ +var FabMoDashboard = function(options) { + this.version = '{{FABMO_VERSION}}'; + this.target = window.parent; + this.window = window; + this._setOptions(options); + this._id = 0; + this._handlers = {}; + this.status = {}; + this.isReady = false; + this._event_listeners = { + 'status' : [], + 'job_start' : [], + 'job_end' : [], + 'change' : [], + 'disconnect' : [], + 'reconnect' : [], + 'video_frame' : [], + 'upload_progress':[] + }; + this._setupMessageListener(); + // listen for escape key press to quit the engine + document.onkeyup = function (e) { + if(e.keyCode == 27) { + console.warn("ESC key pressed - quitting engine."); + this.stop(); + } + }.bind(this); + + if(!this.options.defer) { + this.ready(); + } +} + +FabMoDashboard.prototype._setOptions = function(options) { + options = options || {}; + this.options = {}; + this.options.defer = options.defer || false; +} + +/** + * @method isPresent + * @return {boolean} True if running in the actual FabMo dashboard. False otherwise. + */ +FabMoDashboard.prototype.isPresent = function() { + try { + return window.self !== window.top; + } catch (e) { + return true; + } +} + +FabMoDashboard.prototype._download = function(data, strFileName, strMimeType) { + // https://github.com/rndme/download + // data can be a string, Blob, File, or dataURL + + var self = window // this script is only for browsers anyway... + var u = "application/octet-stream" // this default mime also triggers iframe downloads + var m = strMimeType || u; + var x = data; + var D = document; + var a = D.createElement("a"); + var z = function(a){return String(a);}; + var B = (self.Blob || self.MozBlob || self.WebKitBlob || z); + var B=B.call ? B.bind(self) : Blob; + var fn = strFileName || "download"; + var blob; + var fr; + + blob = x instanceof B ? x : new B([x], {type: m}) ; + + function d2b(u) { + var p= u.split(/[:;,]/), + t= p[1], + dec= p[2] == "base64" ? atob : decodeURIComponent, + bin= dec(p.pop()), + mx= bin.length, + i= 0, + uia= new Uint8Array(mx); + for(i;i<mx;++i) { uia[i]=bin.charCodeAt(i); } + return new B([uia], {type: t}); + } + + function saver(url, winMode){ + + if ('download' in a) { //html5 A[download] + a.href = url; + a.setAttribute("download", fn); + a.innerHTML = "downloading..."; + D.body.appendChild(a); + setTimeout(function() { + a.click(); + D.body.removeChild(a); + if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(a.href);}, 250 );} + }, 66); + return true; + } + + if(typeof safari !=="undefined" ){ // handle non-a[download] safari as best we can: + url="data:"+url.replace(/^data:([\w\/\-\+]+)/, u); + if(!window.open(url)){ // popup blocked, offer direct download: + if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; } + } + return true; + } + + //do iframe dataURL download (old ch+FF): + var f = D.createElement("iframe"); + D.body.appendChild(f); + + if(!winMode){ // force a mime that will download: + url="data:"+url.replace(/^data:([\w\/\-\+]+)/, u); + } + f.src=url; + setTimeout(function(){ D.body.removeChild(f); }, 333); + + }//end saver + + if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL) + return navigator.msSaveBlob(blob, fn); + } + + if(self.URL){ // simple fast and modern way using Blob and URL: + saver(self.URL.createObjectURL(blob), true); + }else{ + // handle non-Blob()+non-URL browsers: + if(typeof blob === "string" || blob.constructor===z ){ + try{ + return saver( "data:" + m + ";base64," + self.btoa(blob) ); + }catch(y){ + return saver( "data:" + m + "," + encodeURIComponent(blob) ); + } + } + + // Blob but not URL: + fr=new FileReader(); + fr.onload=function(e){ + saver(this.result); + }; + fr.readAsDataURL(blob); + } + return true; +} // _download + +FabMoDashboard.prototype._call = function(name, data, callback) { + + if(this.isPresent()) { + message = {"call":name, "data":data} + if(callback) { + message.id = this._id++; + this._handlers[message.id] = callback; + } + this.target.postMessage(message, '*'); + } else { + this._simulateCall(name, data, callback); + } +} + +FabMoDashboard.prototype._simulateCall = function(name, data, callback) { + toaster(); + var toast = document.getElementById('alert-toaster'); + var text = document.getElementById('alert-text'); + switch(name) { + + case "submitJob": + var files = []; + data.jobs.forEach(function(job) { + var name = job.filename || job.file.name; + this._download(job.file, name, "text/plain"); + files.push(name); + }.bind(this)); + + // Data.length + if(data.jobs.length === 1) { + var msg = "Job Submitted: " + data.jobs[0].filename + } else { + var msg = data.jobs.length + " Jobs Submitted: " + files.join(','); + } + text.textContent = msg; + showToaster(toast); + callback(null, {}) + break; + + case "runGCode": + text.textContent = "GCode sent to tool: " + data; + showToaster(toast); + break; + + case "runSBP": + text.textContent = "OpenSBP sent to tool: " + data; + showToaster(toast); + break; + + case "showDRO": + text.textContent = "DRO Shown."; + showToaster(toast); + break; + + case "hideDRO": + text.textContent = "DRO Hidden."; + showToaster(toaster); + break; + + default: + text.textContent = name + " called."; + showToaster(toast); + break; + } +} + +FabMoDashboard.prototype._on = function(name, callback) { + + var message = {"on":name} + if(callback) { + this._event_listeners[name].push(callback); + } + this.target.postMessage(message, '*'); +} + +/** + * Bind a callback to the specified event. + * + * @method on + * @param {String} name Event name + * @param {function} callback Event handler + */ +FabMoDashboard.prototype.on = function(name, callback) { + this._on(name, callback); +} + +FabMoDashboard.prototype.off = function(name, callback) { + var listeners = this._event_listeners[name] || []; + if(!callback) { + this._event_listeners[name] = []; + } else { + var idx = listeners.indexOf(5); + if (idx > -1) { + this._event_listeners[name].splice(index, 1); + } + } +} + +FabMoDashboard.prototype._setupMessageListener = function() { + this.window.addEventListener('message', function (evt) { + var message = evt.data; + switch(message.type) { + case 'cb': + if('id' in message) { + if(message.id in this._handlers) { + cb = this._handlers[message.id] + if(message.status === "success") { + cb(null, message.data); + } else { + cb(message.message, null); + } + } + } + break; + + case 'evt': + if('id' in message) { + if(message.id in this._event_listeners) { + listeners = this._event_listeners[message.id] + for(i in listeners) { + listeners[i](message.data); + } + } + } + break; + } + }.bind(this)); +} + +/** + * Indicate that the app is loaded and ready to go! + * @method ready + */ +FabMoDashboard.prototype.ready = function() { + this._call('ready'); + this.isReady = true; +} + +/** + * Set the message to display while an app is loading. You can call this any time before + * calling the `ready()` function, to indicate loading or setup progress. + * @method setBusy + * @param {String} The message to display. + */ +FabMoDashboard.prototype.setBusyMessage = function(message) { + this._call('setBusyMessage', {'message' : message}); +} + +/** + * If this app was invoked from another app, get the arguments (if any) that were passed on invocation. + * + * @method getAppArgs + * @callback {Object} The arguments passed to this app, or undefined. + */ +FabMoDashboard.prototype.getAppArgs = function(callback) { + this._call("getAppArgs", null, callback); +} + +/** + * Get the information for this app. + * + * @method getAppInfo + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + * @param {Object} callback.info App information + * @param {String} callback.info.name The name of the App + */ +FabMoDashboard.prototype.getAppInfo = function(callback) { + this._call("getAppInfo", null, callback); +} + +/** + * Launch the specified app by app ID, with optional arguments. + * + * @method launchApp + * @param {String} id The id of the app to launch. + * @param {Object} args Arguments object to pass to the app. + * @param {function} callback + * @param {Error} callback.err Error object if there was an error + * @param {Object} callback.app App info for the launched app if launch was successful + */ +FabMoDashboard.prototype.launchApp = function(id, args, callback) { + this._call("launchApp", {'id': id, 'args':args}, callback); +} + +/** + * Show the DRO (Digital ReadOut) in the dashboard if it is not already shown. + * + * @method showDRO + * @param {function} callback Called once the DRO has been displayed. + * @param {Error} callback.err Error object if there was an error. + */ +FabMoDashboard.prototype.showDRO = function(callback) { + this._call("showDRO", null, callback); +} + +/** + * Hide the DRO (Digital ReadOut) in the dashboard if it is not already hidden. + * + * @method hideDRO + * @param {function} callback Called once the DRO has been hidden. + * @param {Error} callback.err Error object if there was an error. + */ +FabMoDashboard.prototype.hideDRO = function(callback) { + this._call("hideDRO", null, callback); +} + +//Modal Functions +FabMoDashboard.prototype.showModal = function(options, callback) { + var callbacks = { + "ok" : options.ok, + "cancel" : options.cancel + } + var showModalCallback = function(err, buttonPressed) { + if(err) { + callback(err); + } + var f = callbacks[buttonPressed](); + if(f) { + f(); + } else { + if(callback) { + callback(); + } + } + } + options.ok = options.ok ? true : false; + options.cancel = options.cancel ? true : false; + + this._call("openModal", options, showModalCallback); +} + +FabMoDashboard.prototype.hideModal = function(options, callback) { + this._call("closeModal", null, callback); +} + +// Footer Functions +// FabMoDashboard.prototype.showFooter = function(callback) { +// this._call("showFooter", null, callback); +// } + +/** + * Show a notification on the dashboard. Notifications typically show up as toaster message somewhere on the dashboard, + * but the dashboard reserves the right to format or even suppress these messages as suits its needs. + * + * @method notify + * @param {String} type Type of message, which can be one of `info`,`warn`,`error`, or `success` + * @param {String} message The text to be displayed in the notification. + * @param {function} callback Called once the message has been displayed + * @param {Error} callback.err Error object if there was an error. + */ +FabMoDashboard.prototype.notify = function(type, message, callback) { + this._call("notify", {'type':type, 'message':message}, callback); +} + +FabMoDashboard.prototype.hideFooter = function(callback) { + this._call("hideFooter", null, callback); +} + +// Notification functions +FabMoDashboard.prototype.notification = function(type,message,callback) { + this._call("notification", {'type':type,'message':message}, callback); +} +FabMoDashboard.prototype.notify = FabMoDashboard.prototype.notification; + +function _makeFile(obj) { + if(window.jQuery && obj instanceof jQuery) { + if(obj.is('input:file')) { + obj = obj[0]; + } else { + obj = obj.find('input:file')[0]; + } + file = obj.files[0]; + } else if(obj instanceof HTMLInputElement) { + file = obj.files[0]; + } else if(obj instanceof File || obj instanceof Blob) { + file = obj; + } else if(typeof obj === "string") { + file = new Blob([obj], {'type' : 'text/plain'}); + } else { + throw new Error('Cannot make File object out of ' + obj); + } + return file; +} + +function _makeApp(obj) { + return {file : _makeFile(obj)}; +} + +function _makeJob(obj) { + var file = null; + + try { + file = _makeFile(obj); + } catch(e) {} + + if(file) { + return {file : file}; + } else { + var job = {}; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + if(key === 'file') { + job['file'] = _makeFile(obj.file); + } else { + job[key] = obj[key]; + } + } + } + return job; + } +} + +/** + * Submit one or more jobs to the dashboard. + * @param {Array|Object|jQuery} jobs A single job object, an array containing multiple job objects, or a jQuery object that points to a file type form input, or a form containing a file type input. + * @param {Object} [options] Options for job submission + * @todo Finish documenting this function + */ +FabMoDashboard.prototype.submitJob = function(jobs, options, callback) { + var args = {jobs : []}; + + if(window.jQuery && jobs instanceof jQuery) { + if(jobs.is('input:file')) { + jobs = obj[0]; + } else { + jobs = jobs.find('input:file')[0]; + } + var files = jobs.files; + if(files.length) { + jobs = []; + for(var i=0; i<files.length; i++) { + jobs.push(files[i]); + } + } + } else { + if(!jobs.length) { + jobs = [jobs]; + } + } + + for(var i=0; i<jobs.length; i++) { + args.jobs.push(_makeJob(jobs[i])); + } + + if(typeof options === 'function') { + callback = options; + options = {}; + } + + args.options = options || {}; + this._call("submitJob", args, callback) +} +FabMoDashboard.prototype.submitJobs = FabMoDashboard.prototype.submitJob; + +FabMoDashboard.prototype.updateOrder = function(data, callback) { + this._call("updateOrder", data, callback); +} + +/** + * Resubmit a job by its ID. Resubmitted jobs come in at the back of the job queue. + * + * @method resubmitJob + * @param {Number} id The ID of the job to resubmit + * @param {Object} options Job submission options + * @param {Object} options Job submission options + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + */ +FabMoDashboard.prototype.resubmitJob = function(id, options, callback) { + if(typeof options === 'function') { + callback = options; + options = {}; + } + var args = { + id : id, + options : options || {} + } + this._call("resubmitJob", args, callback) +} + +/** + * Delete a job (cancels if running, sends to trash otherwise.) + * + * @method deleteJob + * @param {Number} id The ID of the job to delete + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + */ +FabMoDashboard.prototype.deleteJob = function(id, callback) { + this._call("deleteJob", id, callback) +} + +FabMoDashboard.prototype.cancelJob = FabMoDashboard.prototype.cancelJob; + +/** + * Get information about a job. This works for jobs that are pending, currently running, or in the history. + * + * @method getJobInfo + * @param {Number} id The ID of the job to cancel + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + * @param {Object} callback.job Information about the requested job. + * @param {String} callback.job.name Job name + * @param {String} callback.job.description Job description + * @param {Number} callback.job.created_at Job creation time (UTC datetime) + * @param {Number} callback.job.started_at Job start time (UTC datetime) + * @param {Number} callback.job.finished_at Job completion time (UTC datetime) + * @param {String} callback.job.state Current state of the job. One of `pending`,`running`,`finished`,`cancelled` or `failed` + */ +FabMoDashboard.prototype.getJobInfo = function(id, callback) { + this._call("getJobInfo", id, callback) +} + +/** + * Get a list of jobs that are currently pending and running. + * + * @method getJobsInQueue + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + * @param {Object} callback.jobs Object containing both the pending and running jobs + * @param {Array} callback.jobs.pending List of pending jobs. May be empty. + * @param {Array} callback.jobs.running List of running jobs. May be empty. + */ +FabMoDashboard.prototype.getJobsInQueue = function(callback) { + this._call("getJobsInQueue",null, callback); +} + +/** + * Remove all pending jobs from the queue. + * + * @method clearJobQueue + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + */ +FabMoDashboard.prototype.clearJobQueue = function(callback) { + this._call("clearJobQueue",null, callback); +} + +/** + * Get a list of jobs in the history. + * + * @method getJobHistory + * @param {Object} options + * @param {Number} options.start The location in the results to start + * @param {Number} options.count The number of jobs to return + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + * @param {Object[]} callback.jobs Array of jobs in the history + */ +FabMoDashboard.prototype.getJobHistory = function(options, callback) { + this._call("getJobHistory",options, callback); +} + +/** + * Run the next job in the job queue. + * + * @method runNext + * @param {Object} options + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + */ +FabMoDashboard.prototype.runNext = function(callback) { + this._call("runNext",null, callback); +} + +/** + * Pause the execution of the current job or operation. Operation can be resumed. + * + * @method pause + * @param {Object} options + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + */ +FabMoDashboard.prototype.pause = function(callback) { + this._call("pause",null, callback); +} + +/** + * Stop execution of the current job or operation. Operation cannot be resumed. + * + * @method stop + * @param {Object} options + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + */ +FabMoDashboard.prototype.stop = function(callback) { + this._call("stop",null, callback); +} + +/** + * Resume the current operation of the system is paused. + * + * @method resume + * @param {Object} options + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + */ +FabMoDashboard.prototype.resume = function(callback) { + this._call("resume",null, callback); +} + +/** + * Perform a fixed manual move in a single axis. (Sometimes called a nudge) + * + * @method manualMoveFixed + * @param {String} axis One of `x`,`y`,`z`,`a`,`b`,`c` + * @param {Number} speed Speed in current tool units + * @param {distance} distance The distance to move in current units + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + */ +FabMoDashboard.prototype.manualMoveFixed = function(axis, speed, distance, callback) { + this._call("manualMoveFixed",{"axis":axis, "speed": speed, "dist":distance}, callback); +} + +/** + * Start performing a manual move of the specified axis at the specified speed. + * + * @method manualStart + * @param {Number} axis One of `x`,`y`,`z`,`a`,`b`,`c` + * @param {Number} speed Speed in current tool units. Negative to move in the negative direction. + */ +FabMoDashboard.prototype.manualStart = function(axis, speed) { + this._call("manualStart",{"axis":axis, "speed":speed}, callback); +} + +/** + * Send a "heartbeat" to the system, authorizing continued manual movement. Manual moves must be continually + * refreshed with this heartbeat function, or the tool will stop moving. + * + * @method manualHeartbeat + */ +FabMoDashboard.prototype.manualHeartbeat = function() { + this._call("manualHeartbeat",{}, callback); +} + +/** + * Stop the tool immediately. + * + * @method manualStop + */ +FabMoDashboard.prototype.manualStop = function() { + this._call("manualStop",{}, callback); +} + +/** + * Get the list of all the installed apps. + * @method getApps + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + * @param {Object} callback.apps List of app objects representing all installed apps. + */ +FabMoDashboard.prototype.getApps = function(callback) { + this._call("getApps",null,callback); +} + + +FabMoDashboard.prototype.submitApp = function(apps, options, callback) { + var args = {apps : []}; + + if(window.jQuery && apps instanceof jQuery) { + if(apps.is('input:file')) { + apps = apps[0]; + } else { + apps = apps.find('input:file')[0]; + } + var files = apps.files; + if(files.length) { + apps = []; + for(var i=0; i<files.length; i++) { + apps.push(files[i]); + } + } + } else { + if(!apps.length) { + apps = [apps]; + } + } + + for(var i=0; i<apps.length; i++) { + args.apps.push(_makeApp(apps[i])); + } + + if(typeof options === 'function') { + callback = options; + options = {}; + } + + args.options = options || {}; + this._call("submitApp", args, callback) +} +FabMoDashboard.prototype.getUpdaterConfig = function(callback) { + this._call("getUpdaterConfig", null, callback); +} + +FabMoDashboard.prototype.setUpdaterConfig = function(data, callback) { + this._call("setUpdaterConfig", data, callback); +} + +FabMoDashboard.prototype.getInfo = function(callback) { + this._call("getInfo", null, callback); +} + +FabMoDashboard.prototype.getConfig = function(callback) { + this._call("getConfig", null, callback); +} + +FabMoDashboard.prototype.setConfig = function(data, callback) { + this._call("setConfig", data, callback); +} + +FabMoDashboard.prototype.deleteApp = function(id, callback) { + this._call("deleteApp",id,callback); +} + +FabMoDashboard.prototype.runGCode = function(text, callback) { + this._call("runGCode", text, callback); +} + +FabMoDashboard.prototype.runSBP = function(text, callback) { + this._call("runSBP", text, callback); +} + +FabMoDashboard.prototype.connectToWifi = function(ssid, key, callback) { + this._call("connectToWifi", {'ssid':ssid, 'key':key}, callback); +} + +FabMoDashboard.prototype.disconnectFromWifi = function(callback) { + this._call("disconnectFromWifi", null, callback); +} + +FabMoDashboard.prototype.forgetWifi = function(ssid, key, callback) { + this._call("forgetWifi", {'ssid':ssid}, callback); +} + +FabMoDashboard.prototype.enableWifi = function(callback) { + this._call("enableWifi", null, callback); +} + +FabMoDashboard.prototype.disableWifi = function(callback) { + this._call("disableWifi", null, callback); +} + +FabMoDashboard.prototype.enableWifiHotspot = function(callback) { + this._call("enableWifiHotspot", null, callback); +} + +FabMoDashboard.prototype.disableWifiHotspot = function(callback) { + this._call("disableWifiHotspot", null, callback); +} + +FabMoDashboard.prototype.getWifiNetworks = function(callback) { + this._call("getWifiNetworks", null, callback); +} + +FabMoDashboard.prototype.getWifiNetworkHistory = function(callback) { + this._call("getWifiNetworkHistory", null, callback); +} + +FabMoDashboard.prototype.getNetworkIdentity = function(callback) { + this._call("getNetworkIdentity", null, callback); +} + +FabMoDashboard.prototype.setNetworkIdentity = function(identity, callback) { + this._call("setNetworkIdentity", identity, callback); +} + +FabMoDashboard.prototype.isOnline = function(callback) { + this._call("isOnline", null, callback); +} + +/** + * Get a list of all the macros installed on the tool. + * + * @method getMacros + * @param callback + * @param {Error} callback.err Error object if there was an error. + * @param {Object} callback.macros List of macro objects currently installed. + */ +FabMoDashboard.prototype.getMacros = function(callback) { + this._call("getMacros", null, callback); +} + +/** + * Run the specified macro immediately. Macro does not appear in the job history. + * + * @method runMacro + * @param {Number} id The id of the macro to run. + * @param callback + * @param {Error} callback.err Error object if there was an error. + */ +FabMoDashboard.prototype.runMacro = function(id, callback) { + this._call("runMacro", id, callback); +} + +FabMoDashboard.prototype.updateMacro = function(id, macro, callback) { + this._call("updateMacro", {'id':id, 'macro':macro}, callback); +} + +/** + * Request a status report from the system. The status object is returned in the callback to this function, as well as posted + * with the status event. To recieve updates to system status as it changes, you should bind a handler to the status event, + * and _not_ poll using `requestStatus`. + * + * @method requestStatus + * @param {function} callback + * @param {Object} callback.status The status report object + * @param {String} callback.status.state The system state, one of `idle`,`running`,`paused`,`stopped`,`manual` or `dead` + * @param {Number} callback.status.posx The current x-axis position + * @param {Number} callback.status.posy The current y-axis position + * @param {Number} callback.status.posz The current z-axis position + * @param {Number} callback.status.posa The current a-axis position + * @param {Number} callback.status.posb The current b-axis position + * @param {Number} callback.status.posc The current c-axis position + * @param {Number} callback.status.in1 The current state of input 1 (`0` or `1`) + * @param {Number} callback.status.in1 The current state of input 2 (`0` or `1`) + * @param {Number} callback.status.in1 The current state of input 3 (`0` or `1`) + * @param {Number} callback.status.in1 The current state of input 4 (`0` or `1`) + * @param {Number} callback.status.in1 The current state of input 5 (`0` or `1`) + * @param {Number} callback.status.in1 The current state of input 6 (`0` or `1`) + * @param {Number} callback.status.in1 The current state of input 7 (`0` or `1`) + * @param {Number} callback.status.in1 The current state of input 8 (`0` or `1`) + * @param {String} callback.status.unit The current tool units (either `in` or `mm`) + * @param {Number} callback.status.line The line number in the currently running file (if a file is running) + * @param {Number} callback.status.nb_lines The total number of lines in the currently running file (if a file is running) + * @param {boolean} callback.status.auth True if the tool is currently authorized for movement + * @param {Object} callback.status.current_file Object describing the currently running file (or null if not running a file) + * @param {Object} callback.status.job Object describing the currently running job (or null if not running a job) + */ +FabMoDashboard.prototype.requestStatus = function(callback) { + this._call("requestStatus", null, callback); +} + +/** + * Start a connection to the video streaming service on your fabmo device. + * A video_frame event will be triggered each time a new frame is send to the browser. + * + * @method startVideoStreaming + * @param {function} callback + * @param {Object} callback.err Error object if there was an error. + */ +FabMoDashboard.prototype.startVideoStreaming = function(callback) { + this._call("startVideoStreaming", null, callback); +} + + +/** + * Add a listener to the video frame event. + * This will return an Image object, ready to be displayed on a canvas + * @method startVideoStreaming + * @param {function} callback + * @param {Object} callback.err Error object if there was an error. + */ +FabMoDashboard.prototype.onVideoFrame = function (callback) { + this.on('video_frame',function(data){ + imageObj = new Image(); + imageObj.src = "data:image/jpeg;base64,"+data; + imageObj.onload = function(){ + callback(imageObj); + } + }) +}; + +FabMoDashboard.prototype.deleteMacro = function(id, callback) { + this._call("deleteMacro", id, callback); +} + +/** + * Get the configuration object for the currently running app. The configuration object is a JSON object + * of no specific description that is saved with each app. It can be used to store app-specific configuration data. + * + * @method getAppConfig + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + * @param {Object} callback.config Configuration object or {} if no configuration has been saved in the app. + */ +FabMoDashboard.prototype.getAppConfig = function(callback) { + this._call("getAppConfig", null, callback); +} + +/** + * Set the app configuration to the provided option. + * + * @method setAppConfig + * @param {Object} config The configuration object to set. + * @param {function} callback + * @param {Error} callback.err Error object if there was an error. + * @param {Object} callback.config Configuration object or {} if no configuration has been saved in the app. + */ +FabMoDashboard.prototype.setAppConfig = function(config, callback) { + this._call("setAppConfig", config, callback); +} + +/** + * Get the current FabMo version + * + * @method getVersion + * @param {fuction} callback + * @param {Error} callback.err Error object if there was an error. + * @param {Object} callback.version Object describing the fabmo software version. + * @param {String} callback.version.hash The git hash of the currently running FabMo software + * @param {String} callback.version.number The user-friendly release version number (or empty string if not a released version + * @param {boolean} callback.version.debug True if FabMo is running in "debug mode" + * @param {String} callback.version.type The type of FabMo software build. `dev` for development or `release` for released build. + */ +FabMoDashboard.prototype.getVersion = function(callback) { + this._call("getVersion", null, callback); +} + +/** + * Control top level navigation for the dashboard. Usually this is used to open another browser/tab window with a link from within an app, + * or more rarely, to navigate away from the dashboard. + * @method navigate + * @param {String} url The URL to open + * @param {Object} options Options for top-level navigation. + * @param {String} options.target The link target (same as the target argument of `window.open`) + */ +FabMoDashboard.prototype.navigate = function(url, options, callback) { + var loc = window.location.href; + this._call("navigate", {'path' : loc.substr(0, loc.lastIndexOf('/')) + '/', 'url' : url, 'options' : options || {}}, callback); +} + +FabMoDashboard.prototype.getCurrentUser = function(callback){ + this._call("getCurrentUser",null,callback); +} +FabMoDashboard.prototype.addUser = function(user_info,callback){ + this._call("addUser",user_info,callback); +} +FabMoDashboard.prototype.modifyUser = function(user,callback){ + this._call("modifyUser",user,callback); +} +FabMoDashboard.prototype.deleteUser = function(user,callback){ + this._call("deleteUser",user,callback); +} +FabMoDashboard.prototype.getUsers = function(callback){ + this._call("getUsers",null,callback); +} + +FabMoDashboard.prototype.getUpdaterStatus = function(callback){ + this._call("getUpdaterStatus",null,callback); +} + + +var toaster = function () { + var el = document.createElement('div'); + el.setAttribute('id', 'alert-toaster'); + el.style.cssText = 'position:fixed; visibility:hidden; margin: auto; top: 20px; right: 20px; width: 250px; height: 60px; background-color: #F3F3F3; border-radius: 3px; z-index: 1005; box-shadow: 4px 4px 7px -2px rgba(0,0,0,0.75);'; + el.innerHTML = "<span id='alert-text' style= 'position:absolute; margin: auto; top: 0; right: 0; bottom: 0; left: 0; height: 20px; width: 250px; text-align: center;'></span>"; + document.body.appendChild(el); +} +var showToaster = function (toaster) { + toaster.style.visibility = 'visible'; + setTimeout(function(){document.body.removeChild(toaster)}, 1000); +} +return FabMoDashboard; + +})); diff --git a/fabmo/package.json b/fabmo/package.json new file mode 100644 index 0000000000000000000000000000000000000000..224efa5a1405ca8327c0f60667819c989d88083e --- /dev/null +++ b/fabmo/package.json @@ -0,0 +1,10 @@ +{ + "name": "Mods", + "license": "MIT", + "main": "./index.html", + "icon": "icon.png", + "icon_color": "#ffffff", + "version": "0.0.1", + "author": "Ryan Sturmer", + "description": "MIT CBA's FabModules for FabMo" +} diff --git a/files.html b/files.html index 61a4d512d33810469564ac40fe10ff3560b874a6..25fca1112730e19dd6cff43d9e16db3ffa2d4a2b 100644 --- a/files.html +++ b/files.html @@ -8,9 +8,24 @@ window.close() } </script> - <i> .git</i><br> + <a href='./.DS_Store'>.DS_Store</a><br> +<i> .git</i><br> <a href='./.gitignore'>.gitignore</a><br> <a href='./README.md'>README.md</a><br> +<i> fabmo</i><br> + <a href='./fabmo/.DS_Store'>.DS_Store</a><br> + <a href='./fabmo/Makefile'>Makefile</a><br> +<i> build</i><br> + <a href='./fabmo/build/fabmo-mods-app.fma'>fabmo-mods-app.fma</a><br> + <a href='./fabmo/build/icon.png'>icon.png</a><br> +<i> css</i><br> + <a href='./fabmo/css/style.css'>style.css</a><br> + <a href='./fabmo/icon.png'>icon.png</a><br> + <a href='./fabmo/index.html'>index.html</a><br> +<i> js</i><br> + <a href='./fabmo/js/fabmo-mods.js'>fabmo-mods.js</a><br> + <a href='./fabmo/js/fabmo.js'>fabmo.js</a><br> + <a href='./fabmo/package.json'>package.json</a><br> <a href='./files.html'>files.html</a><br> <a href='./index.html'>index.html</a><br> <i> js</i><br> @@ -26,6 +41,7 @@ <a href='./js/udpserver.js'>udpserver.js</a><br> <a href='./make'>make</a><br> <i> modules</i><br> + <a href='./modules/.DS_Store'>.DS_Store</a><br> <i> apa</i><br> <a href='./modules/apa/node'>node</a><br> <i> character</i><br> @@ -45,6 +61,9 @@ <a href='./modules/event/delay'>delay</a><br> <a href='./modules/event/generate'>generate</a><br> <a href='./modules/event/pause'>pause</a><br> +<i> fabmo</i><br> + <a href='./modules/fabmo/status'>status</a><br> + <a href='./modules/fabmo/submit%20job'>submit job</a><br> <i> file</i><br> <a href='./modules/file/save'>save</a><br> <i> graph</i><br> diff --git a/js/mods.js b/js/mods.js index 6b5d12ee0aa7b455ed06662b8999b812787fd0d6..d9960cffa60a7fa94322ce85c722d90191e12e30 100644 --- a/js/mods.js +++ b/js/mods.js @@ -12,7 +12,9 @@ // // closure // -(function(){ +var fabModules; + +(function() { // // globals // @@ -28,6 +30,7 @@ mods.ui = {source:null, link_highlight:'rgb(255,0,0)' } mods.globals = {} +fabModules = mods; // // set up UI // @@ -1379,4 +1382,9 @@ function window_touchup(evt) { window.removeEventListener('touchmove',window_touchmove) window.removeEventListener('touchend',window_touchup) } -})() + +// Things to pass into the global fabModules object +mods.prog_load = prog_load; + +} +)() diff --git a/modules/fabmo/status b/modules/fabmo/status new file mode 100755 index 0000000000000000000000000000000000000000..821ee7cc25d35ed2e5faa85d560c5ceddcaebc68 --- /dev/null +++ b/modules/fabmo/status @@ -0,0 +1,74 @@ +// +// ui button +// +// Neil Gershenfeld +// (c) Massachusetts Institute of Technology 2015,6 +// +// This work may be reproduced, modified, distributed, performed, and +// displayed for any purpose, but must acknowledge the mods +// project. Copyright is retained and must be preserved. The work is +// provided as is; no warranty is provided, and users accept all +// liability. +// +// closure +// +(function(){ +// +// module globals +// +var mod = { + count : 0 +} +// +// name +// +var name = 'Status Report' +// +// initialization +// +var init = function() { + fabmo.on('status', function(stat) { + console.log(outputs) + mod.count++; + outputs.status.event(stat) + }); + fabmo.requestStatus(); +} +// +// inputs +// +var inputs = { + } +// +// outputs +// +var outputs = { + status:{type:"Object", + label:"Status", + event:function(obj){ + mod.content.innerHTML = "Status Reports:<br/>" + mod.count; + mods.output(mod,"status",obj)} + } + } +// +// interface +// +var interface = function(div){ + mod.div = div + mod.content = document.createElement('div') + mod.content.innerHTML = 'Status Reports:<br/>0' + //mod.content.style.padding = mods.ui.padding + //mod.content.style.margin = 1 + div.appendChild(mod.content) +} +// +// return values +// +return ({ + name:name, + init:init, + inputs:inputs, + outputs:outputs, + interface:interface + }) +}()) diff --git a/modules/fabmo/submit job b/modules/fabmo/submit job new file mode 100644 index 0000000000000000000000000000000000000000..1fc963884c3ca21c80d661b0ce68052274dc4166 --- /dev/null +++ b/modules/fabmo/submit job @@ -0,0 +1,95 @@ +// +// save file +// +// Neil Gershenfeld +// (c) Massachusetts Institute of Technology 2016 +// +// This work may be reproduced, modified, distributed, performed, and +// displayed for any purpose, but must acknowledge the mods +// project. Copyright is retained and must be preserved. The work is +// provided as is; no warranty is provided, and users accept all +// liability. +// +// closure +// +(function(){ +// +// module globals +// +var mod = {} +// +// name +// +var name = 'fabmo submit job' +// +// initialization +// +var init = function() { + } +// +// inputs +// +var inputs = { + file:{type:'object', + event:function(evt){ + mod.name = evt.detail.name + mod.contents = evt.detail.contents + submit_job() + }}} +// +// outputs +// +var outputs = {} +// +// interface +// +var interface = function(div){ + mod.div = div + // + // info + // + var text = document.createTextNode('name:') + div.appendChild(text) + mod.nametext = text + div.appendChild(document.createElement('br')) + var text = document.createTextNode('size:') + div.appendChild(text) + mod.sizetext = text + div.appendChild(document.createElement('br')) + } +// +// local functions +// +function submit_job() { +/* var a = document.createElement('a') + a.setAttribute('href','data:text/plain;charset=utf-8,'+ encodeURIComponent(mod.contents)) +*/ + var code = mod.contents + console.log(code); + fabmo.submitJob({ + file : code, + filename : mod.name + }, { stayHere : true }); +/* + a.setAttribute('download',mod.namea) + a.style.display = 'none' + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + */ + mod.nametext.nodeValue = 'name: '+mod.name + mods.fit(mod.div) + mod.sizetext.nodeValue = 'size: '+mod.contents.length + mods.fit(mod.div) + } +// +// return values +// +return ({ + name:name, + init:init, + inputs:inputs, + outputs:outputs, + interface:interface + }) +}()) diff --git a/modules/index.html b/modules/index.html index b31f0ff962b4b6b107cc4c02865331fbd6c95341..75a1cceb01cc3be8807e0efc89b0450c44a1ade0 100644 --- a/modules/index.html +++ b/modules/index.html @@ -28,6 +28,9 @@ <a href="javascript:handler('modules/event/delay')">delay</a><br> <a href="javascript:handler('modules/event/generate')">generate</a><br> <a href="javascript:handler('modules/event/pause')">pause</a><br> +<i>fabmo</i><br> + <a href="javascript:handler('modules/fabmo/status')">status</a><br> + <a href="javascript:handler('modules/fabmo/submit%20job')">submit job</a><br> <i>file</i><br> <a href="javascript:handler('modules/file/save')">save</a><br> <i>graph</i><br>