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;
|
||||
margin: 0.5rem 1rem; }
|
||||
|
||||
section {
|
||||
body > section {
|
||||
padding: 0.2rem;
|
||||
|
||||
& > h3 {
|
||||
h3 {
|
||||
border-bottom: solid 2px $accent1;
|
||||
color: $accent2;
|
||||
font-family: Play, Jura, Exo, Rationale, Quicksand, Average Sans, sans-serif;
|
||||
@ -131,13 +131,19 @@ section {
|
||||
.next-action {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
span { display: none; }
|
||||
|
||||
.action { display: inline-block; }
|
||||
|
||||
.date, .project {
|
||||
background: $bgColor2;
|
||||
border-radius: 5px;
|
||||
color: $mutedFgColor;
|
||||
display: inline-block;
|
||||
font-size: 66%;
|
||||
margin: 0 0.5rem;
|
||||
max-width: 33%;
|
||||
padding: 0 0.5em;
|
||||
padding: 0 0.5rem;
|
||||
white-space: nowrap; }
|
||||
|
||||
.details {
|
||||
@ -147,12 +153,100 @@ section {
|
||||
|
||||
.project:before {
|
||||
content: 'Project: ';
|
||||
display: inline-block;
|
||||
font-family: Play;
|
||||
font-variant: small-caps; }
|
||||
|
||||
.date:before {
|
||||
content: 'Due: ';
|
||||
display: inline-block;
|
||||
font-family: Play;
|
||||
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