From 4622ebd8d9eee226013ec63e285c608639c9659a Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Thu, 4 Dec 2014 12:46:55 +0100 Subject: [PATCH] Use url query params to trak state in the app --- src/js/app.js | 28 +++--------- src/js/controller/app/action-bar.js | 6 +-- src/js/controller/app/mail-list.js | 64 +++++++++++++++------------- src/js/controller/app/navigation.js | 66 +++++++++++++++++++++-------- src/js/controller/app/read.js | 33 ++++++++++++++- src/js/util/backbutton-handler.js | 55 ------------------------ src/tpl/nav.html | 4 +- 7 files changed, 123 insertions(+), 133 deletions(-) delete mode 100644 src/js/util/backbutton-handler.js diff --git a/src/js/app.js b/src/js/app.js index b519202..fb314a0 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -35,8 +35,7 @@ var axe = require('axe-logger'), WriteCtrl = require('./controller/app/write'), NavigationCtrl = require('./controller/app/navigation'), ActionBarCtrl = require('./controller/app/action-bar'), - StatusDisplayCtrl = require('./controller/app/status-display'), - backButtonUtil = require('./util/backbutton-handler'); + StatusDisplayCtrl = require('./controller/app/status-display'); // include angular modules require('./app-config'); @@ -109,24 +108,11 @@ app.config(function($routeProvider, $animateProvider) { templateUrl: 'tpl/login-privatekey-download.html', controller: LoginPrivateKeyDownloadCtrl }); - - // - // main app routes - // - - var accountRoute = { + $routeProvider.when('/account', { templateUrl: 'tpl/desktop.html', - controller: NavigationCtrl - }; - - $routeProvider.when('/account', accountRoute); - $routeProvider.when('/account/:folderIndex', accountRoute); - $routeProvider.when('/account/:folderIndex/:uid', accountRoute); - - // - // Default route - // - + controller: NavigationCtrl, + reloadOnSearch: false // don't reload controllers in main app when query params change + }); $routeProvider.otherwise({ redirectTo: '/login' }); @@ -138,10 +124,6 @@ app.config(function($routeProvider, $animateProvider) { app.run(function($rootScope) { // global state... inherited to all child scopes $rootScope.state = {}; - - // attach the back button handler to the root scope - backButtonUtil.attachHandler($rootScope); - // attach fastclick FastClick.attach(document.body); }); diff --git a/src/js/controller/app/action-bar.js b/src/js/controller/app/action-bar.js index dfaa147..eff39db 100644 --- a/src/js/controller/app/action-bar.js +++ b/src/js/controller/app/action-bar.js @@ -15,7 +15,7 @@ var ActionBarCtrl = function($scope, email, dialog, statusDisplay) { } // close read state - $scope.state.read.open = false; + $scope.state.read.toggle(false); statusDisplay.update('Moving message...'); @@ -76,7 +76,7 @@ var ActionBarCtrl = function($scope, email, dialog, statusDisplay) { } // close read state - $scope.state.read.open = false; + $scope.state.read.toggle(false); statusDisplay.update('Deleting message...'); @@ -122,7 +122,7 @@ var ActionBarCtrl = function($scope, email, dialog, statusDisplay) { // close read state if (!keepOpen) { - $scope.state.read.open = false; + $scope.state.read.toggle(false); } var originalState = message.unread; diff --git a/src/js/controller/app/mail-list.js b/src/js/controller/app/mail-list.js index 07de3ee..705ef92 100644 --- a/src/js/controller/app/mail-list.js +++ b/src/js/controller/app/mail-list.js @@ -1,17 +1,17 @@ 'use strict'; -var searchTimeout, firstSelect; +var searchTimeout; // // Constants // -var INIT_DISPLAY_LEN = 20, +var INIT_DISPLAY_LEN = 50, SCROLL_DISPLAY_LEN = 10, FOLDER_TYPE_INBOX = 'Inbox', 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 @@ -24,6 +24,30 @@ var MailListCtrl = function($scope, $timeout, $location, $routeParams, $filter, */ $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 // @@ -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 */ @@ -66,11 +86,10 @@ var MailListCtrl = function($scope, $timeout, $location, $routeParams, $filter, $scope.state.mailList.selected = message; - if (!firstSelect) { - // only toggle to read view on 2nd select in mobile mode - $scope.state.read.toggle(true); + if ($location.search().dev) { + // stop here in dev mode + return; } - firstSelect = false; keychain.refreshKeyForUserId({ 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.state.actionBar.flagMessage(message, flagged); }; @@ -141,7 +155,7 @@ var MailListCtrl = function($scope, $timeout, $location, $routeParams, $filter, $scope.searchText = undefined; // in development, display dummy mail objects - if ($routeParams.dev) { + if ($location.search().dev) { statusDisplay.update('Last update: ', new Date()); currentFolder().messages = dummy.listMails(); return; @@ -157,15 +171,9 @@ var MailListCtrl = function($scope, $timeout, $location, $routeParams, $filter, } // sort message by uid - currentFolder().messages.sort(byUidDescending); + messages.sort(byUidDescending); // set display buffer to first messages - $scope.displayMessages = currentFolder().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]); - } + $scope.displayMessages = messages.slice(0, INIT_DISPLAY_LEN); }); /** @@ -300,16 +308,12 @@ var MailListCtrl = function($scope, $timeout, $location, $routeParams, $filter, title: title, message: message, onClick: function() { - // force toggle into read mode when notification is clicked - firstSelect = false; - // remove from pending notificatiosn var index = $scope.pendingNotifications.indexOf(note); if (index !== -1) { $scope.pendingNotifications.splice(index, 1); } - - // mark message as read + // open the message $scope.navigate(_.findWhere(currentFolder().messages, { uid: unreadMsgs[0].uid })); diff --git a/src/js/controller/app/navigation.js b/src/js/controller/app/navigation.js index 3528dd1..f66a02f 100644 --- a/src/js/controller/app/navigation.js +++ b/src/js/controller/app/navigation.js @@ -1,7 +1,5 @@ 'use strict'; -var backBtnHandler = require('../../util/backbutton-handler'); - // // Constants // @@ -13,21 +11,17 @@ var NOTIFICATION_SENT_TIMEOUT = 2000; // Controller // -var NavigationCtrl = function($scope, $routeParams, $location, account, email, outbox, notification, appConfig, dialog, dummy) { - if (!$routeParams.dev && !account.isLoggedIn()) { +var NavigationCtrl = function($scope, $location, account, email, outbox, notification, appConfig, dialog, dummy) { + if (!$location.search().dev && !account.isLoggedIn()) { $location.path('/'); // init app return; - } else if (!$routeParams.folderIndex) { - $location.path('/account/0'); // navigate to default account's inbox by default - return; } - var folderIndex = $routeParams.folderIndex, - str = appConfig.string, + var str = appConfig.string, config = appConfig.config; // - // scope functions + // scope state // $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.state.nav.currentFolder = folder; $scope.state.nav.toggle(false); @@ -76,16 +100,22 @@ var NavigationCtrl = function($scope, $routeParams, $location, account, email, o // Start // - // handle back button - backBtnHandler.start(); // init folders initializeFolders(); - // select current folder on init - if ($scope.account.folders && $scope.account.folders.length > folderIndex) { - // navigate to the selected folder index - $scope.openFolder($scope.account.folders[folderIndex]); - } + // 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) { + // navigate to the selected folder index + $scope.openFolder($scope.account.folders[folderIndex]); + } + }); // connect imap/smtp clients on first startup account.onConnect(function(err) { @@ -107,7 +137,7 @@ var NavigationCtrl = function($scope, $routeParams, $location, account, email, o function initializeFolders() { // create dummy folder in dev environment only - if ($routeParams.dev) { + if ($location.search().dev) { $scope.$root.account = {}; $scope.account.folders = dummy.listFolders(); return; diff --git a/src/js/controller/app/read.js b/src/js/controller/app/read.js index 4092644..5be0273 100644 --- a/src/js/controller/app/read.js +++ b/src/js/controller/app/read.js @@ -4,10 +4,14 @@ // 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; + // + // scope state + // + // set default value so that the popover height is correct on init $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) { + if ($location.search().dev || !address) { + return; + } + $scope.keyId = 'Searching...'; keychain.getReceiverPublicKey(address, function(err, pubkey) { if (err) { @@ -41,7 +70,7 @@ var ReadCtrl = function($scope, email, invitation, outbox, pgp, keychain, appCon }; $scope.$watch('state.mailList.selected', function(mail) { - if (!mail) { + if ($location.search().dev || !mail) { return; } diff --git a/src/js/util/backbutton-handler.js b/src/js/util/backbutton-handler.js deleted file mode 100644 index 2990bc7..0000000 --- a/src/js/util/backbutton-handler.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/src/tpl/nav.html b/src/tpl/nav.html index ebfbc13..f10e5ba 100644 --- a/src/tpl/nav.html +++ b/src/tpl/nav.html @@ -7,7 +7,7 @@