├── 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 |
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 |
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 |
5 |
6 |
7 |
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 | [](http://badge.fury.io/js/dashing-js)
6 | [](https://david-dm.org/fabiocaseri/dashing-js)
7 | [](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);
--------------------------------------------------------------------------------
![]()
4 | 5 |