Use url query params to trak state in the app

This commit is contained in:
Tankred Hase 2014-12-04 12:46:55 +01:00
parent 95cbe0ea11
commit 4622ebd8d9
7 changed files with 123 additions and 133 deletions

View File

@ -35,8 +35,7 @@ var axe = require('axe-logger'),
WriteCtrl = require('./controller/app/write'), WriteCtrl = require('./controller/app/write'),
NavigationCtrl = require('./controller/app/navigation'), NavigationCtrl = require('./controller/app/navigation'),
ActionBarCtrl = require('./controller/app/action-bar'), ActionBarCtrl = require('./controller/app/action-bar'),
StatusDisplayCtrl = require('./controller/app/status-display'), StatusDisplayCtrl = require('./controller/app/status-display');
backButtonUtil = require('./util/backbutton-handler');
// include angular modules // include angular modules
require('./app-config'); require('./app-config');
@ -109,24 +108,11 @@ app.config(function($routeProvider, $animateProvider) {
templateUrl: 'tpl/login-privatekey-download.html', templateUrl: 'tpl/login-privatekey-download.html',
controller: LoginPrivateKeyDownloadCtrl controller: LoginPrivateKeyDownloadCtrl
}); });
$routeProvider.when('/account', {
//
// main app routes
//
var accountRoute = {
templateUrl: 'tpl/desktop.html', templateUrl: 'tpl/desktop.html',
controller: NavigationCtrl controller: NavigationCtrl,
}; reloadOnSearch: false // don't reload controllers in main app when query params change
});
$routeProvider.when('/account', accountRoute);
$routeProvider.when('/account/:folderIndex', accountRoute);
$routeProvider.when('/account/:folderIndex/:uid', accountRoute);
//
// Default route
//
$routeProvider.otherwise({ $routeProvider.otherwise({
redirectTo: '/login' redirectTo: '/login'
}); });
@ -138,10 +124,6 @@ app.config(function($routeProvider, $animateProvider) {
app.run(function($rootScope) { app.run(function($rootScope) {
// global state... inherited to all child scopes // global state... inherited to all child scopes
$rootScope.state = {}; $rootScope.state = {};
// attach the back button handler to the root scope
backButtonUtil.attachHandler($rootScope);
// attach fastclick // attach fastclick
FastClick.attach(document.body); FastClick.attach(document.body);
}); });

View File

@ -15,7 +15,7 @@ var ActionBarCtrl = function($scope, email, dialog, statusDisplay) {
} }
// close read state // close read state
$scope.state.read.open = false; $scope.state.read.toggle(false);
statusDisplay.update('Moving message...'); statusDisplay.update('Moving message...');
@ -76,7 +76,7 @@ var ActionBarCtrl = function($scope, email, dialog, statusDisplay) {
} }
// close read state // close read state
$scope.state.read.open = false; $scope.state.read.toggle(false);
statusDisplay.update('Deleting message...'); statusDisplay.update('Deleting message...');
@ -122,7 +122,7 @@ var ActionBarCtrl = function($scope, email, dialog, statusDisplay) {
// close read state // close read state
if (!keepOpen) { if (!keepOpen) {
$scope.state.read.open = false; $scope.state.read.toggle(false);
} }
var originalState = message.unread; var originalState = message.unread;

View File

@ -1,17 +1,17 @@
'use strict'; 'use strict';
var searchTimeout, firstSelect; var searchTimeout;
// //
// Constants // Constants
// //
var INIT_DISPLAY_LEN = 20, var INIT_DISPLAY_LEN = 50,
SCROLL_DISPLAY_LEN = 10, SCROLL_DISPLAY_LEN = 10,
FOLDER_TYPE_INBOX = 'Inbox', FOLDER_TYPE_INBOX = 'Inbox',
NOTIFICATION_INBOX_TIMEOUT = 5000; NOTIFICATION_INBOX_TIMEOUT = 5000;
var MailListCtrl = function($scope, $timeout, $location, $routeParams, $filter, statusDisplay, notification, email, keychain, dialog, search, dummy) { var MailListCtrl = function($scope, $timeout, $location, $filter, statusDisplay, notification, email, keychain, dialog, search, dummy) {
// //
// Init // Init
@ -24,6 +24,30 @@ var MailListCtrl = function($scope, $timeout, $location, $routeParams, $filter,
*/ */
$scope.pendingNotifications = []; $scope.pendingNotifications = [];
//
// url/history handling
//
/**
* Set the route to a message which will go to read mode
*/
$scope.navigate = function(message) {
$location.search('uid', message.uid);
};
$scope.loc = $location;
$scope.$watch('(loc.search()).uid', function(uid) {
if (typeof uid === 'undefined') {
// no uid specified in url... select no message
$scope.select();
return;
}
// select the message specified by the uid in the url
$scope.select(_.findWhere(currentFolder().messages, {
uid: typeof uid === 'string' ? parseInt(uid, 10) : uid
}));
});
// //
// scope functions // scope functions
// //
@ -50,10 +74,6 @@ var MailListCtrl = function($scope, $timeout, $location, $routeParams, $filter,
}); });
}; };
$scope.navigate = function(message) {
$location.path('/account/' + $routeParams.folderIndex + '/' + message.uid);
};
/** /**
* Called when clicking on an message list item * Called when clicking on an message list item
*/ */
@ -66,11 +86,10 @@ var MailListCtrl = function($scope, $timeout, $location, $routeParams, $filter,
$scope.state.mailList.selected = message; $scope.state.mailList.selected = message;
if (!firstSelect) { if ($location.search().dev) {
// only toggle to read view on 2nd select in mobile mode // stop here in dev mode
$scope.state.read.toggle(true); return;
} }
firstSelect = false;
keychain.refreshKeyForUserId({ keychain.refreshKeyForUserId({
userId: message.from[0].address userId: message.from[0].address
@ -102,11 +121,6 @@ var MailListCtrl = function($scope, $timeout, $location, $routeParams, $filter,
} }
}; };
var selectedMessage = _.findWhere(currentFolder().messages, {
uid: $routeParams.uid
});
$scope.select(selectedMessage);
$scope.flag = function(message, flagged) { $scope.flag = function(message, flagged) {
$scope.state.actionBar.flagMessage(message, flagged); $scope.state.actionBar.flagMessage(message, flagged);
}; };
@ -141,7 +155,7 @@ var MailListCtrl = function($scope, $timeout, $location, $routeParams, $filter,
$scope.searchText = undefined; $scope.searchText = undefined;
// in development, display dummy mail objects // in development, display dummy mail objects
if ($routeParams.dev) { if ($location.search().dev) {
statusDisplay.update('Last update: ', new Date()); statusDisplay.update('Last update: ', new Date());
currentFolder().messages = dummy.listMails(); currentFolder().messages = dummy.listMails();
return; return;
@ -157,15 +171,9 @@ var MailListCtrl = function($scope, $timeout, $location, $routeParams, $filter,
} }
// sort message by uid // sort message by uid
currentFolder().messages.sort(byUidDescending); messages.sort(byUidDescending);
// set display buffer to first messages // set display buffer to first messages
$scope.displayMessages = currentFolder().messages.slice(0, INIT_DISPLAY_LEN); $scope.displayMessages = messages.slice(0, INIT_DISPLAY_LEN);
// Shows the next message based on the uid of the currently selected element
if (currentFolder().messages.indexOf(currentMessage()) === -1) {
firstSelect = true; // reset first selection
$scope.navigate($scope.displayMessages[0]);
}
}); });
/** /**
@ -300,16 +308,12 @@ var MailListCtrl = function($scope, $timeout, $location, $routeParams, $filter,
title: title, title: title,
message: message, message: message,
onClick: function() { onClick: function() {
// force toggle into read mode when notification is clicked
firstSelect = false;
// remove from pending notificatiosn // remove from pending notificatiosn
var index = $scope.pendingNotifications.indexOf(note); var index = $scope.pendingNotifications.indexOf(note);
if (index !== -1) { if (index !== -1) {
$scope.pendingNotifications.splice(index, 1); $scope.pendingNotifications.splice(index, 1);
} }
// open the message
// mark message as read
$scope.navigate(_.findWhere(currentFolder().messages, { $scope.navigate(_.findWhere(currentFolder().messages, {
uid: unreadMsgs[0].uid uid: unreadMsgs[0].uid
})); }));

View File

@ -1,7 +1,5 @@
'use strict'; 'use strict';
var backBtnHandler = require('../../util/backbutton-handler');
// //
// Constants // Constants
// //
@ -13,21 +11,17 @@ var NOTIFICATION_SENT_TIMEOUT = 2000;
// Controller // Controller
// //
var NavigationCtrl = function($scope, $routeParams, $location, account, email, outbox, notification, appConfig, dialog, dummy) { var NavigationCtrl = function($scope, $location, account, email, outbox, notification, appConfig, dialog, dummy) {
if (!$routeParams.dev && !account.isLoggedIn()) { if (!$location.search().dev && !account.isLoggedIn()) {
$location.path('/'); // init app $location.path('/'); // init app
return; return;
} else if (!$routeParams.folderIndex) {
$location.path('/account/0'); // navigate to default account's inbox by default
return;
} }
var folderIndex = $routeParams.folderIndex, var str = appConfig.string,
str = appConfig.string,
config = appConfig.config; config = appConfig.config;
// //
// scope functions // scope state
// //
$scope.state.nav = { $scope.state.nav = {
@ -37,6 +31,36 @@ var NavigationCtrl = function($scope, $routeParams, $location, account, email, o
} }
}; };
//
// url/history handling
//
$scope.loc = $location;
// nav open/close state url watcher
$scope.$watch('(loc.search()).nav', function(open) {
// synchronize the url to the scope state
$scope.state.nav.toggle(!!open);
});
$scope.$watch('state.nav.open', function(value) {
// synchronize the scope state to the url
$location.search('nav', value ? true : null);
});
// lightbox state url watcher
$scope.$watch('(loc.search()).lightbox', function(value) {
// synchronize the url to the scope state
$scope.state.lightbox = (value) ? value : undefined;
});
$scope.$watch('state.lightbox', function(value) {
// synchronize the scope state to the url
$location.search('lightbox', value ? value : null);
});
//
// scope functions
//
$scope.openFolder = function(folder) { $scope.openFolder = function(folder) {
$scope.state.nav.currentFolder = folder; $scope.state.nav.currentFolder = folder;
$scope.state.nav.toggle(false); $scope.state.nav.toggle(false);
@ -76,16 +100,22 @@ var NavigationCtrl = function($scope, $routeParams, $location, account, email, o
// Start // Start
// //
// handle back button
backBtnHandler.start();
// init folders // init folders
initializeFolders(); initializeFolders();
// select current folder on init // folder index url watcher
$scope.$watch('(loc.search()).folder', function(folderIndex) {
if (typeof folderIndex === 'undefined') {
$location.search('folder', 0); // navigate to inbox by default
return;
}
// select current folder
folderIndex = typeof folderIndex === 'string' ? parseInt(folderIndex, 10) : folderIndex;
if ($scope.account.folders && $scope.account.folders.length > folderIndex) { if ($scope.account.folders && $scope.account.folders.length > folderIndex) {
// navigate to the selected folder index // navigate to the selected folder index
$scope.openFolder($scope.account.folders[folderIndex]); $scope.openFolder($scope.account.folders[folderIndex]);
} }
});
// connect imap/smtp clients on first startup // connect imap/smtp clients on first startup
account.onConnect(function(err) { account.onConnect(function(err) {
@ -107,7 +137,7 @@ var NavigationCtrl = function($scope, $routeParams, $location, account, email, o
function initializeFolders() { function initializeFolders() {
// create dummy folder in dev environment only // create dummy folder in dev environment only
if ($routeParams.dev) { if ($location.search().dev) {
$scope.$root.account = {}; $scope.$root.account = {};
$scope.account.folders = dummy.listFolders(); $scope.account.folders = dummy.listFolders();
return; return;

View File

@ -4,10 +4,14 @@
// Controller // Controller
// //
var ReadCtrl = function($scope, email, invitation, outbox, pgp, keychain, appConfig, download, auth, dialog) { var ReadCtrl = function($scope, $location, email, invitation, outbox, pgp, keychain, appConfig, download, auth, dialog) {
var str = appConfig.string; var str = appConfig.string;
//
// scope state
//
// set default value so that the popover height is correct on init // set default value so that the popover height is correct on init
$scope.keyId = 'No key found.'; $scope.keyId = 'No key found.';
@ -18,7 +22,32 @@ var ReadCtrl = function($scope, email, invitation, outbox, pgp, keychain, appCon
} }
}; };
//
// url/history handling
//
// read state url watcher
$scope.loc = $location;
$scope.$watch('(loc.search()).uid', function(uid) {
// synchronize the url to the scope state
$scope.state.read.toggle(!!uid);
});
$scope.$watch('state.read.open', function(value) {
// close read mode by navigating to folder view
if (!value) {
$location.search('uid', null);
}
});
//
// scope functions
//
$scope.getKeyId = function(address) { $scope.getKeyId = function(address) {
if ($location.search().dev || !address) {
return;
}
$scope.keyId = 'Searching...'; $scope.keyId = 'Searching...';
keychain.getReceiverPublicKey(address, function(err, pubkey) { keychain.getReceiverPublicKey(address, function(err, pubkey) {
if (err) { if (err) {
@ -41,7 +70,7 @@ var ReadCtrl = function($scope, email, invitation, outbox, pgp, keychain, appCon
}; };
$scope.$watch('state.mailList.selected', function(mail) { $scope.$watch('state.mailList.selected', function(mail) {
if (!mail) { if ($location.search().dev || !mail) {
return; return;
} }

View File

@ -1,55 +0,0 @@
'use strict';
var axe = require('axe-logger'),
DEBUG_TAG = 'backbutton handler';
/**
* The back button handler introduces meaningful behavior fo rthe back button:
* if there's an open lightbox, close it;
* if the reader is open in mobile mode, close it;
* if the navigation is open, close it;
* if there's nothing else open, shut down the app;
*
* @type {Object}
*/
var backBtnHandler = {
attachHandler: function(scope) {
this.scope = scope;
},
start: function() {
document.addEventListener("backbutton", handleBackButton, false);
},
stop: function() {
document.removeEventListener("backbutton", handleBackButton, false);
}
};
function handleBackButton(event) {
axe.debug(DEBUG_TAG, 'back button pressed');
// this disarms the default behavior which we NEVER want
event.preventDefault();
event.stopPropagation();
if (backBtnHandler.scope.state.lightbox) {
// closes the lightbox (error msgs, writer, ...)
backBtnHandler.scope.state.lightbox = undefined;
axe.debug(DEBUG_TAG, 'lightbox closed');
backBtnHandler.scope.$apply();
} else if (backBtnHandler.scope.state.read && backBtnHandler.scope.state.read.open) {
// closes the reader
backBtnHandler.scope.state.read.toggle(false);
axe.debug(DEBUG_TAG, 'reader closed');
backBtnHandler.scope.$apply();
} else if (backBtnHandler.scope.state.nav && backBtnHandler.scope.state.nav.open) {
// closes the navigation
backBtnHandler.scope.state.nav.toggle(false);
axe.debug(DEBUG_TAG, 'navigation closed');
backBtnHandler.scope.$apply();
} else {
// exits the app
navigator.app.exitApp();
}
}
module.exports = backBtnHandler;

