UNPKG

dreemgl

Version:

DreemGL is an open-source multi-screen prototyping framework for mediated environments, with a visual editor and shader styling for webGL and DALi runtimes written in JavaScript. As a toolkit for gpu-accelerated multiscreen development, DreemGL includes

325 lines (277 loc) 9.84 kB
/* DreemGL is a collaboration between Teeming Society & Samsung Electronics, sponsored by Samsung and others. Copyright 2015-2016 Teeming Society. Licensed under the Apache License, Version 2.0 (the "License"); You may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ // parse a color string into a [r,g,b] 0-1 float array define.class(function(require){ var fs = require('fs') var FileWatcher = require('./filewatcher') this.atConstructor = function( args, //Object: Process arguments compname, //String: name of the composition options) { //Object: config options if (!options) { options = {} } this.options = options this.args = args this.compname = compname var BusServer; if (this.options.busclass) { BusServer = require(this.options.busclass) } else { BusServer = require('$system/rpc/busserver') } this.busserver = new BusServer(compname) // lets give it a session this.session = Math.random() * 1000000 this.components = {} this.paths = "" this.pathset = '{' for(var key in define.paths){ if(this.paths) { this.paths += ',\n\t\t' this.pathset += ',' } this.paths += '$'+key+':"$root/'+key+'"' this.pathset += '"'+key+'":1' } this.pathset += '}' // If files are not being served remorely, watch them for changes if (this.options.serveremote !== true) { this.slow_watcher = new FileWatcher() this.slow_watcher.atChange = function(){ // lets reload this app this.reload() }.bind(this) // lets compile and run the dreem composition define.atRequire = function(filename){ this.slow_watcher.watch(filename) }.bind(this) } this.reload() } // Called when any of the dependent files change for this composition this.atChange = function(){ } // Destroys all objects maintained by the composition this.destroy = function(){ if(this.mycomposition && this.mycomposition.destroy) this.mycomposition.destroy() this.mycomposition = undefined } this.loadComposition = function(){ console.log("Reloading composition "+this.filename) require.clearCache() var file = define.expandVariables(this.filename) var Composition = require(file) //TODO: xxx needs method to load from remote source, not just file system? this.composition = new Composition(this.busserver, this.session, this.composition) } this.reload = function(){ this.destroy() // lets fill require.clearCache() this.title = define.fileName(this.compname) // lets see if our composition is a dir or a jsfile var jsname = this.compname+'.js' try{ if(fs.existsSync(define.expandVariables(jsname))){ this.filename = jsname return this.loadComposition() } else{ var jsname = this.compname + '/index.js' if(fs.existsSync(define.expandVariables(jsname))){ this.filename = jsname return this.loadComposition() } } } catch(e){ console.log(e.stack) } finally{ } } this.loadHTML = function(title, boot, paths, pathset){ // These rpc attributes we will write directly into the header so that they are available even before the screen connects var preloadattrs = {}; var additionalHeader = ""; if (this.composition) { // preload_rpc_attributes can be `true` to include everything or an array of `rpcobj_rpcattr` identifiers var preload = this.composition.preload_rpc_attributes; if (preload === true) { preloadattrs = this.composition.server_attributes; } else if (Array.isArray(preload)) { for (var i = 0; i< preload.length;i++) { var key = preload[i]; var attr = this.composition.server_attributes[key]; if (attr) { preloadattrs[key] = attr } } } additionalHeader = this.composition.headHTML || ""; } var additionalScripts = "" var additionalDefines = "" if (this.options) { if (this.options.scripts) { for (var s = 0; s < this.options.scripts.length;s++) { var scriptURL = this.options.scripts[s] additionalScripts += '<script src="' + scriptURL + '"></script>\n' } } if (this.options.clientdefines) { for (var defkey in this.options.clientdefines) { var defval = this.options.clientdefines[defkey] if (typeof (defval) === 'string') { defval = '"' + defval +'"' } additionalDefines += '$' + defkey + ':' + defval + ',\n' } } } return '<html lang="en">\n'+ //TODO: xxx make this a template loadable from options ' <head>\n'+ ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n'+ ' <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0">\n'+ ' <meta name="apple-mobile-web-app-capable" content="yes">\n'+ ' <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">\n'+ ' <meta name="format-detection" content="telephone=no">\n'+ ' <title>' + title + '</title>\n'+ ' <style>\n'+ ' .unselectable{\n'+ ' -webkit-user-select: none;\n'+ ' -moz-user-select: none;\n'+ ' -user-select: none;\n'+ ' }\n'+ ' body {background-color:white;margin:0;padding:0;height:100%;overflow:hidden;}\n'+ ' </style>\n'+ additionalScripts + ' <script type="text/javascript">\n'+ ' window.define = {\n'+ additionalDefines + ' $platform:"webgl",\n'+ ' paths:'+pathset+',\n'+ ' '+paths+',\n'+ ' main:["$system/base/math", "' + boot + '"],\n'+ ' atMain:function(require, modules){\n'+ ' define.endLoader()\n'+ ' require(modules[0])\n'+ ' var Composition = require(modules[1])\n'+ ' var serverattrs = ' + JSON.stringify(preloadattrs) + '\n'+ ' var renderTarget;' + '\n'+ ' define.rootComposition = new Composition(define.rootComposition, undefined, serverattrs, renderTarget)\n'+ ' },\n'+ ' atEnd:function(){\n'+ ' define.startLoader()\n'+ ' }\n'+ ' }\n'+ ' </script>\n'+ ' <script type="text/javascript" src="/system/base/define.js"></script>\n'+ additionalHeader + ' </head>\n'+ ' <body class="unselectable">\n'+ ' </body>\n'+ '</html>\n' } this.request = function(req, res){ if(req.method == 'POST' && (define.$unsafeorigin || (this.options && this.options.whitelist))){ // lets do an RPC call if(!define.$unsafeorigin && this.options.whitelist.indexOf(req.headers.origin) === -1){ console.log("WRONG ORIGIN POST API RECEIVED. " + req.headers.origin + " not in:", this.options.whitelist) res.end() return false } var boundary; if (req.headers && req.headers['content-type']) { var type = req.headers['content-type']; if (type) { var m = /^multipart\/form-data; boundary=(.*)$/.exec(type) if (m) { boundary = m[1]; } } } var buffer; req.on('data', function(data){ if (boundary) { if (!buffer) { buffer = new Buffer(data) } else { buffer = Buffer.concat([buffer, data]) } } }) req.on('end', function() { if (boundary && buffer && buffer.indexOf) { var cursor = buffer.indexOf(boundary) + boundary.length; cursor = buffer.indexOf("filename=", cursor) + "filename=".length; var q = buffer[cursor]; var filename = buffer.slice(cursor + 1, buffer.indexOf(q, cursor + 1)).toString(); cursor = buffer.indexOf("\r\n\r\n", cursor) + "\r\n\r\n".length; var filedata = buffer.slice(cursor, buffer.indexOf("\r\n--" + boundary)); var compfile = this.composition.constructor.module.filename; var compdir = compfile.substring(0, compfile.lastIndexOf('/')); filename = compdir + "/" + filename.replace(/[^A-Za-z0-9_.-]/g,''); if (!define.$writefile){ console.log("writefile api disabled, use -writefile to turn it on. Writefile api is always limited to localhost origins.") res.writeHead(501); } else { try{ var fullname = define.expandVariables(filename); fs.writeFile(fullname, filedata); console.log("[UPLOAD] Wrote", filedata.length, "bytes to", fullname); res.writeHead(200); } catch(e){ res.writeHead(503); } } res.end(); } else if (buffer) { try{ var json = JSON.parse(buffer.toString()) this.composition.postAPI(json, {send:function(msg){ res.writeHead(200, {"Content-Type":"text/json"}) res.write(JSON.stringify(msg)) res.end() }}) } catch(e){ res.writeHead(500, {"Content-Type": "text/html"}) res.write('FAIL') res.end() } } else { res.writeHead(500, {"Content-Type": "text/html"}) res.write('FAIL') res.end() } }.bind(this)) return } var header = { "Cache-control": "max-age=0", "Content-Type": "text/html" } // nodejs root if(req.headers['client-type'] === 'nodejs'){ res.writeHead(200, {"Content-type":"text/json"}) res.write(JSON.stringify({title:this.title, boot:this.filename, paths:define.paths })) res.end() return } if (!this.filename) { res.writeHead(404, header) res.write('<body>Sorry, we could not find an application at that URL. <a href="/docs/api/index.html">Try reading the documentation?</a></body>') res.end() return } var html = this.loadHTML(this.title, this.filename, this.paths, this.pathset) res.writeHead(200, header) res.write(html) res.end() } })