From 452b04c1b373712c189c1c696266dbac0e6f5a54 Mon Sep 17 00:00:00 2001 From: Radek Stepan Date: Tue, 5 Nov 2013 12:44:15 +0000 Subject: [PATCH] closes #30 --- .bowerrc | 3 + .gitignore | 4 +- Gruntfile.coffee | 73 + Makefile | 22 +- README.md | 10 +- bower.json | 13 + build/{build.css => app.bundle.css} | 335 +- build/{build.js => app.bundle.js} | 36046 ++++++++++---------------- build/app.bundle.min.css | 1 + build/app.bundle.min.js | 8 + build/app.css | 39 + build/app.js | 1307 + build/app.min.css | 1 + build/app.min.js | 1 + package.json | 8 +- public/app.bundle.min.css | 1 + public/app.bundle.min.js | 1 + public/build.css | 1 - public/build.js | 1 - public/index.html | 6 +- src/app.coffee | 3 - src/component.json | 39 - src/modules/config.coffee | 2 +- src/modules/graph.coffee | 27 +- src/modules/issues.coffee | 7 +- src/modules/milestones.coffee | 3 +- src/modules/repo.coffee | 3 +- src/modules/request.coffee | 9 +- src/modules/require.coffee | 5 + src/styles/app.styl | 23 + test/config.coffee | 6 + test/issues.coffee | 6 + test/milestones.coffee | 8 +- test/request.coffee | 7 +- 34 files changed, 14867 insertions(+), 23162 deletions(-) create mode 100644 .bowerrc create mode 100644 Gruntfile.coffee create mode 100644 bower.json rename build/{build.css => app.bundle.css} (57%) rename build/{build.js => app.bundle.js} (62%) create mode 100644 build/app.bundle.min.css create mode 100644 build/app.bundle.min.js create mode 100644 build/app.css create mode 100644 build/app.js create mode 100644 build/app.min.css create mode 100644 build/app.min.js create mode 120000 public/app.bundle.min.css create mode 120000 public/app.bundle.min.js delete mode 120000 public/build.css delete mode 120000 public/build.js delete mode 100644 src/component.json create mode 100644 src/modules/require.coffee diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..25c9a0f --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "vendor" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0113f0c..38f11f6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ node_modules/ .idea/ *.log src/components/ -config.json \ No newline at end of file +config.json +vendor/ +.build_cache~ \ No newline at end of file diff --git a/Gruntfile.coffee b/Gruntfile.coffee new file mode 100644 index 0000000..cead607 --- /dev/null +++ b/Gruntfile.coffee @@ -0,0 +1,73 @@ +module.exports = (grunt) -> + grunt.initConfig + pkg: grunt.file.readJSON("package.json") + + apps_c: + commonjs: + src: [ 'src/**/*.{coffee,js,eco}' ] + dest: 'build/app.js' + options: + main: 'src/app.coffee' + name: [ 'ghbc', 'ghb', 'github-burndown-chart' ] + + stylus: + compile: + options: + paths: [ 'src/styles/app.styl' ] + files: + 'build/app.css': 'src/styles/app.styl' + + concat: + scripts: + src: [ + # Vendor dependencies. + 'vendor/async/lib/async.js' + 'vendor/d3/d3.js' + 'vendor/d3-tip/index.js' + 'vendor/lodash/dist/lodash.js' + 'vendor/marked/lib/marked.js' + 'vendor/superagent/superagent.js' + # Our app. + 'build/app.js' + ] + dest: 'build/app.bundle.js' + options: + separator: ';' # for minification purposes + + styles: + src: [ + # Vendor dependencies. + 'vendor/normalize-css/normalize.css' + # Our style. + 'build/app.css' + ] + dest: 'build/app.bundle.css' + + uglify: + scripts: + files: + 'build/app.min.js': 'build/app.js' + 'build/app.bundle.min.js': 'build/app.bundle.js' + + cssmin: + combine: + files: + 'build/app.min.css': 'build/app.css' + 'build/app.bundle.min.css': 'build/app.bundle.css' + + grunt.loadNpmTasks('grunt-apps-c') + grunt.loadNpmTasks('grunt-contrib-stylus') + grunt.loadNpmTasks('grunt-contrib-concat') + grunt.loadNpmTasks('grunt-contrib-uglify') + grunt.loadNpmTasks('grunt-contrib-cssmin') + + grunt.registerTask('default', [ + 'apps_c' + 'stylus' + 'concat' + ]) + + grunt.registerTask('minify', [ + 'uglify' + 'cssmin' + ]) \ No newline at end of file diff --git a/Makefile b/Makefile index 8c5e059..a778d26 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,22 @@ -test: - ./node_modules/.bin/mocha --compilers coffee:coffee-script --reporter spec --ui exports --bail +install: + npm install + bower install build: - ./node_modules/.bin/apps-b ./src/ ./build/ + grunt + +minify: + grunt minify + +watch: + watch --color -n 1 make build publish: + build + minify git checkout gh-pages - git show master:build/build.js > build.js - git show master:build/build.css > build.css + git show master:build/app.bundle.min.js > app.bundle.min.js + git show master:build/app.bundle.min.css > app.bundle.min.css git add . @status=$$(git status --porcelain); \ if ! test "x$${status}" = x; then \ @@ -16,4 +25,7 @@ publish: fi git checkout master +test: + ./node_modules/.bin/mocha --compilers coffee:coffee-script --reporter spec --ui exports --bail + .PHONY: build test \ No newline at end of file diff --git a/README.md b/README.md index 6bcb929..c2436dc 100644 --- a/README.md +++ b/README.md @@ -90,15 +90,15 @@ Visit the port in question in the browser and continue as before. ##Build It -If you would like to run your own build for a custom version of the app, use the [Apps/B Builder](https://github.com/intermine/apps-b-builder). - -In short: +If you would like to build a custom version of your app, edit the `Gruntfile.coffee` and run the following: ```bash -$ npm install -d -$ make build +$ make install +$ make build ``` +We are using the [Bower](http://bower.io/) package manager and [Grunt](http://gruntjs.com/) task runner. + ##Publish It If you would like to track changes to build files in `gh-pages` branch, execute the following command: diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..a5b9f3f --- /dev/null +++ b/bower.json @@ -0,0 +1,13 @@ +{ + "name": "github-burndown-chart", + "version": "1.0.0-alpha", + "dependencies": { + "lodash": null, + "async": null, + "d3": null, + "d3-tip": null, + "superagent": null, + "normalize-css": null, + "marked": null + } +} \ No newline at end of file diff --git a/build/build.css b/build/app.bundle.css similarity index 57% rename from build/build.css rename to build/app.bundle.css index b399fae..3f8b43c 100644 --- a/build/build.css +++ b/build/app.bundle.css @@ -1,8 +1,3 @@ - - - - - /*! normalize.css v2.1.3 | MIT License | git.io/normalize */ /* ========================================================================== @@ -410,294 +405,42 @@ table { border-spacing: 0; } - -.tip { - position: absolute; - padding: 5px; - z-index: 1000; - /* default offset for edge-cases: https://github.com/component/tip/pull/12 */ - top: 0; - left: 0; -} - -/* effects */ - -.tip.fade { - transition: opacity 100ms; - -moz-transition: opacity 100ms; - -webkit-transition: opacity 100ms; -} - -.tip-hide { - opacity: 0; -} - -.tip { - font-size: 11px; -} - -.tip-inner { - background-color: rgba(0,0,0,.75); - color: #fff; - padding: 8px 10px 7px 10px; - text-align: center; -} - -.tip-inner { - border-radius: 3px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; -} - -.tip-arrow { - position: absolute; - width: 0; - height: 0; - line-height: 0; - border: 5px dashed rgba(0,0,0,.75); -} - -.tip-arrow-north { border-bottom-color: rgba(0,0,0,.75) } -.tip-arrow-south { border-top-color: rgba(0,0,0,.75) } -.tip-arrow-east { border-left-color: rgba(0,0,0,.75) } -.tip-arrow-west { border-right-color: rgba(0,0,0,.75) } - -.tip-north .tip-arrow, -.tip-north-east .tip-arrow, -.tip-north-west .tip-arrow { - top: 0px; - left: 50%; - margin-left: -5px; - border-bottom-style: solid; - border-top: none; - border-left-color: transparent; - border-right-color: transparent -} - -.tip-south .tip-arrow, -.tip-south-east .tip-arrow, -.tip-south-west .tip-arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-top-style: solid; - border-bottom: none; - border-left-color: transparent; - border-right-color: transparent -} - -.tip-east .tip-arrow { - right: 0; - top: 50%; - margin-top: -5px; - border-left-style: solid; - border-right: none; - border-top-color: transparent; - border-bottom-color: transparent -} - -.tip-west .tip-arrow { - left: 0; - top: 50%; - margin-top: -5px; - border-right-style: solid; - border-left: none; - border-top-color: transparent; - border-bottom-color: transparent -} - -.tip-north-west .tip-arrow, -.tip-south-west .tip-arrow { - left: 15px; -} - -.tip-north-east .tip-arrow, -.tip-south-east .tip-arrow { - left: 85%; -} - - -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 400; - src: local('Source Sans Pro'), local('SourceSansPro-Regular'), - url("app/styles/../../fonts/SourceSansPro-Regular.woff") format('woff'); -} -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 600; - src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'), - url("app/styles/../../fonts/SourceSansPro-Semibold.woff") format('woff'); -} -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 700; - src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), - url("app/styles/../../fonts/SourceSansPro-Bold.woff") format('woff'); -} -body { - height: 100%; - background: #d7bcab; - background: -moz-linear-gradient(-45deg, #d7bcab 0%, #cc9485 100%); - background: -webkit-gradient(linear, left top, right bottom, color-stop(0%, #d7bcab), color-stop(100%, #cc9485)); - background: -webkit-linear-gradient(-45deg, #d7bcab 0%, #cc9485 100%); - background: -o-linear-gradient(-45deg, #d7bcab 0%, #cc9485 100%); - background: -ms-linear-gradient(-45deg, #d7bcab 0%, #cc9485 100%); - background: linear-gradient(135deg, #d7bcab 0%, #cc9485 100%); - background-repeat: no-repeat; - background-attachment: fixed; - font-family: 'Source Sans Pro', sans-serif; - padding: 100px; - color: #64584c; -} -ul { - list-style-type: none; - padding: 0; -} -ul li { - padding: 0; -} -h2 { - font-size: 16px; - text-transform: uppercase; -} -.box { - background: #fff; - box-shadow: 2px 4px 6px rgba(0,0,0,0.2); -} -.box.generic, -.box.info, -.box.error, -.box.success { - padding: 20px 0; - border-top: 4px solid #eac85d; - width: 50%; - margin: 0 auto; -} -.box.info { - border-top-color: #5f90b0; -} -.box.error { - border-top-color: #e45e39; -} -.box.success { - border-top-color: #4db07a; -} -.box a { - color: #64584c; -} -.box h1 { - margin: 0; - padding: 20px; - color: #64584c; - font-size: 20px; - text-transform: uppercase; -} -.box h2 { - margin: 0; - padding: 0 20px 20px; -} -.box p { - margin: 5px 0; - padding: 0 20px; -} -.box p.description { - margin: -10px 0 0 0; -} -#graph { - height: 200px; - position: relative; -} -#graph #tooltip { - position: absolute; - top: 0; - left: 0; -} -#graph svg path.line { - fill: none; - stroke-width: 1px; - clip-path: url("app/styles/#clip"); -} -#graph svg path.line.actual { - stroke: #64584c; - stroke-width: 3px; -} -#graph svg path.line.ideal { - stroke: #cacaca; - stroke-width: 3px; -} -#graph svg path.line.trendline { - stroke: #64584c; - stroke-width: 1.5px; - stroke-dasharray: 5, 5; -} -#graph svg line.today { - stroke: #cacaca; - stroke-width: 1px; - shape-rendering: crispEdges; - stroke-dasharray: 5, 5; -} -#graph svg circle { - fill: #64584c; - stroke: transparent; - stroke-width: 15px; - cursor: pointer; -} -#graph svg .axis { - shape-rendering: crispEdges; -} -#graph svg .axis line { - stroke: rgba(202,202,202,0.25); - shape-rendering: crispEdges; -} -#graph svg .axis text { - font-weight: bold; - fill: #cacaca; -} -#graph svg .axis path { - display: none; -} -#progress { - padding: 20px; - border-radius: 0 0 6px 6px; -} -#progress:after { - clear: both; - display: block; - content: ""; -} -#progress .bars { - position: relative; -} -#progress .bars div { - border-radius: 6px; - height: 12px; -} -#progress .bars div.closed { - position: absolute; - top: 0; - left: 0; - background: #4daf7c; -} -#progress .bars div.closed:not(.done) { - border-radius: 6px 0 0 6px; -} -#progress .bars div.opened { - width: 100%; - background: #e55f3a; -} -#progress h2 { - margin: 10px 0 0 0; - padding: 0; -} -#progress h2.closed { - float: left; - color: #4daf7c; -} -#progress h2.opened { - float: right; - color: #e55f3a; -} +body{height:100%;background:#d7bcab;background:-moz--webkit-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-moz--moz-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-moz--o-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-moz--ms-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-moz-linear-gradient(-45deg, #d7bcab 0%, #cc9485 100%);background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #d7bcab), color-stop(100%, #cc9485));background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #d7bcab), color-stop(100%, #cc9485));background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #d7bcab), color-stop(100%, #cc9485));background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #d7bcab), color-stop(100%, #cc9485));background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #d7bcab), color-stop(100%, #cc9485));background:-webkit--webkit-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-webkit--moz-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-webkit--o-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-webkit--ms-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-webkit-linear-gradient(-45deg, #d7bcab 0%, #cc9485 100%);background:-o--webkit-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-o--moz-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-o--o-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-o--ms-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-o-linear-gradient(-45deg, #d7bcab 0%, #cc9485 100%);background:-ms--webkit-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-ms--moz-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-ms--o-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-ms--ms-linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background:-ms-linear-gradient(-45deg, #d7bcab 0%, #cc9485 100%);background:-webkit-linear-gradient(315deg, #d7bcab 0%, #cc9485 100%);background:-moz-linear-gradient(315deg, #d7bcab 0%, #cc9485 100%);background:-o-linear-gradient(315deg, #d7bcab 0%, #cc9485 100%);background:-ms-linear-gradient(315deg, #d7bcab 0%, #cc9485 100%);background:linear-gradient(135deg, #d7bcab 0%, #cc9485 100%);background-repeat:no-repeat;background-attachment:fixed;font-family:'Source Sans Pro',sans-serif;padding:100px;color:#64584c} +ul{list-style-type:none;padding:0;} +ul li{padding:0} +h2{font-size:16px;text-transform:uppercase} +.box{background:#fff;-webkit-box-shadow:2px 4px 6px rgba(0,0,0,0.2);box-shadow:2px 4px 6px rgba(0,0,0,0.2);} +.box.generic,.box.info,.box.error,.box.success{padding:20px 0;border-top:4px solid #eac85d;width:50%;margin:0 auto} +.box.info{border-top-color:#5f90b0} +.box.error{border-top-color:#e45e39} +.box.success{border-top-color:#4db07a} +.box a{color:#64584c} +.box h1{margin:0;padding:20px;color:#64584c;font-size:20px;text-transform:uppercase} +.box h2{margin:0;padding:0 20px 20px} +.box p{margin:5px 0;padding:0 20px;} +.box p.description{margin:-10px 0 0 0} +#graph{height:200px;position:relative;} +#graph #tooltip{position:absolute;top:0;left:0} +#graph svg path.line{fill:none;stroke-width:1px;clip-path:url("#clip");} +#graph svg path.line.actual{stroke:#64584c;stroke-width:3px} +#graph svg path.line.ideal{stroke:#cacaca;stroke-width:3px} +#graph svg path.line.trendline{stroke:#64584c;stroke-width:1.5px;stroke-dasharray:5,5} +#graph svg line.today{stroke:#cacaca;stroke-width:1px;shape-rendering:crispEdges;stroke-dasharray:5,5} +#graph svg circle{fill:#64584c;stroke:transparent;stroke-width:15px;cursor:pointer} +#graph svg .axis{shape-rendering:crispEdges;} +#graph svg .axis line{stroke:rgba(202,202,202,0.25);shape-rendering:crispEdges} +#graph svg .axis text{font-weight:bold;fill:#cacaca} +#graph svg .axis path{display:none} +.d3-tip{margin-top:-10px;font-size:11px;padding:8px 10px 7px 10px;text-align:center;background:rgba(0,0,0,0.75);color:#fff;-webkit-border-radius:3px;border-radius:3px;} +.d3-tip:after{width:100%;color:rgba(0,0,0,0.8);content:"\25BC";position:absolute} +.d3-tip.n:after{margin:-3px 0 0 0;top:100%;left:0} +#progress{padding:20px;-webkit-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;} +#progress:after{clear:both;display:block;content:""} +#progress .bars{position:relative;} +#progress .bars div{-webkit-border-radius:6px;border-radius:6px;height:12px;} +#progress .bars div.closed{position:absolute;top:0;left:0;background:#4daf7c;} +#progress .bars div.closed:not(.done){-webkit-border-radius:6px 0 0 6px;border-radius:6px 0 0 6px} +#progress .bars div.opened{width:100%;background:#e55f3a} +#progress h2{margin:10px 0 0 0;padding:0;} +#progress h2.closed{float:left;color:#4daf7c} +#progress h2.opened{float:right;color:#e55f3a} diff --git a/build/build.js b/build/app.bundle.js similarity index 62% rename from build/build.js rename to build/app.bundle.js index cb6d08a..0a9cfbf 100644 --- a/build/build.js +++ b/build/app.bundle.js @@ -1,212 +1,10511 @@ +/*global setImmediate: false, setTimeout: false, console: false */ +(function () { -/** - * Require the given path. - * - * @param {String} path - * @return {Object} exports - * @api public - */ + var async = {}; -function require(path, parent, orig) { - var resolved = require.resolve(path); + // global on the server, window in the browser + var root, previous_async; - // lookup failed - if (null == resolved) { - orig = orig || path; - parent = parent || 'root'; - var err = new Error('Failed to require "' + orig + '" from "' + parent + '"'); - err.path = orig; - err.parent = parent; - err.require = true; - throw err; + root = this; + if (root != null) { + previous_async = root.async; + } + + async.noConflict = function () { + root.async = previous_async; + return async; + }; + + function only_once(fn) { + var called = false; + return function() { + if (called) throw new Error("Callback was already called."); + called = true; + fn.apply(root, arguments); + } + } + + //// cross-browser compatiblity functions //// + + var _each = function (arr, iterator) { + if (arr.forEach) { + return arr.forEach(iterator); + } + for (var i = 0; i < arr.length; i += 1) { + iterator(arr[i], i, arr); + } + }; + + var _map = function (arr, iterator) { + if (arr.map) { + return arr.map(iterator); + } + var results = []; + _each(arr, function (x, i, a) { + results.push(iterator(x, i, a)); + }); + return results; + }; + + var _reduce = function (arr, iterator, memo) { + if (arr.reduce) { + return arr.reduce(iterator, memo); + } + _each(arr, function (x, i, a) { + memo = iterator(memo, x, i, a); + }); + return memo; + }; + + var _keys = function (obj) { + if (Object.keys) { + return Object.keys(obj); + } + var keys = []; + for (var k in obj) { + if (obj.hasOwnProperty(k)) { + keys.push(k); + } + } + return keys; + }; + + //// exported async module functions //// + + //// nextTick implementation with browser-compatible fallback //// + if (typeof process === 'undefined' || !(process.nextTick)) { + if (typeof setImmediate === 'function') { + async.nextTick = function (fn) { + setImmediate(fn); + }; + } + else { + async.nextTick = function (fn) { + setTimeout(fn, 0); + }; + } + } + else { + async.nextTick = process.nextTick; + } + + async.each = function (arr, iterator, callback) { + callback = callback || function () {}; + if (!arr.length) { + return callback(); + } + var completed = 0; + _each(arr, function (x) { + iterator(x, only_once(function (err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + if (completed >= arr.length) { + callback(null); + } + } + })); + }); + }; + async.forEach = async.each; + + async.eachSeries = function (arr, iterator, callback) { + callback = callback || function () {}; + if (!arr.length) { + return callback(); + } + var completed = 0; + var iterate = function () { + var sync = true; + iterator(arr[completed], function (err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + if (completed >= arr.length) { + callback(null); + } + else { + if (sync) { + async.nextTick(iterate); + } + else { + iterate(); + } + } + } + }); + sync = false; + }; + iterate(); + }; + async.forEachSeries = async.eachSeries; + + async.eachLimit = function (arr, limit, iterator, callback) { + var fn = _eachLimit(limit); + fn.apply(null, [arr, iterator, callback]); + }; + async.forEachLimit = async.eachLimit; + + var _eachLimit = function (limit) { + + return function (arr, iterator, callback) { + callback = callback || function () {}; + if (!arr.length || limit <= 0) { + return callback(); + } + var completed = 0; + var started = 0; + var running = 0; + + (function replenish () { + if (completed >= arr.length) { + return callback(); + } + + while (running < limit && started < arr.length) { + started += 1; + running += 1; + iterator(arr[started - 1], function (err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + running -= 1; + if (completed >= arr.length) { + callback(); + } + else { + replenish(); + } + } + }); + } + })(); + }; + }; + + + var doParallel = function (fn) { + return function () { + var args = Array.prototype.slice.call(arguments); + return fn.apply(null, [async.each].concat(args)); + }; + }; + var doParallelLimit = function(limit, fn) { + return function () { + var args = Array.prototype.slice.call(arguments); + return fn.apply(null, [_eachLimit(limit)].concat(args)); + }; + }; + var doSeries = function (fn) { + return function () { + var args = Array.prototype.slice.call(arguments); + return fn.apply(null, [async.eachSeries].concat(args)); + }; + }; + + + var _asyncMap = function (eachfn, arr, iterator, callback) { + var results = []; + arr = _map(arr, function (x, i) { + return {index: i, value: x}; + }); + eachfn(arr, function (x, callback) { + iterator(x.value, function (err, v) { + results[x.index] = v; + callback(err); + }); + }, function (err) { + callback(err, results); + }); + }; + async.map = doParallel(_asyncMap); + async.mapSeries = doSeries(_asyncMap); + async.mapLimit = function (arr, limit, iterator, callback) { + return _mapLimit(limit)(arr, iterator, callback); + }; + + var _mapLimit = function(limit) { + return doParallelLimit(limit, _asyncMap); + }; + + // reduce only has a series version, as doing reduce in parallel won't + // work in many situations. + async.reduce = function (arr, memo, iterator, callback) { + async.eachSeries(arr, function (x, callback) { + iterator(memo, x, function (err, v) { + memo = v; + callback(err); + }); + }, function (err) { + callback(err, memo); + }); + }; + // inject alias + async.inject = async.reduce; + // foldl alias + async.foldl = async.reduce; + + async.reduceRight = function (arr, memo, iterator, callback) { + var reversed = _map(arr, function (x) { + return x; + }).reverse(); + async.reduce(reversed, memo, iterator, callback); + }; + // foldr alias + async.foldr = async.reduceRight; + + var _filter = function (eachfn, arr, iterator, callback) { + var results = []; + arr = _map(arr, function (x, i) { + return {index: i, value: x}; + }); + eachfn(arr, function (x, callback) { + iterator(x.value, function (v) { + if (v) { + results.push(x); + } + callback(); + }); + }, function (err) { + callback(_map(results.sort(function (a, b) { + return a.index - b.index; + }), function (x) { + return x.value; + })); + }); + }; + async.filter = doParallel(_filter); + async.filterSeries = doSeries(_filter); + // select alias + async.select = async.filter; + async.selectSeries = async.filterSeries; + + var _reject = function (eachfn, arr, iterator, callback) { + var results = []; + arr = _map(arr, function (x, i) { + return {index: i, value: x}; + }); + eachfn(arr, function (x, callback) { + iterator(x.value, function (v) { + if (!v) { + results.push(x); + } + callback(); + }); + }, function (err) { + callback(_map(results.sort(function (a, b) { + return a.index - b.index; + }), function (x) { + return x.value; + })); + }); + }; + async.reject = doParallel(_reject); + async.rejectSeries = doSeries(_reject); + + var _detect = function (eachfn, arr, iterator, main_callback) { + eachfn(arr, function (x, callback) { + iterator(x, function (result) { + if (result) { + main_callback(x); + main_callback = function () {}; + } + else { + callback(); + } + }); + }, function (err) { + main_callback(); + }); + }; + async.detect = doParallel(_detect); + async.detectSeries = doSeries(_detect); + + async.some = function (arr, iterator, main_callback) { + async.each(arr, function (x, callback) { + iterator(x, function (v) { + if (v) { + main_callback(true); + main_callback = function () {}; + } + callback(); + }); + }, function (err) { + main_callback(false); + }); + }; + // any alias + async.any = async.some; + + async.every = function (arr, iterator, main_callback) { + async.each(arr, function (x, callback) { + iterator(x, function (v) { + if (!v) { + main_callback(false); + main_callback = function () {}; + } + callback(); + }); + }, function (err) { + main_callback(true); + }); + }; + // all alias + async.all = async.every; + + async.sortBy = function (arr, iterator, callback) { + async.map(arr, function (x, callback) { + iterator(x, function (err, criteria) { + if (err) { + callback(err); + } + else { + callback(null, {value: x, criteria: criteria}); + } + }); + }, function (err, results) { + if (err) { + return callback(err); + } + else { + var fn = function (left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }; + callback(null, _map(results.sort(fn), function (x) { + return x.value; + })); + } + }); + }; + + async.auto = function (tasks, callback) { + callback = callback || function () {}; + var keys = _keys(tasks); + if (!keys.length) { + return callback(null); + } + + var results = {}; + + var listeners = []; + var addListener = function (fn) { + listeners.unshift(fn); + }; + var removeListener = function (fn) { + for (var i = 0; i < listeners.length; i += 1) { + if (listeners[i] === fn) { + listeners.splice(i, 1); + return; + } + } + }; + var taskComplete = function () { + _each(listeners.slice(0), function (fn) { + fn(); + }); + }; + + addListener(function () { + if (_keys(results).length === keys.length) { + callback(null, results); + callback = function () {}; + } + }); + + _each(keys, function (k) { + var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k]; + var taskCallback = function (err) { + if (err) { + callback(err); + // stop subsequent errors hitting callback multiple times + callback = function () {}; + } + else { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + results[k] = args; + async.nextTick(taskComplete); + } + }; + var requires = task.slice(0, Math.abs(task.length - 1)) || []; + var ready = function () { + return _reduce(requires, function (a, x) { + return (a && results.hasOwnProperty(x)); + }, true) && !results.hasOwnProperty(k); + }; + if (ready()) { + task[task.length - 1](taskCallback, results); + } + else { + var listener = function () { + if (ready()) { + removeListener(listener); + task[task.length - 1](taskCallback, results); + } + }; + addListener(listener); + } + }); + }; + + async.waterfall = function (tasks, callback) { + callback = callback || function () {}; + if (!tasks.length) { + return callback(); + } + var wrapIterator = function (iterator) { + return function (err) { + if (err) { + callback.apply(null, arguments); + callback = function () {}; + } + else { + var args = Array.prototype.slice.call(arguments, 1); + var next = iterator.next(); + if (next) { + args.push(wrapIterator(next)); + } + else { + args.push(callback); + } + async.nextTick(function () { + iterator.apply(null, args); + }); + } + }; + }; + wrapIterator(async.iterator(tasks))(); + }; + + var _parallel = function(eachfn, tasks, callback) { + callback = callback || function () {}; + if (tasks.constructor === Array) { + eachfn.map(tasks, function (fn, callback) { + if (fn) { + fn(function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + callback.call(null, err, args); + }); + } + }, callback); + } + else { + var results = {}; + eachfn.each(_keys(tasks), function (k, callback) { + tasks[k](function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + results[k] = args; + callback(err); + }); + }, function (err) { + callback(err, results); + }); + } + }; + + async.parallel = function (tasks, callback) { + _parallel({ map: async.map, each: async.each }, tasks, callback); + }; + + async.parallelLimit = function(tasks, limit, callback) { + _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback); + }; + + async.series = function (tasks, callback) { + callback = callback || function () {}; + if (tasks.constructor === Array) { + async.mapSeries(tasks, function (fn, callback) { + if (fn) { + fn(function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + callback.call(null, err, args); + }); + } + }, callback); + } + else { + var results = {}; + async.eachSeries(_keys(tasks), function (k, callback) { + tasks[k](function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + results[k] = args; + callback(err); + }); + }, function (err) { + callback(err, results); + }); + } + }; + + async.iterator = function (tasks) { + var makeCallback = function (index) { + var fn = function () { + if (tasks.length) { + tasks[index].apply(null, arguments); + } + return fn.next(); + }; + fn.next = function () { + return (index < tasks.length - 1) ? makeCallback(index + 1): null; + }; + return fn; + }; + return makeCallback(0); + }; + + async.apply = function (fn) { + var args = Array.prototype.slice.call(arguments, 1); + return function () { + return fn.apply( + null, args.concat(Array.prototype.slice.call(arguments)) + ); + }; + }; + + var _concat = function (eachfn, arr, fn, callback) { + var r = []; + eachfn(arr, function (x, cb) { + fn(x, function (err, y) { + r = r.concat(y || []); + cb(err); + }); + }, function (err) { + callback(err, r); + }); + }; + async.concat = doParallel(_concat); + async.concatSeries = doSeries(_concat); + + async.whilst = function (test, iterator, callback) { + if (test()) { + var sync = true; + iterator(function (err) { + if (err) { + return callback(err); + } + if (sync) { + async.nextTick(function () { + async.whilst(test, iterator, callback); + }); + } + else { + async.whilst(test, iterator, callback); + } + }); + sync = false; + } + else { + callback(); + } + }; + + async.doWhilst = function (iterator, test, callback) { + var sync = true; + iterator(function (err) { + if (err) { + return callback(err); + } + if (test()) { + if (sync) { + async.nextTick(function () { + async.doWhilst(iterator, test, callback); + }); + } + else { + async.doWhilst(iterator, test, callback); + } + } + else { + callback(); + } + }); + sync = false; + }; + + async.until = function (test, iterator, callback) { + if (!test()) { + var sync = true; + iterator(function (err) { + if (err) { + return callback(err); + } + if (sync) { + async.nextTick(function () { + async.until(test, iterator, callback); + }); + } + else { + async.until(test, iterator, callback); + } + }); + sync = false; + } + else { + callback(); + } + }; + + async.doUntil = function (iterator, test, callback) { + var sync = true; + iterator(function (err) { + if (err) { + return callback(err); + } + if (!test()) { + if (sync) { + async.nextTick(function () { + async.doUntil(iterator, test, callback); + }); + } + else { + async.doUntil(iterator, test, callback); + } + } + else { + callback(); + } + }); + sync = false; + }; + + async.queue = function (worker, concurrency) { + function _insert(q, data, pos, callback) { + if(data.constructor !== Array) { + data = [data]; + } + _each(data, function(task) { + var item = { + data: task, + callback: typeof callback === 'function' ? callback : null + }; + + if (pos) { + q.tasks.unshift(item); + } else { + q.tasks.push(item); + } + + if (q.saturated && q.tasks.length === concurrency) { + q.saturated(); + } + async.nextTick(q.process); + }); + } + + var workers = 0; + var q = { + tasks: [], + concurrency: concurrency, + saturated: null, + empty: null, + drain: null, + push: function (data, callback) { + _insert(q, data, false, callback); + }, + unshift: function (data, callback) { + _insert(q, data, true, callback); + }, + process: function () { + if (workers < q.concurrency && q.tasks.length) { + var task = q.tasks.shift(); + if (q.empty && q.tasks.length === 0) { + q.empty(); + } + workers += 1; + var sync = true; + var next = function () { + workers -= 1; + if (task.callback) { + task.callback.apply(task, arguments); + } + if (q.drain && q.tasks.length + workers === 0) { + q.drain(); + } + q.process(); + }; + var cb = only_once(function () { + var cbArgs = arguments; + + if (sync) { + async.nextTick(function () { + next.apply(null, cbArgs); + }); + } else { + next.apply(null, arguments); + } + }); + worker(task.data, cb); + sync = false; + } + }, + length: function () { + return q.tasks.length; + }, + running: function () { + return workers; + } + }; + return q; + }; + + async.cargo = function (worker, payload) { + var working = false, + tasks = []; + + var cargo = { + tasks: tasks, + payload: payload, + saturated: null, + empty: null, + drain: null, + push: function (data, callback) { + if(data.constructor !== Array) { + data = [data]; + } + _each(data, function(task) { + tasks.push({ + data: task, + callback: typeof callback === 'function' ? callback : null + }); + if (cargo.saturated && tasks.length === payload) { + cargo.saturated(); + } + }); + async.nextTick(cargo.process); + }, + process: function process() { + if (working) return; + if (tasks.length === 0) { + if(cargo.drain) cargo.drain(); + return; + } + + var ts = typeof payload === 'number' + ? tasks.splice(0, payload) + : tasks.splice(0); + + var ds = _map(ts, function (task) { + return task.data; + }); + + if(cargo.empty) cargo.empty(); + working = true; + worker(ds, function () { + working = false; + + var args = arguments; + _each(ts, function (data) { + if (data.callback) { + data.callback.apply(null, args); + } + }); + + process(); + }); + }, + length: function () { + return tasks.length; + }, + running: function () { + return working; + } + }; + return cargo; + }; + + var _console_fn = function (name) { + return function (fn) { + var args = Array.prototype.slice.call(arguments, 1); + fn.apply(null, args.concat([function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (typeof console !== 'undefined') { + if (err) { + if (console.error) { + console.error(err); + } + } + else if (console[name]) { + _each(args, function (x) { + console[name](x); + }); + } + } + }])); + }; + }; + async.log = _console_fn('log'); + async.dir = _console_fn('dir'); + /*async.info = _console_fn('info'); + async.warn = _console_fn('warn'); + async.error = _console_fn('error');*/ + + async.memoize = function (fn, hasher) { + var memo = {}; + var queues = {}; + hasher = hasher || function (x) { + return x; + }; + var memoized = function () { + var args = Array.prototype.slice.call(arguments); + var callback = args.pop(); + var key = hasher.apply(null, args); + if (key in memo) { + callback.apply(null, memo[key]); + } + else if (key in queues) { + queues[key].push(callback); + } + else { + queues[key] = [callback]; + fn.apply(null, args.concat([function () { + memo[key] = arguments; + var q = queues[key]; + delete queues[key]; + for (var i = 0, l = q.length; i < l; i++) { + q[i].apply(null, arguments); + } + }])); + } + }; + memoized.memo = memo; + memoized.unmemoized = fn; + return memoized; + }; + + async.unmemoize = function (fn) { + return function () { + return (fn.unmemoized || fn).apply(null, arguments); + }; + }; + + async.times = function (count, iterator, callback) { + var counter = []; + for (var i = 0; i < count; i++) { + counter.push(i); + } + return async.map(counter, iterator, callback); + }; + + async.timesSeries = function (count, iterator, callback) { + var counter = []; + for (var i = 0; i < count; i++) { + counter.push(i); + } + return async.mapSeries(counter, iterator, callback); + }; + + async.compose = function (/* functions... */) { + var fns = Array.prototype.reverse.call(arguments); + return function () { + var that = this; + var args = Array.prototype.slice.call(arguments); + var callback = args.pop(); + async.reduce(fns, args, function (newargs, fn, cb) { + fn.apply(that, newargs.concat([function () { + var err = arguments[0]; + var nextargs = Array.prototype.slice.call(arguments, 1); + cb(err, nextargs); + }])) + }, + function (err, results) { + callback.apply(that, [err].concat(results)); + }); + }; + }; + + // AMD / RequireJS + if (typeof define !== 'undefined' && define.amd) { + define([], function () { + return async; + }); + } + // Node.js + else if (typeof module !== 'undefined' && module.exports) { + module.exports = async; + } + // included directly via + + diff --git a/src/app.coffee b/src/app.coffee index 409e9d8..fef7c3b 100644 --- a/src/app.coffee +++ b/src/app.coffee @@ -1,7 +1,4 @@ #!/usr/bin/env coffee -async = require 'async' -{ _ } = require 'lodash' - config = require './modules/config' regex = require './modules/regex' render = require './modules/render' diff --git a/src/component.json b/src/component.json deleted file mode 100644 index eee5de9..0000000 --- a/src/component.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "app", - "main": "app.js", - "version": "1.0.0-alpha", - "dependencies": { - "lodash/lodash": "*", - "caolan/async": "*", - "mbostock/d3": "*", - "visionmedia/superagent": "*", - "necolas/normalize.css": "*", - "component/tip": "*", - "component/aurora-tip": "*", - "component/marked": "*" - }, - "styles": [ - "styles/fonts.css" - ], - "apps-b": [ - "app.coffee", - - "modules/config.coffee", - "modules/graph.coffee", - "modules/issues.coffee", - "modules/milestones.coffee", - "modules/regex.coffee", - "modules/request.coffee", - "modules/render.coffee", - "modules/repo.coffee", - - "templates/error.eco", - "templates/graph.eco", - "templates/info.eco", - "templates/label.eco", - "templates/loading.eco", - "templates/progress.eco", - - "styles/app.styl" - ] -} \ No newline at end of file diff --git a/src/modules/config.coffee b/src/modules/config.coffee index 38a40fa..5f415f9 100644 --- a/src/modules/config.coffee +++ b/src/modules/config.coffee @@ -1,5 +1,5 @@ #!/usr/bin/env coffee -{ _ } = require 'lodash' +{ _ } = require './require' request = require './request' regex = require './regex' diff --git a/src/modules/graph.coffee b/src/modules/graph.coffee index d0d481d..9b07939 100644 --- a/src/modules/graph.coffee +++ b/src/modules/graph.coffee @@ -1,9 +1,7 @@ #!/usr/bin/env coffee -{ _ } = require 'lodash' -d3 = require 'd3' -Tip = require 'tip' +{ _, d3 } = require './require' -reg = require './regex' +reg = require './regex' module.exports = @@ -219,7 +217,10 @@ module.exports = .attr("d", line.interpolate("linear").y( (d) -> y(d.points) )(actual)) # Collect the tooltip here. - tooltip = null + tooltip = d3.tip().attr('class', 'd3-tip').html ({ number, title }) -> + "##{number}: #{title}" + + svg.call(tooltip) # Show when we closed an issue. svg.selectAll("a.issue") @@ -234,19 +235,7 @@ module.exports = .attr("cx", ({ date }) -> x date ) .attr("cy", ({ points }) -> y points ) .attr("r", ({ radius }) -> 5 ) # fixed for now - .on('mouseover', ({ date, points, title, number }) -> - # Pass a title string. - tooltip = new Tip "##{number}: #{title}" - # Absolutely position the div. - div = document.querySelector '#tooltip' - div.style.left = x(date) + margin.left + 'px' - div.style.top = -10 + y(points) + margin.top + 'px' - # And now show us on the div. - tooltip.show '#tooltip' - ) - .on('mouseout', (d) -> - # Hide after a time has passed if exists. - tooltip?.hide(200) - ) + .on('mouseover', tooltip.show) + .on('mouseout', tooltip.hide) cb null \ No newline at end of file diff --git a/src/modules/issues.coffee b/src/modules/issues.coffee index 512b5cf..70b0938 100644 --- a/src/modules/issues.coffee +++ b/src/modules/issues.coffee @@ -1,9 +1,8 @@ #!/usr/bin/env coffee -{ _ } = require 'lodash' -async = require 'async' +{ _, async } = require './require' -req = require './request' -reg = require './regex' +req = require './request' +reg = require './regex' module.exports = diff --git a/src/modules/milestones.coffee b/src/modules/milestones.coffee index 76855d2..e1c4889 100644 --- a/src/modules/milestones.coffee +++ b/src/modules/milestones.coffee @@ -1,6 +1,5 @@ #!/usr/bin/env coffee -{ _ } = require 'lodash' -marked = require 'marked' +{ _, marked } = require './require' request = require './request' diff --git a/src/modules/repo.coffee b/src/modules/repo.coffee index 08ab8d0..c76ef93 100644 --- a/src/modules/repo.coffee +++ b/src/modules/repo.coffee @@ -1,6 +1,5 @@ #!/usr/bin/env coffee -{ _ } = require 'lodash' -async = require 'async' +{ _, async } = require './require' milestones = require './milestones' issues = require './issues' diff --git a/src/modules/request.coffee b/src/modules/request.coffee index f96d7a2..ae367b3 100644 --- a/src/modules/request.coffee +++ b/src/modules/request.coffee @@ -1,9 +1,8 @@ #!/usr/bin/env coffee -sa = require 'superagent' -{ _ } = require 'lodash' +{ superagent, _ } = require './require' # Custom JSON parser. -sa.parse = +superagent.parse = 'application/json': (res) -> try JSON.parse res @@ -29,7 +28,7 @@ module.exports = # Get config from our host always. 'config': (cb) -> - sa + superagent .get("http://#{window.location.host + window.location.pathname}config.json") .set('Content-Type', 'application/json') .end _.partialRight respond, cb @@ -39,7 +38,7 @@ request = ({ protocol, host, token, path }, query, noun, cb) -> # Make the query params. q = ( "#{k}=#{v}" for k, v of query ).join('&') - req = sa + req = superagent # The URI. .get("#{protocol}://#{host}/repos/#{path}/#{noun}?#{q}") # The content type. diff --git a/src/modules/require.coffee b/src/modules/require.coffee new file mode 100644 index 0000000..9c921d3 --- /dev/null +++ b/src/modules/require.coffee @@ -0,0 +1,5 @@ +#!/usr/bin/env coffee +# So we can easily stub these. +module.exports = { + _, superagent, d3, async, marked +} \ No newline at end of file diff --git a/src/styles/app.styl b/src/styles/app.styl index e876587..3a65a77 100644 --- a/src/styles/app.styl +++ b/src/styles/app.styl @@ -1,3 +1,5 @@ +@import 'nib' + // color definitions $closed = #4DAF7C $opened = #E55F3A @@ -138,6 +140,27 @@ h2 path display: none +// tooltips +.d3-tip + margin-top: -10px + font-size: 11px + padding: 8px 10px 7px 10px + text-align: center + background: rgba(0,0,0,0.75) + color: #fff + border-radius: 3px + + &:after + width: 100% + color: rgba(0,0,0,0.8) + content: "\25BC" + position: absolute + + &.n:after + margin: -3px 0 0 0 + top: 100% + left: 0 + // progression graph #progress padding: 20px diff --git a/test/config.coffee b/test/config.coffee index b79df89..2cd4c19 100644 --- a/test/config.coffee +++ b/test/config.coffee @@ -7,6 +7,12 @@ req = {} config = proxy path.resolve(__dirname, '../src/modules/config.coffee'), './request': req + './require': + '_': require 'lodash' + 'superagent': null + 'd3': null + 'async': null + 'marked': null { size_label } = require path.resolve __dirname, '../src/modules/regex.coffee' diff --git a/test/issues.coffee b/test/issues.coffee index da2636b..aeabe8a 100644 --- a/test/issues.coffee +++ b/test/issues.coffee @@ -9,6 +9,12 @@ regex = require path.resolve(__dirname, '../src/modules/regex.coffee') issues = proxy path.resolve(__dirname, '../src/modules/issues.coffee'), './request': req + './require': + '_': require 'lodash' + 'superagent': null + 'd3': null + 'async': require 'async' + 'marked': null repo = { 'milestone': { 'number': no } } diff --git a/test/milestones.coffee b/test/milestones.coffee index f28c7a5..368d566 100644 --- a/test/milestones.coffee +++ b/test/milestones.coffee @@ -7,6 +7,12 @@ req = {} milestones = proxy path.resolve(__dirname, '../src/modules/milestones.coffee'), './request': req + './require': + '_': require 'lodash' + 'superagent': null + 'd3': null + 'async': null + 'marked': require 'marked' module.exports = @@ -166,8 +172,6 @@ module.exports = do done 'milestones - has description': (done) -> - marked = require 'marked' - req.all_milestones = (opts, cb) -> cb null, [ { diff --git a/test/request.coffee b/test/request.coffee index 51386ca..e8daee8 100644 --- a/test/request.coffee +++ b/test/request.coffee @@ -10,7 +10,12 @@ class Superagent end: (cb) -> cb @response request = proxy path.resolve(__dirname, '../src/modules/request.coffee'), - 'superagent': sa = new Superagent() + './require': + '_': require 'lodash' + 'superagent': sa = new Superagent() + 'd3': null + 'async': null + 'marked': null module.exports =