router loading projects from local storage

This commit is contained in:
Radek Stepan 2014-10-08 20:04:10 -07:00
parent 1cee7fe269
commit be2acdd564
30 changed files with 826 additions and 2529 deletions

View File

@ -37,7 +37,7 @@ module.exports = (grunt) ->
'vendor/firebase/firebase.js'
'vendor/firebase-simple-login/firebase-simple-login.js'
'vendor/superagent/superagent.js'
'vendor/localforage/dist/localforage.js'
'vendor/lscache/lscache.js'
'vendor/async/lib/async.js'
'vendor/moment/moment.js'
'vendor/d3/d3.js'

View File

@ -4,8 +4,7 @@
###Main
1. Load projects from `localStorage` (use sync). If we are on *Project* or *Chart* page, add it behind the scenes.
1. Now we fetch all milestones for our repo if we don't have any cached already. This calculates the points for each milestone. *The first two steps can happen on a Router level*
1. Now we fetch all milestones for our repo if we don't have any cached already. This calculates the points for each milestone.
1. Continue with page-specific actions.
###GitHub

View File

@ -9,7 +9,6 @@
"ractive-ractive": "rstacruz/ractive-ractive",
"firebase": "~1.0.21",
"firebase-simple-login": "~1.6.3",
"localforage": "~0.9.2",
"superagent": "~0.19.0",
"async": "~0.9.0",
"moment": "~2.8.3",
@ -17,6 +16,7 @@
"d3-tip": "~0.6.5",
"marked": "~0.3.2",
"director": "~1.2.2",
"ractive-transitions-fade": "~0.1.2"
"ractive-transitions-fade": "~0.1.2",
"lscache": "~1.0.2"
}
}

View File

