diff --git a/NEWS b/NEWS index 09deea7ed..5288e6534 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ Enhancements - [web] expose all email addresses in autocompletion of message editor (#3443) - [web] Gravatar service can now be disabled (#3600) - [web] collapsable mail accounts (#3493) + - [web] show progress indicator when loading messages and cards Bug fixes - [web] fixed creation of chip on blur (sgTransformOnBlur directive) diff --git a/UI/Templates/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/ContactsUI/UIxContactFoldersView.wox index ab4337098..80b5f510c 100644 --- a/UI/Templates/ContactsUI/UIxContactFoldersView.wox +++ b/UI/Templates/ContactsUI/UIxContactFoldersView.wox @@ -411,17 +411,22 @@
+ ng-show="currentCard.$isList()">
+
+ +
diff --git a/UI/Templates/MailerUI/UIxMailFolderTemplate.wox b/UI/Templates/MailerUI/UIxMailFolderTemplate.wox index 59ad127d1..8657aa4f2 100644 --- a/UI/Templates/MailerUI/UIxMailFolderTemplate.wox +++ b/UI/Templates/MailerUI/UIxMailFolderTemplate.wox @@ -302,6 +302,11 @@ forward attach_file +
+ +
@@ -309,7 +314,7 @@ ng-show="mailbox.service.selectedFolder.$isLoading"> + md-diameter="32"> index _this.idsMap[data.id] = i; diff --git a/UI/WebServerResources/js/Contacts/AddressBookController.js b/UI/WebServerResources/js/Contacts/AddressBookController.js index f67110421..cca2da3e9 100644 --- a/UI/WebServerResources/js/Contacts/AddressBookController.js +++ b/UI/WebServerResources/js/Contacts/AddressBookController.js @@ -17,7 +17,6 @@ vm.selectCard = selectCard; vm.toggleCardSelection = toggleCardSelection; vm.newComponent = newComponent; - vm.notSelectedComponent = notSelectedComponent; vm.unselectCards = unselectCards; vm.confirmDeleteSelectedCards = confirmDeleteSelectedCards; vm.copySelectedCards = copySelectedCards; @@ -79,10 +78,6 @@ } } - function notSelectedComponent(currentCard, type) { - return (currentCard && currentCard.c_component == type && !currentCard.selected); - } - function unselectCards() { _.forEach(vm.selectedFolder.$cards, function(card) { card.selected = false; @@ -180,12 +175,10 @@ }); } else { - promises.push(vm.selectedFolder.$getCard(card.id).then(function(card) { - return card.$futureCardData.then(function(data) { - _.forEach(data.refs, function(ref) { - if (ref.email.length) - recipients.push(ref.$shortFormat()); - }); + promises.push(card.$reload().then(function(card) { + _.forEach(card.refs, function(ref) { + if (ref.email.length) + recipients.push(ref.$shortFormat()); }); })); } diff --git a/UI/WebServerResources/js/Contacts/Card.service.js b/UI/WebServerResources/js/Contacts/Card.service.js index 98264fa5b..4c2925a68 100644 --- a/UI/WebServerResources/js/Contacts/Card.service.js +++ b/UI/WebServerResources/js/Contacts/Card.service.js @@ -70,9 +70,11 @@ } angular.module('SOGo.ContactsUI') .constant('sgCard_STATUS', { - NOT_LOADED: 0, - LOADING: 1, - LOADED: 2 + NOT_LOADED: 0, + DELAYED_LOADING: 1, + LOADING: 2, + LOADED: 3, + DELAYED_MS: 300 }) .factory('Card', Card.$factory); @@ -164,6 +166,33 @@ }); }; + /** + * @function $isLoading + * @memberof Card.prototype + * @returns true if the Card definition is still being retrieved from server after a specific delay + * @see sgCard_STATUS + */ + Card.prototype.$isLoading = function() { + return this.$loaded == Card.STATUS.LOADING; + }; + + /** + * @function $reload + * @memberof Message.prototype + * @desc Fetch the viewable message body along with other metadata such as the list of attachments. + * @returns a promise of the HTTP operation + */ + Card.prototype.$reload = function() { + var futureCardData; + + if (this.$futureCardData) + return this; + + futureCardData = Card.$$resource.fetch([this.pid, this.id].join('/'), 'view'); + + return this.$unwrap(futureCardData); + }; + /** * @function $save * @memberof Card.prototype @@ -498,7 +527,11 @@ var _this = this; // Card is not loaded yet - this.$loaded = Card.STATUS.LOADING; + this.$loaded = Card.STATUS.DELAYED_LOADING; + Card.$timeout(function() { + if (_this.$loaded != Card.STATUS.LOADED) + _this.$loaded = Card.STATUS.LOADING; + }, Card.STATUS.DELAYED_MS); // Expose the promise this.$futureCardData = futureCardData.then(function(data) { @@ -523,6 +556,8 @@ return _this; }); + + return this.$futureCardData; }; /** diff --git a/UI/WebServerResources/js/Contacts/Contacts.app.js b/UI/WebServerResources/js/Contacts/Contacts.app.js index f6bd3e15d..d10d1abe1 100644 --- a/UI/WebServerResources/js/Contacts/Contacts.app.js +++ b/UI/WebServerResources/js/Contacts/Contacts.app.js @@ -64,7 +64,9 @@ }, resolve: { stateCard: stateCard - } + }, + onEnter: onEnterCard, + onExit: onExitCard }) .state('app.addressbook.card.view', { url: '/view', @@ -129,10 +131,37 @@ /** * @ngInject */ - stateCard.$inject = ['$stateParams', 'stateAddressbook']; - function stateCard($stateParams, stateAddressbook) { + stateCard.$inject = ['$state', '$stateParams', 'stateAddressbook']; + function stateCard($state, $stateParams, stateAddressbook) { + var card; + + card = _.find(stateAddressbook.$cards, function(cardObject) { + return (cardObject.id == $stateParams.cardId); + }); + + if (card) { + return card.$reload(); + } + else { + // Card not found + $state.go('app.addressbook'); + } + } + + /** + * @ngInject + */ + onEnterCard.$inject = ['$stateParams', 'stateAddressbook']; + function onEnterCard($stateParams, stateAddressbook) { stateAddressbook.selectedCard = $stateParams.cardId; - return stateAddressbook.$getCard($stateParams.cardId); + } + + /** + * @ngInject + */ + onExitCard.$inject = ['stateAddressbook']; + function onExitCard(stateMailbox) { + delete stateAddressbook.selectedCard; } /** diff --git a/UI/WebServerResources/js/Mailer/Mailbox.service.js b/UI/WebServerResources/js/Mailer/Mailbox.service.js index 64eef92a3..b5224eec2 100644 --- a/UI/WebServerResources/js/Mailer/Mailbox.service.js +++ b/UI/WebServerResources/js/Mailer/Mailbox.service.js @@ -600,7 +600,7 @@ * @return the index of the first deleted message */ Mailbox.prototype.$_deleteMessages = function(uids, messages) { - var _this = this, selectedMessages, selectedUIDs, unseen, firstIndex = this.$messages.length; + var _this = this, selectedUIDs, _$messages, unseen, firstIndex = this.$messages.length; // Decrement the unseen count unseen = _.filter(messages, function(message, i) { return !message.isread; }); diff --git a/UI/WebServerResources/js/Mailer/Mailer.app.js b/UI/WebServerResources/js/Mailer/Mailer.app.js index cdb279d38..200caff7d 100644 --- a/UI/WebServerResources/js/Mailer/Mailer.app.js +++ b/UI/WebServerResources/js/Mailer/Mailer.app.js @@ -61,13 +61,13 @@ controllerAs: 'viewer' } }, - onEnter: onEnterMessage, - onExit: onExitMessage, resolve: { stateMailbox: stateVirtualMailboxOfMessage, stateMessages: stateMessages, stateMessage: stateMessage - } + }, + onEnter: onEnterMessage, + onExit: onExitMessage }) .state('mail.account.inbox', { url: '/inbox', @@ -279,7 +279,7 @@ }); if (message) { - return message.$reload(); + return message.$reload({useCache: true}); } else { // Message not found diff --git a/UI/WebServerResources/js/Mailer/Message.service.js b/UI/WebServerResources/js/Mailer/Message.service.js index 1eb0cf9f5..2b578e48f 100644 --- a/UI/WebServerResources/js/Mailer/Message.service.js +++ b/UI/WebServerResources/js/Mailer/Message.service.js @@ -39,8 +39,9 @@ * @desc The factory we'll use to register with Angular * @returns the Message constructor */ - Message.$factory = ['$q', '$timeout', '$log', 'sgSettings', 'Gravatar', 'Resource', 'Preferences', function($q, $timeout, $log, Settings, Gravatar, Resource, Preferences) { + Message.$factory = ['$q', '$timeout', '$log', 'sgSettings', 'sgMessage_STATUS', 'Gravatar', 'Resource', 'Preferences', function($q, $timeout, $log, Settings, Message_STATUS, Gravatar, Resource, Preferences) { angular.extend(Message, { + STATUS: Message_STATUS, $q: $q, $timeout: $timeout, $log: $log, @@ -72,6 +73,13 @@ angular.module('SOGo.MailerUI', ['SOGo.Common']); } angular.module('SOGo.MailerUI') + .constant('sgMessage_STATUS', { + NOT_LOADED: 0, + DELAYED_LOADING: 1, + LOADING: 2, + LOADED: 3, + DELAYED_MS: 300 + }) .factory('Message', Message.$factory); /** @@ -503,15 +511,29 @@ }); }; + /** + * @function $isLoading + * @memberof Message.prototype + * @returns true if the Message content is still being retrieved from server after a specific delay + * @see sgMessage_STATUS + */ + Message.prototype.$isLoading = function() { + return this.$loaded == Message.STATUS.LOADING; + }; + /** * @function $reload * @memberof Message.prototype * @desc Fetch the viewable message body along with other metadata such as the list of attachments. + * @param {object} [options] - set {useCache: true} to use already fetched data * @returns a promise of the HTTP operation */ Message.prototype.$reload = function(options) { var futureMessageData; + if (options && options.useCache && this.$futureMessageData) + return this; + futureMessageData = Message.$$resource.fetch(this.$absolutePath(options), 'view'); return this.$unwrap(futureMessageData); @@ -638,6 +660,13 @@ Message.prototype.$unwrap = function(futureMessageData) { var _this = this; + // Message is not loaded yet + this.$loaded = Message.STATUS.DELAYED_LOADING; + Message.$timeout(function() { + if (_this.$loaded != Message.STATUS.LOADED) + _this.$loaded = Message.STATUS.LOADING; + }, Message.STATUS.DELAYED_MS); + // Resolve and expose the promise this.$futureMessageData = futureMessageData.then(function(data) { // Calling $timeout will force Angular to refresh the view @@ -653,6 +682,7 @@ angular.extend(_this, data); _this.$formatFullAddresses(); _this.$loadUnsafeContent = false; + _this.$loaded = Message.STATUS.LOADED; return _this; }); }); diff --git a/UI/WebServerResources/scss/components/progressLinear/progress-linear.scss b/UI/WebServerResources/scss/components/progressLinear/progress-linear.scss index ae84c9a0e..b17295053 100644 --- a/UI/WebServerResources/scss/components/progressLinear/progress-linear.scss +++ b/UI/WebServerResources/scss/components/progressLinear/progress-linear.scss @@ -1,2 +1,9 @@ /// progressLinear.scss -*- Mode: scss; indent-tabs-mode: nil; basic-offset: 2 -*- @import 'extends'; + +.sg-progress-linear-bottom { + bottom: 0; + left: 0; + position: absolute; + right: 0; +} \ No newline at end of file