add a project form
This commit is contained in:
parent
0c8cadf38b
commit
3eaf4740be
|
@ -15,7 +15,7 @@ $ npm start
|
|||
# Server started on port 8080
|
||||
```
|
||||
|
||||
##CHANGELOG
|
||||
##Changelog
|
||||
|
||||
3.0.0
|
||||
###v3.0.0
|
||||
- switch to React & Flux architecture
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { RouterMixin, navigate } from 'react-mini-router';
|
||||
import _ from 'lodash';
|
||||
import lodash from './mixins/lodash.js';
|
||||
|
||||
import ProjectsPage from './pages/ProjectsPage.jsx';
|
||||
import MilestonesPage from './pages/MilestonesPage.jsx';
|
||||
|
@ -70,13 +71,14 @@ export default React.createClass({
|
|||
},
|
||||
|
||||
// Route to a link.
|
||||
// TODO: make this a named route.
|
||||
navigate: navigate
|
||||
},
|
||||
|
||||
// Show projects.
|
||||
projects() {
|
||||
document.title = 'Burnchart: GitHub Burndown Chart as a Service';
|
||||
actions.emit('projects.load');
|
||||
process.nextTick(() => { actions.emit('projects.load'); });
|
||||
return <ProjectsPage />;
|
||||
},
|
||||
|
||||
|
@ -92,6 +94,7 @@ export default React.createClass({
|
|||
|
||||
// Add a project.
|
||||
addProject() {
|
||||
document.title = 'Add a project';
|
||||
return <AddProjectPage />;
|
||||
},
|
||||
|
||||
|
@ -114,13 +117,7 @@ export default React.createClass({
|
|||
return <div />;
|
||||
} else {
|
||||
blank = true;
|
||||
|
||||
// TODO: Hide any notifications.
|
||||
// mediator.fire '!app/notify/hide'
|
||||
|
||||
// Each page is starting in a loading state.
|
||||
actions.emit('system.loading', true);
|
||||
|
||||
actions.emit('system.loading', false);
|
||||
return this.renderCurrentRoute();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
import React from 'react';
|
||||
|
||||
import App from '../App.jsx';
|
||||
|
||||
import actions from '../actions/appActions.js';
|
||||
|
||||
import Icon from './Icon.jsx';
|
||||
import S from './Space.jsx';
|
||||
|
||||
export default React.createClass({
|
||||
|
||||
displayName: 'AddProjectForm.jsx',
|
||||
|
||||
// Sign user in.
|
||||
_onSignIn() {
|
||||
actions.emit('user.signin');
|
||||
},
|
||||
|
||||
_onChange(evt) {
|
||||
this.setState({ 'val': evt.target.value });
|
||||
},
|
||||
|
||||
_onKeyUp(evt) {
|
||||
if (evt.key == 'Enter') {
|
||||
this._onAdd();
|
||||
}
|
||||
},
|
||||
|
||||
_onAdd() {
|
||||
let [ owner, name ] = this.state.val.split('/');
|
||||
actions.emit('projects.add', { owner, name });
|
||||
// Redirect to the dashboard.
|
||||
App.navigate('/');
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return { 'val': '' };
|
||||
},
|
||||
|
||||
render() {
|
||||
let user;
|
||||
if (!('uid' in this.props.user)) {
|
||||
user = (
|
||||
<span><S />If you'd like to add a private GitHub repo,
|
||||
<S /><a onClick={this._onSignIn}>Sign In</a> first.</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="add">
|
||||
<div className="header">
|
||||
<h2>Add a Project</h2>
|
||||
<p>Type the name of a GitHub repository that has some
|
||||
milestones with issues.{user}</p>
|
||||
</div>
|
||||
|
||||
<div className="form">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="text" placeholder="user/repo" autoComplete="off"
|
||||
onChange={this._onChange} value={this.state.val} onKeyUp={this._onKeyUp} />
|
||||
</td>
|
||||
<td><a onClick={this._onAdd}>Add</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="protip">
|
||||
<Icon name="rocket"/> Protip: To see if a milestone is on track or not,
|
||||
make sure it has a due date assigned to it.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
import React from 'react';
|
||||
|
||||
export default React.createClass({
|
||||
|
||||
displayName: 'Space.jsx',
|
||||
|
||||
render() {
|
||||
return <span> </span>;
|
||||
}
|
||||
|
||||
});
|
|
@ -54,7 +54,7 @@ export default class Store extends EventEmitter {
|
|||
let obj = this.get(key);
|
||||
if (_.isArray(obj)) {
|
||||
// TODO: Don't assume a string.
|
||||
this.set(`${key}.${obj.length}`, val);
|
||||
this.set(`${key}.${obj.length}`, val); // TODO: won't emit for root key
|
||||
return obj.length - 1;
|
||||
} else {
|
||||
this.set(key, [ val ]);
|
||||
|
|
|
@ -27,8 +27,11 @@ export default {
|
|||
return obj;
|
||||
},
|
||||
|
||||
_onChange(store, val, key) {;
|
||||
_onChange(store, val, key) {
|
||||
// TODO: this is not the right approach!
|
||||
if (this.isMounted()) {
|
||||
this.setState(this._getData(store));
|
||||
}
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
_.mixin({
|
||||
pluckMany: (source, keys) => {
|
||||
if (!_.isArray(keys)) {
|
||||
throw '`keys` needs to be an Array';
|
||||
}
|
||||
|
||||
return _.map(source, (item) => {
|
||||
let obj = {};
|
||||
for (let key of keys) {
|
||||
obj[key] = item[key];
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
},
|
||||
|
||||
isInt: (val) => {
|
||||
return !isNaN(val) && parseInt(Number(val)) === val &&
|
||||
!isNaN(parseInt(val, 10));
|
||||
}
|
||||
});
|
|
@ -148,7 +148,7 @@ let isValid = (obj) => {
|
|||
let rules = {
|
||||
owner: (x) => { return (typeof x !== "undefined" && x !== null); },
|
||||
name: (x) => { return (typeof x !== "undefined" && x !== null); },
|
||||
milestone: (x) => { return _.isFinite(x); }
|
||||
milestone: (x) => { return _.isInt(x); } // mixin
|
||||
};
|
||||
|
||||
for (let key in obj) {
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
import actions from '../actions/appActions.js';
|
||||
|
||||
import Page from '../mixins/Page.js';
|
||||
|
||||
import Notify from '../components/Notify.jsx';
|
||||
import Header from '../components/Header.jsx';
|
||||
import AddProjectForm from '../components/AddProjectForm.jsx';
|
||||
|
||||
export default React.createClass({
|
||||
|
||||
|
@ -17,7 +20,11 @@ export default React.createClass({
|
|||
<Notify />
|
||||
<Header {...this.state} />
|
||||
|
||||
<div id="page" />
|
||||
<div id="page">
|
||||
<div id="content" className="wrap">
|
||||
<AddProjectForm user={this.state.app.user} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<div className="wrap">
|
||||
|
|
|
@ -4,7 +4,6 @@ import Page from '../mixins/Page.js';
|
|||
|
||||
import Notify from '../components/Notify.jsx';
|
||||
import Header from '../components/Header.jsx';
|
||||
|
||||
import Projects from '../components/Projects.jsx';
|
||||
import Hero from '../components/Hero.jsx';
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ class AppStore extends Store {
|
|||
constructor() {
|
||||
super({
|
||||
system: {
|
||||
loading: false
|
||||
loading: true
|
||||
},
|
||||
user: {}
|
||||
});
|
||||
|
|
|
@ -45,9 +45,11 @@ class ProjectsStore extends Store {
|
|||
this.set('user', user);
|
||||
});
|
||||
|
||||
// Persist projects in local storage (sans milestones).
|
||||
this.on('list', (projects) => {
|
||||
lscache.set('projects', _.pluckMany(projects, [ 'owner', 'name' ]));
|
||||
// Persist projects in local storage (sans milestones and issues).
|
||||
this.on('list.*', () => {
|
||||
if (process.browser) {
|
||||
lscache.set('projects', _.pluckMany(this.get('list'), [ 'owner', 'name' ]));
|
||||
}
|
||||
});
|
||||
|
||||
// Reset our index and re-sort.
|
||||
|
@ -62,13 +64,22 @@ class ProjectsStore extends Store {
|
|||
onProjectsLoad() {
|
||||
let list = this.get('list');
|
||||
|
||||
let done = (err) => {
|
||||
actions.emit('system.loading', false);
|
||||
};
|
||||
|
||||
// Quit if we have no projects.
|
||||
if (!list.length) return;
|
||||
if (!list.length) return done();
|
||||
|
||||
actions.emit('system.loading', true);
|
||||
|
||||
// Wait for the user to get resolved.
|
||||
this.get('user', (user) => {
|
||||
// For all projects.
|
||||
async.map(list, (project, cb) => {
|
||||
// Skip any projects that already have milestones.
|
||||
if ('milestones' in project) return cb();
|
||||
|
||||
// Fetch their milestones.
|
||||
milestones.fetchAll(user, project, (err, list) => {
|
||||
// Save the error if project does not exist.
|
||||
|
@ -109,10 +120,7 @@ class ProjectsStore extends Store {
|
|||
});
|
||||
}, cb);
|
||||
});
|
||||
// All done, any errors are ignored as saved on projects.
|
||||
}, (err) => {
|
||||
actions.emit('system.loading', false);
|
||||
});
|
||||
}, done);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import { assert } from 'chai';
|
||||
import { noCallThru } from 'proxyquire';
|
||||
import path from 'path';
|
||||
import moment from 'moment';
|
||||
|
||||
let proxy = noCallThru();
|
||||
|
||||
import stats from '../src/js/modules/stats.js';
|
||||
|
||||
export default {
|
||||
|
|
Loading…
Reference in New Issue