@ -442,7 +442,9 @@ table {
.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}
html,body{margin:0;padding:0;height:100%}
body{color:#3e4457;font-family:'MuseoSans500Regular',sans-serif}
#app{position:relative;height:auto !important;min-height:100%}
a{text-decoration:none;color:#aaafbf;cursor:pointer}
h1,h2,h3,p{margin:0}
ul{list-style-type:none;margin:0;padding:0;}
@ -519,4 +521,4 @@ ul li{display:inline-block}
#content #projects .header a{font-family:'MuseoSlab500Regular',serif}
#content #projects .footer{background:#f9fafb;color:#aaafbf;-webkit-box-shadow:inset 0 1px 2px rgba(221,225,237,0.2);box-shadow:inset 0 1px 2px rgba(221,225,237,0.2);border-top:1px solid #dde1ed;text-align:right;font-family:'MuseoSlab500Regular',serif;}
#content #projects .footer .icon{color:#aaafbf}
#footer{border-top:1px solid #f3f4f8;text-align:center;padding:30px;margin-top:30px;font-family:'MuseoSlab500Regular',serif}
#footer{position:absolute;width:100%;bottom:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-top:1px solid #f3f4f8;text-align:center;padding:30px;margin-top:30px;font-family:'MuseoSlab500Regular',serif}

View File

@ -35,7 +35,9 @@
.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}
html,body{margin:0;padding:0;height:100%}
body{color:#3e4457;font-family:'MuseoSans500Regular',sans-serif}
#app{position:relative;height:auto !important;min-height:100%}
a{text-decoration:none;color:#aaafbf;cursor:pointer}
h1,h2,h3,p{margin:0}
ul{list-style-type:none;margin:0;padding:0;}
@ -112,4 +114,4 @@ ul li{display:inline-block}
#content #projects .header a{font-family:'MuseoSlab500Regular',serif}
#content #projects .footer{background:#f9fafb;color:#aaafbf;-webkit-box-shadow:inset 0 1px 2px rgba(221,225,237,0.2);box-shadow:inset 0 1px 2px rgba(221,225,237,0.2);border-top:1px solid #dde1ed;text-align:right;font-family:'MuseoSlab500Regular',serif;}
#content #projects .footer .icon{color:#aaafbf}
#footer{border-top:1px solid #f3f4f8;text-align:center;padding:30px;margin-top:30px;font-family:'MuseoSlab500Regular',serif}
#footer{position:absolute;width:100%;bottom:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-top:1px solid #f3f4f8;text-align:center;padding:30px;margin-top:30px;font-family:'MuseoSlab500Regular',serif}

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,7 @@
router = require('./modules/router');
App = Ractive.extend({
'template': require('./templates/layout'),
'template': require('./templates/app'),
'components': {
Header: Header,
Notify: Notify
@ -44,6 +44,7 @@
Model = require('../utils/model');
module.exports = new Model({
'name': 'models/config',
"data": {
"firebase": "burnchart",
"provider": "github",
@ -80,46 +81,31 @@
user = require('./user');
module.exports = new Model({
'data': {
'list': []
'name': 'models/projects',
find: function(project) {
return _.find(this.data.list, project);
},
load: function(projects) {
if (projects == null) {
projects = [];
exists: function() {
return !!this.find.apply(this, arguments);
},
add: function(project) {
if (!this.exists(project)) {
return this.push('list', project);
}
return async.each(projects, function(project, cb) {
return mediator.fire('!projects/add', project);
}, function(err) {
if (err) {
throw err;
}
});
},
add: function(repo, done) {
var _this = this;
return request.allMilestones(repo, function(err, res) {
var milestones;
if (err) {
return done(err);
}
milestones = _.pluckMany(res, config.get('fields.milestone'));
_this.push('list', _.merge(repo, {
milestones: milestones
}));
return done();
});
},
clear: function() {
return this.set('list', []);
},
onconstruct: function() {
localforage.getItem('projects', _.bind(this.load, this));
mediator.on('!projects/add', _.bind(this.add, this));
return mediator.on('!projects/clear', _.bind(this.clear, this));
},
onrender: function() {
this.set('list', lscache.get('projects') || []);
return this.observe('list', function(projects) {
return localforage.setItem('projects', projects);
return lscache.set('projects', _.pluckMany(projects, ['owner', 'name']));
}, {
'init': false
});
}
});
@ -136,6 +122,7 @@
Model = require('../utils/model');
system = new Model({
'name': 'models/system',
'data': {
'loading': false
}
@ -169,6 +156,7 @@
Model = require('../utils/model');
module.exports = new Model({
'name': 'models/user',
'data': {
'provider': "local",
'id': "0",
@ -500,7 +488,11 @@
// mediator.coffee
root.require.register('burnchart/src/modules/mediator.js', function(exports, require, module) {
module.exports = new Ractive();
var Mediator;
Mediator = Ractive.extend({});
module.exports = new Mediator();
});
@ -523,26 +515,12 @@
}
return cb(null, null, m);
});
} else {
return request.allMilestones(repo, function(err, data) {
var m;
if (err) {
return cb(err);
}
if (!data.length) {
return cb(null, "No open milestones for repo " + repo.path);
}
m = data[0];
m = _.rest(data, {
'due_on': null
});
m = m[0] ? m[0] : data[0];
if (m.open_issues + m.closed_issues === 0) {
return cb(null, "No issues for milestone `" + m.title + "`");
}
return cb(null, null, m);
});
}
},
getAll: function(repo, cb) {
return request.allMilestones(repo, function(err, data) {
return cb(err, null, data);
});
}
};
@ -764,7 +742,7 @@
// router.coffee
root.require.register('burnchart/src/modules/router.js', function(exports, require, module) {
var el, mediator, route, routes, system,
var addProject, c, el, mediator, route, routes, system, view,
__slice = [].slice;
mediator = require('./mediator');
@ -773,11 +751,37 @@
el = '#page';
addProject = function(page, owner, name) {
return mediator.fire('!projects/add', {
owner: owner,
name: name
});
};
c = function(name, fns) {
var fn, _i, _len, _results;
if (fns == null) {
fns = [];
}
_results = [];
for (_i = 0, _len = fns.length; _i < _len; _i++) {
fn = fns[_i];
_results.push(_.partial(fn, name));
}
return _results;
};
view = null;
route = function() {
var Page, args, page;
page = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
if (view != null) {
view.teardown();
}
mediator.fire('!app/notify/hide');
Page = require("../views/pages/" + page);
return new Page({
return view = new Page({
el: el,
'data': {
'route': args
@ -786,10 +790,10 @@
};
routes = {
'/': _.partial(route, 'index'),
'/new/project': _.partial(route, 'new'),
'/:owner/:name': _.partial(route, 'project'),
'/:owner/:name/:milestone': _.partial(route, 'chart'),
'/': c('index', [route]),
'/new/project': c('new', [route]),
'/:owner/:name': c('project', [addProject, route]),
'/:owner/:name/:milestone': c('chart', [addProject, route]),
'/reset': function() {
mediator.fire('!projects/clear');
return window.location.hash = '#';
@ -807,11 +811,20 @@
};
module.exports = Router(routes).configure({
'strict': false
'strict': false,
notfound: function() {
throw 404;
}
});
});
// app.mustache
root.require.register('burnchart/src/templates/app.js', function(exports, require, module) {
module.exports = ["<div id=\"app\">"," <Notify/>"," <Header/>",""," <div id=\"page\">"," <!-- content loaded from a router -->"," </div>",""," <div id=\"footer\">"," <div class=\"wrap\">"," &copy; 2012-2014 <a href=\"http://cloudfi.re\">Cloudfire Systems</a>"," </div>"," </div>","</div>"].join("\n");
});
// header.mustache
root.require.register('burnchart/src/templates/header.js', function(exports, require, module) {
@ -830,16 +843,10 @@
module.exports = ["{{#code}}"," <span class=\"icon {{icon}}\">{{{ '&#' + code + ';' }}}</span>","{{/code}}"].join("\n");
});
// layout.mustache
root.require.register('burnchart/src/templates/layout.js', function(exports, require, module) {
module.exports = ["<Notify/>","<Header/>","","<div id=\"page\">"," <!-- content loaded from a router -->","</div>","","<div id=\"footer\">"," <div class=\"wrap\">"," &copy; 2012-2014 <a href=\"http://cloudfi.re\">Cloudfire Systems</a>"," </div>","</div>"].join("\n");
});
// milestones.mustache
root.require.register('burnchart/src/templates/milestones.js', function(exports, require, module) {
module.exports = ["{{#milestones.length}}"," <div id=\"projects\">"," <div class=\"header\">"," <a href=\"#\" class=\"sort\"><Icons icon=\"sort-alphabet\"/> Sorted by priority</a>"," <h2>Milestones</h2>"," </div>",""," <table>"," {{#milestones}}"," <tr>"," <td>"," <a class=\"milestone\" href=\"#{{owner}}/{{name}}/{{number}}\">{{ title }}</a>"," </td>"," <td style=\"width:1%\">"," <div class=\"progress\">"," <span class=\"percent\">{{Math.floor(format.progress(closed_issues, open_issues))}}%</span>"," <span class=\"due\">{{{ format.due(due_on) }}}</span>"," <div class=\"outer bar\">"," <div class=\"inner bar {{format.onTime(this)}}\" style=\"width:{{format.progress(closed_issues, open_issues)}}%\"></div>"," </div>"," </div>"," </td>"," </tr>"," {{/milestones}}"," </table>",""," <div class=\"footer\">"," <a href=\"#\"><Icons icon=\"cog\"/> Edit</a>"," </div>"," </div>","{{/milestones.length}}"].join("\n");
module.exports = ["<div id=\"projects\">"," <div class=\"header\">"," <a href=\"#\" class=\"sort\"><Icons icon=\"sort-alphabet\"/> Sorted by priority</a>"," <h2>Milestones</h2>"," </div>",""," <table>"," {{#project.milestones}}"," <tr>"," <td>"," <a class=\"milestone\" href=\"#{{project.owner}}/{{project.name}}/{{number}}\">{{ title }}</a>"," </td>"," <td style=\"width:1%\">"," <div class=\"progress\">"," <span class=\"percent\">{{Math.floor(format.progress(closed_issues, open_issues))}}%</span>"," <span class=\"due\">{{{ format.due(due_on) }}}</span>"," <div class=\"outer bar\">"," <div class=\"inner bar {{format.onTime(this)}}\" style=\"width:{{format.progress(closed_issues, open_issues)}}%\"></div>"," </div>"," </div>"," </td>"," </tr>"," {{/project.milestones}}"," </table>",""," <div class=\"footer\">"," <a href=\"#\"><Icons icon=\"cog\"/> Edit</a>"," </div>","</div>"].join("\n");
});
// notify.mustache
@ -869,7 +876,7 @@
// project.mustache
root.require.register('burnchart/src/templates/pages/project.js', function(exports, require, module) {
module.exports = ["<div id=\"title\">"," <div class=\"wrap\">"," <h2 class=\"title\">{{route.join('/')}}</h2>"," </div>","</div>","","<div id=\"content\" class=\"wrap\">"," <Milestones owner=\"{{route[0]}}\" name=\"{{route[1]}}\"/>","</div>"].join("\n");
module.exports = ["{{#ready}}"," <div id=\"title\">"," <div class=\"wrap\">"," <h2 class=\"title\">{{route.join('/')}}</h2>"," </div>"," </div>",""," <div id=\"content\" class=\"wrap\">"," <Milestones project=\"{{project}}\"/>"," </div>","{{/ready}}"].join("\n");
});
// projects.mustache
@ -1003,11 +1010,16 @@
Icons = require('./icons');
module.exports = Ractive.extend({
'name': 'views/header',
'template': require('../templates/header'),
'data': {
'user': user,
'icon': 'fire-station'
},
'components': {
Icons: Icons
},
'adapt': [Ractive.adaptors.Ractive],
onconstruct: function() {
return this.on('!login', function() {
return firebase.login(function(err) {
@ -1022,11 +1034,7 @@
return system.observe('loading', function(ya) {
return _this.set('icon', ya ? 'spinner1' : 'fire-station');
});
},
'components': {
Icons: Icons
},
'adapt': [Ractive.adaptors.Ractive]
}
});
});
@ -1043,6 +1051,7 @@
Icons = require('./icons');
module.exports = Ractive.extend({
'name': 'views/hero',
'template': require('../templates/hero'),
'data': {
projects: projects
@ -1078,6 +1087,7 @@
};
module.exports = Ractive.extend({
'name': 'views/icons',
'template': require('../templates/icons'),
'isolated': true,
onrender: function() {
@ -1106,17 +1116,12 @@
Icons = require('./icons');
module.exports = Ractive.extend({
'name': 'views/milestones',
'template': require('../templates/milestones'),
'components': {
Icons: Icons
},
'adapt': [Ractive.adaptors.Ractive],
onconstruct: function() {
return this.set('milestones', _.filter(projects.get('list'), {
'owner': this.get('owner'),
'name': this.get('name')
}));
}
'adapt': [Ractive.adaptors.Ractive]
});
});
@ -1133,6 +1138,7 @@
HEIGHT = 68;
module.exports = Ractive.extend({
'name': 'views/notify',
'template': require('../templates/notify'),
'data': {
'top': HEIGHT,
@ -1145,6 +1151,10 @@
'ttl': 5e3
}
},
'components': {
Icons: Icons
},
'adapt': [Ractive.adaptors.Ractive],
show: function(opts) {
var pos;
this.set('hidden', false);
@ -1176,11 +1186,7 @@
mediator.on('!app/notify', _.bind(this.show, this));
mediator.on('!app/notify/hide', _.bind(this.hide, this));
return this.on('close', this.hide);
},
'components': {
Icons: Icons
},
'adapt': [Ractive.adaptors.Ractive]
}
});
});
@ -1199,6 +1205,7 @@
format = require('../../utils/format');
module.exports = Ractive.extend({
'name': 'views/pages/chart',
'template': require('../../templates/pages/chart'),
'adapt': [Ractive.adaptors.Ractive],
'data': {
@ -1207,6 +1214,7 @@
onrender: function() {
var name, owner, route, _ref,
_this = this;
return;
_ref = this.get('route'), owner = _ref[0], name = _ref[1], milestone = _ref[2];
route = {
owner: owner,
@ -1246,6 +1254,7 @@
format = require('../../utils/format');
module.exports = Ractive.extend({
'name': 'views/pages/index',
'template': require('../../templates/pages/index'),
'components': {
Hero: Hero,
@ -1275,6 +1284,7 @@
key = require('../../utils/key');
module.exports = Ractive.extend({
'name': 'views/pages/new',
'template': require('../../templates/pages/new'),
'data': {
'value': 'radekstepan/disposable',
@ -1317,19 +1327,66 @@
// project.coffee
root.require.register('burnchart/src/views/pages/project.js', function(exports, require, module) {
var Milestones;
var Milestones, mediator, milestone, projects, system;
Milestones = require('../milestones');
projects = require('../../models/projects');
system = require('../../models/system');
milestone = require('../../modules/milestone');
mediator = require('../../modules/mediator');
module.exports = Ractive.extend({
'name': 'views/pages/project',
'template': require('../../templates/pages/project'),
'components': {
Milestones: Milestones
},
'data': {
'ready': false
},
onrender: function() {
var name, owner, _ref;
var done, name, owner, project, _ref,
_this = this;
_ref = this.get('route'), owner = _ref[0], name = _ref[1];
return document.title = "" + owner + "/" + name;
document.title = "" + owner + "/" + name;
this.set('project', project = projects.find({
owner: owner,
name: name
}));
if (!project) {
throw 500;
}
if (project.milestones) {
return this.set('ready', true);
}
done = system.async();
return milestone.getAll(project, function(err, warn, list) {
done();
if (err) {
return mediator.fire('!app/notify', {
'text': err.toString(),
'type': 'alert',
'system': true,
'ttl': null
});
}
if (warn) {
return mediator.fire('!app/notify', {
'text': warn.toString(),
'type': 'warn',
'system': true,
'ttl': null
});
}
return _this.set({
'project.milestones': list,
'ready': true
});
});
}
});
@ -1347,6 +1404,7 @@
Icons = require('./icons');
module.exports = Ractive.extend({
'name': 'views/projects',
'template': require('../templates/projects'),
'data': {
projects: projects

View File

@ -1,20 +1,20 @@
( require "./#{key}" for key in [
'utils/mixins'
'models/projects'
'models/projects' # will load projects from localStorage
] )
Header = require './views/header'
Notify = require './views/notify'
router = require './modules/router'
App = Ractive.extend
'template': require './templates/layout'
'template': require './templates/app'
'components': { Header, Notify }
onrender: ->
# Start the router.
router.init '/'
module.exports = new App()

View File

@ -2,6 +2,8 @@ Model = require '../utils/model'
module.exports = new Model
'name': 'models/config'
"data":
# Firebase app name.
"firebase": "burnchart"

View File

@ -7,44 +7,30 @@ user = require './user'
module.exports = new Model
'data':
'list': []
'name': 'models/projects'
load: (projects=[]) ->
# Fetch milestones for each of these projects.
async.each projects, (project, cb) ->
mediator.fire '!projects/add', project
, (err) ->
throw err if err
find: (project) ->
_.find @data.list, project
add: (repo, done) ->
# TODO: warn when we are adding an existing repo (or
# silently go to index again).
exists: ->
!!@find.apply @, arguments
# Fetch milestones (which validates repo too).
request.allMilestones repo, (err, res) =>
return done err if err
# Pluck these fields for milestones.
milestones = _.pluckMany res, config.get('fields.milestone')
# Push to the stack.
@push 'list', _.merge repo, { milestones }
# Call back.
do done
# Push to the stack unless it exists already.
add: (project) ->
@push 'list', project unless @exists project
clear: ->
@set 'list', []
onconstruct: ->
# Initialize with items stored locally.
localforage.getItem 'projects', _.bind @load, @
mediator.on '!projects/add', _.bind @add, @
mediator.on '!projects/clear', _.bind @clear, @
mediator.on '!projects/add', _.bind @add, @
mediator.on '!projects/clear', _.bind @clear, @
onrender: ->
# Persist projects in local storage.
# Init the projects.
@set 'list', lscache.get('projects') or []
# Persist projects in local storage (sans milestones).
@observe 'list', (projects) ->
localforage.setItem 'projects', projects
lscache.set 'projects', _.pluckMany projects, [ 'owner', 'name' ]
, 'init': no

View File

@ -3,6 +3,9 @@ Model = require '../utils/model'
# System state.
system = new Model
'name': 'models/system'
'data':
'loading': no

View File

@ -4,6 +4,8 @@ Model = require '../utils/model'
# Currently logged-in user.
module.exports = new Model
'name': 'models/user'
# Default to a local user.
'data':
'provider': "local"

View File

@ -1 +1,3 @@
module.exports = new Ractive()
Mediator = Ractive.extend {}
module.exports = new Mediator()

View File

@ -2,7 +2,8 @@
request = require './request'
module.exports =
# Get current/specified milestone for a repo.
# Get a specific milestone for a repo.
get: (repo, cb) ->
# Get a specific milestone.
if repo.milestone
@ -15,21 +16,26 @@ module.exports =
cb null, null, m
# Get the current milestone out of many.
else
request.allMilestones repo, (err, data) ->
# Errors?
return cb err if err
# Empty warning?
return cb null, "No open milestones for repo #{repo.path}" unless data.length
# The first milestone should be ending soonest.
m = data[0]
# Filter milestones without due date.
m = _.rest data, { 'due_on' : null }
# The first milestone should be ending soonest. Prefer milestones with due dates.
m = if m[0] then m[0] else data[0]
# Empty milestone?
if m.open_issues + m.closed_issues is 0
return cb null, "No issues for milestone `#{m.title}`"
# Get all milestones.
getAll: (repo, cb) ->
request.allMilestones repo, (err, data) ->
cb err, null, data
cb null, null, m
# # Get the current milestone out of many.
# else
# request.allMilestones repo, (err, data) ->
# # Errors?
# return cb err if err
# # Empty warning?
# return cb null, "No open milestones for repo #{repo.path}" unless data.length
# # The first milestone should be ending soonest.
# m = data[0]
# # Filter milestones without due date.
# m = _.rest data, { 'due_on' : null }
# # The first milestone should be ending soonest. Prefer milestones with due dates.
# m = if m[0] then m[0] else data[0]
# # Empty milestone?
# if m.open_issues + m.closed_issues is 0
# return cb null, "No issues for milestone `#{m.title}`"
# cb null, null, m

View File

@ -3,15 +3,31 @@ system = require '../models/system'
el = '#page'
# Add a project from a route.
addProject = (page, owner, name) ->
mediator.fire '!projects/add', { owner, name }
# Preapply all functions with our page name/context.
c = (name, fns=[]) ->
( _.partial fn, name for fn in fns )
view = null
route = (page, args...) ->
# Unrender the previous one.
do view?.teardown
# Hide any notifications.
mediator.fire '!app/notify/hide'
# Require the new one.
Page = require "../views/pages/#{page}"
new Page { el, 'data': { 'route': args } }
# Render it.
view = new Page { el, 'data': { 'route': args } }
routes =
'/': _.partial route, 'index'
'/new/project': _.partial route, 'new'
'/:owner/:name': _.partial route, 'project'
'/:owner/:name/:milestone': _.partial route, 'chart'
'/': c 'index', [ route ]
'/new/project': c 'new', [ route ]
# The following two routes add a project in the background.
'/:owner/:name': c 'project', [ addProject, route ]
'/:owner/:name/:milestone': c 'chart', [ addProject, route ]
# TODO: remove in production.
'/reset': ->
mediator.fire '!projects/clear'
@ -29,4 +45,6 @@ routes =
# Flatiron Director router.
module.exports = Router(routes).configure
'strict': no # allow trailing slashes
'strict': no # allow trailing slashes
notfound: ->
throw 404

View File

@ -6,10 +6,20 @@ $sans_serif_font = 'MuseoSans500Regular', sans-serif
$strong_color = #C1041C
$highlight_color = #FFBB2A
html, body
margin: 0
padding: 0
height: 100%
body
color: #3E4457
font-family: $sans_serif_font
#app
position: relative
height: auto !important
min-height: 100%
a
text-decoration: none
color: #AAAFBF
@ -373,6 +383,10 @@ ul
color: #AAAFBF
#footer
position: absolute
width: 100%
bottom: 0
box-sizing: border-box
border-top: 1px solid #F3F4F8
text-align: center
padding: 30px

View File

@ -0,0 +1,14 @@
<div id="app">
<Notify/>
<Header/>
<div id="page">
<!-- content loaded from a router -->
</div>
<div id="footer">
<div class="wrap">
&copy; 2012-2014 <a href="http://cloudfi.re">Cloudfire Systems</a>
</div>
</div>
</div>

View File

@ -1,12 +0,0 @@
<Notify/>
<Header/>
<div id="page">
<!-- content loaded from a router -->
</div>
<div id="footer">
<div class="wrap">
&copy; 2012-2014 <a href="http://cloudfi.re">Cloudfire Systems</a>
</div>
</div>

View File

@ -1,31 +1,29 @@
{{#milestones.length}}
<div id="projects">
<div class="header">
<a href="#" class="sort"><Icons icon="sort-alphabet"/> Sorted by priority</a>
<h2>Milestones</h2>
</div>
<table>
{{#milestones}}
<tr>
<td>
<a class="milestone" href="#{{owner}}/{{name}}/{{number}}">{{ title }}</a>
</td>
<td style="width:1%">
<div class="progress">
<span class="percent">{{Math.floor(format.progress(closed_issues, open_issues))}}%</span>
<span class="due">{{{ format.due(due_on) }}}</span>
<div class="outer bar">
<div class="inner bar {{format.onTime(this)}}" style="width:{{format.progress(closed_issues, open_issues)}}%"></div>
</div>
</div>
</td>
</tr>
{{/milestones}}
</table>
<div class="footer">
<a href="#"><Icons icon="cog"/> Edit</a>
</div>
<div id="projects">
<div class="header">
<a href="#" class="sort"><Icons icon="sort-alphabet"/> Sorted by priority</a>
<h2>Milestones</h2>
</div>
{{/milestones.length}}
<table>
{{#project.milestones}}
<tr>
<td>
<a class="milestone" href="#{{project.owner}}/{{project.name}}/{{number}}">{{ title }}</a>
</td>
<td style="width:1%">
<div class="progress">
<span class="percent">{{Math.floor(format.progress(closed_issues, open_issues))}}%</span>
<span class="due">{{{ format.due(due_on) }}}</span>
<div class="outer bar">
<div class="inner bar {{format.onTime(this)}}" style="width:{{format.progress(closed_issues, open_issues)}}%"></div>
</div>
</div>
</td>
</tr>
{{/project.milestones}}
</table>
<div class="footer">
<a href="#"><Icons icon="cog"/> Edit</a>
</div>
</div>

View File

@ -1,9 +1,11 @@
<div id="title">
<div class="wrap">
<h2 class="title">{{route.join('/')}}</h2>
{{#ready}}
<div id="title">
<div class="wrap">
<h2 class="title">{{route.join('/')}}</h2>
</div>
</div>
</div>
<div id="content" class="wrap">
<Milestones owner="{{route[0]}}" name="{{route[1]}}"/>
</div>
<div id="content" class="wrap">
<Milestones project="{{project}}"/>
</div>
{{/ready}}

View File

@ -5,6 +5,8 @@ Icons = require './icons'
module.exports = Ractive.extend
'name': 'views/header'
'template': require '../templates/header'
'data':
@ -12,6 +14,10 @@ module.exports = Ractive.extend
# Default app icon.
'icon': 'fire-station'
'components': { Icons }
'adapt': [ Ractive.adaptors.Ractive ]
onconstruct: ->
# Login user.
@on '!login', ->
@ -21,8 +27,4 @@ module.exports = Ractive.extend
onrender: ->
# Switch loading icon with app icon.
system.observe 'loading', (ya) =>
@set 'icon', if ya then 'spinner1' else 'fire-station'
'components': { Icons }
'adapt': [ Ractive.adaptors.Ractive ]
@set 'icon', if ya then 'spinner1' else 'fire-station'

View File

@ -4,6 +4,8 @@ Icons = require './icons'
module.exports = Ractive.extend
'name': 'views/hero'
'template': require '../templates/hero'
'data': { projects }

View File

@ -17,6 +17,8 @@ codes =
module.exports = Ractive.extend
'name': 'views/icons'
'template': require '../templates/icons'
'isolated': yes

View File

@ -4,13 +4,10 @@ Icons = require './icons'
module.exports = Ractive.extend
'name': 'views/milestones'
'template': require '../templates/milestones'
'components': { Icons }
'adapt': [ Ractive.adaptors.Ractive ]
onconstruct: ->
@set 'milestones', _.filter projects.get('list'),
'owner': @get 'owner'
'name': @get 'name'
'adapt': [ Ractive.adaptors.Ractive ]

View File

@ -5,6 +5,8 @@ HEIGHT = 68 # height of div in px
module.exports = Ractive.extend
'name': 'views/notify'
'template': require '../templates/notify'
'data':
@ -17,6 +19,10 @@ module.exports = Ractive.extend
'icon': 'megaphone'
'ttl': 5e3
'components': { Icons }
'adapt': [ Ractive.adaptors.Ractive ]
# Show a notification.
show: (opts) ->
@set 'hidden', no
@ -52,8 +58,4 @@ module.exports = Ractive.extend
mediator.on '!app/notify/hide', _.bind @hide, @
# Close us prematurely...
@on 'close', @hide
'components': { Icons }
'adapt': [ Ractive.adaptors.Ractive ]
@on 'close', @hide

View File

@ -5,6 +5,8 @@ format = require '../../utils/format'
module.exports = Ractive.extend
'name': 'views/pages/chart'
'template': require '../../templates/pages/chart'
'adapt': [ Ractive.adaptors.Ractive ]
@ -12,9 +14,11 @@ module.exports = Ractive.extend
'data': { format }
onrender: ->
return
[ owner, name, milestone ] = @get 'route'
route = { owner, name, milestone }
document.title = "#{owner}/#{name}/#{milestone}"
milestone.get route, (err, warn, obj) =>

View File

@ -4,6 +4,8 @@ format = require '../../utils/format'
module.exports = Ractive.extend
'name': 'views/pages/index'
'template': require '../../templates/pages/index'
'components': { Hero, Projects }

View File

@ -5,6 +5,8 @@ key = require '../../utils/key'
module.exports = Ractive.extend
'name': 'views/pages/new'
'template': require '../../templates/pages/new'
'data': { 'value': 'radekstepan/disposable', user }

View File

@ -1,12 +1,56 @@
Milestones = require '../milestones'
projects = require '../../models/projects'
system = require '../../models/system'
milestone = require '../../modules/milestone'
mediator = require '../../modules/mediator'
module.exports = Ractive.extend
'name': 'views/pages/project'
'template': require '../../templates/pages/project'
'components': { Milestones }
'data':
'ready': no
onrender: ->
[ owner, name ] = @get 'route'
document.title = "#{owner}/#{name}"
document.title = "#{owner}/#{name}"
# Get the associated project.
@set 'project', project = projects.find { owner, name }
# Should not happen...
throw 500 unless project
# Does it have milestones already?
return @set('ready', yes) if project.milestones
# We are loading the milestones then.
done = do system.async
milestone.getAll project, (err, warn, list) =>
do done
return mediator.fire '!app/notify', {
'text': do err.toString
'type': 'alert'
'system': yes
'ttl': null
} if err
return mediator.fire '!app/notify', {
'text': do warn.toString
'type': 'warn'
'system': yes
'ttl': null
} if warn
# Save the milestones.
@set
'project.milestones': list
'ready': yes

View File

@ -4,6 +4,8 @@ Icons = require './icons'
module.exports = Ractive.extend
'name': 'views/projects'
'template': require '../templates/projects'
'data': { projects }