Version 1.0: Working with TimeStamper API and GTDServlet.
This commit is contained in:
parent
f00ec0e7a9
commit
1ea1d7d75f
14
Makefile
Normal file
14
Makefile
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
build :
|
||||||
|
mkdir -p build/css
|
||||||
|
cp src/www/*.* build
|
||||||
|
cp -r src/www/js build
|
||||||
|
cp -r resources/* build/.
|
||||||
|
sass src/www/css/personal-display.scss build/css/personal-display.css
|
||||||
|
tar czf personal-display.tar.gz build
|
||||||
|
|
||||||
|
clean :
|
||||||
|
rm -r build
|
||||||
|
|
||||||
|
local-deploy: build
|
||||||
|
cp -r build ~/temp/server
|
||||||
|
ssh jdb-server 'rm -r ~/public_html/personal-display; mv temp/build ~/public_html/personal-display'
|
@ -1,66 +0,0 @@
|
|||||||
<!DOCTYPE HTML>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>What I am Doing</title>
|
|
||||||
<link rel="stylesheet" href="css/display.css" type="text/css">
|
|
||||||
<link
|
|
||||||
href='http://fonts.googleapis.com/css?family=Average+Sans|Quicksand:300,400,700|Oleo+Script+Swash+Caps|Ubuntu|Rationale|Exo:200,400|Cantarell|Jura|Play|Seaweed+Script|Rosario|Bubbler+One|Spinnaker|Advent+Pro'
|
|
||||||
rel='stylesheet' type='text/css'>
|
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<section id=current-task>
|
|
||||||
<h3>Current Activity</h3>
|
|
||||||
<span class=task>QD Lobby Map: Meeting with JCA.</span>
|
|
||||||
<div class=task-notes>
|
|
||||||
<p>Discussed current display procedure, plans for new display.
|
|
||||||
Thoughts for new display:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><p>Predefined window of time (TBD) for the orders to be shown on the map. All
|
|
||||||
orders displayed will be chosen based on when they were placed.</p></li>
|
|
||||||
<li><p>Discussed whether we wanted to have a periodic refresh, meaning we would
|
|
||||||
blank the display at the beginning of the week/month and let it fill back in
|
|
||||||
as orders come in, or keep a rolling refresh, meaning we would drop orders
|
|
||||||
from the display when they are more than a week/month old.</p>
|
|
||||||
|
|
||||||
<p>In other words, does the display show the current week/month or does it show
|
|
||||||
the last 7/30 days?</p>
|
|
||||||
|
|
||||||
<p>The decision was to expirement and see which was a more impressive display.</p></li>
|
|
||||||
<li><p>I need to investigate the latest version of KML in case there are new
|
|
||||||
features available that would be useful.</p></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
<p>Other notes:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>QD is running Google Earth 7.1 (latest Beta)</li>
|
|
||||||
<li>Chris will send me the Order Tour source code.</li>
|
|
||||||
<li>We planned to have my old workstation setup and configured with GoToMyPC to
|
|
||||||
allow access to the QD development environment.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section id=priorities>
|
|
||||||
<h3>Next Actions (unsorted)</h3>
|
|
||||||
<div class=next-action>
|
|
||||||
<span class=action>Respond to ELance job proposal.</span>
|
|
||||||
<span class=date>Mon, 08/05</span>
|
|
||||||
</div>
|
|
||||||
<div class=next-action>
|
|
||||||
<span class=action>Create matching project folder structure in
|
|
||||||
the <em>done</em> folder as the project folder and move action items
|
|
||||||
appropriately.</span>
|
|
||||||
<span class=project><a href=/project/gtd-cli">GTD CLI</a></span>
|
|
||||||
</div>
|
|
||||||
<div class=next-action>
|
|
||||||
<span class=action>Implement category drill down.</span>
|
|
||||||
<span class=project><a href="/project/time-analyzer">Time analyzer software</a></span>
|
|
||||||
<span class=details>Double-clicking on a category should show a
|
|
||||||
graph of sub-items in that category.</span>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
BIN
resources/img/loading-spinner.gif
Normal file
BIN
resources/img/loading-spinner.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@ -110,10 +110,10 @@ body {
|
|||||||
font-family: Advent Pro, Rosario, Jura, Average Sans, Cantarell;
|
font-family: Advent Pro, Rosario, Jura, Average Sans, Cantarell;
|
||||||
margin: 0.5rem 1rem; }
|
margin: 0.5rem 1rem; }
|
||||||
|
|
||||||
section {
|
body > section {
|
||||||
padding: 0.2rem;
|
padding: 0.2rem;
|
||||||
|
|
||||||
& > h3 {
|
h3 {
|
||||||
border-bottom: solid 2px $accent1;
|
border-bottom: solid 2px $accent1;
|
||||||
color: $accent2;
|
color: $accent2;
|
||||||
font-family: Play, Jura, Exo, Rationale, Quicksand, Average Sans, sans-serif;
|
font-family: Play, Jura, Exo, Rationale, Quicksand, Average Sans, sans-serif;
|
||||||
@ -131,13 +131,19 @@ section {
|
|||||||
.next-action {
|
.next-action {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
|
span { display: none; }
|
||||||
|
|
||||||
|
.action { display: inline-block; }
|
||||||
|
|
||||||
.date, .project {
|
.date, .project {
|
||||||
background: $bgColor2;
|
background: $bgColor2;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
color: $mutedFgColor;
|
color: $mutedFgColor;
|
||||||
|
display: inline-block;
|
||||||
font-size: 66%;
|
font-size: 66%;
|
||||||
|
margin: 0 0.5rem;
|
||||||
max-width: 33%;
|
max-width: 33%;
|
||||||
padding: 0 0.5em;
|
padding: 0 0.5rem;
|
||||||
white-space: nowrap; }
|
white-space: nowrap; }
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
@ -147,12 +153,100 @@ section {
|
|||||||
|
|
||||||
.project:before {
|
.project:before {
|
||||||
content: 'Project: ';
|
content: 'Project: ';
|
||||||
|
display: inline-block;
|
||||||
font-family: Play;
|
font-family: Play;
|
||||||
font-variant: small-caps; }
|
font-variant: small-caps; }
|
||||||
|
|
||||||
.date:before {
|
.date:before {
|
||||||
content: 'Due: ';
|
content: 'Due: ';
|
||||||
|
display: inline-block;
|
||||||
font-family: Play;
|
font-family: Play;
|
||||||
font-variant: small-caps; }
|
font-variant: small-caps; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#config-dialog {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
form {
|
||||||
|
border: solid thin $accent1;
|
||||||
|
background: $bgColor;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-left: 10%;
|
||||||
|
margin-right: 10%;
|
||||||
|
margin-top: 1em;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
position: relative;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 32em;
|
||||||
|
|
||||||
|
.validate-tips { display: block; }
|
||||||
|
|
||||||
|
.wait-overlay {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
padding: 1em 0.5em;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
bottom:0;
|
||||||
|
left: 0;
|
||||||
|
right: 0; }
|
||||||
|
|
||||||
|
.button-panel {
|
||||||
|
padding: 0.5em;
|
||||||
|
text-align: right;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.global-config { float: left; }
|
||||||
|
|
||||||
|
.save-button {
|
||||||
|
border: $accent2 solid thin;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.1em 0.3em; } } }
|
||||||
|
|
||||||
|
.config-section-header { color: $accent2; }
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 6rem; }
|
||||||
|
|
||||||
|
input, select { width: 8rem; }
|
||||||
|
|
||||||
|
.timestamper-config, .gtd-config {
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
width: 15em; }
|
||||||
|
|
||||||
|
.category-name {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 0.2rem;
|
||||||
|
width: 8rem; }
|
||||||
|
|
||||||
|
.remove-button {
|
||||||
|
color: $mutedFgColor;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 2rem;
|
||||||
|
width: 4rem; }
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display-style: block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 } }
|
||||||
|
}
|
73
src/www/index.html
Normal file
73
src/www/index.html
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>What I am Doing</title>
|
||||||
|
<link rel="stylesheet" href="css/personal-display.css" type="text/css">
|
||||||
|
<link href='//fonts.googleapis.com/css?family=Play|Advent+Pro' rel='stylesheet' type='text/css'>
|
||||||
|
|
||||||
|
<!-- PROD Libraries
|
||||||
|
-->
|
||||||
|
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.10.0/jquery.min.js" defer></script>
|
||||||
|
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js" defer></script>
|
||||||
|
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js" defer></script>
|
||||||
|
|
||||||
|
<!-- DEV Libraries
|
||||||
|
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.10.0/jquery.js" defer></script>
|
||||||
|
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore.js" defer></script>
|
||||||
|
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone.js" defer></script>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script src="js/personal-display.js" type="text/javascript" defer></script>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id=current-task>
|
||||||
|
<h3>Current Activity</h3>
|
||||||
|
<span class=task>Loading...</span>
|
||||||
|
<div class=task-notes></div>
|
||||||
|
</section>
|
||||||
|
<section id=priorities>
|
||||||
|
<h3>Next Actions (unsorted)</h3>
|
||||||
|
</section>
|
||||||
|
<section id=config-dialog>
|
||||||
|
<form>
|
||||||
|
<h3>Configuration</h3>
|
||||||
|
<span class=validate-tips></span>
|
||||||
|
<section class=timestamper-config>
|
||||||
|
<span class=config-section-header>TimeStamper</span>
|
||||||
|
<div><label>Server Name: </label>
|
||||||
|
<input type=text class=host></div>
|
||||||
|
<div><label>Username: </label>
|
||||||
|
<input type=text class=username></div>
|
||||||
|
<div><label>Password: </label>
|
||||||
|
<input type=password class=password></div>
|
||||||
|
<div><label>Timeline: </label>
|
||||||
|
<select class=timeline>
|
||||||
|
<option value="none">None</option>
|
||||||
|
</select></div>
|
||||||
|
</section>
|
||||||
|
<section class=gtd-config>
|
||||||
|
<span class=config-section-header>Getting Things Done</span>
|
||||||
|
<div><label>Server Name: </label>
|
||||||
|
<input type=text class=host></div>
|
||||||
|
<div><label>Username: </label>
|
||||||
|
<input type=text class=username></div>
|
||||||
|
<div><label>Password: </label>
|
||||||
|
<input type=password class=password></div>
|
||||||
|
<ul> <li><label>Context: </label>
|
||||||
|
<select class=category>
|
||||||
|
<option class=default-option value="none">Add a category...</option>
|
||||||
|
</select></li> </ul>
|
||||||
|
</section>
|
||||||
|
<div class=button-panel>
|
||||||
|
<div class=global-config>
|
||||||
|
<label>Refresh (sec): </label>
|
||||||
|
<input type=text class=refresh></div>
|
||||||
|
<div class=save-button><a href="#">Save and Close</a></div>
|
||||||
|
</div>
|
||||||
|
<div class=wait-overlay><img src="img/loading-spinner.gif"><br><span></span></div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
</html>
|
499
src/www/js/personal-display.js
Normal file
499
src/www/js/personal-display.js
Normal file
@ -0,0 +1,499 @@
|
|||||||
|
(function() {
|
||||||
|
var root = this;
|
||||||
|
|
||||||
|
var PD = root.PersonalDisplay = {};
|
||||||
|
|
||||||
|
PD.hasHTML5LocalStorage = function() {
|
||||||
|
try {
|
||||||
|
return 'localStorage' in window && window['localStorage'] !== null; }
|
||||||
|
catch (e) { return false; } }
|
||||||
|
|
||||||
|
/// ## Models
|
||||||
|
|
||||||
|
PD.TimelineMarkModel = Backbone.Model.extend({
|
||||||
|
initialize: function() { _.bindAll(this, "equals"); },
|
||||||
|
equals: function(that) { return this.id == that.id; }
|
||||||
|
});
|
||||||
|
|
||||||
|
PD.GTDEntryModel = Backbone.Model.extend({
|
||||||
|
initialize: function() { _.bindAll(this, "equals"); },
|
||||||
|
equals: function(that) { return this.id == that.id; }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ## Views
|
||||||
|
|
||||||
|
PD.CurrentActivityView = Backbone.View.extend({
|
||||||
|
el: $("#current-task"),
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
_.bindAll(this, "render");
|
||||||
|
|
||||||
|
this.model.on('change', this.render, this); },
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
this.$el.find(".task").text(this.model.get("mark"));
|
||||||
|
this.$el.find(".task-notes").text(this.model.get("notes"));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
PD.GTDNextActionView = Backbone.View.extend({
|
||||||
|
className: "next-action",
|
||||||
|
tagName: "div",
|
||||||
|
ignoredProperties: ["action", "details", "id"],
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
_.bindAll(this, "render");
|
||||||
|
$("#priorities").append(this.el);
|
||||||
|
|
||||||
|
this.model.on('change', this.render, this); },
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var elements = [];
|
||||||
|
|
||||||
|
// Look for the "action" property first
|
||||||
|
var actionEl = $(document.createElement("span"))
|
||||||
|
.addClass("action")
|
||||||
|
actionEl.text(this.model.get("action").toString());
|
||||||
|
elements.push(actionEl);
|
||||||
|
|
||||||
|
// Add span elements for each of the other attributes.
|
||||||
|
_.each(this.model.attributes, function(val, key) {
|
||||||
|
if (!_.contains(this.ignoredProperties, key)) {
|
||||||
|
var el = $(document.createElement("span"))
|
||||||
|
.addClass(key.toString());
|
||||||
|
el.text(val);
|
||||||
|
elements.push(el); } }, this);
|
||||||
|
|
||||||
|
// Finally, look for the "details" property
|
||||||
|
if (this.model.get("details")) {
|
||||||
|
var detailEl = $(document.createElement("span"))
|
||||||
|
.addClass("details");
|
||||||
|
detailEl.text(this.model.get("details").toString());
|
||||||
|
elements.push(detailEl); }
|
||||||
|
|
||||||
|
// Clear the old data and add our new elements in order
|
||||||
|
this.$el.empty();
|
||||||
|
_.each(elements, function(el) { this.$el.append(el); }, this);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
PD.GTDNextActionCollection = Backbone.Collection.extend({
|
||||||
|
model: PD.GTDEntryModel,
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
_.bindAll(this, "addNextActionView", "removeNextActionView");
|
||||||
|
this.views = {};
|
||||||
|
this.on('add', this.addNextActionView);
|
||||||
|
this.on('remove', this.removeNextActionView); },
|
||||||
|
|
||||||
|
addNextActionView: function(entryModel) {
|
||||||
|
var view = new PD.GTDNextActionView({model: entryModel});
|
||||||
|
view.render();
|
||||||
|
this.views[entryModel.get("id")] = view; },
|
||||||
|
|
||||||
|
removeNextActionView: function(entryModel) {
|
||||||
|
var view = this.views[entryModel.get("id")];
|
||||||
|
view.$el.remove(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
PD.ConfigDialog = Backbone.View.extend({
|
||||||
|
el: $("#config-dialog"),
|
||||||
|
|
||||||
|
events: {
|
||||||
|
"blur .timestamper-config .password" : "tsLogin",
|
||||||
|
"blur .gtd-config .password" : "gtdLogin",
|
||||||
|
|
||||||
|
"change .gtd-config .category" : "addCategory",
|
||||||
|
"click .remove-button" : "removeCategory",
|
||||||
|
|
||||||
|
"click .save-button" : "saveAndClose" },
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
_.bindAll(this, "show", "hide", "tsLogin", "gtdLogin",
|
||||||
|
"loadTsData", "loadGtdData", "addCategory", "makeCategoryItem",
|
||||||
|
"removeCategory", "saveAndClose"); },
|
||||||
|
|
||||||
|
show: function() {
|
||||||
|
var $tsSection = this.$el.find(".timestamper-config");
|
||||||
|
var $gtdSection = this.$el.find(".gtd-config");
|
||||||
|
|
||||||
|
// Load TimeStamper configuration values.
|
||||||
|
if (PD.tsCfg) {
|
||||||
|
$tsSection.find(".username").val(PD.tsCfg.username);
|
||||||
|
$tsSection.find(".password").val(PD.tsCfg.password);
|
||||||
|
$tsSection.find(".host").val(PD.tsCfg.host);
|
||||||
|
|
||||||
|
if (PD.tsAuth) {
|
||||||
|
this.loadTsData(PD.tsCfg.host, PD.tsCfg.username); }
|
||||||
|
|
||||||
|
this.$el.find('.timeline').val(PD.tsCfg.timelineId); }
|
||||||
|
|
||||||
|
// Or suggest a default server.
|
||||||
|
else { $tsSection.find(".host").val("timestamper.jdb-labs.com"); }
|
||||||
|
|
||||||
|
// Load GTD configuration values.
|
||||||
|
if (PD.gtdCfg) {
|
||||||
|
$gtdSection.find(".username").val(PD.gtdCfg.username);
|
||||||
|
$gtdSection.find(".password").val(PD.gtdCfg.password);
|
||||||
|
$gtdSection.find(".host").val(PD.gtdCfg.host);
|
||||||
|
|
||||||
|
if (PD.gtdAuth) { this.loadGtdData(PD.gtdCfg.host); }
|
||||||
|
|
||||||
|
// Create the items for the selected categories
|
||||||
|
$(".category-name").parent().remove();
|
||||||
|
_.forEach(PD.gtdCfg.categories, this.makeCategoryItem); }
|
||||||
|
|
||||||
|
this.$el.find('.refresh').val(
|
||||||
|
PD.refreshPeriod ? PD.refreshPeriod / 1000 : 15);
|
||||||
|
|
||||||
|
this.$el.fadeIn(); },
|
||||||
|
|
||||||
|
hide: function() { this.$el.fadeOut(); },
|
||||||
|
|
||||||
|
tsLogin: function() {
|
||||||
|
var username = this.$el.find(".timestamper-config .username").val();
|
||||||
|
var password = this.$el.find(".timestamper-config .password").val();
|
||||||
|
var host = this.$el.find(".timestamper-config .host").val();
|
||||||
|
|
||||||
|
if (!PD.tsCfg) { PD.tsCfg = {}; }
|
||||||
|
|
||||||
|
// Hide the configuration dialog.
|
||||||
|
this.$el.find(".wait-overlay span").text("Connecting to " + host);
|
||||||
|
this.$el.find(".wait-overlay").fadeIn();
|
||||||
|
|
||||||
|
// Try to log in to the TimeStamper service.
|
||||||
|
$.ajax({
|
||||||
|
url: "https://" + host + "/ts_api/login",
|
||||||
|
xhrFields: { withCredentials: true },
|
||||||
|
processData: false,
|
||||||
|
type: 'POST',
|
||||||
|
async: false,
|
||||||
|
|
||||||
|
data: JSON.stringify(
|
||||||
|
{"username": username, "password": password}),
|
||||||
|
|
||||||
|
error: function(jqXHR, textStatus, error) {
|
||||||
|
if (jqXHR.status == 401) { $(".validate-tips")
|
||||||
|
.text("Invalid username/password combination for " +
|
||||||
|
"the TimeStamper service."); }
|
||||||
|
else { $(".validate-tips").text("There was an error " +
|
||||||
|
"trying to log into the TimeStamper service: " +
|
||||||
|
error); }
|
||||||
|
PD.tsAuth = false; },
|
||||||
|
|
||||||
|
success: function(data, textStatus, jqXHR) {
|
||||||
|
PD.tsAuth = true;
|
||||||
|
|
||||||
|
$(".validate-tips").text("");
|
||||||
|
|
||||||
|
// Load the user's timelines.
|
||||||
|
PD.configDialog.loadTsData(host, username);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Success or failure we hide the wait overlay.
|
||||||
|
this.$el.find(".wait-overlay").fadeOut();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
gtdLogin: function() {
|
||||||
|
var username = this.$el.find(".gtd-config .username").val();
|
||||||
|
var password = this.$el.find(".gtd-config .password").val();
|
||||||
|
var host = this.$el.find(".gtd-config .host").val();
|
||||||
|
|
||||||
|
if (!PD.gtdCfg) { PD.gtdCfg = {}; }
|
||||||
|
|
||||||
|
// Hide the configuration dialog.
|
||||||
|
this.$el.find(".wait-overlay span").text("Connecting to " + host);
|
||||||
|
this.$el.find(".wait-overlay").fadeIn();
|
||||||
|
|
||||||
|
// Try to log in to the GTD service.
|
||||||
|
$.ajax({
|
||||||
|
url: "http://" + host + "/gtd/login",
|
||||||
|
xhrFields: { withCredentials: true },
|
||||||
|
processData: false,
|
||||||
|
type: 'POST',
|
||||||
|
async: false,
|
||||||
|
|
||||||
|
data: JSON.stringify(
|
||||||
|
{"username": username, "password": password}),
|
||||||
|
|
||||||
|
error: function(jqXHR, textStatus, error) {
|
||||||
|
if (jqXHR.status == 401) { $(".validate-tips")
|
||||||
|
.text("Invalid username/password combination for " +
|
||||||
|
"the Getting Things Done service."); }
|
||||||
|
else { $(".validate-tips").text("There was an error " +
|
||||||
|
"trying to log into the Getting Things Done service: " +
|
||||||
|
error); }
|
||||||
|
PD.gtdAuth = false; },
|
||||||
|
|
||||||
|
success: function(data, textStatus, jqXHR) {
|
||||||
|
PD.gtdAuth = true;
|
||||||
|
|
||||||
|
$(".validate-tips").text("");
|
||||||
|
|
||||||
|
PD.configDialog.loadGtdData(host); }
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$el.find(".wait-overlay").fadeOut();
|
||||||
|
},
|
||||||
|
|
||||||
|
loadTsData: function(host, username) {
|
||||||
|
// (Re)load the user's timelines.
|
||||||
|
PD.tsCfg.timelines = JSON.parse($.ajax({
|
||||||
|
url: 'https://' + host + '/ts_api/timelines/' + username,
|
||||||
|
xhrFields: { withCredentials: true },
|
||||||
|
async: false}).responseText);
|
||||||
|
|
||||||
|
// Populate the available timelines list.
|
||||||
|
var $timelineSelectEl = this.$el.find(".timestamper-config .timeline");
|
||||||
|
$timelineSelectEl.empty();
|
||||||
|
_.forEach(PD.tsCfg.timelines, function(timeline) {
|
||||||
|
var $optionEl = $(document.createElement("option"));
|
||||||
|
$optionEl.attr("value", timeline.id);
|
||||||
|
$optionEl.text(timeline.description);
|
||||||
|
$timelineSelectEl.append($optionEl); }); },
|
||||||
|
|
||||||
|
loadGtdData: function(host) {
|
||||||
|
// Load the user's contexts
|
||||||
|
PD.gtdCfg.contexts = JSON.parse($.ajax({
|
||||||
|
url: 'http://' + host + '/gtd/contexts',
|
||||||
|
xhrFields: { withCredentials: true },
|
||||||
|
async: false }).responseText);
|
||||||
|
|
||||||
|
// Load the user's projects
|
||||||
|
PD.gtdCfg.projects = JSON.parse($.ajax({
|
||||||
|
url: 'http://' + host + '/gtd/projects',
|
||||||
|
xhrFields: { withCredentials: true },
|
||||||
|
async: false }).responseText);
|
||||||
|
|
||||||
|
// Populate the available contexts and projects drop-down.
|
||||||
|
var $categorySelectEl = $(".gtd-config .category")
|
||||||
|
$categorySelectEl.empty();
|
||||||
|
$categorySelectEl.append(
|
||||||
|
"<option class=default-option value='none'>" +
|
||||||
|
"Add a category...</option>");
|
||||||
|
_.forEach(PD.gtdCfg.contexts.concat(PD.gtdCfg.projects),
|
||||||
|
function(category) {
|
||||||
|
var $optionEl = $(document.createElement("option"));
|
||||||
|
$optionEl.attr("value", category.id);
|
||||||
|
$optionEl.text(category.id);
|
||||||
|
$categorySelectEl.append($optionEl); });
|
||||||
|
$categorySelectEl[0].selectedIndex = 0; },
|
||||||
|
|
||||||
|
makeCategoryItem: function(catName) {
|
||||||
|
var $liEl = $(
|
||||||
|
"<li class><span class=remove-button>remove</span>" +
|
||||||
|
"<span class=category-name></span></li>");
|
||||||
|
$liEl.find('.category-name').text(catName);
|
||||||
|
this.$el.find(".gtd-config ul").append($liEl); },
|
||||||
|
|
||||||
|
addCategory: function(source) {
|
||||||
|
var selectEl = source.target;
|
||||||
|
var $selectEl = $(selectEl);
|
||||||
|
if (selectEl.selectedIndex == 0) { return; }
|
||||||
|
this.makeCategoryItem($selectEl.val());
|
||||||
|
selectEl.selectedIndex = 0; },
|
||||||
|
|
||||||
|
removeCategory: function(source) {
|
||||||
|
$(source.target).parent().remove(); },
|
||||||
|
|
||||||
|
saveAndClose: function() {
|
||||||
|
if (!PD.tsCfg) { PD.tsCfg = {}; }
|
||||||
|
if (!PD.gtdCfg) { PD.gtdCfg = {}; }
|
||||||
|
|
||||||
|
// Save TimeStamper configuration.
|
||||||
|
var $tsEl = this.$el.find(".timestamper-config");
|
||||||
|
PD.tsCfg.host = $tsEl.find(".host").val();
|
||||||
|
PD.tsCfg.username = $tsEl.find(".username").val();
|
||||||
|
PD.tsCfg.password = $tsEl.find(".password").val();
|
||||||
|
PD.tsCfg.timelineId = $tsEl.find(".timeline").val();
|
||||||
|
|
||||||
|
// Save Getting Things Done configuration.
|
||||||
|
var $gtdEl = this.$el.find(".gtd-config");
|
||||||
|
PD.gtdCfg.host = $gtdEl.find(".host").val();
|
||||||
|
PD.gtdCfg.username = $gtdEl.find(".username").val();
|
||||||
|
PD.gtdCfg.password = $gtdEl.find(".password").val();
|
||||||
|
PD.gtdCfg.categories = _.map(
|
||||||
|
this.$el.find(".category-name"),
|
||||||
|
function(span) { return $(span).text(); });
|
||||||
|
|
||||||
|
// Save global data
|
||||||
|
PD.refreshPeriod = parseInt(this.$el.find(".refresh").val()) * 1000;
|
||||||
|
|
||||||
|
if (PD.hasHTML5LocalStorage()) {
|
||||||
|
localStorage.setItem("tsCfg", JSON.stringify(PD.tsCfg));
|
||||||
|
localStorage.setItem("gtdCfg", JSON.stringify(PD.gtdCfg));
|
||||||
|
localStorage.setItem("refreshPeriod",
|
||||||
|
JSON.stringify(PD.refreshPeriod)); }
|
||||||
|
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
PD.Main = Backbone.View.extend({
|
||||||
|
el: $("body"),
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
|
||||||
|
_.bindAll(this, "refresh");
|
||||||
|
|
||||||
|
// Create our config dialog view.
|
||||||
|
PD.configDialog = new PD.ConfigDialog();
|
||||||
|
|
||||||
|
// Create our initial models and views.
|
||||||
|
PD.currentActivityModel = new PD.TimelineMarkModel({});
|
||||||
|
PD.currentActivityView = new PD.CurrentActivityView(
|
||||||
|
{model: PD.currentActivityModel})
|
||||||
|
|
||||||
|
// Test for localStorage support
|
||||||
|
if (!PD.hasHTML5LocalStorage()) {
|
||||||
|
alert("Your browser does not support HTML5 localStorage." +
|
||||||
|
"Without this I cannot store your preferences.");
|
||||||
|
PD.configDialog.show(); }
|
||||||
|
else {
|
||||||
|
PD.tsCfg = JSON.parse(localStorage.getItem('tsCfg'));
|
||||||
|
PD.gtdCfg = JSON.parse(localStorage.getItem('gtdCfg'));
|
||||||
|
PD.refreshPeriod = JSON.parse(
|
||||||
|
localStorage.getItem('refreshPeriod')); }
|
||||||
|
|
||||||
|
PD.gtdNextActionCollection = new PD.GTDNextActionCollection();
|
||||||
|
|
||||||
|
// Perform the initial refresh.
|
||||||
|
this.refresh();
|
||||||
|
|
||||||
|
// Schedule future refreshes.
|
||||||
|
setInterval(this.refresh, PD.refreshPeriod ? PD.refreshPeriod : 15000);
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh: function() {
|
||||||
|
// If the dialog is still open we skip this sync to give the user
|
||||||
|
// a chance to finish configuration.
|
||||||
|
if ($("#config-dialog").is(":visible")) { return; }
|
||||||
|
|
||||||
|
// Otherwise, if we do not have configuration information, open the
|
||||||
|
// dialog so the user can enter it.
|
||||||
|
if (!(PD.tsCfg && PD.gtdCfg)) { PD.configDialog.show(); return; }
|
||||||
|
|
||||||
|
// Check that we are authenticated to the services we need. Try to
|
||||||
|
// authenticate if we are not.
|
||||||
|
if (!PD.tsAuth) {
|
||||||
|
$.ajax({
|
||||||
|
url: "https://" + PD.tsCfg.host + "/ts_api/login",
|
||||||
|
xhrFields: { withCredentials: true },
|
||||||
|
processData: false,
|
||||||
|
type: "POST",
|
||||||
|
async: false,
|
||||||
|
|
||||||
|
data: JSON.stringify(
|
||||||
|
{ "username": PD.tsCfg.username,
|
||||||
|
"password": PD.tsCfg.password }),
|
||||||
|
|
||||||
|
error: function(jqXHR, textStatus, error) {
|
||||||
|
// TODO: Handle error.
|
||||||
|
PD.tsAuth=false;
|
||||||
|
alert("Unable to authenticate to the TimeStamper " +
|
||||||
|
"service: " + error);
|
||||||
|
PD.configDialog.show(); },
|
||||||
|
|
||||||
|
success: function(data, textStatus, jqXHR) {
|
||||||
|
PD.tsAuth = true; }}); }
|
||||||
|
|
||||||
|
if (!PD.gtdAuth) {
|
||||||
|
$.ajax({
|
||||||
|
url: "http://" + PD.gtdCfg.host + "/gtd/login",
|
||||||
|
xhrFields: { withCredentials: true },
|
||||||
|
processData: false,
|
||||||
|
type: "POST",
|
||||||
|
async: false,
|
||||||
|
|
||||||
|
data: JSON.stringify(
|
||||||
|
{ "username": PD.gtdCfg.username,
|
||||||
|
"password": PD.gtdCfg.password }),
|
||||||
|
|
||||||
|
error: function(jqXHR, textStatus, error) {
|
||||||
|
// TODO: Handle error.
|
||||||
|
PD.gtdAuth=false;
|
||||||
|
alert("Unable to authenticate to the GTD service: " +
|
||||||
|
error);
|
||||||
|
PD.configDialog.show(); },
|
||||||
|
|
||||||
|
success: function(data, textStatus, jqXHR) {
|
||||||
|
PD.gtdAuth = true; }}); }
|
||||||
|
|
||||||
|
|
||||||
|
// Check that we have successfully authenticated to both services.
|
||||||
|
// If we are not, we will skip this refresh.
|
||||||
|
if (!(PD.tsAuth && PD.gtdAuth)) { return; }
|
||||||
|
|
||||||
|
// Get the latest timestamp from the TimeStamper service.
|
||||||
|
$.ajax({
|
||||||
|
url: "https://" + PD.tsCfg.host + "/ts_api/entries/" +
|
||||||
|
PD.tsCfg.username + "/" + PD.tsCfg.timelineId,
|
||||||
|
xhrFields: { withCredentials: true },
|
||||||
|
data: {"order": "asc" },
|
||||||
|
dataType: 'json',
|
||||||
|
type: 'GET',
|
||||||
|
async: true,
|
||||||
|
|
||||||
|
error: function(jqXHR, textStatus, errorText) {
|
||||||
|
if (jqXHR.status == 401) { PD.tsAuth = false; }
|
||||||
|
else {
|
||||||
|
alert("Unable to retrieve current timestamp: " + errorText);
|
||||||
|
PD.configDialog.show(); }
|
||||||
|
},
|
||||||
|
|
||||||
|
success: function(data, textStatus, jqXHR) {
|
||||||
|
PD.currentActivityModel.set(data[0]); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the list of GTD entries for each of our categories.
|
||||||
|
var categories = _.reduce(
|
||||||
|
PD.gtdCfg.categories,
|
||||||
|
function(acc, cat) { return acc ? acc + "," + cat : cat; }, "");
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "http://" + PD.gtdCfg.host + "/gtd/next-actions/" +
|
||||||
|
categories,
|
||||||
|
xhrFields: { withCredentials: true },
|
||||||
|
dataType: 'json',
|
||||||
|
type: 'GET',
|
||||||
|
async: true,
|
||||||
|
|
||||||
|
error: function(jqXHR, textStatus, errorText) {
|
||||||
|
if (jqXHR.status == 401) { PD.gtdAtuh = false; }
|
||||||
|
else if (jqXHR.status == 500) { return; }
|
||||||
|
else {
|
||||||
|
alert("Unable to retrieve next actions: " + errorText);
|
||||||
|
PD.configDialog.show(); }
|
||||||
|
},
|
||||||
|
|
||||||
|
success: function(data, textStatus, jqXHR) {
|
||||||
|
var collection = PD.gtdNextActionCollection;
|
||||||
|
|
||||||
|
// Add all the retrieved items to the collection.
|
||||||
|
_.forEach(data, function(actionAttr) {
|
||||||
|
|
||||||
|
// Try to find this entry in out collection.
|
||||||
|
var model = collection.get(actionAttr.id);
|
||||||
|
// Update it if found
|
||||||
|
if (model) { model.set(actionAttr); }
|
||||||
|
// Insert a new model if not found.
|
||||||
|
else { collection.add(
|
||||||
|
new PD.GTDEntryModel(actionAttr)); }});
|
||||||
|
|
||||||
|
// Look through our collection for entries that are no
|
||||||
|
// longer in our retrieved data and remove them.
|
||||||
|
collection.forEach(function(model) {
|
||||||
|
if (!_.any(data, model.equals)) {
|
||||||
|
collection.remove(model); }});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
PD.main = new PD.Main();
|
||||||
|
}).call(this);
|
Loading…
x
Reference in New Issue
Block a user