first commit

This commit is contained in:
Sami Samhuri 2011-07-03 14:07:05 -07:00
commit d284851af6
12 changed files with 760 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules
*.tmproj

24
config.js Normal file
View file

@ -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
}

125
model.js Normal file
View file

@ -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)
}

32
package.json Normal file
View file

@ -0,0 +1,32 @@
{ "name" : "deferredvis-server"
, "version" : "0.0.1"
, "description" : "Visualize your Deferreds"
, "keywords" :
[ "deferred"
, "visualize"
]
, "author" : "Sami Samhuri <sami@samhuri.net>"
, "maintainers" : [
"Sami Samhuri <sami@samhuri.net>"
]
, "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" : {}
}

15
protocol.txt Normal file
View file

@ -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: _ }

5
public/css/style.css Normal file
View file

@ -0,0 +1,5 @@
body { margin: 0
; padding: 20px
; font: 12px/17px 'Helvetica Neue', Helvetica, sans-serif
; background: #f9f9f9
}

13
public/index.html Normal file
View file

@ -0,0 +1,13 @@
<!doctype html>
<meta charset=utf-8>
<title>Deferred Visualizer</title>
<link rel="stylesheet" href="css/style.css">
<canvas id="viewport" width="800" height="600"></canvas>
<noscript><p align="center">You need JavaScript for this bro</p></noscript>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script src="js/arbor.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="js/main.js"></script>

86
public/js/arbor-tween.js Normal file
View file

