├── test ├── mocha.opts └── mincer_test.js ├── templates ├── project │ ├── Procfile │ ├── lib │ │ └── .empty_directory │ ├── widgets │ │ ├── text │ │ │ ├── text.coffee │ │ │ ├── text.html │ │ │ └── text.scss │ │ ├── clock │ │ │ ├── clock.html │ │ │ ├── clock.coffee │ │ │ └── clock.scss │ │ ├── iframe │ │ │ ├── iframe.html │ │ │ ├── iframe.scss │ │ │ └── iframe.coffee │ │ ├── image │ │ │ ├── image.html │ │ │ ├── image.coffee │ │ │ └── image.scss │ │ ├── list │ │ │ ├── list.coffee │ │ │ ├── list.html │ │ │ └── list.scss │ │ ├── graph │ │ │ ├── graph.html │ │ │ ├── graph.coffee │ │ │ └── graph.scss │ │ ├── comments │ │ │ ├── comments.html │ │ │ ├── comments.coffee │ │ │ └── comments.scss │ │ ├── meter │ │ │ ├── meter.html │ │ │ ├── meter.coffee │ │ │ └── meter.scss │ │ └── number │ │ │ ├── number.html │ │ │ ├── number.coffee │ │ │ └── number.scss │ ├── README.md │ ├── public │ │ ├── favicon.ico │ │ └── 404.html │ ├── assets │ │ ├── images │ │ │ └── logo.png │ │ ├── fonts │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ │ ├── javascripts │ │ │ ├── application.coffee │ │ │ ├── gridster │ │ │ │ ├── jquery.leanModal.min.js │ │ │ │ └── jquery.gridster.min.js │ │ │ ├── dashing.gridster.coffee │ │ │ └── jquery.knob.js │ │ └── stylesheets │ │ │ ├── jquery.gridster.css.old │ │ │ ├── jquery.gridster.css │ │ │ ├── application.scss │ │ │ └── font-awesome.css │ ├── package.json.tt │ ├── jobs │ │ ├── convergence.job.js │ │ ├── sample.job.js │ │ └── buzzwords.job.js │ ├── server.js │ └── dashboards │ │ ├── layout.jade │ │ ├── layout.ejs │ │ ├── sample.jade │ │ ├── sample.ejs │ │ ├── sampletv.jade │ │ └── sampletv.ejs ├── widget │ └── %name% │ │ ├── %name%.html │ │ ├── %name%.scss.tt │ │ └── %name%.coffee.tt ├── job │ └── %name%.job.js └── dashboard │ ├── %name%.ejs.tt │ └── %name%.jade.tt ├── .gitignore ├── .npmignore ├── Makefile ├── lib ├── logger.js ├── thor.js ├── utils.js ├── scaffold.js └── dashing.js ├── index.js ├── LICENSE ├── package.json ├── README.md ├── HISTORY.md ├── javascripts ├── dashing.coffee ├── batman.jquery.js └── eventsource.js └── bin └── dashing-js /test/mocha.opts: -------------------------------------------------------------------------------- 1 | -R spec -------------------------------------------------------------------------------- /templates/project/Procfile: -------------------------------------------------------------------------------- 1 | web: node server.js -------------------------------------------------------------------------------- /templates/project/lib/.empty_directory: -------------------------------------------------------------------------------- 1 | .empty_directory -------------------------------------------------------------------------------- /templates/widget/%name%/%name%.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /templates/widget/%name%/%name%.scss.tt: -------------------------------------------------------------------------------- 1 | .widget-{{= name }} { 2 | 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | *.log 3 | *.out 4 | *.pid 5 | *.swp 6 | *.swo 7 | node_modules/ -------------------------------------------------------------------------------- /templates/project/widgets/text/text.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Text extends Dashing.Widget 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | .DS_STORE 3 | *.log 4 | *.out 5 | *.pid 6 | *.swp 7 | *.swo 8 | test/ -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @NODE_ENV=test ./node_modules/.bin/mocha 4 | 5 | .PHONY: test 6 | -------------------------------------------------------------------------------- /templates/project/README.md: -------------------------------------------------------------------------------- 1 | Check out http://fabiocaseri.github.io/dashing-js for more information. -------------------------------------------------------------------------------- /templates/project/widgets/clock/clock.html: -------------------------------------------------------------------------------- 1 |

2 |

-------------------------------------------------------------------------------- /templates/project/widgets/iframe/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /templates/project/widgets/image/image.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /templates/project/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabiocaseri/dashing-js/HEAD/templates/project/public/favicon.ico -------------------------------------------------------------------------------- /templates/project/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabiocaseri/dashing-js/HEAD/templates/project/assets/images/logo.png -------------------------------------------------------------------------------- /templates/project/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabiocaseri/dashing-js/HEAD/templates/project/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /templates/project/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabiocaseri/dashing-js/HEAD/templates/project/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /templates/project/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabiocaseri/dashing-js/HEAD/templates/project/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /templates/project/widgets/iframe/iframe.scss: -------------------------------------------------------------------------------- 1 | .widget-iframe { 2 | padding: 3px 0px 0px 0px !important; 3 | 4 | iframe { 5 | width: 100%; 6 | height: 100%; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /templates/job/%name%.job.js: -------------------------------------------------------------------------------- 1 | function myJob() { 2 | send_event('widget_id', { }); 3 | } 4 | 5 | // Schedule every minute 6 | setInterval(myJob, 1 * 60 * 1000); 7 | // ... and execute immediately first 8 | myJob(); 9 | -------------------------------------------------------------------------------- /templates/project/widgets/list/list.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.List extends Dashing.Widget 2 | ready: -> 3 | if @get('unordered') 4 | $(@node).find('ol').remove() 5 | else 6 | $(@node).find('ul').remove() 7 | -------------------------------------------------------------------------------- /templates/project/widgets/graph/graph.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | -------------------------------------------------------------------------------- /templates/project/widgets/text/text.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | 7 |

8 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | var winston = require('winston'); 2 | 3 | module.exports = logger = new winston.Logger({ 4 | transports: [ 5 | new winston.transports.Console({timestamp: true}), 6 | ], 7 | exitOnError: false, 8 | handleExceptions: true 9 | }); 10 | -------------------------------------------------------------------------------- /templates/dashboard/%name%.ejs.tt: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 | <%- contentFor('title') %> 9 | My dashboard title -------------------------------------------------------------------------------- /templates/dashboard/%name%.jade.tt: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block title 4 | | My dashboard title 5 | 6 | block content 7 | div.gridster 8 | ul 9 | li(data-row='1', data-col='1', data-sizex='1', data-sizey='1') 10 | div(data-id='my_widget', data-view='Text', data-title='My Widget') 11 | -------------------------------------------------------------------------------- /templates/project/package.json.tt: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{= name }}", 3 | "description": "{{= name }}", 4 | "author": "", 5 | "version": "0.0.1", 6 | "private": true, 7 | "dependencies": { 8 | "dashing-js": "~{{= dashing.version }}" 9 | }, 10 | "engine": "node >= 0.10" 11 | } 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var dashing = require('./lib/dashing'); 2 | 3 | module.exports = { 4 | Dashing: dashing.Dashing, 5 | logger: dashing.logger, 6 | 7 | // Exports utility modules 8 | async: require('async'), 9 | 'fs-extra': require('fs-extra'), 10 | request: require('request'), 11 | walker: require('walker') 12 | }; -------------------------------------------------------------------------------- /templates/project/widgets/comments/comments.html: -------------------------------------------------------------------------------- 1 |

2 |
3 |

4 |

5 |
6 | 7 |

8 | -------------------------------------------------------------------------------- /templates/project/widgets/meter/meter.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | -------------------------------------------------------------------------------- /templates/project/widgets/iframe/iframe.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Iframe extends Dashing.Widget 2 | 3 | ready: -> 4 | # This is fired when the widget is done being rendered 5 | 6 | onData: (data) -> 7 | # Handle incoming data 8 | # You can access the html node of this widget with `@node` 9 | # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. 10 | -------------------------------------------------------------------------------- /templates/project/widgets/image/image.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Image extends Dashing.Widget 2 | 3 | ready: -> 4 | # This is fired when the widget is done being rendered 5 | 6 | onData: (data) -> 7 | # Handle incoming data 8 | # You can access the html node of this widget with `@node` 9 | # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. 10 | -------------------------------------------------------------------------------- /templates/widget/%name%/%name%.coffee.tt: -------------------------------------------------------------------------------- 1 | class Dashing.{{= Thor.Util.camel_case(name) }} extends Dashing.Widget 2 | 3 | ready: -> 4 | # This is fired when the widget is done being rendered 5 | 6 | onData: (data) -> 7 | # Handle incoming data 8 | # You can access the html node of this widget with `@node` 9 | # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. -------------------------------------------------------------------------------- /templates/project/widgets/number/number.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | 7 |

8 | 9 |

10 | 11 |

12 | -------------------------------------------------------------------------------- /templates/project/jobs/convergence.job.js: -------------------------------------------------------------------------------- 1 | // Populate the graph with some random points 2 | var points = []; 3 | for (var i = 1; i <= 10; i++) { 4 | points.push({x: i, y: Math.floor(Math.random() * 50)}); 5 | } 6 | var last_x = points[points.length - 1].x; 7 | 8 | setInterval(function() { 9 | points.shift(); 10 | points.push({x: ++last_x, y: Math.floor(Math.random() * 50)}); 11 | 12 | send_event('convergence', {points: points}); 13 | }, 2 * 1000); 14 | -------------------------------------------------------------------------------- /templates/project/widgets/meter/meter.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Meter extends Dashing.Widget 2 | 3 | @accessor 'value', Dashing.AnimatedValue 4 | 5 | constructor: -> 6 | super 7 | @observe 'value', (value) -> 8 | $(@node).find(".meter").val(value).trigger('change') 9 | 10 | ready: -> 11 | meter = $(@node).find(".meter") 12 | meter.attr("data-bgcolor", meter.css("background-color")) 13 | meter.attr("data-fgcolor", meter.css("color")) 14 | meter.knob() 15 | -------------------------------------------------------------------------------- /templates/project/server.js: -------------------------------------------------------------------------------- 1 | var dashing = require('dashing-js').Dashing(); 2 | 3 | // Set your auth token here 4 | //dashing.auth_token = 'YOUR_AUTH_TOKEN'; 5 | 6 | /* 7 | dashing.protected = function(req, res, next) { 8 | // Put any authentication code you want in here. 9 | // This method is run before accessing any resource. 10 | // if (true) next(); 11 | } 12 | */ 13 | 14 | // Set your default dashboard here 15 | //dashing.default_dashboard = 'mydashboard'; 16 | 17 | dashing.start(); 18 | -------------------------------------------------------------------------------- /templates/project/widgets/clock/clock.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Clock extends Dashing.Widget 2 | 3 | ready: -> 4 | setInterval(@startTime, 500) 5 | 6 | startTime: => 7 | today = new Date() 8 | 9 | h = today.getHours() 10 | m = today.getMinutes() 11 | s = today.getSeconds() 12 | m = @formatTime(m) 13 | s = @formatTime(s) 14 | @set('time', h + ":" + m + ":" + s) 15 | @set('date', today.toDateString()) 16 | 17 | formatTime: (i) -> 18 | if i < 10 then "0" + i else i -------------------------------------------------------------------------------- /templates/project/widgets/clock/clock.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #dc5945; 5 | 6 | // ---------------------------------------------------------------------------- 7 | // Widget-clock styles 8 | // ---------------------------------------------------------------------------- 9 | .widget-clock { 10 | 11 | background-color: $background-color; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /templates/project/widgets/image/image.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #4b4b4b; 5 | 6 | // ---------------------------------------------------------------------------- 7 | // Widget-image styles 8 | // ---------------------------------------------------------------------------- 9 | .widget-image { 10 | 11 | background-color: $background-color; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /templates/project/jobs/sample.job.js: -------------------------------------------------------------------------------- 1 | var current_valuation = 0; 2 | var current_karma = 0; 3 | 4 | setInterval(function() { 5 | var last_valuation = current_valuation; 6 | var last_karma = current_karma; 7 | current_valuation = Math.floor(Math.random() * 100); 8 | current_karma = Math.floor(Math.random() * 200000); 9 | 10 | send_event('valuation', {current: current_valuation, last: last_valuation}); 11 | send_event('karma', {current: current_karma, last: last_karma}); 12 | send_event('synergy', {value: Math.floor(Math.random() * 100)}); 13 | }, 2 * 1000); 14 | -------------------------------------------------------------------------------- /templates/project/widgets/list/list.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |
    4 |
  1. 5 | 6 | 7 |
  2. 8 |
9 | 10 | 16 | 17 |

18 |

19 | -------------------------------------------------------------------------------- /templates/project/jobs/buzzwords.job.js: -------------------------------------------------------------------------------- 1 | var buzzwords = ['Paradigm shift', 'Leverage', 'Pivoting', 'Turn-key', 'Streamlininess', 'Exit strategy', 'Synergy', 'Enterprise', 'Web 2.0']; 2 | var buzzword_counts = {}; 3 | 4 | setInterval(function() { 5 | var random_buzzword = buzzwords[Math.floor(Math.random() * buzzwords.length)]; 6 | var value = buzzword_counts[random_buzzword] && buzzword_counts[random_buzzword].value || 0; 7 | buzzword_counts[random_buzzword] = { 8 | label: random_buzzword, 9 | value: (value + 1) % 30 10 | }; 11 | var data = []; 12 | for (var i in buzzword_counts) { 13 | data.push(buzzword_counts[i]); 14 | } 15 | send_event('buzzwords', { items: data }); 16 | }, 2 * 1000); 17 | -------------------------------------------------------------------------------- /templates/project/widgets/comments/comments.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Comments extends Dashing.Widget 2 | 3 | @accessor 'quote', -> 4 | "“#{@get('current_comment')?.body}”" 5 | 6 | ready: -> 7 | @currentIndex = 0 8 | @commentElem = $(@node).find('.comment-container') 9 | @nextComment() 10 | @startCarousel() 11 | 12 | onData: (data) -> 13 | @currentIndex = 0 14 | 15 | startCarousel: -> 16 | setInterval(@nextComment, 8000) 17 | 18 | nextComment: => 19 | comments = @get('comments') 20 | if comments 21 | @commentElem.fadeOut => 22 | @currentIndex = (@currentIndex + 1) % comments.length 23 | @set 'current_comment', comments[@currentIndex] 24 | @commentElem.fadeIn() 25 | -------------------------------------------------------------------------------- /templates/project/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This Dashboard doesn't exist. 5 | 17 | 18 | 19 | 20 | 21 |
22 |

Drats! That Dashboard doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | -------------------------------------------------------------------------------- /templates/project/widgets/text/text.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #ec663c; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.7); 8 | 9 | // ---------------------------------------------------------------------------- 10 | // Widget-text styles 11 | // ---------------------------------------------------------------------------- 12 | .widget-text { 13 | 14 | background-color: $background-color; 15 | 16 | .title { 17 | color: $title-color; 18 | } 19 | 20 | .more-info { 21 | color: $moreinfo-color; 22 | } 23 | 24 | .updated-at { 25 | color: rgba(255, 255, 255, 0.7); 26 | } 27 | 28 | 29 | &.large h3 { 30 | font-size: 65px; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /templates/project/widgets/comments/comments.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #eb9c3c; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.7); 8 | 9 | // ---------------------------------------------------------------------------- 10 | // Widget-comment styles 11 | // ---------------------------------------------------------------------------- 12 | .widget-comments { 13 | 14 | background-color: $background-color; 15 | 16 | .title { 17 | color: $title-color; 18 | margin-bottom: 15px; 19 | } 20 | 21 | .name { 22 | padding-left: 5px; 23 | } 24 | 25 | .comment-container { 26 | display: none; 27 | } 28 | 29 | .more-info { 30 | color: $moreinfo-color; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /templates/project/widgets/number/number.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Number extends Dashing.Widget 2 | @accessor 'current', Dashing.AnimatedValue 3 | 4 | @accessor 'difference', -> 5 | if @get('last') 6 | last = parseInt(@get('last')) 7 | current = parseInt(@get('current')) 8 | if last != 0 9 | diff = Math.abs(Math.round((current - last) / last * 100)) 10 | "#{diff}%" 11 | else 12 | "" 13 | 14 | @accessor 'arrow', -> 15 | if @get('last') 16 | return 'fa fa-arrow-right' if parseInt(@get('current')) == parseInt(@get('last')) 17 | if parseInt(@get('current')) > parseInt(@get('last')) then 'fa fa-arrow-up' else 'fa fa-arrow-down' 18 | 19 | onData: (data) -> 20 | if data.status 21 | # clear existing "status-*" classes 22 | $(@get('node')).attr 'class', (i,c) -> 23 | c.replace /\bstatus-\S+/g, '' 24 | # add new class 25 | $(@get('node')).addClass "status-#{data.status}" 26 | -------------------------------------------------------------------------------- /templates/project/widgets/meter/meter.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #9c4274; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.3); 8 | 9 | $meter-background: darken($background-color, 15%); 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-meter styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-meter { 15 | 16 | background-color: $background-color; 17 | 18 | input.meter { 19 | background-color: $meter-background; 20 | color: #fff; 21 | } 22 | 23 | .title { 24 | color: $title-color; 25 | } 26 | 27 | .more-info { 28 | color: $moreinfo-color; 29 | } 30 | 31 | .updated-at { 32 | color: rgba(0, 0, 0, 0.3); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /templates/project/assets/javascripts/application.coffee: -------------------------------------------------------------------------------- 1 | # dashing.js is located in the dashing framework 2 | # It includes jquery & batman for you. 3 | #= require dashing.js 4 | 5 | #= require_directory . 6 | #= require_tree ../../widgets 7 | 8 | console.log("Yeah! The dashboard has started!") 9 | 10 | Dashing.on 'ready', -> 11 | Dashing.widget_margins ||= [5, 5] 12 | Dashing.widget_base_dimensions ||= [300, 360] 13 | Dashing.numColumns ||= 4 14 | 15 | contentWidth = (Dashing.widget_base_dimensions[0] + Dashing.widget_margins[0] * 2) * Dashing.numColumns 16 | 17 | Batman.setImmediate -> 18 | $('.gridster').width(contentWidth) 19 | $('.gridster ul:first').gridster 20 | widget_margins: Dashing.widget_margins 21 | widget_base_dimensions: Dashing.widget_base_dimensions 22 | avoid_overlapped_widgets: !Dashing.customGridsterLayout 23 | draggable: 24 | stop: Dashing.showGridsterInstructions 25 | start: -> Dashing.currentWidgetPositions = Dashing.getWidgetPositions() 26 | -------------------------------------------------------------------------------- /templates/project/widgets/number/number.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #47bbb3; 5 | $value-color: #fff; 6 | 7 | $title-color: rgba(255, 255, 255, 0.7); 8 | $moreinfo-color: rgba(255, 255, 255, 0.7); 9 | 10 | // ---------------------------------------------------------------------------- 11 | // Widget-number styles 12 | // ---------------------------------------------------------------------------- 13 | .widget-number { 14 | 15 | background-color: $background-color; 16 | 17 | .title { 18 | color: $title-color; 19 | } 20 | 21 | .value { 22 | color: $value-color; 23 | } 24 | 25 | .change-rate { 26 | font-weight: 500; 27 | font-size: 30px; 28 | color: $value-color; 29 | } 30 | 31 | .more-info { 32 | color: $moreinfo-color; 33 | } 34 | 35 | .updated-at { 36 | color: rgba(0, 0, 0, 0.3); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /templates/project/assets/javascripts/gridster/jquery.leanModal.min.js: -------------------------------------------------------------------------------- 1 | // leanModal v1.1 by Ray Stone - http://finelysliced.com.au 2 | // Dual licensed under the MIT and GPL 3 | 4 | (function($){$.fn.extend({leanModal:function(options){var defaults={top:100,overlay:0.5,closeButton:null};var overlay=$("
");$("body").append(overlay);options=$.extend(defaults,options);return this.each(function(){var o=options;$(this).click(function(e){var modal_id=$(this).attr("href");$("#lean_overlay").click(function(){close_modal(modal_id)});$(o.closeButton).click(function(){close_modal(modal_id)});var modal_height=$(modal_id).outerHeight();var modal_width=$(modal_id).outerWidth(); 5 | $("#lean_overlay").css({"display":"block",opacity:0});$("#lean_overlay").fadeTo(200,o.overlay);$(modal_id).css({"display":"block","position":"fixed","opacity":0,"z-index":11000,"left":50+"%","margin-left":-(modal_width/2)+"px","top":o.top+"px"});$(modal_id).fadeTo(200,1);e.preventDefault()})});function close_modal(modal_id){$("#lean_overlay").fadeOut(200);$(modal_id).css({"display":"none"})}}})})(jQuery); 6 | -------------------------------------------------------------------------------- /templates/project/dashboards/layout.jade: -------------------------------------------------------------------------------- 1 | !!! 5 2 | html(lang='en') 3 | head 4 | meta(charset='utf-8') 5 | meta(name="description", content='') 6 | meta(name="viewport", content='width=device-width') 7 | meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1') 8 | title 9 | block title 10 | | dashing-js 11 | 12 | // The javascript and css are managed by sprockets. The files can be found in the /assets folder 13 | script(src='/assets/application.js') 14 | link(rel='stylesheet', href='/assets/application.css') 15 | 16 | link(rel='stylesheet', href='//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700', type='text/css') 17 | link(rel='icon', href='/assets/favicon.ico') 18 | block stylesheets 19 | body 20 | div#container 21 | block content 22 | 23 | block footer 24 | if settings.development 25 | div#saving-instructions 26 | p 27 | i Paste the following at the bottom of #{dashboard}.jade 28 | textarea#gridster-code 29 | a#save-gridster(href='#saving-instructions') Save this layout 30 | 31 | block scripts 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2013 Fabio Caseri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /templates/project/widgets/graph/graph.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Graph extends Dashing.Widget 2 | 3 | @accessor 'current', -> 4 | return @get('displayedValue') if @get('displayedValue') 5 | points = @get('points') 6 | if points 7 | points[points.length - 1].y 8 | 9 | ready: -> 10 | container = $(@node).parent() 11 | # Gross hacks. Let's fix this. 12 | width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) + Dashing.widget_margins[0] * 2 * (container.data("sizex") - 1) 13 | height = (Dashing.widget_base_dimensions[1] * container.data("sizey")) 14 | @graph = new Rickshaw.Graph( 15 | element: @node 16 | width: width 17 | height: height 18 | series: [ 19 | { 20 | color: "#fff", 21 | data: [{x:0, y:0}] 22 | } 23 | ] 24 | ) 25 | 26 | @graph.series[0].data = @get('points') if @get('points') 27 | 28 | x_axis = new Rickshaw.Graph.Axis.Time(graph: @graph) 29 | y_axis = new Rickshaw.Graph.Axis.Y(graph: @graph, tickFormat: Rickshaw.Fixtures.Number.formatKMBT) 30 | @graph.render() 31 | 32 | onData: (data) -> 33 | if @graph 34 | @graph.series[0].data = data.points 35 | @graph.render() 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashing-js", 3 | "description": "Port of Dashing to node.js", 4 | "author": "Fabio Caseri (http://www.fabiocaseri.com)", 5 | "version": "0.1.5", 6 | "license": "MIT", 7 | "dependencies": { 8 | "async": "~0.2.7", 9 | "coffee-script": "~1.6.2", 10 | "commander": "~2.1.0", 11 | "consolidate": "~0.10.0", 12 | "ejs": "~0.8.3", 13 | "express": "~3.4.6", 14 | "express-ejs-layouts": "~0.3.1", 15 | "fs-extra": "~0.8.1", 16 | "jade": "~0.35.0", 17 | "mincer": "~0.5.12", 18 | "node-sass": "~0.7.0", 19 | "node-schedule": "~0.1.8", 20 | "prompt": "~0.2.11", 21 | "request": "~2.30.0", 22 | "unzip": "~0.1.7", 23 | "walker": "~1.0.3", 24 | "winston": "~0.7.1" 25 | }, 26 | "devDependencies": { 27 | "ejs": "*", 28 | "jade": "*", 29 | "mocha": "*", 30 | "should": "*" 31 | }, 32 | "keywords": [ 33 | "dashing", 34 | "dashboard", 35 | "web" 36 | ], 37 | "repository": "git://github.com/fabiocaseri/dashing-js", 38 | "main": "index", 39 | "bin": { 40 | "dashing-js": "./bin/dashing-js" 41 | }, 42 | "scripts": { 43 | "test": "make test" 44 | }, 45 | "engine": "node >= 0.10" 46 | } 47 | -------------------------------------------------------------------------------- /templates/project/dashboards/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%- title %> 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | <% if (typeof(stylesheets) !== 'undefined') { %> 18 | <%- stylesheets %> 19 | <% } %> 20 | 21 | 22 |
23 | <%- body %> 24 |
25 | 26 | <% if (settings.development) { %> 27 |
28 |

Paste the following at the top of <%= dashboard %>.ejs

29 | 30 |
31 | Save this layout 32 | <% } %> 33 | 34 | -------------------------------------------------------------------------------- /templates/project/dashboards/sample.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block title 4 | | My super sweet dashboard 5 | 6 | block content 7 | div.gridster 8 | ul 9 | li(data-row='1', data-col='1', data-sizex='2', data-sizey='1') 10 | div(data-id='welcome', data-view='Text', data-title='Hello', data-text='This is your shiny new dashboard.', data-moreinfo='Protip: You can drag the widgets around!') 11 | li(data-row='1', data-col='1', data-sizex='1', data-sizey='1') 12 | div(data-id='synergy', data-view='Meter', data-title='Synergy', data-min='0', data-max='100') 13 | li(data-row='1', data-col='1', data-sizex='1', data-sizey='2') 14 | div(data-id='buzzwords', data-view='List', data-unordered='true', data-title='Buzzwords', data-moreinfo='# of times said around the office') 15 | li(data-row='1', data-col='1', data-sizex='1', data-sizey='1') 16 | div(data-id='valuation', data-view='Number', data-title='Current Valuation', data-moreinfo='In billions', data-prefix='$') 17 | li(data-row='1', data-col='1', data-sizex='2', data-sizey='1') 18 | div(data-id='convergence', data-view='Graph', data-title='Convergence', style='background-color:#ff9618') 19 | center 20 | div(style='font-size: 12px') Try this: curl -d '{ "auth_token": "YOUR_AUTH_TOKEN", "text": "Hey, Look what I can do!" }' -H "Content-Type: application/json" http://#{request.header('host')}/widgets/welcome 21 | -------------------------------------------------------------------------------- /templates/project/widgets/list/list.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #12b0c5; 5 | $value-color: #fff; 6 | 7 | $title-color: rgba(255, 255, 255, 0.7); 8 | $label-color: rgba(255, 255, 255, 0.7); 9 | $moreinfo-color: rgba(255, 255, 255, 0.7); 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-list styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-list { 15 | 16 | background-color: $background-color; 17 | vertical-align: top !important; 18 | 19 | .title { 20 | color: $title-color; 21 | } 22 | 23 | ol, ul { 24 | margin: 0 15px; 25 | text-align: left; 26 | color: $label-color; 27 | } 28 | 29 | ol { 30 | list-style-position: inside; 31 | } 32 | 33 | li { 34 | margin-bottom: 5px; 35 | } 36 | 37 | .list-nostyle { 38 | list-style: none; 39 | } 40 | 41 | .label { 42 | color: $label-color; 43 | } 44 | 45 | .value { 46 | float: right; 47 | margin-left: 12px; 48 | font-weight: 600; 49 | color: $value-color; 50 | } 51 | 52 | .updated-at { 53 | color: rgba(0, 0, 0, 0.3); 54 | } 55 | 56 | .more-info { 57 | color: $moreinfo-color; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /templates/project/assets/javascripts/dashing.gridster.coffee: -------------------------------------------------------------------------------- 1 | #= require_directory ./gridster 2 | 3 | # This file enables gridster integration (http://gridster.net/) 4 | # Delete it if you'd rather handle the layout yourself. 5 | # You'll miss out on a lot if you do, but we won't hold it against you. 6 | 7 | Dashing.gridsterLayout = (positions) -> 8 | Dashing.customGridsterLayout = true 9 | positions = positions.replace(/^"|"$/g, '') 10 | positions = $.parseJSON(positions) 11 | widgets = $("[data-row^=]") 12 | for widget, index in widgets 13 | $(widget).attr('data-row', positions[index].row) 14 | $(widget).attr('data-col', positions[index].col) 15 | 16 | Dashing.getWidgetPositions = -> 17 | $(".gridster ul:first").gridster().data('gridster').serialize() 18 | 19 | Dashing.showGridsterInstructions = -> 20 | newWidgetPositions = Dashing.getWidgetPositions() 21 | 22 | unless JSON.stringify(newWidgetPositions) == JSON.stringify(Dashing.currentWidgetPositions) 23 | Dashing.currentWidgetPositions = newWidgetPositions 24 | $('#save-gridster').slideDown() 25 | $('#gridster-code').text(" 26 | 31 | ") 32 | 33 | $ -> 34 | $('#save-gridster').leanModal() 35 | 36 | $('#save-gridster').click -> 37 | $('#save-gridster').slideUp() 38 | -------------------------------------------------------------------------------- /templates/project/dashboards/sample.ejs: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  • 4 |
    5 |
  • 6 | 7 |
  • 8 |
    9 |
  • 10 | 11 |
  • 12 |
    13 |
  • 14 | 15 |
  • 16 |
    17 |
  • 18 | 19 |
  • 20 |
    21 |
  • 22 |
23 |
Try this: curl -d '{ "auth_token": "YOUR_AUTH_TOKEN", "text": "Hey, Look what I can do!" }' -H "Content-Type: application/json" http://<%=request.header('host')%>/widgets/welcome
24 |
25 | <%- contentFor('title') %> 26 | My super sweet dashboard -------------------------------------------------------------------------------- /templates/project/widgets/graph/graph.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #dc5945; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.3); 8 | $tick-color: rgba(0, 0, 0, 0.4); 9 | 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-graph styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-graph { 15 | 16 | background-color: $background-color; 17 | position: relative; 18 | 19 | 20 | svg { 21 | position: absolute; 22 | opacity: 0.4; 23 | fill-opacity: 0.4; 24 | left: 0px; 25 | top: 0px; 26 | } 27 | 28 | .title, .value { 29 | position: relative; 30 | z-index: 99; 31 | } 32 | 33 | .title { 34 | color: $title-color; 35 | } 36 | 37 | .more-info { 38 | color: $moreinfo-color; 39 | font-weight: 600; 40 | font-size: 20px; 41 | margin-top: 0; 42 | } 43 | 44 | .x_tick { 45 | position: absolute; 46 | bottom: 0; 47 | .title { 48 | font-size: 20px; 49 | color: $tick-color; 50 | opacity: 0.5; 51 | padding-bottom: 3px; 52 | } 53 | } 54 | 55 | .y_ticks { 56 | font-size: 20px; 57 | fill: $tick-color; 58 | fill-opacity: 1; 59 | } 60 | 61 | .domain { 62 | display: none; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /templates/project/assets/stylesheets/jquery.gridster.css.old: -------------------------------------------------------------------------------- 1 | /*! gridster.js - v0.1.0 - 2012-08-14 2 | * http://gridster.net/ 3 | * Copyright (c) 2012 ducksboard; Licensed MIT */ 4 | 5 | .gridster { 6 | position:relative; 7 | } 8 | 9 | .gridster > * { 10 | margin: 0 auto; 11 | -webkit-transition: height .4s; 12 | -moz-transition: height .4s; 13 | -o-transition: height .4s; 14 | -ms-transition: height .4s; 15 | transition: height .4s; 16 | } 17 | 18 | .gridster .gs_w{ 19 | z-index: 2; 20 | position: absolute; 21 | } 22 | 23 | .ready .gs_w:not(.preview-holder) { 24 | -webkit-transition: opacity .3s, left .3s, top .3s; 25 | -moz-transition: opacity .3s, left .3s, top .3s; 26 | -o-transition: opacity .3s, left .3s, top .3s; 27 | transition: opacity .3s, left .3s, top .3s; 28 | } 29 | 30 | .gridster .preview-holder { 31 | z-index: 1; 32 | position: absolute; 33 | background-color: #fff; 34 | border-color: #fff; 35 | opacity: 0.3; 36 | } 37 | 38 | .gridster .player-revert { 39 | z-index: 10!important; 40 | -webkit-transition: left .3s, top .3s!important; 41 | -moz-transition: left .3s, top .3s!important; 42 | -o-transition: left .3s, top .3s!important; 43 | transition: left .3s, top .3s!important; 44 | } 45 | 46 | .gridster .dragging { 47 | z-index: 10!important; 48 | -webkit-transition: all 0s !important; 49 | -moz-transition: all 0s !important; 50 | -o-transition: all 0s !important; 51 | transition: all 0s !important; 52 | } 53 | 54 | /* Uncomment this if you set helper : "clone" in draggable options */ 55 | /*.gridster .player { 56 | opacity:0; 57 | }*/ -------------------------------------------------------------------------------- /test/mincer_test.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , assert = require('assert') 3 | , Mincer = require('mincer'); 4 | 5 | describe('Mincer', function() { 6 | var templateProjectPath = path.resolve(__dirname, '../templates/project'); 7 | var mincer = {}; 8 | 9 | before(function(done) { 10 | mincer.environment = new Mincer.Environment(); 11 | mincer.assets_prefix = '/assets'; 12 | mincer.environment.appendPath(templateProjectPath + '/assets/javascripts'); 13 | mincer.environment.appendPath(templateProjectPath + '/assets/stylesheets'); 14 | mincer.environment.appendPath(templateProjectPath + '/assets/fonts'); 15 | mincer.environment.appendPath(templateProjectPath + '/assets/images'); 16 | mincer.environment.appendPath(templateProjectPath + '/widgets'); 17 | mincer.environment.appendPath(path.resolve(__dirname, '../javascripts')); 18 | 19 | done(); 20 | }); 21 | 22 | it('should compile "application.js" without errors', function(done) { 23 | mincer.environment.findAsset('application.js').compile(function(err, asset) { 24 | done(); 25 | }); 26 | }); 27 | 28 | 29 | it('should compile "application.css" without errors', function(done) { 30 | mincer.environment.findAsset('application.css').compile(function(err, asset) { 31 | done(); 32 | }); 33 | }); 34 | 35 | /* Mincer 0.5.x */ 36 | /* 37 | it('should compile "application.js" without errors', function(done) { 38 | if (mincer.environment.findAsset('application.js')) { 39 | done(); 40 | } 41 | }); 42 | 43 | 44 | it('should compile "application.css" without errors', function(done) { 45 | if (mincer.environment.findAsset('application.css')) { 46 | done(); 47 | } 48 | }); 49 | */ 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /lib/thor.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs-extra') 2 | , path = require('path') 3 | , scaffold = require('./scaffold'); 4 | 5 | exports.Thor = { 6 | source_root: path.resolve(__dirname, '../templates'), 7 | directory: function(src, dest, locals, options) { 8 | dest = dest || src; 9 | locals = locals || {}; 10 | locals.Thor = this; 11 | options = options || {}; 12 | 13 | scaffold([this.source_root, src].join(path.sep), dest, { 14 | engine: 'ejs', 15 | engine_opts: { 16 | open: '{{', 17 | close: '}}' 18 | }, 19 | ext: 'tt', 20 | namepre: '%', 21 | namesuf: '%', 22 | data: locals, 23 | filterFile: function(file, stat) { 24 | return !file.match(/\.empty_directory$/); 25 | } 26 | }, function(err) { 27 | if (err) console.error(err); 28 | }); 29 | }, 30 | empty_directory: function(dest, options) { 31 | fs.mkdirp(dest, function(err) { 32 | if (err) console.error(err); 33 | }); 34 | }, 35 | Util: { 36 | camel_case: function(str) { 37 | if (str.charAt(0) !== '_') str = '_' + str; 38 | return str.replace(/(_[a-z0-9])/mg, function(s) {return s.replace('_', '').toUpperCase()}); 39 | }, 40 | dash_case: function(str) { 41 | if (str.charAt(0) >= 'A' && str.charAt(0) <= 'Z') str = str.substring(0,1).toLowerCase() + str.substring(1); 42 | return str.replace(/([A-Z])/g, function(s){return '-' + s.toLowerCase()}); 43 | }, 44 | snake_case: function(str) { 45 | if (str.charAt(0) >= 'A' && str.charAt(0) <= 'Z') str = str.substring(0,1).toLowerCase() + str.substring(1); 46 | return str.replace(/([A-Z])/g, function(s){return '_' + s.toLowerCase()}); 47 | } 48 | } 49 | }; -------------------------------------------------------------------------------- /templates/project/assets/stylesheets/jquery.gridster.css: -------------------------------------------------------------------------------- 1 | /*! gridster.js - v0.1.0 - 2013-04-09 2 | * http://gridster.net/ 3 | * Copyright (c) 2013 ducksboard; Licensed MIT */ 4 | 5 | .gridster { 6 | position:relative; 7 | } 8 | 9 | .gridster > * { 10 | margin: 0 auto; 11 | -webkit-transition: height .4s; 12 | -moz-transition: height .4s; 13 | -o-transition: height .4s; 14 | -ms-transition: height .4s; 15 | transition: height .4s; 16 | } 17 | 18 | .gridster .gs_w{ 19 | z-index: 2; 20 | position: absolute; 21 | } 22 | 23 | .ready .gs_w:not(.preview-holder) { 24 | -webkit-transition: opacity .3s, left .3s, top .3s; 25 | -moz-transition: opacity .3s, left .3s, top .3s; 26 | -o-transition: opacity .3s, left .3s, top .3s; 27 | transition: opacity .3s, left .3s, top .3s; 28 | } 29 | 30 | .ready .gs_w:not(.preview-holder) { 31 | -webkit-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; 32 | -moz-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; 33 | -o-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; 34 | transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; 35 | } 36 | 37 | .gridster .preview-holder { 38 | z-index: 1; 39 | position: absolute; 40 | background-color: #fff; 41 | border-color: #fff; 42 | opacity: 0.3; 43 | } 44 | 45 | .gridster .player-revert { 46 | z-index: 10!important; 47 | -webkit-transition: left .3s, top .3s!important; 48 | -moz-transition: left .3s, top .3s!important; 49 | -o-transition: left .3s, top .3s!important; 50 | transition: left .3s, top .3s!important; 51 | } 52 | 53 | .gridster .dragging { 54 | z-index: 10!important; 55 | -webkit-transition: all 0s !important; 56 | -moz-transition: all 0s !important; 57 | -o-transition: all 0s !important; 58 | transition: all 0s !important; 59 | } 60 | 61 | /* Uncomment this if you set helper : "clone" in draggable options */ 62 | /*.gridster .player { 63 | opacity:0; 64 | }*/ 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dashing-js 2 | 3 | Port of [Dashing](http://shopify.github.io/dashing/) to node.js 4 | 5 | [![NPM version](https://badge.fury.io/js/dashing-js.png)](http://badge.fury.io/js/dashing-js) 6 | [![Dependency Status](https://david-dm.org/fabiocaseri/dashing-js.png)](https://david-dm.org/fabiocaseri/dashing-js) 7 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/fabiocaseri/dashing-js/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 8 | 9 | ## Getting Started 10 | 11 | 1. Install from npm 12 | 13 | ```shell 14 | $ npm install -g dashing-js 15 | ``` 16 | 17 | 2. Generate a new project 18 | 19 | ```shell 20 | $ dashing-js new sweet_dashboard_project 21 | ``` 22 | 23 | 3. Change your directory to sweet_dashboard_project and install required modules 24 | 25 | ```shell 26 | $ cd sweet_dashboard_project && npm install 27 | ``` 28 | 29 | 4. Start the server! 30 | 31 | ```shell 32 | $ dashing-js start 33 | ``` 34 | 35 | 5. Point your browser at http://localhost:3030/ and have fun! 36 | 37 | *** 38 | 39 | Every new Dashing project comes with sample widgets & sample dashboards for you to explore. The directory is setup as follows: 40 | 41 | * Assets — All your images, fonts, and js/coffeescript libraries. Uses [Sprockets](https://github.com/sstephenson/sprockets) [Mincer](http://nodeca.github.io/mincer/). 42 | * Dashboards — One .jade file for each dashboard that contains the layout for the widgets. 43 | * Jobs — Your js/coffee jobs for fetching data (e.g for calling third party APIs like twitter). Name them *\*.job.js/\*.job.coffee* 44 | * Lib — Optional js/coffee files to help out your jobs. 45 | * Public — Static files that you want to serve. A good place for a favicon or a custom 404 page. 46 | * Widgets — All the html/css/coffee for individual widgets. 47 | 48 | Run `dashing-js` from command line to find out what command line tools are available to you. 49 | 50 | ## Deployment 51 | dashing-js is setup to be easily deployed on a Joyent Node SmartMachine. This means that: 52 | 53 | 1. The version of Node is defined in package.json 54 | 2. The main script to run is server.js 55 | 3. The web server port is pulled from process.env.PORT 56 | 57 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs-extra'); 2 | 3 | /** 4 | * Check if the given directory `path` is empty. 5 | * 6 | * @param {String} path 7 | * @param {Function} fn 8 | */ 9 | exports.emptyDirectory = function(path, fn) { 10 | fs.readdir(path, function(err, files){ 11 | if (err && 'ENOENT' != err.code) throw err; 12 | fn(!files || !files.length); 13 | }); 14 | }; 15 | 16 | /** 17 | * cp src dest. 18 | * 19 | * @param {String} src 20 | * @param {String} dest 21 | * @param {Function} fn 22 | */ 23 | exports.copy = function(src, dest, fn) { 24 | fs.copy(src, dest, function(err) { 25 | if (err) throw err; 26 | console.log(' \x1b[36mcreate\x1b[0m ' + dest); 27 | fn && fn(); 28 | }); 29 | }; 30 | 31 | /** 32 | * echo str > path. 33 | * 34 | * @param {String} path 35 | * @param {String} str 36 | * @param {Object} options 37 | * @param {Function} fn 38 | */ 39 | exports.write = function(path, str, options, fn) { 40 | fs.writeFile(path, str, options, function(err) { 41 | if (err) throw err; 42 | console.log(' \x1b[36mcreate\x1b[0m ' + path); 43 | fn && fn(); 44 | }); 45 | }; 46 | 47 | /** 48 | * mkdir -p. 49 | * 50 | * @param {String} path 51 | * @param {Function} fn 52 | */ 53 | exports.mkdir = function(path, fn) { 54 | fs.mkdirp(path, 0755, function(err){ 55 | if (err) throw err; 56 | console.log(' \033[36mcreate\033[0m ' + path); 57 | fn && fn(); 58 | }); 59 | }; 60 | 61 | /** 62 | * mkdir -p (sync). 63 | * 64 | * @param {String} path 65 | */ 66 | exports.mkdir.sync = function(path) { 67 | var r = fs.mkdirp.sync(path, 0755); 68 | r && console.log(' \033[36mcreate\033[0m ' + path); 69 | return r; 70 | }; 71 | 72 | /** 73 | * mv. 74 | * 75 | * @param {String} src 76 | * @param {String} dest 77 | * @param {Function} fn 78 | */ 79 | exports.mv = function(src, dest, fn) { 80 | fs.rename(src, dest, function(err){ 81 | if (err) throw err; 82 | console.log(' \033[36mmove\033[0m ' + src + ' -> ' + dest); 83 | fn && fn(); 84 | }); 85 | }; 86 | 87 | /** 88 | * mv (sync). 89 | * 90 | * @param {String} src 91 | * @param {String} dest 92 | */ 93 | exports.mv.sync = function(src, dest) { 94 | var r = fs.renameSync(src, dest); 95 | console.log(' \033[36mmove\033[0m ' + src + ' -> ' + dest); 96 | }; 97 | 98 | /** 99 | * Exit with the given `str`. 100 | * 101 | * @param {String} str 102 | */ 103 | exports.abort = function(str) { 104 | console.error(str); 105 | process.exit(1); 106 | }; 107 | -------------------------------------------------------------------------------- /templates/project/dashboards/sampletv.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block title 4 | | 1080p dashboard 5 | 6 | block content 7 | div.gridster 8 | ul 9 | li(data-row='1', data-col='1', data-sizex='1', data-sizey='1') 10 | div(data-view='Clock') 11 | i.fa.fa-clock-o.icon-background 12 | li(data-row='1', data-col='1', data-sizex='1', data-sizey='1') 13 | div(data-view='Image', data-image='/logo.png') 14 | li(data-row='1', data-col='1', data-sizex='2', data-sizey='1') 15 | div(data-id='welcome', data-view='Text', data-title='Hello', data-text='This is your shiny new 1080p dashboard.', data-moreinfo='Protip: You can drag the widgets around!') 16 | li(data-row='1', data-col='1', data-sizex='1', data-sizey='1') 17 | div(data-id='synergy', data-view='Meter', data-title='Synergy', data-min='0', data-max='100') 18 | li(data-row='1', data-col='1', data-sizex='1', data-sizey='1') 19 | div(data-id='synergy', data-view='Meter', data-moreinfo='In sync with my neighbour!', data-title='Synergy', data-min='0', data-max='100') 20 | li(data-row='1', data-col='1', data-sizex='1', data-sizey='2') 21 | div(data-id='buzzwords', data-view='List', data-unordered='true', data-title='Buzzwords', data-moreinfo='# of times said around the office') 22 | li(data-row='1', data-col='1', data-sizex='1', data-sizey='1') 23 | div(data-id='karma', data-view='Number', data-title='Karma', style='background-color:#96bf48;') 24 | i.fa.fa-heart.icon-background 25 | li(data-row='1', data-col='1', data-sizex='2', data-sizey='2') 26 | div(data-id='convergence', data-view='Graph', data-title='Convergence', style='background-color:#47bbb3;', data-moreinfo='poop') 27 | li(data-row='1', data-col='1', data-sizex='2', data-sizey='1') 28 | div(data-id='twitter_mentions', data-view='Comments', style='background-color:#ff9618;', data-moreinfo='Tweets tagged with #todayilearned') 29 | i.fa.fa-twitter.icon-background 30 | center 31 | div(style='font-size: 12px') Try this: curl -d '{ "auth_token": "YOUR_AUTH_TOKEN", "text": "Hey, Look what I can do!" }' -H "Content-Type: application/json" http://#{request.header('host')}/widgets/welcome 32 | 33 | block scripts 34 | script. 35 | $(function() { 36 | // These settings override the defaults set in application.coffee. You can do this on a per dashboard basis. 37 | Dashing.gridsterLayout('[{"col":2,"row":1},{"col":1,"row":1},{"col":3,"row":1},{"col":2,"row":2},{"col":3,"row":2},{"col":1,"row":2},{"col":5,"row":1},{"col":4,"row":2},{"col":2,"row":3}]') 38 | Dashing.widget_base_dimensions = [370, 340] 39 | Dashing.numColumns = 5 40 | }); 41 | -------------------------------------------------------------------------------- /templates/project/dashboards/sampletv.ejs: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |
    12 |
  • 13 |
    14 | 15 |
  • 16 | 17 |
  • 18 |
    19 |
  • 20 | 21 |
  • 22 |
    23 |
  • 24 | 25 |
  • 26 |
    27 |
  • 28 | 29 |
  • 30 |
    31 |
  • 32 | 33 |
  • 34 |
    35 |
  • 36 | 37 |
  • 38 |
    39 | 40 |
  • 41 | 42 |
  • 43 |
    44 |
  • 45 | 46 |
  • 47 |
    48 | 49 |
  • 50 | 51 |
52 |
Try this: curl -d '{ "auth_token": "YOUR_AUTH_TOKEN", "text": "Hey, Look what I can do!" }' -H "Content-Type: application/json" http://<%=request.header('host')%>/widgets/welcome
53 |
54 | <%- contentFor('title') %> 55 | 1080p dashboard -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 0.1.5 / 2013.12.27 2 | ------------------- 3 | 4 | * Updated libraries versions 5 | - request (~2.29.0 -> ~2.30.0) 6 | * Added IE9+ compatibility. Thanks to @arabold [PR #15] 7 | 8 | 9 | 0.1.4 / 2013.12.12 10 | ------------------- 11 | 12 | * Fixed indentation in jade template to properly show up default widget. Thanks to @arabold [PR #13] 13 | 14 | 15 | 0.1.3 / 2013.12.09 16 | ------------------- 17 | 18 | * Updated libraries versions 19 | - request (~2.27.0 -> ~2.29.0) 20 | * Use different tags when scaffolding with ejs. Fixes #12 21 | * Include custom stylesheets in ejs layout only if defined 22 | 23 | 24 | 0.1.2 / 2013.12.04 25 | ------------------- 26 | 27 | * Handle widgets with multiple capital letters (original [PR #181](https://github.com/Shopify/dashing/pull/181)) 28 | * Use right arrow icon if difference is zero in number widget. Fixes #11 29 | * Upgrade FontAwesome to version 4.0.3 and update new icon names ([Changes](https://github.com/FortAwesome/Font-Awesome/wiki/Upgrading-from-3.2.1-to-4)) 30 | * Use relative url for EventSource to allow running on sub-path (original [PR #209](https://github.com/Shopify/dashing/pull/209)) 31 | * Strip html by default in widgets. Users can disable this with the 'raw' filter. (orig [9f93895bd4](https://github.com/Shopify/dashing/commit/9f93895bd40aad02e88f7ed7bfd954c930aa27db)) 32 | * Update d3 (v3.3.11) and rickshaw (v1.4.5) 33 | 34 | 35 | 0.1.1 / 2013.12.03 36 | ------------------- 37 | 38 | * Fix number widgets in sample dashboard 39 | * Use right arrow icon if difference is zero in number widget. Fixes #11 40 | 41 | 42 | 0.1.0 / 2013.12.03 43 | ------------------- 44 | 45 | * Updated libraries versions 46 | - commander (~1.1.1 -> ~2.1.0) 47 | - consolidate (~0.9.0 -> ~0.10.0) 48 | - express (~3.2.0 -> ~3.4.6) 49 | - fs-extra (~0.6.0 -> ~0.8.1) 50 | - jade (~0.29.0 -> ~0.35.0) 51 | - mincer (~0.4.5 -> ~0.5.12) 52 | - node-sass (0.5.2 -> ~0.7.0) 53 | - request (~2.20.0 -> ~2.27.0) 54 | * Jade version 0.31.0 deprecated implicit text only support for scripts and styles. 55 | * Added library prompt (commander removed confirm & prompt methods) 56 | * Explicit flush response for SSE (connect 2.10.0) 57 | 58 | 59 | 0.0.10 / 2013.07.10 60 | ------------------- 61 | 62 | * Fixed `node-sass` version to 0.5.2 63 | 64 | 65 | 0.0.9 / 2013.07.09 66 | ------------------ 67 | 68 | * Merge fidgety's PL to stick `node-sass` version to 0.5.2 69 | 70 | 71 | 0.0.8 / 2013.07.04 72 | ------------------ 73 | 74 | * Fixed stderr redirection to log file 75 | * Fixed `winston` configuration 76 | * Fixed cli support for job files in coffeescript 77 | 78 | 79 | 0.0.7 / 2013.06.12 80 | ------------------ 81 | 82 | * Refactor to use `winston` logging 83 | * Fixed dameonize stdio redirection 84 | 85 | 86 | 0.0.6 / 2013.05.29 87 | ------------------ 88 | 89 | * Fixed support for coffee job files 90 | * Fixed dameonize stdio redirection 91 | 92 | 93 | 0.0.5 / 2013.05.17 94 | ------------------ 95 | 96 | * Use js native milliseconds when updating data on widgets 97 | 98 | 99 | 0.0.4 / 2013.05.15 100 | ------------------ 101 | 102 | * Possibility to write jobs in coffeescript 103 | 104 | 105 | 0.0.3 / 2013.05.11 106 | ------------------ 107 | 108 | * Updated `node-sass` version, re-enabled widgets animations 109 | * Updated FontAwesome assets to version 3.0.2 110 | * Support for process daemonize 111 | 112 | 113 | 0.0.2 / 2013.05.02 114 | ------------------ 115 | 116 | * Added ability to install widgets from zipballs 117 | * Added ability to add custom stylesheets 118 | * Merge original [PR #115](https://github.com/Shopify/dashing/pull/115) (Added suffix option for number widget) 119 | 120 | 121 | 0.0.1 / 2013.04.26 122 | ------------------ 123 | * First release 124 | -------------------------------------------------------------------------------- /javascripts/dashing.coffee: -------------------------------------------------------------------------------- 1 | #= require jquery 2 | #= require es5-shim 3 | #= require eventsource 4 | #= require batman 5 | #= require batman.jquery 6 | 7 | 8 | Batman.Filters.prettyNumber = (num) -> 9 | num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") unless isNaN(num) 10 | 11 | Batman.Filters.dashize = (str) -> 12 | dashes_rx1 = /([A-Z]+)([A-Z][a-z])/g; 13 | dashes_rx2 = /([a-z\d])([A-Z])/g; 14 | 15 | return str.replace(dashes_rx1, '$1_$2').replace(dashes_rx2, '$1_$2').replace(/_/g, '-').toLowerCase() 16 | 17 | Batman.Filters.shortenedNumber = (num) -> 18 | return num if isNaN(num) 19 | if num >= 1000000000 20 | (num / 1000000000).toFixed(1) + 'B' 21 | else if num >= 1000000 22 | (num / 1000000).toFixed(1) + 'M' 23 | else if num >= 1000 24 | (num / 1000).toFixed(1) + 'K' 25 | else 26 | num 27 | 28 | class window.Dashing extends Batman.App 29 | @root -> 30 | Dashing.params = Batman.URI.paramsFromQuery(window.location.search.slice(1)); 31 | 32 | class Dashing.Widget extends Batman.View 33 | constructor: -> 34 | # Set the view path 35 | @constructor::source = Batman.Filters.underscore(@constructor.getName()) 36 | super 37 | 38 | @mixin($(@node).data()) 39 | Dashing.widgets[@id] ||= [] 40 | Dashing.widgets[@id].push(@) 41 | @mixin(Dashing.lastEvents[@id]) # in case the events from the server came before the widget was rendered 42 | 43 | type = Batman.Filters.dashize(@view) 44 | $(@node).addClass("widget widget-#{type} #{@id}") 45 | 46 | @getName: -> 47 | # We would prefer using @constructor.name here but it's nonstandard and doesn't work on IE 48 | this.toString().match(/^function\s(.+)\(/)[1] 49 | 50 | @accessor 'updatedAtMessage', -> 51 | if updatedAt = @get('updatedAt') 52 | timestamp = new Date(updatedAt) 53 | hours = timestamp.getHours() 54 | minutes = ("0" + timestamp.getMinutes()).slice(-2) 55 | "Last updated at #{hours}:#{minutes}" 56 | 57 | @::on 'ready', -> 58 | Dashing.Widget.fire 'ready' 59 | 60 | receiveData: (data) => 61 | @mixin(data) 62 | @onData(data) 63 | 64 | onData: (data) => 65 | # Widgets override this to handle incoming data 66 | 67 | Dashing.AnimatedValue = 68 | get: Batman.Property.defaultAccessor.get 69 | set: (k, to) -> 70 | if !to? || isNaN(to) 71 | @[k] = to 72 | else 73 | timer = "interval_#{k}" 74 | num = if (!isNaN(@[k]) && @[k]?) then @[k] else 0 75 | unless @[timer] || num == to 76 | to = parseFloat(to) 77 | num = parseFloat(num) 78 | up = to > num 79 | num_interval = Math.abs(num - to) / 90 80 | @[timer] = 81 | setInterval => 82 | num = if up then Math.ceil(num+num_interval) else Math.floor(num-num_interval) 83 | if (up && num > to) || (!up && num < to) 84 | num = to 85 | clearInterval(@[timer]) 86 | @[timer] = null 87 | delete @[timer] 88 | @[k] = num 89 | @set k, to 90 | , 10 91 | @[k] = num 92 | 93 | Dashing.widgets = widgets = {} 94 | Dashing.lastEvents = lastEvents = {} 95 | Dashing.debugMode = false 96 | 97 | source = new EventSource('events') 98 | source.addEventListener 'open', (e) -> 99 | console.log("Connection opened") 100 | 101 | source.addEventListener 'error', (e)-> 102 | console.log("Connection error") 103 | if (e.readyState == EventSource.CLOSED) 104 | console.log("Connection closed") 105 | 106 | source.addEventListener 'message', (e) => 107 | data = JSON.parse(e.data) 108 | if lastEvents[data.id]?.updatedAt != data.updatedAt 109 | if Dashing.debugMode 110 | console.log("Received data for #{data.id}", data) 111 | lastEvents[data.id] = data 112 | if widgets[data.id]?.length > 0 113 | for widget in widgets[data.id] 114 | widget.receiveData(data) 115 | 116 | 117 | $(document).ready -> 118 | Dashing.run() 119 | -------------------------------------------------------------------------------- /lib/scaffold.js: -------------------------------------------------------------------------------- 1 | /** 2 | * scaffold.js 3 | * 4 | * [Forked from hardhat (https://github.com/cpsubrian/hardhat/)] 5 | * Copy files and directorys from `in` to `out`, applying templating and data. 6 | */ 7 | 8 | var fs = require('fs') 9 | , utils = require('./utils') 10 | , async = require('async') 11 | , cons = require('consolidate') 12 | , walker = require('walker'); 13 | 14 | var Array = { 15 | unique: function(array){ 16 | var a = []; 17 | for(var i = 0, l = array.length; i < l; i++) { 18 | for(var j = i + 1; j < l; j++) { 19 | // If this[i] is found later in the array 20 | if (array[i] === array[j]) j = ++i; 21 | } 22 | a.push(array[i]); 23 | } 24 | return a; 25 | } 26 | }; 27 | 28 | // `options` can be omitted to fallback to the defaults. 29 | // If you are using templates, set the template data on `options.data`. 30 | module.exports = function(dirIn, dirOut, options, callback) { 31 | 32 | if (typeof options === 'function') { 33 | callback = options; 34 | options = {}; 35 | } 36 | 37 | // Which consolidate-supported templating engine to use. 38 | options.engine = options.engine || 'handlebars'; 39 | 40 | options.engine_opts = options.engine_opts || {}; 41 | 42 | // Which extension hardhat should apply templating to. 43 | // HardHat will run '*.[ext]'' files through the templating engine and 44 | // remove '.[ext]' from the resulting file. 45 | // 46 | // For example: 'index.html.tpl' will run through the engine and be copied to 47 | // the destination as 'index.html'. 48 | options.ext = options.ext || 'tpl'; 49 | 50 | // The data option is passed to consolidate for templating. 51 | options.data = options.data || {}; 52 | 53 | // All templatable files are assumed to be utf8 by default. 54 | options.encoding = 'utf8'; 55 | 56 | options.namepre = options.namepre || '{{'; 57 | options.namesuf = options.namesuf || '}}'; 58 | 59 | options.filterFile = options.filterFile || function(){return true}; 60 | 61 | for (var key in options.engine_opts) { 62 | options.data[key] = options.engine_opts[key]; 63 | } 64 | 65 | // Ensure dirIn exists. 66 | if (!fs.existsSync(dirIn)) { 67 | return callback(new Error('The input directory does not exist (' + dirIn + ').')); 68 | } 69 | // If dirOut does not exist, try to create it. 70 | if (!fs.existsSync(dirOut)) { 71 | if (!utils.mkdir.sync(dirOut)) { 72 | return callback(new Error('Could not create the output directory (' + dirOut + ').')); 73 | } 74 | } 75 | 76 | // Ensure the caller has chosen a real templating engine. 77 | if (!cons[options.engine]) { 78 | return callback(new Error('The templating engine `' + options.engine + '` does not exist.')); 79 | } 80 | 81 | function evalFileName(name, data) { 82 | var matches = name.match(new RegExp(options.namepre + '\\w+' + options.namesuf, 'g')); 83 | if (matches) { 84 | matches = Array.unique(matches); 85 | for (var i in matches) { 86 | var val = matches[i].substring(1, matches[i].length - 1); 87 | if (data[val]) { 88 | name = name.replace(new RegExp(matches[i], 'g'), data[val]); 89 | } 90 | } 91 | } 92 | return name; 93 | } 94 | 95 | var files = []; 96 | 97 | // Recurse through dirIn. Create any directories found and build an array of 98 | // file paths to copy over. 99 | walker(dirIn) 100 | .on('error', function(err, entry, stat) { 101 | console.log('Got error ' + err + ' on entry ' + entry); 102 | }) 103 | .on('dir', function(dir, stat) { 104 | dir = evalFileName(dir, options.data); 105 | utils.mkdir.sync(dir.replace(dirIn, dirOut)); 106 | }) 107 | .on('file', function(file, stat) { 108 | if (options.filterFile(file, stat)) { 109 | files.push(file); 110 | } 111 | }) 112 | .on('end', function() { 113 | // Now copy over all the files, applying templating where necessary. 114 | var tasks = []; 115 | for (var i in files) { 116 | (function(file) { 117 | var dest = file.replace(dirIn, dirOut); 118 | tasks.push(function(done) { 119 | dest = evalFileName(dest, options.data); 120 | // If the file does not end with `.[ext]`, just copy it. 121 | if (!file.match(new RegExp('.*\.' + options.ext + '$'))) { 122 | utils.copy(file, dest, done); 123 | } else { 124 | // Otherwise, we need to run this file through the templating engine. 125 | dest = dest.replace('.' + options.ext, ''); 126 | cons[options.engine](file, options.data, function(err, templated) { 127 | if (err) return done(err); 128 | utils.write(dest, templated, {encoding: options.encoding}, done); 129 | }); 130 | } 131 | }); 132 | })(files[i]); 133 | } 134 | async.parallel(tasks, callback); 135 | }); 136 | }; 137 | -------------------------------------------------------------------------------- /javascripts/batman.jquery.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | Batman.extend(Batman.DOM, { 4 | querySelectorAll: function(node, selector) { 5 | return jQuery(selector, node); 6 | }, 7 | querySelector: function(node, selector) { 8 | return jQuery(selector, node)[0]; 9 | }, 10 | setInnerHTML: function(node, html) { 11 | var child, childNodes, result, _i, _j, _len, _len1; 12 | childNodes = (function() { 13 | var _i, _len, _ref, _results; 14 | _ref = node.childNodes; 15 | _results = []; 16 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 17 | child = _ref[_i]; 18 | _results.push(child); 19 | } 20 | return _results; 21 | })(); 22 | for (_i = 0, _len = childNodes.length; _i < _len; _i++) { 23 | child = childNodes[_i]; 24 | Batman.DOM.willRemoveNode(child); 25 | } 26 | result = jQuery(node).html(html); 27 | for (_j = 0, _len1 = childNodes.length; _j < _len1; _j++) { 28 | child = childNodes[_j]; 29 | Batman.DOM.didRemoveNode(child); 30 | } 31 | return result; 32 | }, 33 | removeNode: function(node) { 34 | var _ref; 35 | Batman.DOM.willRemoveNode(node); 36 | if ((_ref = node.parentNode) != null) { 37 | _ref.removeChild(node); 38 | } 39 | return Batman.DOM.didRemoveNode(node); 40 | }, 41 | destroyNode: function(node) { 42 | Batman.DOM.willDestroyNode(node); 43 | Batman.DOM.willRemoveNode(node); 44 | jQuery(node).remove(); 45 | Batman.DOM.didRemoveNode(node); 46 | return Batman.DOM.didDestroyNode(node); 47 | }, 48 | appendChild: function(parent, child) { 49 | Batman.DOM.willInsertNode(child); 50 | jQuery(parent).append(child); 51 | return Batman.DOM.didInsertNode(child); 52 | }, 53 | innerText: function(node) { 54 | return jQuery(node).text(); 55 | } 56 | }); 57 | 58 | Batman.Request.prototype._parseResponseHeaders = function(xhr) { 59 | var headers; 60 | return headers = xhr.getAllResponseHeaders().split('\n').reduce(function(acc, header) { 61 | var key, matches, value; 62 | if (matches = header.match(/([^:]*):\s*(.*)/)) { 63 | key = matches[1]; 64 | value = matches[2]; 65 | acc[key] = value; 66 | } 67 | return acc; 68 | }, {}); 69 | }; 70 | 71 | Batman.Request.prototype._prepareOptions = function(data) { 72 | var options, _ref, _this = this; 73 | options = { 74 | url: this.get('url'), 75 | type: this.get('method'), 76 | dataType: this.get('type'), 77 | data: data || this.get('data'), 78 | username: this.get('username'), 79 | password: this.get('password'), 80 | headers: this.get('headers'), 81 | beforeSend: function() { 82 | return _this.fire('loading'); 83 | }, 84 | success: function(response, textStatus, xhr) { 85 | _this.mixin({ 86 | xhr: xhr, 87 | status: xhr.status, 88 | response: response, 89 | responseHeaders: _this._parseResponseHeaders(xhr) 90 | }); 91 | return _this.fire('success', response); 92 | }, 93 | error: function(xhr, status, error) { 94 | _this.mixin({ 95 | xhr: xhr, 96 | status: xhr.status, 97 | response: xhr.responseText, 98 | responseHeaders: _this._parseResponseHeaders(xhr) 99 | }); 100 | xhr.request = _this; 101 | return _this.fire('error', xhr); 102 | }, 103 | complete: function() { 104 | return _this.fire('loaded'); 105 | } 106 | }; 107 | if ((_ref = this.get('method')) === 'PUT' || _ref === 'POST') { 108 | if (!this.hasFileUploads()) { 109 | options.contentType = this.get('contentType'); 110 | if (typeof options.data === 'object') { 111 | options.processData = false; 112 | options.data = Batman.URI.queryFromParams(options.data); 113 | } 114 | } else { 115 | options.contentType = false; 116 | options.processData = false; 117 | options.data = this.constructor.objectToFormData(options.data); 118 | } 119 | } 120 | return options; 121 | }; 122 | 123 | Batman.Request.prototype.send = function(data) { 124 | return jQuery.ajax(this._prepareOptions(data)); 125 | }; 126 | 127 | Batman.mixins.animation = { 128 | show: function(addToParent) { 129 | var jq, show, _ref, _ref1; 130 | jq = $(this); 131 | show = function() { 132 | return jq.show(600); 133 | }; 134 | if (addToParent) { 135 | if ((_ref = addToParent.append) != null) { 136 | _ref.appendChild(this); 137 | } 138 | if ((_ref1 = addToParent.before) != null) { 139 | _ref1.parentNode.insertBefore(this, addToParent.before); 140 | } 141 | jq.hide(); 142 | setTimeout(show, 0); 143 | } else { 144 | show(); 145 | } 146 | return this; 147 | }, 148 | hide: function(removeFromParent) { 149 | var _this = this; 150 | $(this).hide(600, function() { 151 | var _ref; 152 | if (removeFromParent) { 153 | if ((_ref = _this.parentNode) != null) { 154 | _ref.removeChild(_this); 155 | } 156 | } 157 | return Batman.DOM.didRemoveNode(_this); 158 | }); 159 | return this; 160 | } 161 | }; 162 | 163 | }).call(this); -------------------------------------------------------------------------------- /templates/project/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | /* 2 | //=require_directory . 3 | //=require_tree ../../widgets 4 | */ 5 | // ---------------------------------------------------------------------------- 6 | // Sass declarations 7 | // ---------------------------------------------------------------------------- 8 | $background-color: #222; 9 | $text-color: #fff; 10 | 11 | $background-warning-color-1: #e82711; 12 | $background-warning-color-2: #9b2d23; 13 | $text-warning-color: #fff; 14 | 15 | $background-danger-color-1: #eeae32; 16 | $background-danger-color-2: #ff9618; 17 | $text-danger-color: #fff; 18 | 19 | @-webkit-keyframes status-warning-background { 20 | 0% { background-color: $background-warning-color-1; } 21 | 50% { background-color: $background-warning-color-2; } 22 | 100% { background-color: $background-warning-color-1; } 23 | } 24 | @-webkit-keyframes status-danger-background { 25 | 0% { background-color: $background-danger-color-1; } 26 | 50% { background-color: $background-danger-color-2; } 27 | 100% { background-color: $background-danger-color-1; } 28 | } 29 | @mixin animation($animation-name, $duration, $function, $animation-iteration-count:""){ 30 | -webkit-animation: $animation-name $duration $function #{$animation-iteration-count}; 31 | -moz-animation: $animation-name $duration $function #{$animation-iteration-count}; 32 | -ms-animation: $animation-name $duration $function #{$animation-iteration-count}; 33 | } 34 | 35 | // ---------------------------------------------------------------------------- 36 | // Base styles 37 | // ---------------------------------------------------------------------------- 38 | html { 39 | font-size: 100%; 40 | -webkit-text-size-adjust: 100%; 41 | -ms-text-size-adjust: 100%; 42 | } 43 | 44 | body { 45 | margin: 0; 46 | background-color: $background-color; 47 | font-size: 20px; 48 | color: $text-color; 49 | font-family: 'Open Sans', "Helvetica Neue", Helvetica, Arial, sans-serif; 50 | } 51 | 52 | b, strong { 53 | font-weight: bold; 54 | } 55 | 56 | a { 57 | text-decoration: none; 58 | color: inherit; 59 | } 60 | 61 | img { 62 | border: 0; 63 | -ms-interpolation-mode: bicubic; 64 | vertical-align: middle; 65 | } 66 | 67 | img, object { 68 | max-width: 100%; 69 | } 70 | 71 | iframe { 72 | max-width: 100%; 73 | } 74 | 75 | table { 76 | border-collapse: collapse; 77 | border-spacing: 0; 78 | width: 100%; 79 | } 80 | 81 | td { 82 | vertical-align: middle; 83 | } 84 | 85 | ul, ol { 86 | padding: 0; 87 | margin: 0; 88 | } 89 | 90 | h1, h2, h3, h4, h5, p { 91 | padding: 0; 92 | margin: 0; 93 | } 94 | h1 { 95 | margin-bottom: 12px; 96 | text-align: center; 97 | font-size: 30px; 98 | font-weight: 400; 99 | } 100 | h2 { 101 | text-transform: uppercase; 102 | font-size: 76px; 103 | font-weight: 700; 104 | color: $text-color; 105 | } 106 | h3 { 107 | font-size: 25px; 108 | font-weight: 600; 109 | color: $text-color; 110 | } 111 | 112 | // ---------------------------------------------------------------------------- 113 | // Base widget styles 114 | // ---------------------------------------------------------------------------- 115 | .gridster { 116 | margin: 0px auto; 117 | } 118 | 119 | .icon-background { 120 | width: 100%!important; 121 | height: 100%; 122 | position: absolute; 123 | left: 0; 124 | top: 0; 125 | opacity: 0.1; 126 | font-size: 250px; 127 | line-height: 350px; 128 | text-align: center; 129 | } 130 | 131 | .list-nostyle { 132 | list-style: none; 133 | } 134 | 135 | .gridster ul { 136 | list-style: none; 137 | } 138 | 139 | .gs_w { 140 | width: 100%; 141 | display: table; 142 | cursor: pointer; 143 | } 144 | 145 | .widget { 146 | padding: 25px 12px; 147 | text-align: center; 148 | width: 100%; 149 | display: table-cell; 150 | vertical-align: middle; 151 | } 152 | 153 | .widget.status-warning { 154 | background-color: $background-warning-color-1; 155 | @include animation(status-warning-background, 2s, ease, infinite); 156 | 157 | .icon-warning-sign { 158 | display: inline-block; 159 | } 160 | 161 | .title, .more-info { 162 | color: $text-warning-color; 163 | } 164 | } 165 | 166 | .widget.status-danger { 167 | color: $text-danger-color; 168 | background-color: $background-danger-color-1; 169 | @include animation(status-danger-background, 2s, ease, infinite); 170 | 171 | .icon-warning-sign { 172 | display: inline-block; 173 | } 174 | 175 | .title, .more-info { 176 | color: $text-danger-color; 177 | } 178 | } 179 | 180 | .more-info { 181 | font-size: 15px; 182 | position: absolute; 183 | bottom: 32px; 184 | left: 0; 185 | right: 0; 186 | } 187 | 188 | .updated-at { 189 | font-size: 15px; 190 | position: absolute; 191 | bottom: 12px; 192 | left: 0; 193 | right: 0; 194 | } 195 | 196 | #save-gridster { 197 | display: none; 198 | position: fixed; 199 | top: 0; 200 | margin: 0px auto; 201 | left: 50%; 202 | z-index: 1000; 203 | background: black; 204 | width: 190px; 205 | text-align: center; 206 | border: 1px solid white; 207 | border-top: 0px; 208 | margin-left: -95px; 209 | padding: 15px; 210 | } 211 | 212 | #save-gridster:hover { 213 | padding-top: 25px; 214 | } 215 | 216 | #saving-instructions { 217 | display: none; 218 | padding: 10px; 219 | width: 500px; 220 | height: 122px; 221 | z-index: 1000; 222 | background: white; 223 | top: 100px; 224 | color: black; 225 | font-size: 15px; 226 | padding-bottom: 4px; 227 | 228 | textarea { 229 | white-space: nowrap; 230 | width: 494px; 231 | height: 80px; 232 | } 233 | } 234 | 235 | #lean_overlay { 236 | position: fixed; 237 | z-index:100; 238 | top: 0px; 239 | left: 0px; 240 | height:100%; 241 | width:100%; 242 | background: #000; 243 | display: none; 244 | } 245 | 246 | #container { 247 | padding-top: 5px; 248 | } 249 | 250 | 251 | // ---------------------------------------------------------------------------- 252 | // Clearfix 253 | // ---------------------------------------------------------------------------- 254 | .clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } 255 | .clearfix:after { clear: both; } 256 | .clearfix { zoom: 1; } 257 | 258 | -------------------------------------------------------------------------------- /lib/dashing.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | , path = require('path') 3 | , express = require('express') 4 | , Mincer = require('mincer') 5 | , coffee = require('coffee-script'); 6 | 7 | global.SCHEDULER = require('node-schedule'); 8 | 9 | module.exports.logger = logger = require('./logger'); 10 | module.exports.Dashing = function Dashing() { 11 | var dashing = {}; 12 | dashing.root = path.resolve(__dirname, '../../..'); 13 | dashing.NODE_ENV = process.env.NODE_ENV || 'development'; 14 | 15 | dashing.view_engine = process.env.VIEW_ENGINE || 'jade'; 16 | 17 | dashing.mincer = {}; 18 | dashing.mincer.environment = new Mincer.Environment(); 19 | dashing.mincer.assets_prefix = '/assets'; 20 | dashing.mincer.environment.appendPath('assets/javascripts'); 21 | dashing.mincer.environment.appendPath('assets/stylesheets'); 22 | dashing.mincer.environment.appendPath('assets/fonts'); 23 | dashing.mincer.environment.appendPath('assets/images'); 24 | dashing.mincer.environment.appendPath('widgets'); 25 | dashing.mincer.environment.appendPath(path.resolve(__dirname, '../javascripts')); 26 | 27 | dashing.public_folder = dashing.root + '/public'; 28 | dashing.views = dashing.root + '/dashboards'; 29 | dashing.default_dashboard = null; 30 | dashing.port = (process.env.PORT || 3030); 31 | 32 | dashing.protected = function(req, res, next) { 33 | next(); 34 | }; 35 | 36 | dashing._protected = function(req, res, next) { 37 | dashing.protected(req, res, next); 38 | } 39 | 40 | var expressLoggerOptions = { 41 | format: 'dev', 42 | stream: { 43 | write: function(message, encoding) { 44 | logger.info(message); 45 | } 46 | } 47 | }; 48 | 49 | // setup Express 50 | var app = express(); 51 | app.configure('development', function() { 52 | Mincer.logger.use(logger); 53 | }); 54 | app.configure('production', function() { 55 | expressLoggerOptions.format = 'short'; 56 | // In production we assume that assets are not changed between requests, 57 | // so we use cached version of environment. 58 | // All file system methods are cached for the instances lifetime. 59 | dashing.mincer.environment = dashing.mincer.environment.index; 60 | }); 61 | app.configure(function() { 62 | app.set('views', dashing.views); 63 | app.set('view engine', dashing.view_engine); 64 | if (dashing.view_engine === 'ejs') { 65 | app.use(require('express-ejs-layouts')); 66 | } 67 | app.use(express.logger(expressLoggerOptions)); 68 | app.use(express.errorHandler()); 69 | app.use(express.compress()); 70 | app.use(express.json()); 71 | app.use(express.urlencoded()); 72 | app.use(express.methodOverride()); 73 | app.use(dashing.mincer.assets_prefix, Mincer.createServer(dashing.mincer.environment)); 74 | app.use(express.static(dashing.public_folder)); 75 | app.use(app.router); 76 | }); 77 | app.set('development', dashing.NODE_ENV === 'development'); 78 | app.set('production', dashing.NODE_ENV === 'production'); 79 | 80 | var connections = {}; 81 | var history = {}; 82 | 83 | app.get('/events', dashing._protected, function(req, res) { 84 | // let request last as long as possible 85 | req.socket.setTimeout(0); 86 | 87 | var conn = { 88 | id: (new Date().getTime().toString() + Math.floor(Math.random() * 1000).toString()), 89 | send: function(body) { 90 | res.write(body); 91 | res.flush(); // need to flush with .compress() 92 | } 93 | }; 94 | connections[conn.id] = conn; 95 | 96 | // send headers for event-stream connection 97 | res.writeHead(200, { 98 | 'Content-Type': 'text/event-stream', 99 | 'Cache-Control': 'no-cache', 100 | 'Connection': 'keep-alive', 101 | 'X-Accel-Buffering': 'no' // Disable buffering for nginx 102 | }); 103 | res.write('\n'); 104 | res.write(Array(2049).join(' ') + '\n'); // 2kb padding for IE 105 | res.write(latest_events()); 106 | res.flush(); // need to flush with .compress() 107 | 108 | req.on('close', function() { 109 | delete connections[conn.id]; 110 | }); 111 | }); 112 | 113 | app.get('/', function(req, res) { 114 | if (dashing.default_dashboard) { 115 | res.redirect(dashing.default_dashboard); 116 | } else { 117 | first_dashboard(function(err, dashboard) { 118 | if (err) { 119 | next(err); 120 | } else if (dashboard) { 121 | res.redirect(dashboard); 122 | } else { 123 | next(new Error('There are no dashboards in your dashboard directory.')); 124 | } 125 | }); 126 | } 127 | }); 128 | 129 | app.get('/:dashboard', dashing._protected, function(req, res) { 130 | var dashboard = req.params.dashboard; 131 | fs.exists([dashing.views, dashboard + '.' + dashing.view_engine].join(path.sep), function(exists) { 132 | if (exists) { 133 | res.render(dashboard, { 134 | dashboard: dashboard, 135 | request: req 136 | }); 137 | } else { 138 | res.status(404).sendfile(dashing.public_folder + '/404.html'); 139 | } 140 | }); 141 | }); 142 | 143 | app.get('/views/:widget?.html', dashing._protected, function(req, res) { 144 | var widget = req.params.widget; 145 | res.sendfile([dashing.root, 'widgets', widget, widget + '.html'].join(path.sep)); 146 | }); 147 | 148 | app.post('/widgets/:id', function(req, res) { 149 | var auth_token = req.body.auth_token; 150 | if (!dashing.auth_token || dashing.auth_token == auth_token) { 151 | send_event(req.params.id, req.body); 152 | res.send(204); 153 | } else { 154 | res.send(401, 'Invalid API key'); 155 | } 156 | }); 157 | 158 | // The 404 Route (ALWAYS Keep this as the last route) 159 | app.use(function(req, res, next) { 160 | res.status(404).sendfile(dashing.public_folder + '/404.html'); 161 | }); 162 | 163 | // Error handler 164 | app.use(function(err, req, res, next) { 165 | logger.error(err.stack); 166 | res.send(500, err); 167 | }); 168 | 169 | function send_event(id, body) { 170 | body.id = id; 171 | body.updatedAt = Date.now(); 172 | var event = format_event(body); 173 | history[id] = event; 174 | for (var k in connections) { 175 | connections[k].send(event); 176 | } 177 | } 178 | global.send_event = send_event; 179 | 180 | function format_event(body) { 181 | return 'data: ' + JSON.stringify(body) + '\n\n'; 182 | } 183 | 184 | function latest_events() { 185 | var str = []; 186 | for (var id in history) { 187 | str.push(history[id]); 188 | } 189 | return str.join(''); 190 | } 191 | 192 | function first_dashboard(fn) { 193 | fs.readdir(dashing.views, function(err, files) { 194 | if (err) fn(err); 195 | var regex = new RegExp('(\w*)\.' + dashing.view_engine + '$'); 196 | for (var i in files) { 197 | var file = files[i]; 198 | if (file.match(regex) && file !== 'layout.' + dashing.view_engine) { 199 | fn(null, file.substring(0, file.length - (dashing.view_engine.length + 1))); 200 | return; 201 | } 202 | } 203 | fn(null, null); 204 | }); 205 | } 206 | 207 | // Load custom libraries 208 | fs.readdir([dashing.root, 'lib'].join(path.sep), function(err, files) { 209 | if (err) throw err; 210 | for (var i in files) { 211 | var file = [dashing.root, 'lib', files[i]].join(path.sep); 212 | require(file); 213 | } 214 | }); 215 | 216 | // Lod jobs files 217 | var job_path = process.env.JOB_PATH || [dashing.root, 'jobs'].join(path.sep); 218 | fs.readdir(job_path, function(err, files) { 219 | if (err) throw err; 220 | for (var i in files) { 221 | var file = [job_path, files[i]].join(path.sep); 222 | if (file.match(/(\w*)\.job\.(js|coffee)$/)) { 223 | logger.log('Loading job file:', files[i]); 224 | require(file); 225 | } 226 | } 227 | }); 228 | 229 | dashing.start = function() { 230 | app.listen(dashing.port); 231 | logger.info('Listening on http://0.0.0.0:' + dashing.port + (process.env.__daemon === 'false' ? ', CTRL+C to stop' : '')); 232 | } 233 | dashing.app = app; 234 | return dashing; 235 | }; 236 | -------------------------------------------------------------------------------- /bin/dashing-js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs') 4 | , path = require('path') 5 | , spawn = require('child_process').spawn 6 | , pkg = require('../package.json') 7 | , Thor = require('../lib/thor').Thor 8 | , utils = require('../lib/utils') 9 | , program = require('commander') 10 | , prompt = require('prompt') 11 | , request = require('request') 12 | , unzip = require('unzip'); 13 | 14 | program.version(pkg.version); 15 | 16 | auth_token = null; 17 | send_event = function(id, data) { 18 | if (auth_token) { 19 | data.auth_token = auth_token; 20 | } 21 | request({ 22 | url: 'http://localhost:3030/widgets/' + id, 23 | method: 'POST', 24 | json: data 25 | }, function(err, res, body) { 26 | if (!err && res.statusCode == 204) { 27 | console.log('Data Sent to ' + id + ': ' + JSON.stringify(data)); 28 | } else { 29 | console.error('Error sending data'); 30 | console.error(err); 31 | } 32 | }); 33 | }; 34 | 35 | var generators = (function(types) { 36 | var rtn = {}; 37 | for (var i = 0, len = types.length; i < len; i++) { 38 | rtn[types[i]] = (function(type) { 39 | return function(name) { 40 | Thor.directory(type, type + 's', {name: Thor.Util.dash_case(name)}); 41 | } 42 | })(types[i]); 43 | } 44 | return rtn; 45 | })(['widget', 'dashboard', 'job']); 46 | 47 | program.command('new ') 48 | .description('Sets up ALL THE THINGS needed for your dashboard project.') 49 | .option('-f, --force', 'force on non-empty directory') 50 | .action(function(project_name, options) { 51 | project_name = Thor.Util.dash_case(project_name); 52 | utils.emptyDirectory(project_name, function(empty) { 53 | if (empty || options.force) { 54 | createProject(project_name); 55 | } else { 56 | prompt.start(); 57 | prompt.get({ 58 | name: 'yesno', 59 | message: 'destination is not empty, continue?', 60 | validator: /y[es]*|n[o]?/i, 61 | warning: 'Must respond yes or no', 62 | default: 'no' 63 | }, function(err, result) { 64 | if (/^y/i.test(result.yesno)) { 65 | process.stdin.destroy(); 66 | createProject(project_name); 67 | } else { 68 | utils.abort('aborting'); 69 | } 70 | }); 71 | } 72 | }); 73 | }); 74 | 75 | program.command('generate ') 76 | .description('Creates a new widget, dashboard, or job.') 77 | .action(function(type, name) { 78 | if (typeof(generators[type]) === 'function') { 79 | console.log('Generating %s "%s"', type, name); 80 | generators[type](name); 81 | } else { 82 | console.error('Invalid generator. Either use widget, dashboard, or job'); 83 | } 84 | }); 85 | 86 | program.command('install ') 87 | .description('Installs a new widget from a gist or a zipball.') 88 | .option('-p, --proxy ', 'Use proxy') 89 | .action(function(id, options) { 90 | if (options.proxy) { 91 | console.log('Using proxy "%s"', options.proxy); 92 | request = request.defaults({proxy: options.proxy}); 93 | } 94 | if (isNaN(parseInt(id, 10))) { 95 | console.log('Installing zip "%s"', id); 96 | var origDirName, widgetName; 97 | request({ 98 | url: id, 99 | headers: {'User-Agent': pkg.name + '-' + pkg.version} 100 | }).pipe(unzip.Parse()) 101 | .on('entry', function(entry) { 102 | if (entry.type === 'Directory' && !origDirName) { 103 | origDirName = entry.path; 104 | utils.mkdir.sync('widgets/' + origDirName); 105 | entry.autodrain(); 106 | } else if (entry.type === 'File') { 107 | var m = entry.path.match(/(\w*)\.(coffee|html|scss)$/); 108 | var mj = entry.path.match(/(\w*)\.job\.(js|coffee)$/); 109 | if (mj) { 110 | console.log(' \x1b[36mcreate\x1b[0m jobs/' + mj[0]); 111 | entry.pipe(fs.createWriteStream('jobs/' + mj[0])); 112 | } else { 113 | widgetName = m ? m[1] : widgetName; 114 | console.log(' \x1b[36mcreate\x1b[0m widgets/' + entry.path); 115 | entry.pipe(fs.createWriteStream('widgets/' + entry.path)); 116 | } 117 | } 118 | }) 119 | .on('error', function(err) { 120 | console.error('Error reading zip file from: ' + id); 121 | err && console.error(err); 122 | }) 123 | .on('close', function() { 124 | utils.mv.sync('widgets/' + origDirName, 'widgets/' + widgetName); 125 | }); 126 | } else { 127 | console.log('Installing gist "%s"', id); 128 | var public_url = "https://gist.github.com/" + id; 129 | request({ 130 | url: 'https://api.github.com/gists/' + id, 131 | headers: {'User-Agent': pkg.name + '-' + pkg.version} 132 | }, function(err, res, body) { 133 | if (!err && res.statusCode == 200) { 134 | var gist = JSON.parse(body); 135 | var widgetName; 136 | for (var k in gist.files) { 137 | var m = gist.files[k].filename.match(/(\w*)\.(coffee|html|scss)$/); 138 | if (m && !gist.files[k].filename.match(/(\w*)\.job\.coffee$/)) { 139 | widgetName = m[1]; 140 | break; 141 | } 142 | } 143 | utils.mkdir('widgets/' + widgetName, function() { 144 | for (var k in gist.files) { 145 | var filename = gist.files[k].filename; 146 | var outFile = filename; 147 | if (filename.match(/(\w*)\.job\.(js|coffee)$/)) { 148 | utils.write('jobs/' + filename, gist.files[k].content); 149 | } else/* if(filename.match(/(\w*)\.(coffee|html|scss)$/))*/ { 150 | utils.write('widgets/' + widgetName + '/' + filename, gist.files[k].content); 151 | } 152 | } 153 | console.log('if needed. More information for this widget can be found at ' + public_url); 154 | }); 155 | } else { 156 | console.error('Could not find gist at ' + public_url); 157 | res && console.error('[' + res.statusCode + '] ' + body); 158 | err && console.error(err); 159 | } 160 | }); 161 | } 162 | }); 163 | 164 | program.command('start') 165 | .description('Starts the server in style!') 166 | .option('-p, --port ', 'Listen port', 3030) 167 | .option('-j, --job_path ', 'Specify the directory where jobs are stored') 168 | .option('-e, --view_engine ', 'Specify the view engine to use (defaults to jade)') 169 | .option('-d, --daemon', 'Daemonize the server') 170 | .action(function(options) { 171 | var env = process.env || {}; 172 | env.PORT = options.port; 173 | if (options['job_path']) { 174 | env.JOB_PATH = options['job_path']; 175 | } 176 | if (options['view_engine']) { 177 | env.VIEW_ENGINE = options['view_engine']; 178 | } 179 | env.__daemon = !!options['daemon']; 180 | var spawn_opts = { 181 | cwd: process.cwd(), 182 | env: env, 183 | stdio: 'inherit', 184 | }; 185 | if (options['daemon']) { 186 | var outFile = fs.openSync('./out.log', 'a'); 187 | var errFile = fs.openSync('./out.log', 'a'); 188 | spawn_opts.stdio = ['ignore', outFile, errFile]; 189 | spawn_opts.detached = true; 190 | } 191 | var child = spawn(process.execPath, ['server.js'], spawn_opts); 192 | if (options['daemon']) { 193 | child.unref(); 194 | process.exit(0); 195 | } 196 | child.on('close', function(code) { 197 | console.log('child process exited with code ' + code); 198 | }); 199 | }); 200 | 201 | program.command('job ') 202 | .description('Runs the specified job. Make sure to supply your auth token if you have one set.') 203 | .option('-a, --auth_token ', 'The auth_token to use') 204 | .action(function(job_name, options) { 205 | require('coffee-script'); 206 | // Load custom libraries 207 | fs.readdir([process.cwd(), 'lib'].join(path.sep), function(err, files) { 208 | if (err) throw err; 209 | for (var i in files) { 210 | var file = [process.cwd(), 'lib', files[i]].join(path.sep); 211 | require(file); 212 | } 213 | }); 214 | if (options['auth_token']) { 215 | auth_token = options['auth_token']; 216 | } 217 | var exts = ['js', 'coffee']; 218 | var err = true; 219 | for (var i in exts) { 220 | var filePath = [process.cwd(), 'jobs', job_name + '.job.' + exts[i]].join(path.sep); 221 | if (fs.existsSync(filePath)) { 222 | console.log('Loading job file:', filePath); 223 | require(filePath); 224 | err = false; 225 | break; 226 | } 227 | } 228 | if (err) console.error('No job file found for "' + job_name + '". Supported job file types:', exts); 229 | }); 230 | 231 | program.parse(process.argv); 232 | 233 | function createProject(name) { 234 | console.log('Creating "%s"', name); 235 | Thor.directory('project', name, {name: name, dashing: {version: pkg.version}}); 236 | } 237 | -------------------------------------------------------------------------------- /javascripts/eventsource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * eventsource.js 3 | * Available under MIT License (MIT) 4 | * https://github.com/Yaffle/EventSource/ 5 | */ 6 | 7 | /*jslint indent: 2, vars: true, plusplus: true */ 8 | /*global setTimeout, clearTimeout */ 9 | 10 | (function (global) { 11 | "use strict"; 12 | 13 | if (typeof global.EventSource !== 'undefined') return; 14 | 15 | function Map() { 16 | this.data = {}; 17 | } 18 | 19 | Map.prototype = { 20 | get: function (key) { 21 | return this.data[key + "~"]; 22 | }, 23 | set: function (key, value) { 24 | this.data[key + "~"] = value; 25 | }, 26 | "delete": function (key) { 27 | delete this.data[key + "~"]; 28 | } 29 | }; 30 | 31 | function EventTarget() { 32 | this.listeners = new Map(); 33 | } 34 | 35 | function throwError(e) { 36 | setTimeout(function () { 37 | throw e; 38 | }, 0); 39 | } 40 | 41 | EventTarget.prototype = { 42 | dispatchEvent: function (event) { 43 | event.target = this; 44 | var type = String(event.type); 45 | var listeners = this.listeners; 46 | var typeListeners = listeners.get(type); 47 | if (!typeListeners) { 48 | return; 49 | } 50 | var length = typeListeners.length; 51 | var i = -1; 52 | var listener = null; 53 | while (++i < length) { 54 | listener = typeListeners[i]; 55 | try { 56 | listener.call(this, event); 57 | } catch (e) { 58 | throwError(e); 59 | } 60 | } 61 | }, 62 | addEventListener: function (type, callback) { 63 | type = String(type); 64 | var listeners = this.listeners; 65 | var typeListeners = listeners.get(type); 66 | if (!typeListeners) { 67 | typeListeners = []; 68 | listeners.set(type, typeListeners); 69 | } 70 | var i = typeListeners.length; 71 | while (--i >= 0) { 72 | if (typeListeners[i] === callback) { 73 | return; 74 | } 75 | } 76 | typeListeners.push(callback); 77 | }, 78 | removeEventListener: function (type, callback) { 79 | type = String(type); 80 | var listeners = this.listeners; 81 | var typeListeners = listeners.get(type); 82 | if (!typeListeners) { 83 | return; 84 | } 85 | var length = typeListeners.length; 86 | var filtered = []; 87 | var i = -1; 88 | while (++i < length) { 89 | if (typeListeners[i] !== callback) { 90 | filtered.push(typeListeners[i]); 91 | } 92 | } 93 | if (filtered.length === 0) { 94 | listeners["delete"](type); 95 | } else { 96 | listeners.set(type, filtered); 97 | } 98 | } 99 | }; 100 | 101 | function Event(type) { 102 | this.type = type; 103 | this.target = null; 104 | } 105 | 106 | function MessageEvent(type, options) { 107 | Event.call(this, type); 108 | this.data = options.data; 109 | this.lastEventId = options.lastEventId; 110 | } 111 | 112 | MessageEvent.prototype = Event.prototype; 113 | 114 | var XHR = global.XMLHttpRequest; 115 | var XDR = global.XDomainRequest; 116 | var isCORSSupported = Boolean(XHR && ((new XHR()).withCredentials !== undefined)); 117 | var isXHR = isCORSSupported; 118 | var Transport = isCORSSupported ? XHR : XDR; 119 | var WAITING = -1; 120 | var CONNECTING = 0; 121 | var OPEN = 1; 122 | var CLOSED = 2; 123 | var AFTER_CR = 3; 124 | var FIELD_START = 4; 125 | var FIELD = 5; 126 | var VALUE_START = 6; 127 | var VALUE = 7; 128 | var contentTypeRegExp = /^text\/event\-stream;?(\s*charset\=utf\-8)?$/i; 129 | 130 | var MINIMUM_DURATION = 1000; 131 | var MAXIMUM_DURATION = 18000000; 132 | 133 | function getDuration(value, def) { 134 | var n = Number(value) || def; 135 | return (n < MINIMUM_DURATION ? MINIMUM_DURATION : (n > MAXIMUM_DURATION ? MAXIMUM_DURATION : n)); 136 | } 137 | 138 | function fire(that, f, event) { 139 | try { 140 | if (typeof f === "function") { 141 | f.call(that, event); 142 | } 143 | } catch (e) { 144 | throwError(e); 145 | } 146 | } 147 | 148 | function EventSource(url, options) { 149 | url = String(url); 150 | 151 | var withCredentials = Boolean(isCORSSupported && options && options.withCredentials); 152 | var initialRetry = getDuration(options ? options.retry : NaN, 1000); 153 | var heartbeatTimeout = getDuration(options ? options.heartbeatTimeout : NaN, 45000); 154 | var lastEventId = (options && options.lastEventId && String(options.lastEventId)) || ""; 155 | var that = this; 156 | var retry = initialRetry; 157 | var wasActivity = false; 158 | var xhr = new Transport(); 159 | var timeout = 0; 160 | var timeout0 = 0; 161 | var charOffset = 0; 162 | var currentState = WAITING; 163 | var dataBuffer = []; 164 | var lastEventIdBuffer = ""; 165 | var eventTypeBuffer = ""; 166 | var onTimeout = null; 167 | 168 | var state = FIELD_START; 169 | var field = ""; 170 | var value = ""; 171 | 172 | options = null; 173 | 174 | function close() { 175 | currentState = CLOSED; 176 | if (xhr !== null) { 177 | xhr.abort(); 178 | xhr = null; 179 | } 180 | if (timeout !== 0) { 181 | clearTimeout(timeout); 182 | timeout = 0; 183 | } 184 | if (timeout0 !== 0) { 185 | clearTimeout(timeout0); 186 | timeout0 = 0; 187 | } 188 | that.readyState = CLOSED; 189 | } 190 | 191 | function onProgress(isLoadEnd) { 192 | var responseText = currentState === OPEN || currentState === CONNECTING ? xhr.responseText || "" : ""; 193 | var event = null; 194 | var isWrongStatusCodeOrContentType = false; 195 | 196 | if (currentState === CONNECTING) { 197 | var status = 0; 198 | var statusText = ""; 199 | var contentType = ""; 200 | if (isXHR) { 201 | try { 202 | status = Number(xhr.status || 0); 203 | statusText = String(xhr.statusText || ""); 204 | contentType = String(xhr.getResponseHeader("Content-Type") || ""); 205 | } catch (error) { 206 | // https://bugs.webkit.org/show_bug.cgi?id=29121 207 | status = 0; 208 | // FF < 14, WebKit 209 | // https://bugs.webkit.org/show_bug.cgi?id=29658 210 | // https://bugs.webkit.org/show_bug.cgi?id=77854 211 | } 212 | } else { 213 | status = 200; 214 | contentType = xhr.contentType; 215 | } 216 | if (status === 200 && contentTypeRegExp.test(contentType)) { 217 | currentState = OPEN; 218 | wasActivity = true; 219 | retry = initialRetry; 220 | that.readyState = OPEN; 221 | event = new Event("open"); 222 | that.dispatchEvent(event); 223 | fire(that, that.onopen, event); 224 | if (currentState === CLOSED) { 225 | return; 226 | } 227 | } else { 228 | if (status !== 0) { 229 | var message = ""; 230 | if (status !== 200) { 231 | message = "EventSource's response has a status " + status + " " + statusText.replace(/\s+/g, " ") + " that is not 200. Aborting the connection."; 232 | } else { 233 | message = "EventSource's response has a Content-Type specifying an unsupported type: " + contentType.replace(/\s+/g, " ") + ". Aborting the connection."; 234 | } 235 | setTimeout(function () { 236 | throw new Error(message); 237 | }); 238 | isWrongStatusCodeOrContentType = true; 239 | } 240 | } 241 | } 242 | 243 | if (currentState === OPEN) { 244 | if (responseText.length > charOffset) { 245 | wasActivity = true; 246 | } 247 | var i = charOffset - 1; 248 | var length = responseText.length; 249 | var c = "\n"; 250 | while (++i < length) { 251 | c = responseText[i]; 252 | if (state === AFTER_CR && c === "\n") { 253 | state = FIELD_START; 254 | } else { 255 | if (state === AFTER_CR) { 256 | state = FIELD_START; 257 | } 258 | if (c === "\r" || c === "\n") { 259 | if (field === "data") { 260 | dataBuffer.push(value); 261 | } else if (field === "id") { 262 | lastEventIdBuffer = value; 263 | } else if (field === "event") { 264 | eventTypeBuffer = value; 265 | } else if (field === "retry") { 266 | initialRetry = getDuration(value, initialRetry); 267 | retry = initialRetry; 268 | } else if (field === "heartbeatTimeout") {//! 269 | heartbeatTimeout = getDuration(value, heartbeatTimeout); 270 | if (timeout !== 0) { 271 | clearTimeout(timeout); 272 | timeout = setTimeout(onTimeout, heartbeatTimeout); 273 | } 274 | } 275 | value = ""; 276 | field = ""; 277 | if (state === FIELD_START) { 278 | if (dataBuffer.length !== 0) { 279 | lastEventId = lastEventIdBuffer; 280 | if (eventTypeBuffer === "") { 281 | eventTypeBuffer = "message"; 282 | } 283 | event = new MessageEvent(eventTypeBuffer, { 284 | data: dataBuffer.join("\n"), 285 | lastEventId: lastEventIdBuffer 286 | }); 287 | that.dispatchEvent(event); 288 | if (eventTypeBuffer === "message") { 289 | fire(that, that.onmessage, event); 290 | } 291 | if (currentState === CLOSED) { 292 | return; 293 | } 294 | } 295 | dataBuffer.length = 0; 296 | eventTypeBuffer = ""; 297 | } 298 | state = c === "\r" ? AFTER_CR : FIELD_START; 299 | } else { 300 | if (state === FIELD_START) { 301 | state = FIELD; 302 | } 303 | if (state === FIELD) { 304 | if (c === ":") { 305 | state = VALUE_START; 306 | } else { 307 | field += c; 308 | } 309 | } else if (state === VALUE_START) { 310 | if (c !== " ") { 311 | value += c; 312 | } 313 | state = VALUE; 314 | } else if (state === VALUE) { 315 | value += c; 316 | } 317 | } 318 | } 319 | } 320 | charOffset = length; 321 | } 322 | 323 | if ((currentState === OPEN || currentState === CONNECTING) && 324 | (isLoadEnd || isWrongStatusCodeOrContentType || (charOffset > 1024 * 1024) || (timeout === 0 && !wasActivity))) { 325 | currentState = WAITING; 326 | xhr.abort(); 327 | if (timeout !== 0) { 328 | clearTimeout(timeout); 329 | timeout = 0; 330 | } 331 | if (retry > initialRetry * 16) { 332 | retry = initialRetry * 16; 333 | } 334 | if (retry > MAXIMUM_DURATION) { 335 | retry = MAXIMUM_DURATION; 336 | } 337 | timeout = setTimeout(onTimeout, retry); 338 | retry = retry * 2 + 1; 339 | 340 | that.readyState = CONNECTING; 341 | event = new Event("error"); 342 | that.dispatchEvent(event); 343 | fire(that, that.onerror, event); 344 | } else { 345 | if (timeout === 0) { 346 | wasActivity = false; 347 | timeout = setTimeout(onTimeout, heartbeatTimeout); 348 | } 349 | } 350 | } 351 | 352 | function onProgress2() { 353 | onProgress(false); 354 | } 355 | 356 | function onLoadEnd() { 357 | onProgress(true); 358 | } 359 | 360 | if (isXHR) { 361 | // workaround for Opera issue with "progress" events 362 | timeout0 = setTimeout(function f() { 363 | if (xhr.readyState === 3) { 364 | onProgress2(); 365 | } 366 | timeout0 = setTimeout(f, 500); 367 | }, 0); 368 | } 369 | 370 | onTimeout = function () { 371 | timeout = 0; 372 | if (currentState !== WAITING) { 373 | onProgress(false); 374 | return; 375 | } 376 | // loading indicator in Safari, Chrome < 14, Firefox 377 | // https://bugzilla.mozilla.org/show_bug.cgi?id=736723 378 | if (isXHR && (xhr.sendAsBinary !== undefined || xhr.onloadend === undefined) && global.document && global.document.readyState && global.document.readyState !== "complete") { 379 | timeout = setTimeout(onTimeout, 4); 380 | return; 381 | } 382 | // XDomainRequest#abort removes onprogress, onerror, onload 383 | 384 | xhr.onload = xhr.onerror = onLoadEnd; 385 | 386 | if (isXHR) { 387 | // improper fix to match Firefox behaviour, but it is better than just ignore abort 388 | // see https://bugzilla.mozilla.org/show_bug.cgi?id=768596 389 | // https://bugzilla.mozilla.org/show_bug.cgi?id=880200 390 | // https://code.google.com/p/chromium/issues/detail?id=153570 391 | xhr.onabort = onLoadEnd; 392 | 393 | // Firefox 3.5 - 3.6 - ? < 9.0 394 | // onprogress is not fired sometimes or delayed 395 | xhr.onreadystatechange = onProgress2; 396 | } 397 | 398 | xhr.onprogress = onProgress2; 399 | 400 | wasActivity = false; 401 | timeout = setTimeout(onTimeout, heartbeatTimeout); 402 | 403 | charOffset = 0; 404 | currentState = CONNECTING; 405 | dataBuffer.length = 0; 406 | eventTypeBuffer = ""; 407 | lastEventIdBuffer = lastEventId; 408 | value = ""; 409 | field = ""; 410 | state = FIELD_START; 411 | 412 | var s = url.slice(0, 5); 413 | if (s !== "data:" && s !== "blob:") { 414 | s = url + ((url.indexOf("?", 0) === -1 ? "?" : "&") + "lastEventId=" + encodeURIComponent(lastEventId) + "&r=" + String(Math.random() + 1).slice(2)); 415 | } else { 416 | s = url; 417 | } 418 | xhr.open("GET", s, true); 419 | 420 | if (isXHR) { 421 | // withCredentials should be set after "open" for Safari and Chrome (< 19 ?) 422 | xhr.withCredentials = withCredentials; 423 | 424 | xhr.responseType = "text"; 425 | 426 | // Request header field Cache-Control is not allowed by Access-Control-Allow-Headers. 427 | // "Cache-control: no-cache" are not honored in Chrome and Firefox 428 | // https://bugzilla.mozilla.org/show_bug.cgi?id=428916 429 | //xhr.setRequestHeader("Cache-Control", "no-cache"); 430 | xhr.setRequestHeader("Accept", "text/event-stream"); 431 | // Request header field Last-Event-ID is not allowed by Access-Control-Allow-Headers. 432 | //xhr.setRequestHeader("Last-Event-ID", lastEventId); 433 | } 434 | 435 | xhr.send(null); 436 | }; 437 | 438 | EventTarget.call(this); 439 | this.close = close; 440 | this.url = url; 441 | this.readyState = CONNECTING; 442 | this.withCredentials = withCredentials; 443 | 444 | this.onopen = null; 445 | this.onmessage = null; 446 | this.onerror = null; 447 | 448 | onTimeout(); 449 | } 450 | 451 | function F() { 452 | this.CONNECTING = CONNECTING; 453 | this.OPEN = OPEN; 454 | this.CLOSED = CLOSED; 455 | } 456 | F.prototype = EventTarget.prototype; 457 | 458 | EventSource.prototype = new F(); 459 | F.call(EventSource); 460 | 461 | if (Transport) { 462 | // Why replace a native EventSource ? 463 | // https://bugzilla.mozilla.org/show_bug.cgi?id=444328 464 | // https://bugzilla.mozilla.org/show_bug.cgi?id=831392 465 | // https://code.google.com/p/chromium/issues/detail?id=260144 466 | // https://code.google.com/p/chromium/issues/detail?id=225654 467 | // ... 468 | global.NativeEventSource = global.EventSource; 469 | global.EventSource = EventSource; 470 | } 471 | 472 | }(this)); 473 | -------------------------------------------------------------------------------- /templates/project/assets/javascripts/jquery.knob.js: -------------------------------------------------------------------------------- 1 | /*!jQuery Knob*/ 2 | /** 3 | * Downward compatible, touchable dial 4 | * 5 | * Version: 1.2.0 (15/07/2012) 6 | * Requires: jQuery v1.7+ 7 | * 8 | * Copyright (c) 2012 Anthony Terrien 9 | * Under MIT and GPL licenses: 10 | * http://www.opensource.org/licenses/mit-license.php 11 | * http://www.gnu.org/licenses/gpl.html 12 | * 13 | * Thanks to vor, eskimoblood, spiffistan, FabrizioC 14 | */ 15 | $(function () { 16 | 17 | /** 18 | * Kontrol library 19 | */ 20 | "use strict"; 21 | 22 | /** 23 | * Definition of globals and core 24 | */ 25 | var k = {}, // kontrol 26 | max = Math.max, 27 | min = Math.min; 28 | 29 | k.c = {}; 30 | k.c.d = $(document); 31 | k.c.t = function (e) { 32 | return e.originalEvent.touches.length - 1; 33 | }; 34 | 35 | /** 36 | * Kontrol Object 37 | * 38 | * Definition of an abstract UI control 39 | * 40 | * Each concrete component must call this one. 41 | * 42 | * k.o.call(this); 43 | * 44 | */ 45 | k.o = function () { 46 | var s = this; 47 | 48 | this.o = null; // array of options 49 | this.$ = null; // jQuery wrapped element 50 | this.i = null; // mixed HTMLInputElement or array of HTMLInputElement 51 | this.g = null; // 2D graphics context for 'pre-rendering' 52 | this.v = null; // value ; mixed array or integer 53 | this.cv = null; // change value ; not commited value 54 | this.x = 0; // canvas x position 55 | this.y = 0; // canvas y position 56 | this.$c = null; // jQuery canvas element 57 | this.c = null; // rendered canvas context 58 | this.t = 0; // touches index 59 | this.isInit = false; 60 | this.fgColor = null; // main color 61 | this.pColor = null; // previous color 62 | this.dH = null; // draw hook 63 | this.cH = null; // change hook 64 | this.eH = null; // cancel hook 65 | this.rH = null; // release hook 66 | 67 | this.run = function () { 68 | var cf = function (e, conf) { 69 | var k; 70 | for (k in conf) { 71 | s.o[k] = conf[k]; 72 | } 73 | s.init(); 74 | s._configure() 75 | ._draw(); 76 | }; 77 | 78 | if(this.$.data('kontroled')) return; 79 | this.$.data('kontroled', true); 80 | 81 | this.extend(); 82 | this.o = $.extend( 83 | { 84 | // Config 85 | min : this.$.data('min') || 0, 86 | max : this.$.data('max') || 100, 87 | stopper : true, 88 | readOnly : this.$.data('readonly'), 89 | 90 | // UI 91 | cursor : (this.$.data('cursor') === true && 30) 92 | || this.$.data('cursor') 93 | || 0, 94 | thickness : this.$.data('thickness') || 0.35, 95 | width : this.$.data('width') || 200, 96 | height : this.$.data('height') || 200, 97 | displayInput : this.$.data('displayinput') == null || this.$.data('displayinput'), 98 | displayPrevious : this.$.data('displayprevious'), 99 | fgColor : this.$.data('fgcolor') || '#87CEEB', 100 | inline : false, 101 | 102 | // Hooks 103 | draw : null, // function () {} 104 | change : null, // function (value) {} 105 | cancel : null, // function () {} 106 | release : null // function (value) {} 107 | }, this.o 108 | ); 109 | 110 | // routing value 111 | if(this.$.is('fieldset')) { 112 | 113 | // fieldset = array of integer 114 | this.v = {}; 115 | this.i = this.$.find('input') 116 | this.i.each(function(k) { 117 | var $this = $(this); 118 | s.i[k] = $this; 119 | s.v[k] = $this.val(); 120 | 121 | $this.bind( 122 | 'change' 123 | , function () { 124 | var val = {}; 125 | val[k] = $this.val(); 126 | s.val(val); 127 | } 128 | ); 129 | }); 130 | this.$.find('legend').remove(); 131 | 132 | } else { 133 | // input = integer 134 | this.i = this.$; 135 | this.v = this.$.val(); 136 | (this.v == '') && (this.v = this.o.min); 137 | 138 | this.$.bind( 139 | 'change' 140 | , function () { 141 | s.val(s.$.val()); 142 | } 143 | ); 144 | } 145 | 146 | (!this.o.displayInput) && this.$.hide(); 147 | 148 | this.$c = $(''); 151 | this.c = this.$c[0].getContext("2d"); 152 | 153 | this.$ 154 | .wrap($('
')) 157 | .before(this.$c); 158 | 159 | if (this.v instanceof Object) { 160 | this.cv = {}; 161 | this.copy(this.v, this.cv); 162 | } else { 163 | this.cv = this.v; 164 | } 165 | 166 | this.$ 167 | .bind("configure", cf) 168 | .parent() 169 | .bind("configure", cf); 170 | 171 | this._listen() 172 | ._configure() 173 | ._xy() 174 | .init(); 175 | 176 | this.isInit = true; 177 | 178 | this._draw(); 179 | 180 | return this; 181 | }; 182 | 183 | this._draw = function () { 184 | 185 | // canvas pre-rendering 186 | var d = true, 187 | c = document.createElement('canvas'); 188 | 189 | c.width = s.o.width; 190 | c.height = s.o.height; 191 | s.g = c.getContext('2d'); 192 | 193 | s.clear(); 194 | 195 | s.dH 196 | && (d = s.dH()); 197 | 198 | (d !== false) && s.draw(); 199 | 200 | s.c.drawImage(c, 0, 0); 201 | c = null; 202 | }; 203 | 204 | this._touch = function (e) { 205 | 206 | var touchMove = function (e) { 207 | 208 | var v = s.xy2val( 209 | e.originalEvent.touches[s.t].pageX, 210 | e.originalEvent.touches[s.t].pageY 211 | ); 212 | 213 | if (v == s.cv) return; 214 | 215 | if ( 216 | s.cH 217 | && (s.cH(v) === false) 218 | ) return; 219 | 220 | 221 | s.change(v); 222 | s._draw(); 223 | }; 224 | 225 | // get touches index 226 | this.t = k.c.t(e); 227 | 228 | // First touch 229 | touchMove(e); 230 | 231 | // Touch events listeners 232 | k.c.d 233 | .bind("touchmove.k", touchMove) 234 | .bind( 235 | "touchend.k" 236 | , function () { 237 | k.c.d.unbind('touchmove.k touchend.k'); 238 | 239 | if ( 240 | s.rH 241 | && (s.rH(s.cv) === false) 242 | ) return; 243 | 244 | s.val(s.cv); 245 | } 246 | ); 247 | 248 | return this; 249 | }; 250 | 251 | this._mouse = function (e) { 252 | 253 | var mouseMove = function (e) { 254 | var v = s.xy2val(e.pageX, e.pageY); 255 | if (v == s.cv) return; 256 | 257 | if ( 258 | s.cH 259 | && (s.cH(v) === false) 260 | ) return; 261 | 262 | s.change(v); 263 | s._draw(); 264 | }; 265 | 266 | // First click 267 | mouseMove(e); 268 | 269 | // Mouse events listeners 270 | k.c.d 271 | .bind("mousemove.k", mouseMove) 272 | .bind( 273 | // Escape key cancel current change 274 | "keyup.k" 275 | , function (e) { 276 | if (e.keyCode === 27) { 277 | k.c.d.unbind("mouseup.k mousemove.k keyup.k"); 278 | 279 | if ( 280 | s.eH 281 | && (s.eH() === false) 282 | ) return; 283 | 284 | s.cancel(); 285 | } 286 | } 287 | ) 288 | .bind( 289 | "mouseup.k" 290 | , function (e) { 291 | k.c.d.unbind('mousemove.k mouseup.k keyup.k'); 292 | 293 | if ( 294 | s.rH 295 | && (s.rH(s.cv) === false) 296 | ) return; 297 | 298 | s.val(s.cv); 299 | } 300 | ); 301 | 302 | return this; 303 | }; 304 | 305 | this._xy = function () { 306 | var o = this.$c.offset(); 307 | this.x = o.left; 308 | this.y = o.top; 309 | return this; 310 | }; 311 | 312 | this._listen = function () { 313 | 314 | if (!this.o.readOnly) { 315 | this.$c 316 | .bind( 317 | "mousedown" 318 | , function (e) { 319 | e.preventDefault(); 320 | s._xy()._mouse(e); 321 | } 322 | ) 323 | .bind( 324 | "touchstart" 325 | , function (e) { 326 | e.preventDefault(); 327 | s._xy()._touch(e); 328 | } 329 | ); 330 | this.listen(); 331 | } else { 332 | this.$.attr('readonly', 'readonly'); 333 | } 334 | 335 | return this; 336 | }; 337 | 338 | this._configure = function () { 339 | 340 | // Hooks 341 | if (this.o.draw) this.dH = this.o.draw; 342 | if (this.o.change) this.cH = this.o.change; 343 | if (this.o.cancel) this.eH = this.o.cancel; 344 | if (this.o.release) this.rH = this.o.release; 345 | 346 | if (this.o.displayPrevious) { 347 | this.pColor = this.h2rgba(this.o.fgColor, "0.4"); 348 | this.fgColor = this.h2rgba(this.o.fgColor, "0.6"); 349 | } else { 350 | this.fgColor = this.o.fgColor; 351 | } 352 | 353 | return this; 354 | }; 355 | 356 | this._clear = function () { 357 | this.$c[0].width = this.$c[0].width; 358 | }; 359 | 360 | // Abstract methods 361 | this.listen = function () {}; // on start, one time 362 | this.extend = function () {}; // each time configure triggered 363 | this.init = function () {}; // each time configure triggered 364 | this.change = function (v) {}; // on change 365 | this.val = function (v) {}; // on release 366 | this.xy2val = function (x, y) {}; // 367 | this.draw = function () {}; // on change / on release 368 | this.clear = function () { this._clear(); }; 369 | 370 | // Utils 371 | this.h2rgba = function (h, a) { 372 | var rgb; 373 | h = h.substring(1,7) 374 | rgb = [parseInt(h.substring(0,2),16) 375 | ,parseInt(h.substring(2,4),16) 376 | ,parseInt(h.substring(4,6),16)]; 377 | return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + a + ")"; 378 | }; 379 | 380 | this.copy = function (f, t) { 381 | for (var i in f) { t[i] = f[i]; } 382 | }; 383 | }; 384 | 385 | 386 | /** 387 | * k.Dial 388 | */ 389 | k.Dial = function () { 390 | k.o.call(this); 391 | 392 | this.startAngle = null; 393 | this.xy = null; 394 | this.radius = null; 395 | this.lineWidth = null; 396 | this.cursorExt = null; 397 | this.w2 = null; 398 | this.PI2 = 2*Math.PI; 399 | 400 | this.extend = function () { 401 | this.o = $.extend( 402 | { 403 | bgColor : this.$.data('bgcolor') || '#EEEEEE', 404 | angleOffset : this.$.data('angleoffset') || 0, 405 | angleArc : this.$.data('anglearc') || 360, 406 | inline : true 407 | }, this.o 408 | ); 409 | }; 410 | 411 | this.val = function (v) { 412 | if (null != v) { 413 | this.cv = this.o.stopper ? max(min(v, this.o.max), this.o.min) : v; 414 | this.v = this.cv; 415 | this.$.val(this.v); 416 | this._draw(); 417 | } else { 418 | return this.v; 419 | } 420 | }; 421 | 422 | this.xy2val = function (x, y) { 423 | var a, ret; 424 | 425 | a = Math.atan2( 426 | x - (this.x + this.w2) 427 | , - (y - this.y - this.w2) 428 | ) - this.angleOffset; 429 | 430 | if(this.angleArc != this.PI2 && (a < 0) && (a > -0.5)) { 431 | // if isset angleArc option, set to min if .5 under min 432 | a = 0; 433 | } else if (a < 0) { 434 | a += this.PI2; 435 | } 436 | 437 | ret = ~~ (0.5 + (a * (this.o.max - this.o.min) / this.angleArc)) 438 | + this.o.min; 439 | 440 | this.o.stopper 441 | && (ret = max(min(ret, this.o.max), this.o.min)); 442 | 443 | return ret; 444 | }; 445 | 446 | this.listen = function () { 447 | // bind MouseWheel 448 | var s = this, 449 | mw = function (e) { 450 | e.preventDefault(); 451 | 452 | var ori = e.originalEvent 453 | ,deltaX = ori.detail || ori.wheelDeltaX 454 | ,deltaY = ori.detail || ori.wheelDeltaY 455 | ,v = parseInt(s.$.val()) + (deltaX>0 || deltaY>0 ? 1 : deltaX<0 || deltaY<0 ? -1 : 0); 456 | 457 | if ( 458 | s.cH 459 | && (s.cH(v) === false) 460 | ) return; 461 | 462 | s.val(v); 463 | } 464 | , kval, to, m = 1, kv = {37:-1, 38:1, 39:1, 40:-1}; 465 | 466 | this.$ 467 | .bind( 468 | "keydown" 469 | ,function (e) { 470 | var kc = e.keyCode; 471 | kval = parseInt(String.fromCharCode(kc)); 472 | 473 | if (isNaN(kval)) { 474 | 475 | (kc !== 13) // enter 476 | && (kc !== 8) // bs 477 | && (kc !== 9) // tab 478 | && (kc !== 189) // - 479 | && e.preventDefault(); 480 | 481 | // arrows 482 | if ($.inArray(kc,[37,38,39,40]) > -1) { 483 | e.preventDefault(); 484 | 485 | var v = parseInt(s.$.val()) + kv[kc] * m; 486 | 487 | s.o.stopper 488 | && (v = max(min(v, s.o.max), s.o.min)); 489 | 490 | s.change(v); 491 | s._draw(); 492 | 493 | // long time keydown speed-up 494 | to = window.setTimeout( 495 | function () { m*=2; } 496 | ,30 497 | ); 498 | } 499 | } 500 | } 501 | ) 502 | .bind( 503 | "keyup" 504 | ,function (e) { 505 | if (isNaN(kval)) { 506 | if (to) { 507 | window.clearTimeout(to); 508 | to = null; 509 | m = 1; 510 | s.val(s.$.val()); 511 | } 512 | } else { 513 | // kval postcond 514 | (s.$.val() > s.o.max && s.$.val(s.o.max)) 515 | || (s.$.val() < s.o.min && s.$.val(s.o.min)); 516 | } 517 | 518 | } 519 | ); 520 | 521 | this.$c.bind("mousewheel DOMMouseScroll", mw); 522 | this.$.bind("mousewheel DOMMouseScroll", mw) 523 | }; 524 | 525 | this.init = function () { 526 | 527 | if ( 528 | this.v < this.o.min 529 | || this.v > this.o.max 530 | ) this.v = this.o.min; 531 | 532 | this.$.val(this.v); 533 | this.w2 = this.o.width / 2; 534 | this.cursorExt = this.o.cursor / 100; 535 | this.xy = this.w2; 536 | this.lineWidth = this.xy * this.o.thickness; 537 | this.radius = this.xy - this.lineWidth / 2; 538 | 539 | this.o.angleOffset 540 | && (this.o.angleOffset = isNaN(this.o.angleOffset) ? 0 : this.o.angleOffset); 541 | 542 | this.o.angleArc 543 | && (this.o.angleArc = isNaN(this.o.angleArc) ? this.PI2 : this.o.angleArc); 544 | 545 | // deg to rad 546 | this.angleOffset = this.o.angleOffset * Math.PI / 180; 547 | this.angleArc = this.o.angleArc * Math.PI / 180; 548 | 549 | // compute start and end angles 550 | this.startAngle = 1.5 * Math.PI + this.angleOffset; 551 | this.endAngle = 1.5 * Math.PI + this.angleOffset + this.angleArc; 552 | 553 | var s = max( 554 | String(Math.abs(this.o.max)).length 555 | , String(Math.abs(this.o.min)).length 556 | , 2 557 | ) + 2; 558 | 559 | this.o.displayInput 560 | && this.i.css({ 561 | 'width' : ((this.o.width / 2 + 4) >> 0) + 'px' 562 | ,'height' : ((this.o.width / 3) >> 0) + 'px' 563 | ,'position' : 'absolute' 564 | ,'vertical-align' : 'middle' 565 | ,'margin-top' : ((this.o.width / 3) >> 0) + 'px' 566 | ,'margin-left' : '-' + ((this.o.width * 3 / 4 + 2) >> 0) + 'px' 567 | ,'border' : 0 568 | ,'background' : 'none' 569 | ,'font' : 'bold ' + ((this.o.width / s) >> 0) + 'px Arial' 570 | ,'text-align' : 'center' 571 | ,'color' : this.o.fgColor 572 | ,'padding' : '0px' 573 | ,'-webkit-appearance': 'none' 574 | }) 575 | || this.i.css({ 576 | 'width' : '0px' 577 | ,'visibility' : 'hidden' 578 | }); 579 | }; 580 | 581 | this.change = function (v) { 582 | this.cv = v; 583 | this.$.val(v); 584 | }; 585 | 586 | this.angle = function (v) { 587 | return (v - this.o.min) * this.angleArc / (this.o.max - this.o.min); 588 | }; 589 | 590 | this.draw = function () { 591 | 592 | var c = this.g, // context 593 | a = this.angle(this.cv) // Angle 594 | , sat = this.startAngle // Start angle 595 | , eat = sat + a // End angle 596 | , sa, ea // Previous angles 597 | , r = 1; 598 | 599 | c.lineWidth = this.lineWidth; 600 | 601 | this.o.cursor 602 | && (sat = eat - this.cursorExt) 603 | && (eat = eat + this.cursorExt); 604 | 605 | c.beginPath(); 606 | c.strokeStyle = this.o.bgColor; 607 | c.arc(this.xy, this.xy, this.radius, this.endAngle, this.startAngle, true); 608 | c.stroke(); 609 | 610 | if (this.o.displayPrevious) { 611 | ea = this.startAngle + this.angle(this.v); 612 | sa = this.startAngle; 613 | this.o.cursor 614 | && (sa = ea - this.cursorExt) 615 | && (ea = ea + this.cursorExt); 616 | 617 | c.beginPath(); 618 | c.strokeStyle = this.pColor; 619 | c.arc(this.xy, this.xy, this.radius, sa, ea, false); 620 | c.stroke(); 621 | r = (this.cv == this.v); 622 | } 623 | 624 | c.beginPath(); 625 | c.strokeStyle = r ? this.o.fgColor : this.fgColor ; 626 | c.arc(this.xy, this.xy, this.radius, sat, eat, false); 627 | c.stroke(); 628 | }; 629 | 630 | this.cancel = function () { 631 | this.val(this.v); 632 | }; 633 | }; 634 | 635 | $.fn.dial = $.fn.knob = function (o) { 636 | return this.each( 637 | function () { 638 | var d = new k.Dial(); 639 | d.o = o; 640 | d.$ = $(this); 641 | d.run(); 642 | } 643 | ).parent(); 644 | }; 645 | 646 | }); -------------------------------------------------------------------------------- /templates/project/assets/stylesheets/font-awesome.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | /* FONT PATH 6 | * -------------------------- */ 7 | @font-face { 8 | font-family: 'FontAwesome'; 9 | src: url('/assets/fontawesome-webfont.eot?v=4.0.3'); 10 | src: url('/assets/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'), 11 | url('/assets/fontawesome-webfont.woff?v=4.0.3') format('woff'), 12 | url('/assets/fontawesome-webfont.ttf?v=4.0.3') format('truetype'), 13 | url('/assets/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg'); 14 | font-weight: normal; 15 | font-style: normal; 16 | } 17 | .fa { 18 | display: inline-block; 19 | font-family: FontAwesome; 20 | font-style: normal; 21 | font-weight: normal; 22 | line-height: 1; 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | /* makes the font 33% larger relative to the icon container */ 27 | .fa-lg { 28 | font-size: 1.3333333333333333em; 29 | line-height: 0.75em; 30 | vertical-align: -15%; 31 | } 32 | .fa-2x { 33 | font-size: 2em; 34 | } 35 | .fa-3x { 36 | font-size: 3em; 37 | } 38 | .fa-4x { 39 | font-size: 4em; 40 | } 41 | .fa-5x { 42 | font-size: 5em; 43 | } 44 | .fa-fw { 45 | width: 1.2857142857142858em; 46 | text-align: center; 47 | } 48 | .fa-ul { 49 | padding-left: 0; 50 | margin-left: 2.142857142857143em; 51 | list-style-type: none; 52 | } 53 | .fa-ul > li { 54 | position: relative; 55 | } 56 | .fa-li { 57 | position: absolute; 58 | left: -2.142857142857143em; 59 | width: 2.142857142857143em; 60 | top: 0.14285714285714285em; 61 | text-align: center; 62 | } 63 | .fa-li.fa-lg { 64 | left: -1.8571428571428572em; 65 | } 66 | .fa-border { 67 | padding: .2em .25em .15em; 68 | border: solid 0.08em #eeeeee; 69 | border-radius: .1em; 70 | } 71 | .pull-right { 72 | float: right; 73 | } 74 | .pull-left { 75 | float: left; 76 | } 77 | .fa.pull-left { 78 | margin-right: .3em; 79 | } 80 | .fa.pull-right { 81 | margin-left: .3em; 82 | } 83 | .fa-spin { 84 | -webkit-animation: spin 2s infinite linear; 85 | -moz-animation: spin 2s infinite linear; 86 | -o-animation: spin 2s infinite linear; 87 | animation: spin 2s infinite linear; 88 | } 89 | @-moz-keyframes spin { 90 | 0% { 91 | -moz-transform: rotate(0deg); 92 | } 93 | 100% { 94 | -moz-transform: rotate(359deg); 95 | } 96 | } 97 | @-webkit-keyframes spin { 98 | 0% { 99 | -webkit-transform: rotate(0deg); 100 | } 101 | 100% { 102 | -webkit-transform: rotate(359deg); 103 | } 104 | } 105 | @-o-keyframes spin { 106 | 0% { 107 | -o-transform: rotate(0deg); 108 | } 109 | 100% { 110 | -o-transform: rotate(359deg); 111 | } 112 | } 113 | @-ms-keyframes spin { 114 | 0% { 115 | -ms-transform: rotate(0deg); 116 | } 117 | 100% { 118 | -ms-transform: rotate(359deg); 119 | } 120 | } 121 | @keyframes spin { 122 | 0% { 123 | transform: rotate(0deg); 124 | } 125 | 100% { 126 | transform: rotate(359deg); 127 | } 128 | } 129 | .fa-rotate-90 { 130 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); 131 | -webkit-transform: rotate(90deg); 132 | -moz-transform: rotate(90deg); 133 | -ms-transform: rotate(90deg); 134 | -o-transform: rotate(90deg); 135 | transform: rotate(90deg); 136 | } 137 | .fa-rotate-180 { 138 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 139 | -webkit-transform: rotate(180deg); 140 | -moz-transform: rotate(180deg); 141 | -ms-transform: rotate(180deg); 142 | -o-transform: rotate(180deg); 143 | transform: rotate(180deg); 144 | } 145 | .fa-rotate-270 { 146 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 147 | -webkit-transform: rotate(270deg); 148 | -moz-transform: rotate(270deg); 149 | -ms-transform: rotate(270deg); 150 | -o-transform: rotate(270deg); 151 | transform: rotate(270deg); 152 | } 153 | .fa-flip-horizontal { 154 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); 155 | -webkit-transform: scale(-1, 1); 156 | -moz-transform: scale(-1, 1); 157 | -ms-transform: scale(-1, 1); 158 | -o-transform: scale(-1, 1); 159 | transform: scale(-1, 1); 160 | } 161 | .fa-flip-vertical { 162 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); 163 | -webkit-transform: scale(1, -1); 164 | -moz-transform: scale(1, -1); 165 | -ms-transform: scale(1, -1); 166 | -o-transform: scale(1, -1); 167 | transform: scale(1, -1); 168 | } 169 | .fa-stack { 170 | position: relative; 171 | display: inline-block; 172 | width: 2em; 173 | height: 2em; 174 | line-height: 2em; 175 | vertical-align: middle; 176 | } 177 | .fa-stack-1x, 178 | .fa-stack-2x { 179 | position: absolute; 180 | left: 0; 181 | width: 100%; 182 | text-align: center; 183 | } 184 | .fa-stack-1x { 185 | line-height: inherit; 186 | } 187 | .fa-stack-2x { 188 | font-size: 2em; 189 | } 190 | .fa-inverse { 191 | color: #ffffff; 192 | } 193 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 194 | readers do not read off random characters that represent icons */ 195 | .fa-glass:before { 196 | content: "\f000"; 197 | } 198 | .fa-music:before { 199 | content: "\f001"; 200 | } 201 | .fa-search:before { 202 | content: "\f002"; 203 | } 204 | .fa-envelope-o:before { 205 | content: "\f003"; 206 | } 207 | .fa-heart:before { 208 | content: "\f004"; 209 | } 210 | .fa-star:before { 211 | content: "\f005"; 212 | } 213 | .fa-star-o:before { 214 | content: "\f006"; 215 | } 216 | .fa-user:before { 217 | content: "\f007"; 218 | } 219 | .fa-film:before { 220 | content: "\f008"; 221 | } 222 | .fa-th-large:before { 223 | content: "\f009"; 224 | } 225 | .fa-th:before { 226 | content: "\f00a"; 227 | } 228 | .fa-th-list:before { 229 | content: "\f00b"; 230 | } 231 | .fa-check:before { 232 | content: "\f00c"; 233 | } 234 | .fa-times:before { 235 | content: "\f00d"; 236 | } 237 | .fa-search-plus:before { 238 | content: "\f00e"; 239 | } 240 | .fa-search-minus:before { 241 | content: "\f010"; 242 | } 243 | .fa-power-off:before { 244 | content: "\f011"; 245 | } 246 | .fa-signal:before { 247 | content: "\f012"; 248 | } 249 | .fa-gear:before, 250 | .fa-cog:before { 251 | content: "\f013"; 252 | } 253 | .fa-trash-o:before { 254 | content: "\f014"; 255 | } 256 | .fa-home:before { 257 | content: "\f015"; 258 | } 259 | .fa-file-o:before { 260 | content: "\f016"; 261 | } 262 | .fa-clock-o:before { 263 | content: "\f017"; 264 | } 265 | .fa-road:before { 266 | content: "\f018"; 267 | } 268 | .fa-download:before { 269 | content: "\f019"; 270 | } 271 | .fa-arrow-circle-o-down:before { 272 | content: "\f01a"; 273 | } 274 | .fa-arrow-circle-o-up:before { 275 | content: "\f01b"; 276 | } 277 | .fa-inbox:before { 278 | content: "\f01c"; 279 | } 280 | .fa-play-circle-o:before { 281 | content: "\f01d"; 282 | } 283 | .fa-rotate-right:before, 284 | .fa-repeat:before { 285 | content: "\f01e"; 286 | } 287 | .fa-refresh:before { 288 | content: "\f021"; 289 | } 290 | .fa-list-alt:before { 291 | content: "\f022"; 292 | } 293 | .fa-lock:before { 294 | content: "\f023"; 295 | } 296 | .fa-flag:before { 297 | content: "\f024"; 298 | } 299 | .fa-headphones:before { 300 | content: "\f025"; 301 | } 302 | .fa-volume-off:before { 303 | content: "\f026"; 304 | } 305 | .fa-volume-down:before { 306 | content: "\f027"; 307 | } 308 | .fa-volume-up:before { 309 | content: "\f028"; 310 | } 311 | .fa-qrcode:before { 312 | content: "\f029"; 313 | } 314 | .fa-barcode:before { 315 | content: "\f02a"; 316 | } 317 | .fa-tag:before { 318 | content: "\f02b"; 319 | } 320 | .fa-tags:before { 321 | content: "\f02c"; 322 | } 323 | .fa-book:before { 324 | content: "\f02d"; 325 | } 326 | .fa-bookmark:before { 327 | content: "\f02e"; 328 | } 329 | .fa-print:before { 330 | content: "\f02f"; 331 | } 332 | .fa-camera:before { 333 | content: "\f030"; 334 | } 335 | .fa-font:before { 336 | content: "\f031"; 337 | } 338 | .fa-bold:before { 339 | content: "\f032"; 340 | } 341 | .fa-italic:before { 342 | content: "\f033"; 343 | } 344 | .fa-text-height:before { 345 | content: "\f034"; 346 | } 347 | .fa-text-width:before { 348 | content: "\f035"; 349 | } 350 | .fa-align-left:before { 351 | content: "\f036"; 352 | } 353 | .fa-align-center:before { 354 | content: "\f037"; 355 | } 356 | .fa-align-right:before { 357 | content: "\f038"; 358 | } 359 | .fa-align-justify:before { 360 | content: "\f039"; 361 | } 362 | .fa-list:before { 363 | content: "\f03a"; 364 | } 365 | .fa-dedent:before, 366 | .fa-outdent:before { 367 | content: "\f03b"; 368 | } 369 | .fa-indent:before { 370 | content: "\f03c"; 371 | } 372 | .fa-video-camera:before { 373 | content: "\f03d"; 374 | } 375 | .fa-picture-o:before { 376 | content: "\f03e"; 377 | } 378 | .fa-pencil:before { 379 | content: "\f040"; 380 | } 381 | .fa-map-marker:before { 382 | content: "\f041"; 383 | } 384 | .fa-adjust:before { 385 | content: "\f042"; 386 | } 387 | .fa-tint:before { 388 | content: "\f043"; 389 | } 390 | .fa-edit:before, 391 | .fa-pencil-square-o:before { 392 | content: "\f044"; 393 | } 394 | .fa-share-square-o:before { 395 | content: "\f045"; 396 | } 397 | .fa-check-square-o:before { 398 | content: "\f046"; 399 | } 400 | .fa-arrows:before { 401 | content: "\f047"; 402 | } 403 | .fa-step-backward:before { 404 | content: "\f048"; 405 | } 406 | .fa-fast-backward:before { 407 | content: "\f049"; 408 | } 409 | .fa-backward:before { 410 | content: "\f04a"; 411 | } 412 | .fa-play:before { 413 | content: "\f04b"; 414 | } 415 | .fa-pause:before { 416 | content: "\f04c"; 417 | } 418 | .fa-stop:before { 419 | content: "\f04d"; 420 | } 421 | .fa-forward:before { 422 | content: "\f04e"; 423 | } 424 | .fa-fast-forward:before { 425 | content: "\f050"; 426 | } 427 | .fa-step-forward:before { 428 | content: "\f051"; 429 | } 430 | .fa-eject:before { 431 | content: "\f052"; 432 | } 433 | .fa-chevron-left:before { 434 | content: "\f053"; 435 | } 436 | .fa-chevron-right:before { 437 | content: "\f054"; 438 | } 439 | .fa-plus-circle:before { 440 | content: "\f055"; 441 | } 442 | .fa-minus-circle:before { 443 | content: "\f056"; 444 | } 445 | .fa-times-circle:before { 446 | content: "\f057"; 447 | } 448 | .fa-check-circle:before { 449 | content: "\f058"; 450 | } 451 | .fa-question-circle:before { 452 | content: "\f059"; 453 | } 454 | .fa-info-circle:before { 455 | content: "\f05a"; 456 | } 457 | .fa-crosshairs:before { 458 | content: "\f05b"; 459 | } 460 | .fa-times-circle-o:before { 461 | content: "\f05c"; 462 | } 463 | .fa-check-circle-o:before { 464 | content: "\f05d"; 465 | } 466 | .fa-ban:before { 467 | content: "\f05e"; 468 | } 469 | .fa-arrow-left:before { 470 | content: "\f060"; 471 | } 472 | .fa-arrow-right:before { 473 | content: "\f061"; 474 | } 475 | .fa-arrow-up:before { 476 | content: "\f062"; 477 | } 478 | .fa-arrow-down:before { 479 | content: "\f063"; 480 | } 481 | .fa-mail-forward:before, 482 | .fa-share:before { 483 | content: "\f064"; 484 | } 485 | .fa-expand:before { 486 | content: "\f065"; 487 | } 488 | .fa-compress:before { 489 | content: "\f066"; 490 | } 491 | .fa-plus:before { 492 | content: "\f067"; 493 | } 494 | .fa-minus:before { 495 | content: "\f068"; 496 | } 497 | .fa-asterisk:before { 498 | content: "\f069"; 499 | } 500 | .fa-exclamation-circle:before { 501 | content: "\f06a"; 502 | } 503 | .fa-gift:before { 504 | content: "\f06b"; 505 | } 506 | .fa-leaf:before { 507 | content: "\f06c"; 508 | } 509 | .fa-fire:before { 510 | content: "\f06d"; 511 | } 512 | .fa-eye:before { 513 | content: "\f06e"; 514 | } 515 | .fa-eye-slash:before { 516 | content: "\f070"; 517 | } 518 | .fa-warning:before, 519 | .fa-exclamation-triangle:before { 520 | content: "\f071"; 521 | } 522 | .fa-plane:before { 523 | content: "\f072"; 524 | } 525 | .fa-calendar:before { 526 | content: "\f073"; 527 | } 528 | .fa-random:before { 529 | content: "\f074"; 530 | } 531 | .fa-comment:before { 532 | content: "\f075"; 533 | } 534 | .fa-magnet:before { 535 | content: "\f076"; 536 | } 537 | .fa-chevron-up:before { 538 | content: "\f077"; 539 | } 540 | .fa-chevron-down:before { 541 | content: "\f078"; 542 | } 543 | .fa-retweet:before { 544 | content: "\f079"; 545 | } 546 | .fa-shopping-cart:before { 547 | content: "\f07a"; 548 | } 549 | .fa-folder:before { 550 | content: "\f07b"; 551 | } 552 | .fa-folder-open:before { 553 | content: "\f07c"; 554 | } 555 | .fa-arrows-v:before { 556 | content: "\f07d"; 557 | } 558 | .fa-arrows-h:before { 559 | content: "\f07e"; 560 | } 561 | .fa-bar-chart-o:before { 562 | content: "\f080"; 563 | } 564 | .fa-twitter-square:before { 565 | content: "\f081"; 566 | } 567 | .fa-facebook-square:before { 568 | content: "\f082"; 569 | } 570 | .fa-camera-retro:before { 571 | content: "\f083"; 572 | } 573 | .fa-key:before { 574 | content: "\f084"; 575 | } 576 | .fa-gears:before, 577 | .fa-cogs:before { 578 | content: "\f085"; 579 | } 580 | .fa-comments:before { 581 | content: "\f086"; 582 | } 583 | .fa-thumbs-o-up:before { 584 | content: "\f087"; 585 | } 586 | .fa-thumbs-o-down:before { 587 | content: "\f088"; 588 | } 589 | .fa-star-half:before { 590 | content: "\f089"; 591 | } 592 | .fa-heart-o:before { 593 | content: "\f08a"; 594 | } 595 | .fa-sign-out:before { 596 | content: "\f08b"; 597 | } 598 | .fa-linkedin-square:before { 599 | content: "\f08c"; 600 | } 601 | .fa-thumb-tack:before { 602 | content: "\f08d"; 603 | } 604 | .fa-external-link:before { 605 | content: "\f08e"; 606 | } 607 | .fa-sign-in:before { 608 | content: "\f090"; 609 | } 610 | .fa-trophy:before { 611 | content: "\f091"; 612 | } 613 | .fa-github-square:before { 614 | content: "\f092"; 615 | } 616 | .fa-upload:before { 617 | content: "\f093"; 618 | } 619 | .fa-lemon-o:before { 620 | content: "\f094"; 621 | } 622 | .fa-phone:before { 623 | content: "\f095"; 624 | } 625 | .fa-square-o:before { 626 | content: "\f096"; 627 | } 628 | .fa-bookmark-o:before { 629 | content: "\f097"; 630 | } 631 | .fa-phone-square:before { 632 | content: "\f098"; 633 | } 634 | .fa-twitter:before { 635 | content: "\f099"; 636 | } 637 | .fa-facebook:before { 638 | content: "\f09a"; 639 | } 640 | .fa-github:before { 641 | content: "\f09b"; 642 | } 643 | .fa-unlock:before { 644 | content: "\f09c"; 645 | } 646 | .fa-credit-card:before { 647 | content: "\f09d"; 648 | } 649 | .fa-rss:before { 650 | content: "\f09e"; 651 | } 652 | .fa-hdd-o:before { 653 | content: "\f0a0"; 654 | } 655 | .fa-bullhorn:before { 656 | content: "\f0a1"; 657 | } 658 | .fa-bell:before { 659 | content: "\f0f3"; 660 | } 661 | .fa-certificate:before { 662 | content: "\f0a3"; 663 | } 664 | .fa-hand-o-right:before { 665 | content: "\f0a4"; 666 | } 667 | .fa-hand-o-left:before { 668 | content: "\f0a5"; 669 | } 670 | .fa-hand-o-up:before { 671 | content: "\f0a6"; 672 | } 673 | .fa-hand-o-down:before { 674 | content: "\f0a7"; 675 | } 676 | .fa-arrow-circle-left:before { 677 | content: "\f0a8"; 678 | } 679 | .fa-arrow-circle-right:before { 680 | content: "\f0a9"; 681 | } 682 | .fa-arrow-circle-up:before { 683 | content: "\f0aa"; 684 | } 685 | .fa-arrow-circle-down:before { 686 | content: "\f0ab"; 687 | } 688 | .fa-globe:before { 689 | content: "\f0ac"; 690 | } 691 | .fa-wrench:before { 692 | content: "\f0ad"; 693 | } 694 | .fa-tasks:before { 695 | content: "\f0ae"; 696 | } 697 | .fa-filter:before { 698 | content: "\f0b0"; 699 | } 700 | .fa-briefcase:before { 701 | content: "\f0b1"; 702 | } 703 | .fa-arrows-alt:before { 704 | content: "\f0b2"; 705 | } 706 | .fa-group:before, 707 | .fa-users:before { 708 | content: "\f0c0"; 709 | } 710 | .fa-chain:before, 711 | .fa-link:before { 712 | content: "\f0c1"; 713 | } 714 | .fa-cloud:before { 715 | content: "\f0c2"; 716 | } 717 | .fa-flask:before { 718 | content: "\f0c3"; 719 | } 720 | .fa-cut:before, 721 | .fa-scissors:before { 722 | content: "\f0c4"; 723 | } 724 | .fa-copy:before, 725 | .fa-files-o:before { 726 | content: "\f0c5"; 727 | } 728 | .fa-paperclip:before { 729 | content: "\f0c6"; 730 | } 731 | .fa-save:before, 732 | .fa-floppy-o:before { 733 | content: "\f0c7"; 734 | } 735 | .fa-square:before { 736 | content: "\f0c8"; 737 | } 738 | .fa-bars:before { 739 | content: "\f0c9"; 740 | } 741 | .fa-list-ul:before { 742 | content: "\f0ca"; 743 | } 744 | .fa-list-ol:before { 745 | content: "\f0cb"; 746 | } 747 | .fa-strikethrough:before { 748 | content: "\f0cc"; 749 | } 750 | .fa-underline:before { 751 | content: "\f0cd"; 752 | } 753 | .fa-table:before { 754 | content: "\f0ce"; 755 | } 756 | .fa-magic:before { 757 | content: "\f0d0"; 758 | } 759 | .fa-truck:before { 760 | content: "\f0d1"; 761 | } 762 | .fa-pinterest:before { 763 | content: "\f0d2"; 764 | } 765 | .fa-pinterest-square:before { 766 | content: "\f0d3"; 767 | } 768 | .fa-google-plus-square:before { 769 | content: "\f0d4"; 770 | } 771 | .fa-google-plus:before { 772 | content: "\f0d5"; 773 | } 774 | .fa-money:before { 775 | content: "\f0d6"; 776 | } 777 | .fa-caret-down:before { 778 | content: "\f0d7"; 779 | } 780 | .fa-caret-up:before { 781 | content: "\f0d8"; 782 | } 783 | .fa-caret-left:before { 784 | content: "\f0d9"; 785 | } 786 | .fa-caret-right:before { 787 | content: "\f0da"; 788 | } 789 | .fa-columns:before { 790 | content: "\f0db"; 791 | } 792 | .fa-unsorted:before, 793 | .fa-sort:before { 794 | content: "\f0dc"; 795 | } 796 | .fa-sort-down:before, 797 | .fa-sort-asc:before { 798 | content: "\f0dd"; 799 | } 800 | .fa-sort-up:before, 801 | .fa-sort-desc:before { 802 | content: "\f0de"; 803 | } 804 | .fa-envelope:before { 805 | content: "\f0e0"; 806 | } 807 | .fa-linkedin:before { 808 | content: "\f0e1"; 809 | } 810 | .fa-rotate-left:before, 811 | .fa-undo:before { 812 | content: "\f0e2"; 813 | } 814 | .fa-legal:before, 815 | .fa-gavel:before { 816 | content: "\f0e3"; 817 | } 818 | .fa-dashboard:before, 819 | .fa-tachometer:before { 820 | content: "\f0e4"; 821 | } 822 | .fa-comment-o:before { 823 | content: "\f0e5"; 824 | } 825 | .fa-comments-o:before { 826 | content: "\f0e6"; 827 | } 828 | .fa-flash:before, 829 | .fa-bolt:before { 830 | content: "\f0e7"; 831 | } 832 | .fa-sitemap:before { 833 | content: "\f0e8"; 834 | } 835 | .fa-umbrella:before { 836 | content: "\f0e9"; 837 | } 838 | .fa-paste:before, 839 | .fa-clipboard:before { 840 | content: "\f0ea"; 841 | } 842 | .fa-lightbulb-o:before { 843 | content: "\f0eb"; 844 | } 845 | .fa-exchange:before { 846 | content: "\f0ec"; 847 | } 848 | .fa-cloud-download:before { 849 | content: "\f0ed"; 850 | } 851 | .fa-cloud-upload:before { 852 | content: "\f0ee"; 853 | } 854 | .fa-user-md:before { 855 | content: "\f0f0"; 856 | } 857 | .fa-stethoscope:before { 858 | content: "\f0f1"; 859 | } 860 | .fa-suitcase:before { 861 | content: "\f0f2"; 862 | } 863 | .fa-bell-o:before { 864 | content: "\f0a2"; 865 | } 866 | .fa-coffee:before { 867 | content: "\f0f4"; 868 | } 869 | .fa-cutlery:before { 870 | content: "\f0f5"; 871 | } 872 | .fa-file-text-o:before { 873 | content: "\f0f6"; 874 | } 875 | .fa-building-o:before { 876 | content: "\f0f7"; 877 | } 878 | .fa-hospital-o:before { 879 | content: "\f0f8"; 880 | } 881 | .fa-ambulance:before { 882 | content: "\f0f9"; 883 | } 884 | .fa-medkit:before { 885 | content: "\f0fa"; 886 | } 887 | .fa-fighter-jet:before { 888 | content: "\f0fb"; 889 | } 890 | .fa-beer:before { 891 | content: "\f0fc"; 892 | } 893 | .fa-h-square:before { 894 | content: "\f0fd"; 895 | } 896 | .fa-plus-square:before { 897 | content: "\f0fe"; 898 | } 899 | .fa-angle-double-left:before { 900 | content: "\f100"; 901 | } 902 | .fa-angle-double-right:before { 903 | content: "\f101"; 904 | } 905 | .fa-angle-double-up:before { 906 | content: "\f102"; 907 | } 908 | .fa-angle-double-down:before { 909 | content: "\f103"; 910 | } 911 | .fa-angle-left:before { 912 | content: "\f104"; 913 | } 914 | .fa-angle-right:before { 915 | content: "\f105"; 916 | } 917 | .fa-angle-up:before { 918 | content: "\f106"; 919 | } 920 | .fa-angle-down:before { 921 | content: "\f107"; 922 | } 923 | .fa-desktop:before { 924 | content: "\f108"; 925 | } 926 | .fa-laptop:before { 927 | content: "\f109"; 928 | } 929 | .fa-tablet:before { 930 | content: "\f10a"; 931 | } 932 | .fa-mobile-phone:before, 933 | .fa-mobile:before { 934 | content: "\f10b"; 935 | } 936 | .fa-circle-o:before { 937 | content: "\f10c"; 938 | } 939 | .fa-quote-left:before { 940 | content: "\f10d"; 941 | } 942 | .fa-quote-right:before { 943 | content: "\f10e"; 944 | } 945 | .fa-spinner:before { 946 | content: "\f110"; 947 | } 948 | .fa-circle:before { 949 | content: "\f111"; 950 | } 951 | .fa-mail-reply:before, 952 | .fa-reply:before { 953 | content: "\f112"; 954 | } 955 | .fa-github-alt:before { 956 | content: "\f113"; 957 | } 958 | .fa-folder-o:before { 959 | content: "\f114"; 960 | } 961 | .fa-folder-open-o:before { 962 | content: "\f115"; 963 | } 964 | .fa-smile-o:before { 965 | content: "\f118"; 966 | } 967 | .fa-frown-o:before { 968 | content: "\f119"; 969 | } 970 | .fa-meh-o:before { 971 | content: "\f11a"; 972 | } 973 | .fa-gamepad:before { 974 | content: "\f11b"; 975 | } 976 | .fa-keyboard-o:before { 977 | content: "\f11c"; 978 | } 979 | .fa-flag-o:before { 980 | content: "\f11d"; 981 | } 982 | .fa-flag-checkered:before { 983 | content: "\f11e"; 984 | } 985 | .fa-terminal:before { 986 | content: "\f120"; 987 | } 988 | .fa-code:before { 989 | content: "\f121"; 990 | } 991 | .fa-reply-all:before { 992 | content: "\f122"; 993 | } 994 | .fa-mail-reply-all:before { 995 | content: "\f122"; 996 | } 997 | .fa-star-half-empty:before, 998 | .fa-star-half-full:before, 999 | .fa-star-half-o:before { 1000 | content: "\f123"; 1001 | } 1002 | .fa-location-arrow:before { 1003 | content: "\f124"; 1004 | } 1005 | .fa-crop:before { 1006 | content: "\f125"; 1007 | } 1008 | .fa-code-fork:before { 1009 | content: "\f126"; 1010 | } 1011 | .fa-unlink:before, 1012 | .fa-chain-broken:before { 1013 | content: "\f127"; 1014 | } 1015 | .fa-question:before { 1016 | content: "\f128"; 1017 | } 1018 | .fa-info:before { 1019 | content: "\f129"; 1020 | } 1021 | .fa-exclamation:before { 1022 | content: "\f12a"; 1023 | } 1024 | .fa-superscript:before { 1025 | content: "\f12b"; 1026 | } 1027 | .fa-subscript:before { 1028 | content: "\f12c"; 1029 | } 1030 | .fa-eraser:before { 1031 | content: "\f12d"; 1032 | } 1033 | .fa-puzzle-piece:before { 1034 | content: "\f12e"; 1035 | } 1036 | .fa-microphone:before { 1037 | content: "\f130"; 1038 | } 1039 | .fa-microphone-slash:before { 1040 | content: "\f131"; 1041 | } 1042 | .fa-shield:before { 1043 | content: "\f132"; 1044 | } 1045 | .fa-calendar-o:before { 1046 | content: "\f133"; 1047 | } 1048 | .fa-fire-extinguisher:before { 1049 | content: "\f134"; 1050 | } 1051 | .fa-rocket:before { 1052 | content: "\f135"; 1053 | } 1054 | .fa-maxcdn:before { 1055 | content: "\f136"; 1056 | } 1057 | .fa-chevron-circle-left:before { 1058 | content: "\f137"; 1059 | } 1060 | .fa-chevron-circle-right:before { 1061 | content: "\f138"; 1062 | } 1063 | .fa-chevron-circle-up:before { 1064 | content: "\f139"; 1065 | } 1066 | .fa-chevron-circle-down:before { 1067 | content: "\f13a"; 1068 | } 1069 | .fa-html5:before { 1070 | content: "\f13b"; 1071 | } 1072 | .fa-css3:before { 1073 | content: "\f13c"; 1074 | } 1075 | .fa-anchor:before { 1076 | content: "\f13d"; 1077 | } 1078 | .fa-unlock-alt:before { 1079 | content: "\f13e"; 1080 | } 1081 | .fa-bullseye:before { 1082 | content: "\f140"; 1083 | } 1084 | .fa-ellipsis-h:before { 1085 | content: "\f141"; 1086 | } 1087 | .fa-ellipsis-v:before { 1088 | content: "\f142"; 1089 | } 1090 | .fa-rss-square:before { 1091 | content: "\f143"; 1092 | } 1093 | .fa-play-circle:before { 1094 | content: "\f144"; 1095 | } 1096 | .fa-ticket:before { 1097 | content: "\f145"; 1098 | } 1099 | .fa-minus-square:before { 1100 | content: "\f146"; 1101 | } 1102 | .fa-minus-square-o:before { 1103 | content: "\f147"; 1104 | } 1105 | .fa-level-up:before { 1106 | content: "\f148"; 1107 | } 1108 | .fa-level-down:before { 1109 | content: "\f149"; 1110 | } 1111 | .fa-check-square:before { 1112 | content: "\f14a"; 1113 | } 1114 | .fa-pencil-square:before { 1115 | content: "\f14b"; 1116 | } 1117 | .fa-external-link-square:before { 1118 | content: "\f14c"; 1119 | } 1120 | .fa-share-square:before { 1121 | content: "\f14d"; 1122 | } 1123 | .fa-compass:before { 1124 | content: "\f14e"; 1125 | } 1126 | .fa-toggle-down:before, 1127 | .fa-caret-square-o-down:before { 1128 | content: "\f150"; 1129 | } 1130 | .fa-toggle-up:before, 1131 | .fa-caret-square-o-up:before { 1132 | content: "\f151"; 1133 | } 1134 | .fa-toggle-right:before, 1135 | .fa-caret-square-o-right:before { 1136 | content: "\f152"; 1137 | } 1138 | .fa-euro:before, 1139 | .fa-eur:before { 1140 | content: "\f153"; 1141 | } 1142 | .fa-gbp:before { 1143 | content: "\f154"; 1144 | } 1145 | .fa-dollar:before, 1146 | .fa-usd:before { 1147 | content: "\f155"; 1148 | } 1149 | .fa-rupee:before, 1150 | .fa-inr:before { 1151 | content: "\f156"; 1152 | } 1153 | .fa-cny:before, 1154 | .fa-rmb:before, 1155 | .fa-yen:before, 1156 | .fa-jpy:before { 1157 | content: "\f157"; 1158 | } 1159 | .fa-ruble:before, 1160 | .fa-rouble:before, 1161 | .fa-rub:before { 1162 | content: "\f158"; 1163 | } 1164 | .fa-won:before, 1165 | .fa-krw:before { 1166 | content: "\f159"; 1167 | } 1168 | .fa-bitcoin:before, 1169 | .fa-btc:before { 1170 | content: "\f15a"; 1171 | } 1172 | .fa-file:before { 1173 | content: "\f15b"; 1174 | } 1175 | .fa-file-text:before { 1176 | content: "\f15c"; 1177 | } 1178 | .fa-sort-alpha-asc:before { 1179 | content: "\f15d"; 1180 | } 1181 | .fa-sort-alpha-desc:before { 1182 | content: "\f15e"; 1183 | } 1184 | .fa-sort-amount-asc:before { 1185 | content: "\f160"; 1186 | } 1187 | .fa-sort-amount-desc:before { 1188 | content: "\f161"; 1189 | } 1190 | .fa-sort-numeric-asc:before { 1191 | content: "\f162"; 1192 | } 1193 | .fa-sort-numeric-desc:before { 1194 | content: "\f163"; 1195 | } 1196 | .fa-thumbs-up:before { 1197 | content: "\f164"; 1198 | } 1199 | .fa-thumbs-down:before { 1200 | content: "\f165"; 1201 | } 1202 | .fa-youtube-square:before { 1203 | content: "\f166"; 1204 | } 1205 | .fa-youtube:before { 1206 | content: "\f167"; 1207 | } 1208 | .fa-xing:before { 1209 | content: "\f168"; 1210 | } 1211 | .fa-xing-square:before { 1212 | content: "\f169"; 1213 | } 1214 | .fa-youtube-play:before { 1215 | content: "\f16a"; 1216 | } 1217 | .fa-dropbox:before { 1218 | content: "\f16b"; 1219 | } 1220 | .fa-stack-overflow:before { 1221 | content: "\f16c"; 1222 | } 1223 | .fa-instagram:before { 1224 | content: "\f16d"; 1225 | } 1226 | .fa-flickr:before { 1227 | content: "\f16e"; 1228 | } 1229 | .fa-adn:before { 1230 | content: "\f170"; 1231 | } 1232 | .fa-bitbucket:before { 1233 | content: "\f171"; 1234 | } 1235 | .fa-bitbucket-square:before { 1236 | content: "\f172"; 1237 | } 1238 | .fa-tumblr:before { 1239 | content: "\f173"; 1240 | } 1241 | .fa-tumblr-square:before { 1242 | content: "\f174"; 1243 | } 1244 | .fa-long-arrow-down:before { 1245 | content: "\f175"; 1246 | } 1247 | .fa-long-arrow-up:before { 1248 | content: "\f176"; 1249 | } 1250 | .fa-long-arrow-left:before { 1251 | content: "\f177"; 1252 | } 1253 | .fa-long-arrow-right:before { 1254 | content: "\f178"; 1255 | } 1256 | .fa-apple:before { 1257 | content: "\f179"; 1258 | } 1259 | .fa-windows:before { 1260 | content: "\f17a"; 1261 | } 1262 | .fa-android:before { 1263 | content: "\f17b"; 1264 | } 1265 | .fa-linux:before { 1266 | content: "\f17c"; 1267 | } 1268 | .fa-dribbble:before { 1269 | content: "\f17d"; 1270 | } 1271 | .fa-skype:before { 1272 | content: "\f17e"; 1273 | } 1274 | .fa-foursquare:before { 1275 | content: "\f180"; 1276 | } 1277 | .fa-trello:before { 1278 | content: "\f181"; 1279 | } 1280 | .fa-female:before { 1281 | content: "\f182"; 1282 | } 1283 | .fa-male:before { 1284 | content: "\f183"; 1285 | } 1286 | .fa-gittip:before { 1287 | content: "\f184"; 1288 | } 1289 | .fa-sun-o:before { 1290 | content: "\f185"; 1291 | } 1292 | .fa-moon-o:before { 1293 | content: "\f186"; 1294 | } 1295 | .fa-archive:before { 1296 | content: "\f187"; 1297 | } 1298 | .fa-bug:before { 1299 | content: "\f188"; 1300 | } 1301 | .fa-vk:before { 1302 | content: "\f189"; 1303 | } 1304 | .fa-weibo:before { 1305 | content: "\f18a"; 1306 | } 1307 | .fa-renren:before { 1308 | content: "\f18b"; 1309 | } 1310 | .fa-pagelines:before { 1311 | content: "\f18c"; 1312 | } 1313 | .fa-stack-exchange:before { 1314 | content: "\f18d"; 1315 | } 1316 | .fa-arrow-circle-o-right:before { 1317 | content: "\f18e"; 1318 | } 1319 | .fa-arrow-circle-o-left:before { 1320 | content: "\f190"; 1321 | } 1322 | .fa-toggle-left:before, 1323 | .fa-caret-square-o-left:before { 1324 | content: "\f191"; 1325 | } 1326 | .fa-dot-circle-o:before { 1327 | content: "\f192"; 1328 | } 1329 | .fa-wheelchair:before { 1330 | content: "\f193"; 1331 | } 1332 | .fa-vimeo-square:before { 1333 | content: "\f194"; 1334 | } 1335 | .fa-turkish-lira:before, 1336 | .fa-try:before { 1337 | content: "\f195"; 1338 | } 1339 | .fa-plus-square-o:before { 1340 | content: "\f196"; 1341 | } 1342 | -------------------------------------------------------------------------------- /templates/project/assets/javascripts/gridster/jquery.gridster.min.js: -------------------------------------------------------------------------------- 1 | /*! gridster.js - v0.1.0 - 2013-04-09 2 | * http://gridster.net/ 3 | * Copyright (c) 2013 ducksboard; Licensed MIT */ 4 | (function(e,t,n,r){function i(t){return t[0]&&e.isPlainObject(t[0])?this.data=t[0]:this.el=t,this.isCoords=!0,this.coords={},this.init(),this}var s=i.prototype;s.init=function(){this.set(),this.original_coords=this.get()},s.set=function(e,t){var n=this.el;n&&!e&&(this.data=n.offset(),this.data.width=n.width(),this.data.height=n.height());if(n&&e&&!t){var r=n.offset();this.data.top=r.top,this.data.left=r.left}var i=this.data;return this.coords.x1=i.left,this.coords.y1=i.top,this.coords.x2=i.left+i.width,this.coords.y2=i.top+i.height,this.coords.cx=i.left+i.width/2,this.coords.cy=i.top+i.height/2,this.coords.width=i.width,this.coords.height=i.height,this.coords.el=n||!1,this},s.update=function(t){if(!t&&!this.el)return this;if(t){var n=e.extend({},this.data,t);return this.data=n,this.set(!0,!0)}return this.set(!0),this},s.get=function(){return this.coords},e.fn.coords=function(){if(this.data("coords"))return this.data("coords");var e=new i(this,arguments[0]);return this.data("coords",e),e}})(jQuery,window,document),function(e,t,n,r){function s(t,n,r){this.options=e.extend(i,r),this.$element=t,this.last_colliders=[],this.last_colliders_coords=[],typeof n=="string"||n instanceof jQuery?this.$colliders=e(n,this.options.colliders_context).not(this.$element):this.colliders=e(n),this.init()}var i={colliders_context:n.body},o=s.prototype;o.init=function(){this.find_collisions()},o.overlaps=function(e,t){var n=!1,r=!1;if(t.x1>=e.x1&&t.x1<=e.x2||t.x2>=e.x1&&t.x2<=e.x2||e.x1>=t.x1&&e.x2<=t.x2)n=!0;if(t.y1>=e.y1&&t.y1<=e.y2||t.y2>=e.y1&&t.y2<=e.y2||e.y1>=t.y1&&e.y2<=t.y2)r=!0;return n&&r},o.detect_overlapping_region=function(e,t){var n="",r="";return e.y1>t.cy&&e.y1t.y1&&e.y2t.cx&&e.x1t.x1&&e.x2this.player_max_left?i=this.player_max_left:i=o&&(t=n+30,t0&&(s.scrollTop(t),this.scrollOffset=this.scrollOffset-30))},f.calculate_positions=function(e){this.window_height=s.height()},f.drag_handler=function(t){var n=t.target.nodeName;if(this.disabled||t.which!==1&&!o)return;if(this.ignore_drag(t))return;var r=this,i=!0;return this.$player=e(t.currentTarget),this.el_init_pos=this.get_actual_pos(this.$player),this.mouse_init_pos=this.get_mouse_pos(t),this.offsetY=this.mouse_init_pos.top-this.el_init_pos.top,this.on_pointer_events_move=function(e){var t=r.get_mouse_pos(e),n=Math.abs(t.left-r.mouse_init_pos.left),s=Math.abs(t.top-r.mouse_init_pos.top);return n>r.options.distance||s>r.options.distance?i?(i=!1,r.on_dragstart.call(r,e),!1):(r.is_dragging===!0&&r.on_dragmove.call(r,e),!1):!1},this.$body.on(u.move,this.on_pointer_events_move),!1},f.on_dragstart=function(t){t.preventDefault(),this.drag_start=!0,this.is_dragging=!0;var r=this.$container.offset();return this.baseX=Math.round(r.left),this.baseY=Math.round(r.top),this.doc_height=e(n).height(),this.options.helper==="clone"?(this.$helper=this.$player.clone().appendTo(this.$container).addClass("helper"),this.helper=!0):this.helper=!1,this.scrollOffset=0,this.el_init_offset=this.$player.offset(),this.player_width=this.$player.width(),this.player_height=this.$player.height(),this.player_max_left=this.$container.width()-this.player_width+this.options.offset_left,this.options.start&&this.options.start.call(this.$player,t,{helper:this.helper?this.$helper:this.$player}),!1},f.on_dragmove=function(e){var t=this.get_offset(e);this.options.autoscroll&&this.manage_scroll(t),(this.helper?this.$helper:this.$player).css({position:"absolute",left:t.left,top:t.top});var n={position:{left:t.left,top:t.top}};return this.options.drag&&this.options.drag.call(this.$player,e,n),!1},f.on_dragstop=function(e){var t=this.get_offset(e);this.drag_start=!1;var n={position:{left:t.left,top:t.top}};return this.options.stop&&this.options.stop.call(this.$player,e,n),this.helper&&this.$helper.remove(),!1},f.on_select_start=function(e){if(this.disabled)return;if(this.ignore_drag(e))return;return!1},f.enable=function(){this.disabled=!1},f.disable=function(){this.disabled=!0},f.destroy=function(){this.disable(),this.$container.off("selectstart",this.proxied_on_select_start),this.$container.off(u.start,this.proxied_drag_handler),this.$body.off(u.end,this.proxied_pointer_events_end),this.$body.off(u.move,this.on_pointer_events_move),e(t).unbind("resize",this.on_window_resize),e.removeData(this.$container,"drag")},f.ignore_drag=function(t){return this.options.handle?!e(t.target).is(this.options.handle):e.inArray(t.target.nodeName,this.options.ignore_dragging)>=0},e.fn.drag=function(t){return this.each(function(){e.data(this,"drag")||e.data(this,"drag",new a(this,t))})}}(jQuery,window,document),function(e,t,n,r){function o(t,n){this.options=e.extend(!0,i,n),this.$el=e(t),this.$wrapper=this.$el.parent(),this.$widgets=this.$el.children(this.options.widget_selector).addClass("gs_w"),this.widgets=[],this.$changed=e([]),this.wrapper_width=this.$wrapper.width(),this.min_widget_width=this.options.widget_margins[0]*2+this.options.widget_base_dimensions[0],this.min_widget_height=this.options.widget_margins[1]*2+this.options.widget_base_dimensions[1],this.instanceId=s++,this.init()}var i={namespace:"",widget_selector:"li",widget_margins:[10,10],widget_base_dimensions:[400,225],extra_rows:0,extra_cols:0,min_cols:1,min_rows:15,max_size_x:6,autogenerate_stylesheet:!0,avoid_overlapped_widgets:!0,serialize_params:function(e,t){return{col:t.col,row:t.row,size_x:t.size_x,size_y:t.size_y}},collision:{},draggable:{distance:4},style_tag_id_prefix:"gridster-style-tags-"},s=0,u=o.prototype;u.init=function(){this.generate_grid_and_stylesheet(),this.get_widgets_from_DOM(),this.set_dom_grid_height(),this.$wrapper.addClass("ready"),this.draggable(),this.on_window_resize=throttle(e.proxy(this.recalculate_faux_grid,this),200),e(t).bind("resize",this.on_window_resize)},u.disable=function(){return this.$wrapper.find(".player-revert").removeClass("player-revert"),this.drag_api.disable(),this},u.enable=function(){return this.drag_api.enable(),this},u.add_widget=function(t,n,r,i,s){var o;n||(n=1),r||(r=1),!i&!s?o=this.next_position(n,r):(o={col:i,row:s},this.empty_cells(i,s,n,r));var u=e(t).attr({"data-col":o.col,"data-row":o.row,"data-sizex":n,"data-sizey":r}).addClass("gs_w").appendTo(this.$el).hide();return this.$widgets=this.$widgets.add(u),this.register_widget(u),this.add_faux_rows(o.size_y),this.set_dom_grid_height(),u.fadeIn()},u.resize_widget=function(t,n,r){var i=t.coords().grid;n||(n=i.size_x),r||(r=i.size_y),n>this.cols&&(n=this.cols);var s=this.get_cells_occupied(i),o=i.size_x,u=i.size_y,a=i.col,f=a,l=n>o,c=r>u;if(a+n-1>this.cols){var h=a+(n-1)-this.cols,p=a-h;f=Math.max(1,p)}var d={col:f,row:i.row,size_x:n,size_y:r},v=this.get_cells_occupied(d),m=[];e.each(s.cols,function(t,n){e.inArray(n,v.cols)===-1&&m.push(n)});var g=[];e.each(v.cols,function(t,n){e.inArray(n,s.cols)===-1&&g.push(n)});var y=[];e.each(s.rows,function(t,n){e.inArray(n,v.rows)===-1&&y.push(n)});var b=[];e.each(v.rows,function(t,n){e.inArray(n,s.rows)===-1&&b.push(n)}),this.remove_from_gridmap(i);if(g.length){var w=[f,i.row,n,Math.min(u,r),t];this.empty_cells.apply(this,w)}if(b.length){var E=[f,i.row,n,r,t];this.empty_cells.apply(this,E)}i.col=f,i.size_x=n,i.size_y=r,this.add_to_gridmap(d,t),t.data("coords").update({width:n*this.options.widget_base_dimensions[0]+(n-1)*this.options.widget_margins[0]*2,height:r*this.options.widget_base_dimensions[1]+(r-1)*this.options.widget_margins[1]*2}),r>u&&this.add_faux_rows(r-u),n>o&&this.add_faux_cols(n-o),t.attr({"data-col":f,"data-sizex":n,"data-sizey":r});if(m.length){var S=[m[0],i.row,m.length,Math.min(u,r),t];this.remove_empty_cells.apply(this,S)}if(y.length){var x=[f,i.row,n,r,t];this.remove_empty_cells.apply(this,x)}return t},u.empty_cells=function(t,n,r,i,s){var o=this.widgets_below({col:t,row:n-i,size_x:r,size_y:i});return o.not(s).each(e.proxy(function(t,r){var s=e(r).coords().grid;if(!(s.row<=n+i-1))return;var o=n+i-s.row;this.move_widget_down(e(r),o)},this)),this.set_dom_grid_height(),this},u.remove_empty_cells=function(t,n,r,i,s){var o=this.widgets_below({col:t,row:n,size_x:r,size_y:i});return o.not(s).each(e.proxy(function(t,n){this.move_widget_up(e(n),i)},this)),this.set_dom_grid_height(),this},u.next_position=function(e,t){e||(e=1),t||(t=1);var n=this.gridmap,r=n.length,i=[],s;for(var o=1;o",{"class":"preview-holder","data-row":this.$player.attr("data-row"),"data-col":this.$player.attr("data-col"),css:{width:i.width,height:i.height}}).appendTo(this.$el),this.options.draggable.start&&this.options.draggable.start.call(this,t,n)},u.on_drag=function(e,t){if(this.$player===null)return!1;var n={left:t.position.left+this.baseX,top:t.position.top+this.baseY};this.colliders_data=this.collision_api.get_closest_colliders(n),this.on_overlapped_column_change(this.on_start_overlapping_column,this.on_stop_overlapping_column),this.on_overlapped_row_change(this.on_start_overlapping_row,this.on_stop_overlapping_row),this.helper&&this.$player&&this.$player.css({left:t.position.left,top:t.position.top}),this.options.draggable.drag&&this.options.draggable.drag.call(this,e,t)},u.on_stop_drag=function(e,t){this.$helper.add(this.$player).add(this.$wrapper).removeClass("dragging"),t.position.left=t.position.left+this.baseX,t.position.top=t.position.top+this.baseY,this.colliders_data=this.collision_api.get_closest_colliders(t.position),this.on_overlapped_column_change(this.on_start_overlapping_column,this.on_stop_overlapping_column),this.on_overlapped_row_change(this.on_start_overlapping_row,this.on_stop_overlapping_row),this.$player.addClass("player-revert").removeClass("player").attr({"data-col":this.placeholder_grid_data.col,"data-row":this.placeholder_grid_data.row}).css({left:"",top:""}),this.$changed=this.$changed.add(this.$player),this.cells_occupied_by_player=this.get_cells_occupied(this.placeholder_grid_data),this.set_cells_player_occupies(this.placeholder_grid_data.col,this.placeholder_grid_data.row),this.$player.coords().grid.row=this.placeholder_grid_data.row,this.$player.coords().grid.col=this.placeholder_grid_data.col,this.options.draggable.stop&&this.options.draggable.stop.call(this,e,t),this.$preview_holder.remove(),this.$player=null,this.$helper=null,this.placeholder_grid_data={},this.player_grid_data={},this.cells_occupied_by_placeholder={},this.cells_occupied_by_player={},this.set_dom_grid_height()},u.on_overlapped_column_change=function(t,n){if(!this.colliders_data.length)return this;var r=this.get_targeted_columns(this.colliders_data[0].el.data.col),i=this.last_cols.length,s=r.length,o;for(o=0;on.row?1:-1}),t},u.sort_by_row_and_col_asc=function(e){return e=e.sort(function(e,t){return e.row>t.row||e.row===t.row&&e.col>t.col?1:-1}),e},u.sort_by_col_asc=function(e){return e=e.sort(function(e,t){return e.col>t.col?1:-1}),e},u.sort_by_row_desc=function(e){return e=e.sort(function(e,t){return e.row+e.size_y=0&&e.inArray(n,r.rows)>=0},u.is_placeholder_in=function(t,n){var r=this.cells_occupied_by_placeholder||{};return this.is_placeholder_in_col(t)&&e.inArray(n,r.rows)>=0},u.is_placeholder_in_col=function(t){var n=this.cells_occupied_by_placeholder||[];return e.inArray(t,n.cols)>=0},u.is_empty=function(e,t){return typeof this.gridmap[e]!="undefined"&&typeof this.gridmap[e][t]!="undefined"&&this.gridmap[e][t]===!1?!0:!1},u.is_occupied=function(e,t){return this.gridmap[e]?this.gridmap[e][t]?!0:!1:!1},u.is_widget=function(e,t){var n=this.gridmap[e];return n?(n=n[t],n?n:!1):!1},u.is_widget_under_player=function(e,t){return this.is_widget(e,t)?this.is_player_in(e,t):!1},u.get_widgets_under_player=function(t){t||(t=this.cells_occupied_by_player||{cols:[],rows:[]});var n=e([]);return e.each(t.cols,e.proxy(function(r,i){e.each(t.rows,e.proxy(function(e,t){this.is_widget(i,t)&&(n=n.add(this.gridmap[i][t]))},this))},this)),n},u.set_placeholder=function(t,n){var r=e.extend({},this.placeholder_grid_data),i=this.widgets_below({col:r.col,row:r.row,size_y:r.size_y,size_x:r.size_x}),s=t+r.size_x-1;s>this.cols&&(t-=s-t);var o=this.placeholder_grid_data.row0){if(!(this.is_empty(e,u)||this.is_player(e,u)||this.is_widget(e,u)&&o[u].is(s)))break;r[e].push(u),i=u0){if(this.is_widget(s,u)&&!this.is_player_in(s,u)&&!o[u].is(e.el))break;!this.is_player(s,u)&&!this.is_placeholder_in(s,u)&&!this.is_player_in(s,u)&&r[s].push(u),u=t?e[r[0]]:!1},u.get_widgets_overlapped=function(){var t,n=e([]),r=[],i=this.cells_occupied_by_player.rows.slice(0);return i.reverse(),e.each(this.cells_occupied_by_player.cols,e.proxy(function(t,s){e.each(i,e.proxy(function(t,i){if(!this.gridmap[s])return!0;var o=this.gridmap[s][i];this.is_occupied(s,i)&&!this.is_player(o)&&e.inArray(o,r)===-1&&(n=n.add(o),r.push(o))},this))},this)),n},u.on_start_overlapping_column=function(e){this.set_player(e,!1)},u.on_start_overlapping_row=function(e){this.set_player(!1,e)},u.on_stop_overlapping_column=function(e){this.set_player(e,!1);var t=this;this.for_each_widget_below(e,this.cells_occupied_by_player.rows[0],function(e,n){t.move_widget_up(this,t.player_grid_data.size_y)})},u.on_stop_overlapping_row=function(e){this.set_player(!1,e);var t=this,n=this.cells_occupied_by_player.cols;for(var r=0,i=n.length;r0&&this.move_widget_down(r,s)},this)),u.row=a,this.update_widget_position(u,t),t.attr("data-row",u.row),this.$changed=this.$changed.add(t),s.push(t)}},u.can_go_up_to_row=function(t,n,r){var i=this.gridmap,s=!0,o=[],u=t.row,a;this.for_each_column_occupied(t,function(e){var t=i[e];o[e]=[],a=u;while(a--){if(!this.is_empty(e,a)||!!this.is_placeholder_in(e,a))break;o[e].push(a)}if(!o[e].length)return s=!1,!0});if(!s)return!1;a=r;for(a=1;a0?n:0},u.widgets_below=function(t){var n=e.isPlainObject(t)?t:t.coords().grid,r=this,i=this.gridmap,s=n.row+n.size_y-1,o=e([]);return this.for_each_column_occupied(n,function(t){r.for_each_widget_below(t,s,function(t,n){if(!r.is_player(this)&&e.inArray(this,o)===-1)return o=o.add(this),!0})}),this.sort_by_row_asc(o)},u.set_cells_player_occupies=function(e,t){return this.remove_from_gridmap(this.placeholder_grid_data),this.placeholder_grid_data.col=e,this.placeholder_grid_data.row=t,this.add_to_gridmap(this.placeholder_grid_data,this.$player),this},u.empty_cells_player_occupies=function(){return this.remove_from_gridmap(this.placeholder_grid_data),this},u.can_go_up=function(e){var t=e.coords().grid,n=t.row,r=n-1,i=this.gridmap,s=[],o=!0;return n===1?!1:(this.for_each_column_occupied(t,function(e){var t=this.is_widget(e,r);if(this.is_occupied(e,r)||this.is_player(e,r)||this.is_placeholder_in(e,r)||this.is_player_in(e,r))return o=!1,!0}),o)},u.can_move_to=function(e,t,n,r){var i=this.gridmap,s=e.el,o={size_y:e.size_y,size_x:e.size_x,col:t,row:n},u=!0,a=t+e.size_x-1;return a>this.cols?!1:r&&r0&&this.is_widget(r,h)&&e.inArray(o[r][h],c)===-1){u=s.call(o[r][h],r,h),c.push(o[r][h]);if(u)break}},"for_each/below":function(){for(h=i+1,a=o[r].length;h=1;i--)for(e=t[i].length-1;e>=1;e--)if(this.is_widget(i,e)){n.push(e),r[e]=i;break}var s=Math.max.apply(Math,n);return this.highest_occupied_cell={col:r[s],row:s},this.highest_occupied_cell},u.get_widgets_from=function(t,n){var r=this.gridmap,i=e();return t&&(i=i.add(this.$widgets.filter(function(){var n=e(this).attr("data-col");return n===t||n>t}))),n&&(i=i.add(this.$widgets.filter(function(){var t=e(this).attr("data-row");return t===n||t>n}))),i},u.set_dom_grid_height=function(){var e=this.get_highest_occupied_cell().row;return this.$el.css("height",e*this.min_widget_height),this},u.generate_stylesheet=function(e){var t="",n=this.options.max_size_x,r=0,i=0,s,o;e||(e={}),e.cols||(e.cols=this.cols),e.rows||(e.rows=this.rows),e.namespace||(e.namespace=this.options.namespace),e.widget_base_dimensions||(e.widget_base_dimensions=this.options.widget_base_dimensions),e.widget_margins||(e.widget_margins=this.options.widget_margins),e.min_widget_width=e.widget_margins[0]*2+e.widget_base_dimensions[0],e.min_widget_height=e.widget_margins[1]*2+e.widget_base_dimensions[1];for(s=e.cols;s>=0;s--)t+=e.namespace+' [data-col="'+(s+1)+'"] { left:'+(s*e.widget_base_dimensions[0]+s*e.widget_margins[0]+(s+1)*e.widget_margins[0])+"px;} ";for(s=e.rows;s>=0;s--)t+=e.namespace+' [data-row="'+(s+1)+'"] { top:'+(s*e.widget_base_dimensions[1]+s*e.widget_margins[1]+(s+1)*e.widget_margins[1])+"px;} ";for(var u=1;u<=e.rows;u++)t+=e.namespace+' [data-sizey="'+u+'"] { height:'+(u*e.widget_base_dimensions[1]+(u-1)*e.widget_margins[1]*2)+"px;}";for(var a=1;a<=n;a++)t+=e.namespace+' [data-sizex="'+a+'"] { width:'+(a*e.widget_base_dimensions[0]+(a-1)*e.widget_margins[0]*2)+"px;}";return this.add_style_tag(t)},u.add_style_tag=function(e){var t=n,r=t.createElement("style");return t.getElementsByTagName("head")[0].appendChild(r),r.setAttribute("type","text/css"),r.setAttribute("id",this.options.style_tag_id_prefix+this.instanceId),r.styleSheet?r.styleSheet.cssText=e:r.appendChild(n.createTextNode(e)),this},u.remove_style_tag=function(){e("#"+this.options.style_tag_id_prefix+this.instanceId).remove()},u.generate_faux_grid=function(e,t){this.faux_grid=[],this.gridmap=[];var n,r;for(n=t;n>0;n--){this.gridmap[n]=[];for(r=e;r>0;r--)this.add_faux_cell(r,n)}return this},u.add_faux_cell=function(t,n){var r=e({left:this.baseX+(n-1)*this.min_widget_width,top:this.baseY+(t-1)*this.min_widget_height,width:this.min_widget_width,height:this.min_widget_height,col:n,row:t,original_col:n,original_row:t}).coords();return e.isArray(this.gridmap[n])||(this.gridmap[n]=[]),this.gridmap[n][t]=!1,this.faux_grid.push(r),this},u.add_faux_rows=function(e){var t=this.rows,n=t+(e||1);for(var r=n;r>t;r--)for(var i=this.cols;i>=1;i--)this.add_faux_cell(r,i);return this.rows=n,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this},u.add_faux_cols=function(e){var t=this.cols,n=t+(e||1);for(var r=t;r=1;i--)this.add_faux_cell(i,r);return this.cols=n,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this},u.recalculate_faux_grid=function(){var n=this.$wrapper.width();return this.baseX=(e(t).width()-n)/2,this.baseY=this.$wrapper.offset().top,e.each(this.faux_grid,e.proxy(function(e,t){this.faux_grid[e]=t.update({left:this.baseX+(t.data.col-1)*this.min_widget_width,top:this.baseY+(t.data.row-1)*this.min_widget_height})},this)),this},u.get_widgets_from_DOM=function(){return this.$widgets.each(e.proxy(function(t,n){this.register_widget(e(n))},this)),this},u.generate_grid_and_stylesheet=function(){var n=this.$wrapper.width(),r=this.$wrapper.height(),i=Math.floor(n/this.min_widget_width)+this.options.extra_cols,s=this.$widgets.map(function(){return e(this).attr("data-col")});s=Array.prototype.slice.call(s,0),s.length||(s=[0]);var o=Math.max.apply(Math,s),u=this.options.extra_rows;return this.$widgets.each(function(t,n){u+=+e(n).attr("data-sizey")}),this.cols=Math.max(o,i,this.options.min_cols),this.rows=Math.max(u,this.options.min_rows),this.baseX=(e(t).width()-n)/2,this.baseY=this.$wrapper.offset().top,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this.generate_faux_grid(this.rows,this.cols)},u.destroy=function(){e(t).unbind("resize",this.on_window_resize),this.drag_api&&this.drag_api.destroy(),this.remove_style_tag(),this.$el.remove()},e.fn.gridster=function(t){return this.each(function(){e(this).data("gridster")||e(this).data("gridster",new o(this,t))})},e.Gridster=u}(jQuery,window,document); --------------------------------------------------------------------------------