From d284851af691891e1d0856ba927f1c89c9a3b1e7 Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Sun, 3 Jul 2011 14:07:05 -0700 Subject: [PATCH] first commit --- .gitignore | 2 + config.js | 24 ++++ model.js | 125 ++++++++++++++++++++ package.json | 32 +++++ protocol.txt | 15 +++ public/css/style.css | 5 + public/index.html | 13 ++ public/js/arbor-tween.js | 86 ++++++++++++++ public/js/arbor.js | 67 +++++++++++ public/js/main.js | 249 +++++++++++++++++++++++++++++++++++++++ server.js | 129 ++++++++++++++++++++ tools.js | 13 ++ 12 files changed, 760 insertions(+) create mode 100644 .gitignore create mode 100644 config.js create mode 100644 model.js create mode 100644 package.json create mode 100644 protocol.txt create mode 100644 public/css/style.css create mode 100644 public/index.html create mode 100644 public/js/arbor-tween.js create mode 100644 public/js/arbor.js create mode 100644 public/js/main.js create mode 100644 server.js create mode 100644 tools.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cf117f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +*.tmproj diff --git a/config.js b/config.js new file mode 100644 index 0000000..e7cdb54 --- /dev/null +++ b/config.js @@ -0,0 +1,24 @@ +module.exports = + +{ env: process.env.NODE_ENV || 'development' + +, loggerFormat: [ ':remote-addr' + , '-' + , ':response-timems' + , '[:date]' + , '":method :url HTTP/:http-version"' + , ':status' + , ':res[content-length]' + , '":referrer"' + , '":user-agent"' + ].join(' ') + +, sessionSecret: '8dce9a5af7733469651f81390d2cb3dda5bfb1ef' + +, host: '0.0.0.0' +, port: 3030 + +, webHost: '0.0.0.0' +, webPort: 8080 + +} diff --git a/model.js b/model.js new file mode 100644 index 0000000..629fb6a --- /dev/null +++ b/model.js @@ -0,0 +1,125 @@ +////////////// +/// Models /// +////////////// + +function RemoteDataSource(conn, id) { + this.id = id + this.connection = conn + this.deferreds = [] + this.executed = false + this.cancelled = false + this.completed = false + this.result = null + this.events = [] + + this.addEvent('created') +} + +RemoteDataSource.prototype.addEvent = function(/* name, ... */) { + var args = [].slice.call(arguments) + args.unshift(new Date()) + this.events.push(args) + // TODO emit a node event +} + +RemoteDataSource.prototype.removeDeferred = function(d) { + this.deferreds.push(d) +} + +RemoteDataSource.prototype.removeDeferred = function(d) { + var i = this.deferreds.indexOf(d) + if (i > -1) { + this.deferreds.splice(i, 1) + } +} + +RemoteDataSource.prototype.remove = function(data) { + this.addEvent('removed', data) +} + +RemoteDataSource.prototype.cancel = function(data) { + this.cancelled = new Date() + this.addEvent('cancelled', data) +} + +RemoteDataSource.prototype.execute = function(data) { + this.executed = new Date() + this.addEvent('executed', data) +} + +RemoteDataSource.prototype.complete = function(result, data) { + this.completed = new Date() + this.result = result + this.addEvent('completed', result, data) +} + +function RemoteDeferred(conn, id, data) { + this.id = id + this.connection = conn + this.links = [] + this.linkIndex = 0 + this.called = false + this.running = false + this.pauseCount = 0 + this.finalized = false + this.hasFinalizer = false + this.resolved = false + this.rejected = false + this.result = null + this.events = [] + + this.addEvent('created', data) +} + +RemoteDeferred.prototype.addEvent = function(/* name, ... */) { + var args = [].slice.call(arguments) + args.unshift(new Date()) + this.events.push(args) + // TODO emit a node event +} + +RemoteDeferred.prototype.remove = function(data) { + if (this.dataSource) { + this.dataSource.removeDeferred(this) + } + this.addEvent('removed', data) +} + +RemoteDeferred.prototype.cancel = function(data) { + this.cancelled = new Date() + this.addEvent('cancelled', data) +} + +RemoteDeferred.prototype.resolve = function(data) { + this.resolved = new Date() + this.result = data.result + this.addEvent('resolved', data) +} + +RemoteDeferred.prototype.reject = function(data) { + this.rejected = new Date() + this.result = data.result + this.addEvent('rejected', data) +} + +RemoteDeferred.prototype.addLink = function(data) { + this.links.push(tools.mixin({ ran: false }, data)) + this.addEvent('link-added', data) +} + +RemoteDeferred.prototype.runLink = function(data) { + var link = this.links[this.linkIndex++] + link.ran = new Date() + tools.mixin(link, data) // data contains: result, file, and line + this.addEvent('link-ran', data) +} + +RemoteDeferred.prototype.addFinalizer = function(data) { + this.hasFinalizer = new Date() + this.addEvent('finalizer-added', data) +} + +RemoteDeferred.prototype.finalize = function(data) { + this.finalized = new Date() + this.addEvent('finalized', data) +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..cbf1946 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ "name" : "deferredvis-server" +, "version" : "0.0.1" +, "description" : "Visualize your Deferreds" +, "keywords" : + [ "deferred" + , "visualize" + ] +, "author" : "Sami Samhuri " +, "maintainers" : [ + "Sami Samhuri " + ] +, "homepage" : "http://samhuri.net/proj/DeferredVis-server" +, "main" : "server" +, "bin" : { "deferredvis-server" : "./server.js" } +, "engines" : { "node" : "0.4.x" } +, "repository" : + { "type" : "git" + ,"url" : "git://github.com/samsonjs/DeferredVis-server.git" + } +, "licenses" : + [ { "type" : "MIT" + , "url" : "http://github.com/samsonjs/DeferredVis-server/raw/master/LICENSE" + } + ] +, "dependencies" : + { "connect" : "1.4.x" + , "connect-redis" : "1.0.x" + , "express" : "2.4.x" + , "socket.io" : "0.7.x" + } +, "devDependencies" : {} +} diff --git a/protocol.txt b/protocol.txt new file mode 100644 index 0000000..ca7008c --- /dev/null +++ b/protocol.txt @@ -0,0 +1,15 @@ +datasource-created 0x12345 +datasource-removed 0x12345 +datasource-cancelled 0x12345 +datasource-executed 0x12345 +datasource-completed 0x12345 { result: _, success: _ } + +deferred-created 0x67890 { dataSourceId: ? } +deferred-removed 0x67890 +deferred-cancelled 0x67890 +deferred-resolved 0x67890 { result: _ } +deferred-rejected 0x67890 { result: _ } +deferred-paused 0x67890 { myLink: ?, distantLink: ? } +deferred-unpaused 0x67890 +deferred-link-added 0x67890 { linkId: _, finalizer: _, file: _, line: _ } +deferred-link-ran 0x67890 { linkId: _, finalizer: _, result: _ } diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..a4c1044 --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,5 @@ +body { margin: 0 + ; padding: 20px + ; font: 12px/17px 'Helvetica Neue', Helvetica, sans-serif + ; background: #f9f9f9 + } diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..aaf25b7 --- /dev/null +++ b/public/index.html @@ -0,0 +1,13 @@ + + +Deferred Visualizer + + + + + + + + + + diff --git a/public/js/arbor-tween.js b/public/js/arbor-tween.js new file mode 100644 index 0000000..67f4c59 --- /dev/null +++ b/public/js/arbor-tween.js @@ -0,0 +1,86 @@ +// +// arbor-tween.js +// smooth transitions with a realtime clock +// +// Copyright (c) 2011 Samizdat Drafting Co. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +// Easing Equations in easing.js: +// Copyright © 2001 Robert Penner. All rights reserved. +// +// Open source under the BSD License. Redistribution and use in source +// and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// Neither the name of the author nor the names of contributors may be +// used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +(function($){ + + /* etc.js */ var trace=function(msg){if(typeof(window)=="undefined"||!window.console){return}var len=arguments.length;var args=[];for(var i=0;i0){return a[0]}else{return null}}; + /* colors.js */ var Colors=(function(){var f=/#[0-9a-f]{6}/i;var b=/#(..)(..)(..)/;var c=function(h){var g=h.toString(16);return(g.length==2)?g:"0"+g};var a=function(g){return parseInt(g,16)};var d=function(g){if(!g||typeof g!="object"){return false}var h=objkeys(g).sort().join("");if(h=="abgr"){return true}};var e={CSS:{aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"},decode:function(h){var g=arguments.length;for(var l=g-1;l>=0;l--){if(arguments[l]===undefined){g--}}var k=arguments;if(!h){return null}if(g==1&&d(h)){return h}var j=null;if(typeof h=="string"){var o=1;if(g==2){o=k[1]}var n=e.CSS[h.toLowerCase()];if(n!==undefined){h=n}var m=h.match(f);if(m){vals=h.match(b);if(!vals||!vals.length||vals.length!=4){return null}j={r:a(vals[1]),g:a(vals[2]),b:a(vals[3]),a:o}}}else{if(typeof h=="number"){if(g>=3){j={r:k[0],g:k[1],b:k[2],a:1};if(g>=4){j.a*=k[3]}}else{if(g>=1){j={r:k[0],g:k[0],b:k[0],a:1};if(g==2){j.a*=k[1]}}}}}return j},validate:function(g){if(!g||typeof g!="string"){return false}if(e.CSS[g.toLowerCase()]!==undefined){return true}if(g.match(f)){return true}return false},mix:function(h,g,k){var j=e.decode(h);var i=e.decode(g)},blend:function(g,j){j=(j!==undefined)?Math.max(0,Math.min(1,j)):1;var h=e.decode(g);if(!h){return null}if(j==1){return g}var h=g;if(typeof g=="string"){h=e.decode(g)}var i=objcopy(h);i.a*=j;return nano("rgba({r},{g},{b},{a})",i)},encode:function(g){if(!d(g)){g=e.decode(g);if(!d(g)){return null}}if(g.a==1){return nano("#{r}{g}{b}",{r:c(g.r),g:c(g.g),b:c(g.b)})}else{return nano("rgba({r},{g},{b},{a})",g)}}};return e})(); + /* easing.js */ var Easing=(function(){var a={linear:function(f,e,h,g){return h*(f/g)+e},quadin:function(f,e,h,g){return h*(f/=g)*f+e},quadout:function(f,e,h,g){return -h*(f/=g)*(f-2)+e},quadinout:function(f,e,h,g){if((f/=g/2)<1){return h/2*f*f+e}return -h/2*((--f)*(f-2)-1)+e},cubicin:function(f,e,h,g){return h*(f/=g)*f*f+e},cubicout:function(f,e,h,g){return h*((f=f/g-1)*f*f+1)+e},cubicinout:function(f,e,h,g){if((f/=g/2)<1){return h/2*f*f*f+e}return h/2*((f-=2)*f*f+2)+e},quartin:function(f,e,h,g){return h*(f/=g)*f*f*f+e},quartout:function(f,e,h,g){return -h*((f=f/g-1)*f*f*f-1)+e},quartinout:function(f,e,h,g){if((f/=g/2)<1){return h/2*f*f*f*f+e}return -h/2*((f-=2)*f*f*f-2)+e},quintin:function(f,e,h,g){return h*(f/=g)*f*f*f*f+e},quintout:function(f,e,h,g){return h*((f=f/g-1)*f*f*f*f+1)+e},quintinout:function(f,e,h,g){if((f/=g/2)<1){return h/2*f*f*f*f*f+e}return h/2*((f-=2)*f*f*f*f+2)+e},sinein:function(f,e,h,g){return -h*Math.cos(f/g*(Math.PI/2))+h+e},sineout:function(f,e,h,g){return h*Math.sin(f/g*(Math.PI/2))+e},sineinout:function(f,e,h,g){return -h/2*(Math.cos(Math.PI*f/g)-1)+e},expoin:function(f,e,h,g){return(f==0)?e:h*Math.pow(2,10*(f/g-1))+e},expoout:function(f,e,h,g){return(f==g)?e+h:h*(-Math.pow(2,-10*f/g)+1)+e},expoinout:function(f,e,h,g){if(f==0){return e}if(f==g){return e+h}if((f/=g/2)<1){return h/2*Math.pow(2,10*(f-1))+e}return h/2*(-Math.pow(2,-10*--f)+2)+e},circin:function(f,e,h,g){return -h*(Math.sqrt(1-(f/=g)*f)-1)+e},circout:function(f,e,h,g){return h*Math.sqrt(1-(f=f/g-1)*f)+e},circinout:function(f,e,h,g){if((f/=g/2)<1){return -h/2*(Math.sqrt(1-f*f)-1)+e}return h/2*(Math.sqrt(1-(f-=2)*f)+1)+e},elasticin:function(g,e,k,j){var h=1.70158;var i=0;var f=k;if(g==0){return e}if((g/=j)==1){return e+k}if(!i){i=j*0.3}if(f1){for(var l=a.length-2;l>=0;l++){var n=a[l];for(var j in n.to){if(j in d){delete n.to[j]}else{d[j]=true}}for(var j in n.colors){if(j in d){delete n.colors[j]}else{d[j]=true}}if($.isEmptyObject(n.colors)&&$.isEmptyObject(n.to)){a.splice(l,1)}}}c=false},interpolate:function(e,h,i,g){g=(g||"").toLowerCase();var d=Easing.linear;if(g in Easing){d=Easing[g]}var f=d(e,0,1,1);if(Colors.validate(h)&&Colors.validate(i)){return lerpRGB(f,h,i)}else{if(!isNaN(h)){return lerpNumber(f,h,i)}else{if(typeof h=="string"){return(f<0.5)?h:i}}}},tick:function(){var f=true;for(var d in a){f=false;break}if(f){return}var e=new Date().valueOf();$.each(a,function(i,h){var g=false;$.each(h,function(p,t){var o=t.ease((e-t.t0),0,1,t.dur);o=Math.min(1,o);var r=t.from;var s=t.to;var j=t.colors;var l=t.node.data;var m=(o==1);for(var n in s){switch(typeof s[n]){case"number":l[n]=lerpNumber(o,r[n],s[n]);if(n=="alpha"){l[n]=Math.max(0,Math.min(1,l[n]))}break;case"string":if(m){l[n]=s[n]}break}}for(var n in j){if(m){l[n]=j[n][2]}else{var q=lerpRGB(o,j[n][0],j[n][1]);l[n]=Colors.encode(q)}}if(m){t.completed=true;g=true}});if(g){a[i]=$.map(h,function(j){if(!j.completed){return j}});if(a[i].length==0){delete a[i]}}});c=$.isEmptyObject(a);return c}};return b.init()};var lerpNumber=function(a,c,b){return c+a*(b-c)};var lerpRGB=function(b,d,c){b=Math.max(Math.min(b,1),0);var a={};$.each("rgba".split(""),function(e,f){a[f]=Math.round(d[f]+b*(c[f]-d[f]))});return a}; + + arbor = (typeof(arbor)!=='undefined') ? arbor : {} + $.extend(arbor, { + // not really user-serviceable; use the ParticleSystem’s .tween* methods instead + Tween:Tween, + + // immutable object with useful methods + colors:{ + CSS:Colors.CSS, // dictionary: {colorname:#fef2e2,...} + validate:Colors.validate, // ƒ(str) -> t/f + decode:Colors.decode, // ƒ(hexString_or_cssColor) -> {r,g,b,a} + encode:Colors.encode, // ƒ({r,g,b,a}) -> hexOrRgbaString + blend:Colors.blend // ƒ(color, opacity) -> rgbaString + } + }) + +})(this.jQuery) + + + + + diff --git a/public/js/arbor.js b/public/js/arbor.js new file mode 100644 index 0000000..a25dd24 --- /dev/null +++ b/public/js/arbor.js @@ -0,0 +1,67 @@ +// +// arbor.js - version 0.91 +// a graph vizualization toolkit +// +// Copyright (c) 2011 Samizdat Drafting Co. +// Physics code derived from springy.js, copyright (c) 2010 Dennis Hotson +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +(function($){ + + /* etc.js */ var trace=function(msg){if(typeof(window)=="undefined"||!window.console){return}var len=arguments.length;var args=[];for(var i=0;i0){return a[0]}else{return null}}; + /* kernel.js */ var Kernel=function(b){var k=window.location.protocol=="file:"&&navigator.userAgent.toLowerCase().indexOf("chrome")>-1;var a=(window.Worker!==undefined&&!k);var i=null;var c=null;var f=[];f.last=new Date();var l=null;var e=null;var d=null;var h=null;var g=false;var j={system:b,tween:null,nodes:{},init:function(){if(typeof(Tween)!="undefined"){c=Tween()}else{if(typeof(arbor.Tween)!="undefined"){c=arbor.Tween()}else{c={busy:function(){return false},tick:function(){return true},to:function(){trace("Please include arbor-tween.js to enable tweens");c.to=function(){};return}}}}j.tween=c;var m=b.parameters();if(a){trace("using web workers");l=setInterval(j.screenUpdate,m.timeout);i=new Worker(arbor_path()+"arbor.js");i.onmessage=j.workerMsg;i.onerror=function(n){trace("physics:",n)};i.postMessage({type:"physics",physics:objmerge(m,{timeout:Math.ceil(m.timeout)})})}else{trace("couldn't use web workers, be careful...");i=Physics(m.dt,m.stiffness,m.repulsion,m.friction,j.system._updateGeometry);j.start()}return j},graphChanged:function(m){if(a){i.postMessage({type:"changes",changes:m})}else{i._update(m)}j.start()},particleModified:function(n,m){if(a){i.postMessage({type:"modify",id:n,mods:m})}else{i.modifyNode(n,m)}j.start()},physicsModified:function(m){if(!isNaN(m.timeout)){if(a){clearInterval(l);l=setInterval(j.screenUpdate,m.timeout)}else{clearInterval(d);d=null}}if(a){i.postMessage({type:"sys",param:m})}else{i.modifyPhysics(m)}j.start()},workerMsg:function(n){var m=n.data.type;if(m=="geometry"){j.workerUpdate(n.data)}else{trace("physics:",n.data)}},_lastPositions:null,workerUpdate:function(m){j._lastPositions=m;j._lastBounds=m.bounds},_lastFrametime:new Date().valueOf(),_lastBounds:null,_currentRenderer:null,screenUpdate:function(){var n=new Date().valueOf();var m=false;if(j._lastPositions!==null){j.system._updateGeometry(j._lastPositions);j._lastPositions=null;m=true}if(c&&c.busy()){m=true}if(j.system._updateBounds(j._lastBounds)){m=true}if(m){var o=j.system.renderer;if(o!==undefined){if(o!==e){o.init(j.system);e=o}if(c){c.tick()}o.redraw();var p=f.last;f.last=new Date();f.push(f.last-p);if(f.length>50){f.shift()}}}},physicsUpdate:function(){if(c){c.tick()}i.tick();var n=j.system._updateBounds();if(c&&c.busy()){n=true}var o=j.system.renderer;var m=new Date();var o=j.system.renderer;if(o!==undefined){if(o!==e){o.init(j.system);e=o}o.redraw({timestamp:m})}var q=f.last;f.last=m;f.push(f.last-q);if(f.length>50){f.shift()}var p=i.systemEnergy();if((p.mean+p.max)/2<0.05){if(h===null){h=new Date().valueOf()}if(new Date().valueOf()-h>1000){clearInterval(d);d=null}else{}}else{h=null}},fps:function(n){if(n!==undefined){var q=1000/Math.max(1,targetFps);j.physicsModified({timeout:q})}var r=0;for(var p=0,o=f.length;p0);if(w){$.extend(c.adjacency[B][C].data,x.data);return}else{c.edges[x._id]=x;c.adjacency[B][C].push(x);var v=(x.length!==undefined)?x.length:1;j.push({t:"addSpring",id:x._id,fm:B,to:C,l:v});g._notify()}return x},pruneEdge:function(A){j.push({t:"dropSpring",id:A._id});delete c.edges[A._id];for(var v in c.adjacency){for(var B in c.adjacency[v]){var w=c.adjacency[v][B];for(var z=w.length-1;z>=0;z--){if(c.adjacency[v][B][z]._id===A._id){c.adjacency[v][B].splice(z,1)}}}}g._notify()},getEdges:function(w,v){w=g.getNode(w);v=g.getNode(v);if(!w||!v){return[]}if(typeof(c.adjacency[w._id])!=="undefined"&&typeof(c.adjacency[w._id][v._id])!=="undefined"){return c.adjacency[w._id][v._id]}return[]},getEdgesFrom:function(v){v=g.getNode(v);if(!v){return[]}if(typeof(c.adjacency[v._id])!=="undefined"){var w=[];$.each(c.adjacency[v._id],function(y,x){w=w.concat(x)});return w}return[]},getEdgesTo:function(v){v=g.getNode(v);if(!v){return[]}var w=[];$.each(c.edges,function(y,x){if(x.target==v){w.push(x)}});return w},eachEdge:function(v){$.each(c.edges,function(z,x){var y=c.nodes[x.source._id]._p;var w=c.nodes[x.target._id]._p;if(y.x==null||w.x==null){return}y=(u!==null)?g.toScreen(y):y;w=(u!==null)?g.toScreen(w):w;if(y&&w){v.call(g,x,y,w)}})},prune:function(w){var v={dropped:{nodes:[],edges:[]}};if(w===undefined){$.each(c.nodes,function(y,x){v.dropped.nodes.push(x);g.pruneNode(x)})}else{g.eachNode(function(y){var x=w.call(g,y,{from:g.getEdgesFrom(y),to:g.getEdgesTo(y)});if(x){v.dropped.nodes.push(y);g.pruneNode(y)}})}return v},graft:function(w){var v={added:{nodes:[],edges:[]}};if(w.nodes){$.each(w.nodes,function(y,x){var z=g.getNode(y);if(z){z.data=x}else{v.added.nodes.push(g.addNode(y,x))}c.kernel.start()})}if(w.edges){$.each(w.edges,function(z,x){var y=g.getNode(z);if(!y){v.added.nodes.push(g.addNode(z,{}))}$.each(x,function(D,A){var C=g.getNode(D);if(!C){v.added.nodes.push(g.addNode(D,{}))}var B=g.getEdges(z,D);if(B.length>0){B[0].data=A}else{v.added.edges.push(g.addEdge(z,D,A))}})})}return v},merge:function(w){var v={added:{nodes:[],edges:[]},dropped:{nodes:[],edges:[]}};$.each(c.edges,function(A,z){if((w.edges[z.source.name]===undefined||w.edges[z.source.name][z.target.name]===undefined)){g.pruneEdge(z);v.dropped.edges.push(z)}});var y=g.prune(function(A,z){if(w.nodes[A.name]===undefined){v.dropped.nodes.push(A);return true}});var x=g.graft(w);v.added.nodes=v.added.nodes.concat(x.added.nodes);v.added.edges=v.added.edges.concat(x.added.edges);v.dropped.nodes=v.dropped.nodes.concat(y.dropped.nodes);v.dropped.edges=v.dropped.edges.concat(y.dropped.edges);return v},tweenNode:function(y,v,x){var w=g.getNode(y);if(w){c.tween.to(w,v,x)}},tweenEdge:function(w,v,z,y){if(y===undefined){g._tweenEdge(w,v,z)}else{var x=g.getEdges(w,v);$.each(x,function(A,B){g._tweenEdge(B,z,y)})}},_tweenEdge:function(w,v,x){if(w&&w._id!==undefined){c.tween.to(w,v,x)}},_updateGeometry:function(y){if(y!=undefined){var v=(y.epoch1||A.y*u.height>1){n=_newBounds;return true}else{return false}},energy:function(){return a},bounds:function(){var w=null;var v=null;$.each(c.nodes,function(z,y){if(!w){w=new Point(y._p);v=new Point(y._p);return}var x=y._p;if(x.x===null||x.y===null){return}if(x.x>w.x){w.x=x.x}if(x.y>w.y){w.y=x.y}if(x.x0){c.kernel.graphChanged(j);j=[];h=null}},};c.kernel=Kernel(g);c.tween=c.kernel.tween||null;Node.prototype.__defineGetter__("p",function(){var w=this;var v={};v.__defineGetter__("x",function(){return w._p.x});v.__defineSetter__("x",function(x){c.kernel.particleModified(w._id,{x:x})});v.__defineGetter__("y",function(){return w._p.y});v.__defineSetter__("y",function(x){c.kernel.particleModified(w._id,{y:x})});v.__proto__=Point.prototype;return v});Node.prototype.__defineSetter__("p",function(v){this._p.x=v.x;this._p.y=v.y;c.kernel.particleModified(this._id,{x:v.x,y:v.y})});Node.prototype.__defineGetter__("mass",function(){return this._mass});Node.prototype.__defineSetter__("mass",function(v){this._mass=v;c.kernel.particleModified(this._id,{m:v})});Node.prototype.__defineSetter__("tempMass",function(v){c.kernel.particleModified(this._id,{_m:v})});Node.prototype.__defineGetter__("fixed",function(){return this._fixed});Node.prototype.__defineSetter__("fixed",function(v){this._fixed=v;c.kernel.particleModified(this._id,{f:v?1:0})});return g}; + /* barnes-hut.js */ var BarnesHutTree=function(){var b=[];var a=0;var e=null;var d=0.5;var c={init:function(g,h,f){d=f;a=0;e=c._newBranch();e.origin=g;e.size=h.subtract(g)},insert:function(j){var f=e;var g=[j];while(g.length){var h=g.shift();var m=h._m||h.m;var p=c._whichQuad(h,f);if(f[p]===undefined){f[p]=h;f.mass+=m;if(f.p){f.p=f.p.add(h.p.multiply(m))}else{f.p=h.p.multiply(m)}}else{if("origin" in f[p]){f.mass+=(m);if(f.p){f.p=f.p.add(h.p.multiply(m))}else{f.p=h.p.multiply(m)}f=f[p];g.unshift(h)}else{var l=f.size.divide(2);var n=new Point(f.origin);if(p[0]=="s"){n.y+=l.y}if(p[1]=="e"){n.x+=l.x}var o=f[p];f[p]=c._newBranch();f[p].origin=n;f[p].size=l;f.mass=m;f.p=h.p.multiply(m);f=f[p];if(o.p.x===h.p.x&&o.p.y===h.p.y){var k=l.x*0.08;var i=l.y*0.08;o.p.x=Math.min(n.x+l.x,Math.max(n.x,o.p.x-k/2+Math.random()*k));o.p.y=Math.min(n.y+l.y,Math.max(n.y,o.p.y-i/2+Math.random()*i))}g.push(o);g.unshift(h)}}}},applyForces:function(m,g){var f=[e];while(f.length){node=f.shift();if(node===undefined){continue}if(m===node){continue}if("f" in node){var k=m.p.subtract(node.p);var l=Math.max(1,k.magnitude());var i=((k.magnitude()>0)?k:Point.random(1)).normalize();m.applyForce(i.multiply(g*(node._m||node.m)).divide(l*l))}else{var j=m.p.subtract(node.p.divide(node.mass)).magnitude();var h=Math.sqrt(node.size.x*node.size.y);if(h/j>d){f.push(node.ne);f.push(node.nw);f.push(node.se);f.push(node.sw)}else{var k=m.p.subtract(node.p.divide(node.mass));var l=Math.max(1,k.magnitude());var i=((k.magnitude()>0)?k:Point.random(1)).normalize();m.applyForce(i.multiply(g*(node.mass)).divide(l*l))}}}},_whichQuad:function(i,f){if(i.p.exploded()){return null}var h=i.p.subtract(f.origin);var g=f.size.divide(2);if(h.y-1){o.splice(p,1)}delete c.particles[r];delete l.particles[r]},modifyNode:function(r,p){if(r in c.particles){var q=c.particles[r];if("x" in p){q.p.x=p.x}if("y" in p){q.p.y=p.y}if("m" in p){q.m=p.m}if("f" in p){q.fixed=(p.f===1)}if("_m" in p){if(q._m===undefined){q._m=q.m}q.m=p._m}}},addSpring:function(t){var s=t.id;var p=t.l;var r=c.particles[t.fm];var q=c.particles[t.to];if(r!==undefined&&q!==undefined){c.springs[s]=new Spring(r,q,p,i.stiffness);k.push(c.springs[s]);r.connections++;q.connections++;delete l.particles[t.fm];delete l.particles[t.to]}},dropSpring:function(s){var r=s.id;var q=c.springs[r];q.point1.connections--;q.point2.connections--;var p=$.inArray(q,k);if(p>-1){k.splice(p,1)}delete c.springs[r]},_update:function(p){d++;$.each(p,function(q,r){if(r.t in i){i[r.t](r)}});return d},tick:function(){i.tendParticles();i.eulerIntegrator(i.dt);i.tock()},tock:function(){var p=[];$.each(c.particles,function(r,q){p.push(r);p.push(q.p.x);p.push(q.p.y)});if(h){h({geometry:p,epoch:d,energy:b,bounds:g})}},tendParticles:function(){$.each(c.particles,function(q,p){if(p._m!==undefined){if(Math.abs(p.m-p._m)<1){p.m=p._m;delete p._m}else{p.m*=0.98}}p.v.x=p.v.y=0})},eulerIntegrator:function(p){if(i.repulsion>0){if(i.theta>0){i.applyBarnesHutRepulsion()}else{i.applyBruteForceRepulsion()}}if(i.stiffness>0){i.applySprings()}i.applyCenterDrift();if(i.gravity){i.applyCenterGravity()}i.updateVelocity(p);i.updatePosition(p)},applyBruteForceRepulsion:function(){$.each(c.particles,function(q,p){$.each(c.particles,function(s,r){if(p!==r){var u=p.p.subtract(r.p);var v=Math.max(1,u.magnitude());var t=((u.magnitude()>0)?u:Point.random(1)).normalize();p.applyForce(t.multiply(i.repulsion*(r._m||r.m)*0.5).divide(v*v*0.5));r.applyForce(t.multiply(i.repulsion*(p._m||p.m)*0.5).divide(v*v*-0.5))}})})},applyBarnesHutRepulsion:function(){if(!g.topleft||!g.bottomright){return}var q=new Point(g.bottomright);var p=new Point(g.topleft);f.init(p,q,i.theta);$.each(c.particles,function(s,r){f.insert(r)});$.each(c.particles,function(s,r){f.applyForces(r,i.repulsion)})},applySprings:function(){$.each(c.springs,function(t,p){var s=p.point2.p.subtract(p.point1.p);var q=p.length-s.magnitude();var r=((s.magnitude()>0)?s:Point.random(1)).normalize();p.point1.applyForce(r.multiply(p.k*q*-0.5));p.point2.applyForce(r.multiply(p.k*q*0.5))})},applyCenterDrift:function(){var q=0;var r=new Point(0,0);$.each(c.particles,function(t,s){r.add(s.p);q++});if(q==0){return}var p=r.divide(-q);$.each(c.particles,function(t,s){s.applyForce(p)})},applyCenterGravity:function(){$.each(c.particles,function(r,p){var q=p.p.multiply(-1);p.applyForce(q.multiply(i.repulsion/100))})},updateVelocity:function(p){$.each(c.particles,function(t,q){if(q.fixed){q.v=new Point(0,0);q.f=new Point(0,0);return}var s=q.v.magnitude();q.v=q.v.add(q.f.multiply(p)).multiply(1-i.friction);q.f.x=q.f.y=0;var r=q.v.magnitude();if(r>j){q.v=q.v.divide(r*r)}})},updatePosition:function(q){var r=0,p=0,u=0;var t=null;var s=null;$.each(c.particles,function(w,v){v.p=v.p.add(v.v.multiply(q));var x=v.v.magnitude();var z=x*x;r+=z;p=Math.max(z,p);u++;if(!t){t=new Point(v.p.x,v.p.y);s=new Point(v.p.x,v.p.y);return}var y=v.p;if(y.x===null||y.y===null){return}if(y.x>t.x){t.x=y.x}if(y.y>t.y){t.y=y.y}if(y.x1000){e.stop()}else{}}else{c=null}},tock:function(h){h.type="geometry";postMessage(h)},modifyNode:function(i,h){a.modifyNode(i,h);e.go()},modifyPhysics:function(h){a.modifyPhysics(h)},update:function(h){var i=a._update(h)}};return e};var physics=PhysicsWorker();onmessage=function(a){if(!a.data.type){postMessage("¿kérnèl?");return}if(a.data.type=="physics"){var b=a.data.physics;physics.init(a.data.physics);return}switch(a.data.type){case"modify":physics.modifyNode(a.data.id,a.data.mods);break;case"changes":physics.update(a.data.changes);physics.go();break;case"start":physics.go();break;case"stop":physics.stop();break;case"sys":var b=a.data.param||{};if(!isNaN(b.timeout)){physics.timeout(b.timeout)}physics.modifyPhysics(b);physics.go();break}}; + })() + + + arbor = (typeof(arbor)!=='undefined') ? arbor : {} + $.extend(arbor, { + // object constructors (don't use ‘new’, just call them) + ParticleSystem:ParticleSystem, + Point:function(x, y){ return new Point(x, y) }, + + // immutable object with useful methods + etc:{ + trace:trace, // ƒ(msg) -> safe console logging + dirname:dirname, // ƒ(path) -> leading part of path + basename:basename, // ƒ(path) -> trailing part of path + ordinalize:ordinalize, // ƒ(num) -> abbrev integers (and add commas) + objcopy:objcopy, // ƒ(old) -> clone an object + objcmp:objcmp, // ƒ(a, b, strict_ordering) -> t/f comparison + objkeys:objkeys, // ƒ(obj) -> array of all keys in obj + objmerge:objmerge, // ƒ(dst, src) -> like $.extend but non-destructive + uniq:uniq, // ƒ(arr) -> array of unique items in arr + arbor_path:arbor_path, // ƒ() -> guess the directory of the lib code + } + }) + +})(this.jQuery) \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js new file mode 100644 index 0000000..e21756b --- /dev/null +++ b/public/js/main.js @@ -0,0 +1,249 @@ +(function($) { + + var Renderer = function(canvas) { + var canvas = $(canvas).get(0) + var ctx = canvas.getContext('2d'); + var win = $(window) + var particleSystem + + var that = { + init: function(system) { + // + // the particle system will call the init function once, right before the + // first frame is to be drawn. it's a good place to set up the canvas and + // to pass the canvas size to the particle system + // + // save a reference to the particle system for use in the .redraw() loop + particleSystem = system + + win.resize(that.resize) + that.resize() + + // set up some event handlers to allow for node-dragging + that.initMouseHandling() + }, + + // XXX why doesn't this fill the screen? + resize: function() { + canvas.width = win.width() - 160 + canvas.height = .75 * win.height() - 160 + particleSystem.screenSize(canvas.width, canvas.height) + particleSystem.screenPadding(80) // leave an extra 80px of whitespace per side + that.redraw() + }, + + redraw: function() { + if (!particleSystem) return + + // + // redraw will be called repeatedly during the run whenever the node positions + // change. the new positions for the nodes can be accessed by looking at the + // .p attribute of a given node. however the p.x & p.y values are in the coordinates + // of the particle system rather than the screen. you can either map them to + // the screen yourself, or use the convenience iterators .eachNode (and .eachEdge) + // which allow you to step through the actual node objects but also pass an + // x,y point in the screen's coordinate system + // + ctx.fillStyle = "white" + ctx.fillRect(0,0, canvas.width, canvas.height) + + particleSystem.eachEdge(function(edge, pt1, pt2) { + // edge: {source:Node, target:Node, length:#, data:{}} + // pt1: {x:#, y:#} source position in screen coords + // pt2: {x:#, y:#} target position in screen coords + + // draw a line from pt1 to pt2 + ctx.strokeStyle = edge.data.color || '#444' + ctx.lineWidth = 1 + ctx.beginPath() + ctx.moveTo(pt1.x, pt1.y) + ctx.lineTo(pt2.x, pt2.y) + ctx.stroke() + }) + + particleSystem.eachNode(function(node, pt) { + // node: {mass:#, p:{x,y}, name:"", data:{}} + // pt: {x:#, y:#} node position in screen coords + + // draw a rectangle centered at pt + var w = 10 + ctx.fillStyle = node.data.color || 'black' + ctx.fillRect(pt.x-w/2, pt.y-w/2, w,w) + + ctx.font = "14pt Helvetica" + ctx.fillText(node.data.type + ' ' + node.data.name, pt.x + w, pt.y) + if (node.data.extra) { + ctx.fillText(node.data.extra, pt.x + w, pt.y + 15) + } + }) + }, + + initMouseHandling: function() { + // no-nonsense drag and drop (thanks springy.js) + var dragged = null; + + // set up a handler object that will initially listen for mousedowns then + // for moves and mouseups while dragging + var handler = { + clicked: function(e) { + var pos = $(canvas).offset(); + _mouseP = arbor.Point(e.pageX-pos.left, e.pageY-pos.top) + dragged = particleSystem.nearest(_mouseP); + + if (dragged && dragged.node !== null) { + console.log('clicked ' + dragged.node.name) + // while we're dragging, don't let physics move the node + dragged.node.fixed = true + } + + $(canvas).bind('mousemove', handler.dragged) + win.bind('mouseup', handler.dropped) + + return false + }, + dragged: function(e) { + var pos = $(canvas).offset(); + var s = arbor.Point(e.pageX-pos.left, e.pageY-pos.top) + + if (dragged && dragged.node !== null) { + var p = particleSystem.fromScreen(s) + dragged.node.p = p + } + + return false + }, + + dropped: function(e) { + if (dragged === null || dragged.node === undefined) return + if (dragged.node !== null) dragged.node.fixed = false + dragged.node.tempMass = 1000 + dragged = null + $(canvas).unbind('mousemove', handler.dragged) + win.unbind('mouseup', handler.dropped) + _mouseP = null + return false + } + } + + // start listening + $(canvas).mousedown(handler.clicked); + + }, + + } + return that + } + + var lastLinks = {} // maps deferred IDs to the last link of their chain + , EventHandlers = + { 'datasource-created': function(sys, ev) { + sys.addNode(ev.id, { mass: 20.0, fixed: true, color: 'goldenrod', type: ev.data.class || 'data source', name: ev.id }) + } + , 'datasource-removed': function(sys, ev) { + sys.getNode(ev.id).data.extra = '(complete)' + } + , 'datasource-cancelled': function(sys, ev) { + sys.getNode(ev.id).data.color = 'yellow' + } + , 'datasource-executed': function(sys, ev) { + sys.getNode(ev.id).data.color = '#005' + } + , 'datasource-completed': function(sys, ev) { + var node = sys.getNode(ev.id) + node.data.color = '#050' + node.data.extra = ev.data.result + } + + , 'deferred-created': function(sys, ev) { + sys.addNode(ev.id, { mass: 10.0, type: ev.data.class || 'deferred', name: ev.id }) + lastLinks[ev.id] = ev.id + console.log('deferred-created: data source id is ', ev.data.dataSourceId, 'ev.id = ', ev.id) + if (ev.data.dataSourceId) { + sys.addEdge(ev.data.dataSourceId, ev.id) + } + } + , 'deferred-removed': function(sys, ev) { + // delete lastLinks[ev.id] + // if (ev.data && ev.data.dataSourceId) { + // sys.pruneEdge(sys.getEdge(ev.data.dataSourceId, ev.id)) + // } + // else { + // sys.pruneNode(ev.id) + // } + } + , 'deferred-cancelled': function(sys, ev) { + sys.getNode(ev.id).data.color = 'yellow' + } + , 'deferred-resolved': function(sys, ev) { + var node = sys.getNode(ev.id) + node.data.color = '#050' + node.data.extra = '(complete: ' + ev.data.result + ')' + } + , 'deferred-rejected': function(sys, ev) { + var node = sys.getNode(ev.id) + node.data.color = '#500' + node.data.extra = '(complete: ' + ev.data.result + ')' + } + , 'deferred-paused': function(sys, ev) { + sys.getNode(ev.id).data.extra = '(paused)' + if (ev.data.distantLink) { + sys.addEdge(ev.data.distantLink, ev.data.myLink, { color: '#FF510C' }) + } + } + , 'deferred-unpaused': function(sys, ev) { + delete sys.getNode(ev.id).data.extra + } + , 'deferred-link-added': function(sys, ev) { + var data = { mass: 5.0, name: ev.data.linkId, extra: ev.data.file + ':' + ev.data.line } + if (ev.data.finalizer) { + data.type = 'finalizer' + data.color = '#744' + sys.addNode(ev.data.linkId, data) + sys.addEdge(ev.id, ev.data.linkId) + } + else { + data.type = 'link' + data.color = '#666' + sys.addNode(ev.data.linkId, data) + sys.addEdge(lastLinks[ev.id], ev.data.linkId) + lastLinks[ev.id] = ev.data.linkId + } + } + , 'deferred-link-ran': function(sys, ev) { + var node = sys.getNode(ev.data.linkId) + node.data.color = '#999' + node.data.extra += '\n' + ev.data.result + } + } + + $(document).ready(function() { + var sys = arbor.ParticleSystem(50, 1, 0.9) // create the system with sensible repulsion/stiffness/friction + sys.parameters({ gravity: false }) // use center-gravity to make the graph settle nicely (ymmv) + sys.renderer = Renderer("#viewport") // our newly created renderer will have its .init() method called shortly by sys... + + var events = [] + , deferreds = {} + var socket = io.connect('http://localhost') + socket.on('event', function (ev) { + events.push(ev) + console.log('"' + ev.name + '"', '"' + ev.id + '"', ev.payload) + if (ev.payload && ev.payload !== '(null)') { + try { + ev.data = JSON.parse(ev.payload) + } + catch (e) { + console.warn('payload is not JSON: ', ev.payload) + } + } + var handler = EventHandlers[ev.name] + if (typeof handler === 'function') { + handler(sys, ev) + sys.renderer.redraw() + } + else { + console.error('no handler for ' + ev.name) + } + }) + }) + +})(this.jQuery) diff --git a/server.js b/server.js new file mode 100644 index 0000000..2b3875a --- /dev/null +++ b/server.js @@ -0,0 +1,129 @@ +var fs = require('fs') + , net = require('net') + , express = require('express') + , RedisStore = require('connect-redis')(express) + , socketIO = require('socket.io') + , config = require('./config') + , tools = require('./tools') + + // tcp server, communicates w/ Deferred host + , server + + // express and socket.io app + , app + , io + + // Event log + , events = [] + +exports.start = function() { + startEventListener() + startWebServer() +} + +function parseEvents(s) { + var eventStrings = s.trim().split('\r\n') + eventStrings.forEach(function(s) { + var i = s.indexOf(' ') + , j = s.indexOf(' ', i + 1) + , ev = {} + if (j === -1) j = s.length + ev.name = s.slice(0, i) + ev.id = s.slice(i + 1, j) + ev.payload = s.slice(j + 1) || null + events.push(ev) + io.sockets.emit('event', ev) + }) +} + +function startEventListener() { + server = net.createServer(function(conn) { + var buf = '' + + console.log(conn.fd + ' CONNECT') + + conn.on('data', function(d) { + buf += d + if (buf[buf.length - 1] === '\n') { + parseEvents(buf) + buf = '' + } + }) + + conn.on('end', function() { + console.log(conn.fd + ' END') + }) + }) + + server.on('error', function(e) { + console.log(e) + console.log(e.stack) + }) + + server.listen(config.port, config.host) +} + +// A custom static file handler + +var FileTypes = +{ css: 'text/css' +, html: 'text/html' +, js: 'application/javascript' +} + +function staticFileHandler(file, type) { + var path = file.charAt(0) === '/' ? file : (__dirname + '/public/' + file) + , data = fs.readFileSync(path) + , dev = config.env === 'development' + , ext + + if (!type) { + ext = (file || '').toLowerCase().split('.').pop() + type = '' + (FileTypes[ext] || 'application/octet-stream') + } + + return function(req, res) { + // Always reload in development + if (dev) data = fs.readFileSync(path, 'binary') + res.send(data, { 'content-type': type }) + } +} + +function startWebServer() { + app = express.createServer( + express.logger({ format: config.loggerFormat }) + , express.bodyParser() + , express.methodOverride() + , express.cookieParser() + , express.session({ secret: config.sessionSecret, store: new RedisStore() }) + ) + + app.configure('development', function() { + app.use(express.errorHandler({ dumpExceptions: true, showStack: true })) + }) + + app.configure('production', function() { + app.use(express.errorHandler()) + }) + + app.get('/', staticFileHandler('index.html')) + app.get('/js/main.js', staticFileHandler('js/main.js')) + app.get('/js/arbor.js', staticFileHandler('js/arbor.js')) + app.get('/js/arbor-tween.js', staticFileHandler('js/arbor-tween.js')) + app.get('/css/style.css', staticFileHandler('css/style.css')) + + socketize(app) + app.listen(config.webPort, config.webHost) +} + +function socketize(app) { + io = socketIO.listen(app) + io.sockets.on('connection', function(socket) { + // New clients get the backlog of events + events.forEach(function(ev) { + socket.emit('event', ev) + }) + }) +} + +if (require.main === module) exports.start() diff --git a/tools.js b/tools.js new file mode 100644 index 0000000..1ab9a29 --- /dev/null +++ b/tools.js @@ -0,0 +1,13 @@ +module.exports = { mixin: mixin } + +function mixin(a, b, c) { + for (var key in b) { + a[key] = b[key] + } + if (c) { + var args = [].slice.call(arguments) + args.splice(1, 1) + return mixin.apply(null, args) + } + return a +}