@ -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;i<len;i++){args.push("arguments["+i+"]")}eval("console.log("+args.join(",")+")")};var dirname=function(a){var b=a.replace(/^\/?(.*?)\/?$/,"$1").split("/");b.pop();return"/"+b.join("/")};var basename=function(b){var c=b.replace(/^\/?(.*?)\/?$/,"$1").split("/");var a=c.pop();if(a==""){return null}else{return a}};var _ordinalize_re=/(\d)(?=(\d\d\d)+(?!\d))/g;var ordinalize=function(a){var b=""+a;if(a<11000){b=(""+a).replace(_ordinalize_re,"$1,")}else{if(a<1000000){b=Math.floor(a/1000)+"k"}else{if(a<1000000000){b=(""+Math.floor(a/1000)).replace(_ordinalize_re,"$1,")+"m"}}}return b};var nano=function(a,b){return a.replace(/\{([\w\-\.]*)}/g,function(f,c){var d=c.split("."),e=b[d.shift()];$.each(d,function(){if(e.hasOwnProperty(this)){e=e[this]}else{e=f}});return e})};var objcopy=function(a){if(a===undefined){return undefined}if(a===null){return null}if(a.parentNode){return a}switch(typeof a){case"string":return a.substring(0);break;case"number":return a+0;break;case"boolean":return a===true;break}var b=($.isArray(a))?[]:{};$.each(a,function(d,c){b[d]=objcopy(c)});return b};var objmerge=function(d,b){d=d||{};b=b||{};var c=objcopy(d);for(var a in b){c[a]=b[a]}return c};var objcmp=function(e,c,d){if(!e||!c){return e===c}if(typeof e!=typeof c){return false}if(typeof e!="object"){return e===c}else{if($.isArray(e)){if(!($.isArray(c))){return false}if(e.length!=c.length){return false}}else{var h=[];for(var f in e){if(e.hasOwnProperty(f)){h.push(f)}}var g=[];for(var f in c){if(c.hasOwnProperty(f)){g.push(f)}}if(!d){h.sort();g.sort()}if(h.join(",")!==g.join(",")){return false}}var i=true;$.each(e,function(a){var b=objcmp(e[a],c[a]);i=i&&b;if(!i){return false}});return i}};var objkeys=function(b){var a=[];$.each(b,function(d,c){if(b.hasOwnProperty(d)){a.push(d)}});return a};var objcontains=function(c){if(!c||typeof c!="object"){return false}for(var b=1,a=arguments.length;b<a;b++){if(c.hasOwnProperty(arguments[b])){return true}}return false};var uniq=function(b){var a=b.length;var d={};for(var c=0;c<a;c++){d[b[c]]=true}return objkeys(d)};var arbor_path=function(){var a=$("script").map(function(b){var c=$(this).attr("src");if(!c){return}if(c.match(/arbor[^\/\.]*.js|dev.js/)){return c.match(/.*\//)||"/"}});if(a.length>0){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(f<Math.abs(k)){f=k;var h=i/4}else{var h=i/(2*Math.PI)*Math.asin(k/f)}return -(f*Math.pow(2,10*(g-=1))*Math.sin((g*j-h)*(2*Math.PI)/i))+e},elasticout: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(f<Math.abs(k)){f=k;var h=i/4}else{var h=i/(2*Math.PI)*Math.asin(k/f)}return f*Math.pow(2,-10*g)*Math.sin((g*j-h)*(2*Math.PI)/i)+k+e},elasticinout:function(g,e,k,j){var h=1.70158;var i=0;var f=k;if(g==0){return e}if((g/=j/2)==2){return e+k}if(!i){i=j*(0.3*1.5)}if(f<Math.abs(k)){f=k;var h=i/4}else{var h=i/(2*Math.PI)*Math.asin(k/f)}if(g<1){return -0.5*(f*Math.pow(2,10*(g-=1))*Math.sin((g*j-h)*(2*Math.PI)/i))+e}return f*Math.pow(2,-10*(g-=1))*Math.sin((g*j-h)*(2*Math.PI)/i)*0.5+k+e},backin:function(f,e,i,h,g){if(g==undefined){g=1.70158}return i*(f/=h)*f*((g+1)*f-g)+e},backout:function(f,e,i,h,g){if(g==undefined){g=1.70158}return i*((f=f/h-1)*f*((g+1)*f+g)+1)+e},backinout:function(f,e,i,h,g){if(g==undefined){g=1.70158}if((f/=h/2)<1){return i/2*(f*f*(((g*=(1.525))+1)*f-g))+e}return i/2*((f-=2)*f*(((g*=(1.525))+1)*f+g)+2)+e},bouncein:function(f,e,h,g){return h-a.bounceOut(g-f,0,h,g)+e},bounceout:function(f,e,h,g){if((f/=g)<(1/2.75)){return h*(7.5625*f*f)+e}else{if(f<(2/2.75)){return h*(7.5625*(f-=(1.5/2.75))*f+0.75)+e}else{if(f<(2.5/2.75)){return h*(7.5625*(f-=(2.25/2.75))*f+0.9375)+e}else{return h*(7.5625*(f-=(2.625/2.75))*f+0.984375)+e}}}},bounceinout:function(f,e,h,g){if(f<g/2){return a.bounceIn(f*2,0,h,g)*0.5+e}return a.bounceOut(f*2-g,0,h,g)*0.5+h*0.5+e}};return a})();
/* tween.js */ var Tween=function(){var a={};var c=true;var b={init:function(){return b},busy:function(){var e=false;for(var d in a){e=true;break}return e},to:function(g,e,p){var f=new Date().valueOf();var d={};var q={from:{},to:{},colors:{},node:g,t0:f,t1:f+e*1000,dur:e*1000};var o="linear";for(var j in p){if(j=="easing"){var h=p[j].toLowerCase();if(h in Easing){o=h}continue}else{if(j=="delay"){var m=(p[j]||0)*1000;q.t0+=m;q.t1+=m;continue}}if(Colors.validate(p[j])){q.colors[j]=[Colors.decode(g.data[j]),Colors.decode(p[j]),p[j]];d[j]=true}else{q.from[j]=(g.data[j]!=undefined)?g.data[j]:p[j];q.to[j]=p[j];d[j]=true}}q.ease=Easing[o];if(a[g._id]===undefined){a[g._id]=[]}a[g._id].push(q);if(a.length>1){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 ParticleSystems .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)

67
public/js/arbor.js Normal file

File diff suppressed because one or more lines are too long

249
public/js/main.js Normal file
View file

@ -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)

129
server.js Normal file
View file

@ -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()

13
tools.js Normal file
View file

@ -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
}