View File

@ -7,7 +7,7 @@
<ul class="nav__folders"> <ul class="nav__folders">
<li ng-repeat="folder in account.folders" ng-if="folder.wellknown" ng-hide="folder.type === 'Outbox' && folder.count < 1" <li ng-repeat="folder in account.folders" ng-if="folder.wellknown" ng-hide="folder.type === 'Outbox' && folder.count < 1"
class="nav__folder" ng-class="{'nav__folder--open': state.nav.currentFolder === folder}"> class="nav__folder" ng-class="{'nav__folder--open': state.nav.currentFolder === folder}">
<a href="#/account/{{$index}}"> <a href="#/account?folder={{$index}}">
<svg ng-if="folder.type === 'Inbox'" role="presentation"> <svg ng-if="folder.type === 'Inbox'" role="presentation">
<use xlink:href="#icon-inbox" /> <use xlink:href="#icon-inbox" />
</svg> </svg>
@ -38,7 +38,7 @@
<ul class="nav__folders"> <ul class="nav__folders">
<li ng-repeat="folder in account.folders" ng-if="!folder.wellknown" <li ng-repeat="folder in account.folders" ng-if="!folder.wellknown"
class="nav__folder" ng-class="{'nav__folder--open': state.nav.currentFolder === folder}"> class="nav__folder" ng-class="{'nav__folder--open': state.nav.currentFolder === folder}">
<a href="#/account/{{$index}}"> <a href="#/account?folder={{$index}}">
<svg role="presentation"><use xlink:href="#icon-folder" /></svg> <svg role="presentation"><use xlink:href="#icon-folder" /></svg>
{{folder.name}} {{folder.name}}
<span ng-show="folder.count > 0" class="nav__counter">{{folder.count}}</span> <span ng-show="folder.count > 0" class="nav__counter">{{folder.count}}</span>