2013-06-30 22:38:05 +02:00
|
|
|
/** @jsx React.DOM */
|
|
|
|
var StateIcon = React.createClass({
|
|
|
|
render: function() {
|
|
|
|
var icon_names = {"TODO": "icon-check-empty",
|
|
|
|
"DOING": "icon-sign-blank",
|
|
|
|
"DONE": "icon-check"};
|
|
|
|
return <i class={icon_names[this.props.state]}></i>;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2013-07-04 21:51:31 +02:00
|
|
|
var StoryTaskRow = React.createClass({
|
2013-07-04 23:55:43 +02:00
|
|
|
changeState: React.autoBind(function(event) {
|
|
|
|
$.ajax({
|
|
|
|
url: "/tasks/state",
|
|
|
|
type: "POST",
|
|
|
|
data: {'id': this.props.task.id},
|
|
|
|
dataType: 'json',
|
2013-07-07 23:46:15 +02:00
|
|
|
mimeType: 'textPlain',
|
|
|
|
success: function (data, textStatus, jqXHR) {
|
|
|
|
if (data.status == "ok")
|
|
|
|
this.setState({state: data.state});
|
|
|
|
}.bind(this)
|
2013-07-04 23:55:43 +02:00
|
|
|
});
|
|
|
|
}),
|
2013-07-07 23:46:15 +02:00
|
|
|
getInitialState: function () {
|
|
|
|
return {state: this.props.task.state};
|
|
|
|
},
|
2013-07-04 23:55:43 +02:00
|
|
|
moveUp: React.autoBind(function(event) {
|
|
|
|
$.ajax({
|
|
|
|
url: "tasks/up",
|
|
|
|
type: "POST",
|
|
|
|
data: {'id': this.props.task.id},
|
|
|
|
dataType: 'json',
|
|
|
|
mimeType: 'textPlain'
|
|
|
|
});
|
|
|
|
}),
|
|
|
|
moveDown: React.autoBind(function(event) {
|
|
|
|
$.ajax({
|
|
|
|
url: "tasks/down",
|
|
|
|
type: "POST",
|
|
|
|
data: {'id': this.props.task.id},
|
|
|
|
dataType: 'json',
|
|
|
|
mimeType: 'textPlain'
|
|
|
|
});
|
|
|
|
}),
|
2013-07-04 21:51:31 +02:00
|
|
|
render: function() {
|
|
|
|
return (
|
|
|
|
<tr>
|
|
|
|
<td class="span1">
|
2013-07-04 23:55:43 +02:00
|
|
|
<i class="icon-arrow-up" onClick={this.moveUp}></i>
|
|
|
|
<i class="icon-arrow-down" onClick={this.moveDown}></i>
|
2013-07-04 21:51:31 +02:00
|
|
|
</td>
|
|
|
|
<td class="span2">
|
2013-07-04 23:55:43 +02:00
|
|
|
<span onClick={this.changeState}>
|
2013-07-07 23:46:15 +02:00
|
|
|
<StateIcon state={this.state.state} /> {" "}
|
|
|
|
{this.state.state}
|
2013-07-04 21:51:31 +02:00
|
|
|
</span>
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{this.props.task.description}
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var StoryTaskTable = React.createClass({
|
|
|
|
render: function() {
|
|
|
|
var taskNodes = this.props.tasks.map(function (task) {
|
|
|
|
return <StoryTaskRow task={task} />;
|
2013-07-05 00:02:10 +02:00
|
|
|
}.bind(this));
|
|
|
|
|
2013-07-04 21:51:31 +02:00
|
|
|
return (
|
|
|
|
<table class="table table-striped">
|
|
|
|
{taskNodes}
|
|
|
|
</table>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var StoryTaskForm = React.createClass({
|
|
|
|
handleSubmit: React.autoBind(function() {
|
|
|
|
var text = this.refs.text.getDOMNode().value.trim();
|
|
|
|
|
|
|
|
this.props.onTaskSubmit({description: text});
|
|
|
|
|
|
|
|
this.refs.text.getDOMNode().value = '';
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}),
|
|
|
|
render: function() {
|
|
|
|
return (
|
|
|
|
<form onSubmit={this.handleSubmit}>
|
|
|
|
<fieldset>
|
|
|
|
<legend>New task</legend>
|
|
|
|
<div class="input-append">
|
|
|
|
<input type="text" ref="text" class="input-medium" />
|
|
|
|
<button type="submit" class="btn btn-primary">
|
|
|
|
Send
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</fieldset>
|
|
|
|
</form>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2013-06-30 22:38:05 +02:00
|
|
|
var StoryData = React.createClass({
|
2013-07-04 21:51:31 +02:00
|
|
|
handleTaskSubmit: React.autoBind(function (task) {
|
2013-07-05 00:37:44 +02:00
|
|
|
task.storyId = this.props.data.id;
|
|
|
|
|
2013-07-04 21:51:31 +02:00
|
|
|
$.ajax({
|
2013-07-05 00:37:44 +02:00
|
|
|
url: "/stories/tasks/new",
|
2013-07-04 21:51:31 +02:00
|
|
|
type: "POST",
|
|
|
|
data: task,
|
|
|
|
dataType: 'json',
|
|
|
|
mimeType: 'textPlain'
|
|
|
|
});
|
|
|
|
}),
|
2013-06-30 22:38:05 +02:00
|
|
|
render: function() {
|
2013-07-05 00:02:10 +02:00
|
|
|
var taskTable = null;
|
|
|
|
|
|
|
|
if (this.props.data.tasks)
|
|
|
|
taskTable = <StoryTaskTable tasks={this.props.data.tasks} />;
|
|
|
|
|
2013-06-30 22:38:05 +02:00
|
|
|
if (this.props.data) {
|
|
|
|
return (<div>
|
|
|
|
Assignee: {this.props.data.assignee}
|
|
|
|
<pre>{this.props.data.content}</pre>
|
2013-07-05 00:02:10 +02:00
|
|
|
{taskTable}
|
2013-07-04 21:51:31 +02:00
|
|
|
<StoryTaskForm onTaskSubmit={this.handleTaskSubmit} />
|
2013-06-30 22:38:05 +02:00
|
|
|
</div>);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var StoryRow = React.createClass({
|
|
|
|
render: function() {
|
|
|
|
var sdata = null;
|
|
|
|
|
|
|
|
if (this.state.content)
|
|
|
|
sdata = <StoryData data={this.state.content} />;
|
|
|
|
|
|
|
|
return (
|
|
|
|
<tr>
|
2013-07-04 23:45:07 +02:00
|
|
|
<td class="span1">
|
2013-06-30 22:38:05 +02:00
|
|
|
<i class="icon-arrow-up" onClick={this.moveUp}></i>
|
|
|
|
<i class="icon-arrow-down" onClick={this.moveDown}></i>
|
|
|
|
</td>
|
2013-07-04 23:45:07 +02:00
|
|
|
<td class="span2">
|
2013-06-30 22:38:05 +02:00
|
|
|
<span onClick={this.changeState}>
|
2013-07-07 23:46:15 +02:00
|
|
|
<StateIcon state={this.state.state} /> {" "}
|
|
|
|
{this.state.state}
|
2013-06-30 22:38:05 +02:00
|
|
|
</span>
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
<a onClick={this.handleClick}>
|
|
|
|
As a {this.props.story.role}, I
|
|
|
|
{this.props.story.necessity} to
|
|
|
|
{this.props.story.title}
|
|
|
|
</a>
|
|
|
|
<br />
|
|
|
|
{sdata}
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
},
|
|
|
|
getInitialState: function() {
|
|
|
|
return {state: this.props.story.state,
|
|
|
|
content: null};
|
|
|
|
},
|
|
|
|
handleClick: React.autoBind(function(event) {
|
|
|
|
if (!!this.state.content) {
|
|
|
|
this.setState({content: null});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
$.get('/stories/' + this.props.story.id, null,
|
|
|
|
function (data, textStatus, jqXHR) {
|
|
|
|
self.setState({content: data});
|
|
|
|
}, 'json');
|
|
|
|
}),
|
|
|
|
changeState: React.autoBind(function(event) {
|
|
|
|
$.ajax({
|
|
|
|
url: "/stories/state",
|
|
|
|
type: "POST",
|
|
|
|
data: {'id': this.props.story.id},
|
|
|
|
dataType: 'json',
|
|
|
|
mimeType: 'textPlain',
|
2013-07-07 23:46:15 +02:00
|
|
|
success: function(data, textStatus, jqXHR) {
|
|
|
|
if (data.status == "ok")
|
|
|
|
this.setState({state: data.state});
|
2013-06-30 22:38:05 +02:00
|
|
|
}.bind(this)
|
|
|
|
});
|
|
|
|
}),
|
|
|
|
moveUp: React.autoBind(function(event) {
|
|
|
|
$.ajax({
|
|
|
|
url: "/stories/up",
|
|
|
|
type: "POST",
|
|
|
|
data: {'id': this.props.story.id},
|
|
|
|
dataType: 'json',
|
|
|
|
mimeType: 'textPlain',
|
2013-07-07 23:46:15 +02:00
|
|
|
success: function (data, textStatus, jqXHR) {
|
|
|
|
if (data.status == "ok")
|
|
|
|
this.props.onMoved(1);
|
2013-06-30 22:38:05 +02:00
|
|
|
}.bind(this)
|
|
|
|
});
|
|
|
|
}),
|
|
|
|
moveDown: React.autoBind(function(event) {
|
|
|
|
$.ajax({
|
|
|
|
url: "/stories/down",
|
|
|
|
type: "POST",
|
|
|
|
data: {'id': this.props.story.id},
|
|
|
|
dataType: 'json',
|
|
|
|
mimeType: 'textPlain',
|
|
|
|
success: function (data) {
|
|
|
|
this.props.onMoved(-1);
|
|
|
|
}.bind(this)
|
|
|
|
});
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
var StoryTable = React.createClass({
|
|
|
|
handleMoved: React.autoBind(function(direction) {
|
2013-07-07 23:46:15 +02:00
|
|
|
this.props.onStoryMoved(direction);
|
2013-06-30 22:38:05 +02:00
|
|
|
}),
|
|
|
|
render: function() {
|
2013-07-07 22:36:51 +02:00
|
|
|
var storyNodes = this.props.data.map(function (story) {
|
2013-06-30 22:38:05 +02:00
|
|
|
return <StoryRow story={story} onMoved={this.handleMoved} />;
|
|
|
|
}.bind(this));
|
|
|
|
return (
|
|
|
|
<table class="table table-striped">
|
|
|
|
{storyNodes}
|
|
|
|
</table>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var StoryForm = React.createClass({
|
|
|
|
handleSubmit: React.autoBind(function() {
|
|
|
|
var role = this.refs.role.getDOMNode().value.trim();
|
|
|
|
var necessity = this.refs.necessity.getDOMNode().value.trim();
|
|
|
|
var headline = this.refs.headline.getDOMNode().value.trim();
|
|
|
|
var content = this.refs.content.getDOMNode().value.trim();
|
|
|
|
|
|
|
|
this.props.onStorySubmit({role: role,
|
|
|
|
necessity: necessity,
|
|
|
|
headline: headline,
|
|
|
|
content: content});
|
|
|
|
|
|
|
|
this.refs.role.getDOMNode().value = '';
|
|
|
|
this.refs.necessity.getDOMNode().value = '';
|
|
|
|
this.refs.headline.getDOMNode().value = '';
|
|
|
|
this.refs.content.getDOMNode().value = '';
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}),
|
|
|
|
render: function() {
|
|
|
|
return (
|
|
|
|
<form onSubmit={this.handleSubmit}>
|
|
|
|
<fieldset>
|
|
|
|
<legend class="toggle">New story</legend>
|
|
|
|
<div id="new-story">
|
|
|
|
<div class="input-prepend input-append">
|
|
|
|
<span class="add-on">As a </span>
|
|
|
|
<input type="text" class="input-medium" ref="role"
|
|
|
|
placeholder="person" />
|
|
|
|
<span class="add-on"> I </span>
|
|
|
|
<input type="text" class="input-small"
|
|
|
|
ref="necessity" placeholder="would like" />
|
|
|
|
<span class="add-on"> to </span>
|
|
|
|
<input type="text" class="input-xxlarge"
|
|
|
|
ref="headline" placeholder="fill in this form..." />
|
2013-07-04 23:45:07 +02:00
|
|
|
<button class="btn btn-primary" type="submit">!</button>
|
|
|
|
<br />
|
2013-06-30 22:38:05 +02:00
|
|
|
<textarea ref="content"></textarea>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</fieldset>
|
|
|
|
</form>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var StoryPage = React.createClass({
|
2013-07-07 22:36:51 +02:00
|
|
|
loadStoriesFromServer: function() {
|
|
|
|
$.ajax({
|
|
|
|
url: this.props.url,
|
|
|
|
mimeType: 'textPlain',
|
|
|
|
success: function(data) {
|
|
|
|
this.setState({data: eval(data)});
|
|
|
|
}.bind(this)
|
|
|
|
});
|
|
|
|
},
|
|
|
|
getInitialState: function() {
|
|
|
|
return {data: []};
|
|
|
|
},
|
|
|
|
componentWillMount: function() {
|
|
|
|
this.loadStoriesFromServer();
|
|
|
|
setInterval(
|
|
|
|
this.loadStoriesFromServer.bind(this),
|
|
|
|
this.props.pollInterval
|
|
|
|
);
|
|
|
|
},
|
2013-07-07 23:46:15 +02:00
|
|
|
handleStoryMoved: React.autoBind(function (direction) {
|
|
|
|
this.loadStoriesFromServer();
|
|
|
|
}),
|
2013-06-30 22:38:05 +02:00
|
|
|
handleStorySubmit: React.autoBind(function (story) {
|
|
|
|
$.ajax({
|
|
|
|
url: "/stories/new",
|
|
|
|
type: "POST",
|
|
|
|
data: story,
|
|
|
|
dataType: 'json',
|
2013-07-07 22:36:51 +02:00
|
|
|
mimeType: 'textPlain',
|
|
|
|
success: function (data, textStatus, jqXHR) {
|
|
|
|
if (data.status == "ok")
|
|
|
|
this.loadStoriesFromServer();
|
|
|
|
}.bind(this),
|
|
|
|
error: function (jqXHR, textStatus, errorThrown) {
|
|
|
|
alert("error: " + errorThrown);
|
|
|
|
}.bind(this)
|
2013-06-30 22:38:05 +02:00
|
|
|
});
|
|
|
|
}),
|
|
|
|
render: function() {
|
|
|
|
return (
|
|
|
|
<div>
|
2013-07-07 23:46:15 +02:00
|
|
|
<StoryTable data={this.state.data} onStoryMoved={this.handleStoryMoved} />
|
2013-06-30 22:38:05 +02:00
|
|
|
<StoryForm onStorySubmit={this.handleStorySubmit} />
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
React.renderComponent(
|
2013-07-07 22:36:51 +02:00
|
|
|
<StoryPage url="/stories" pollInterval={5000} />,
|
2013-06-30 22:38:05 +02:00
|
|
|
document.getElementById('content')
|
|
|
|
);
|