(js) Show loading progress for messages and cards
This commit is contained in:
parent
c46a5f8df7
commit
5c2aedb3c9
1
NEWS
1
NEWS
|
@ -5,6 +5,7 @@ Enhancements
|
||||||
- [web] expose all email addresses in autocompletion of message editor (#3443)
|
- [web] expose all email addresses in autocompletion of message editor (#3443)
|
||||||
- [web] Gravatar service can now be disabled (#3600)
|
- [web] Gravatar service can now be disabled (#3600)
|
||||||
- [web] collapsable mail accounts (#3493)
|
- [web] collapsable mail accounts (#3493)
|
||||||
|
- [web] show progress indicator when loading messages and cards
|
||||||
|
|
||||||
Bug fixes
|
Bug fixes
|
||||||
- [web] fixed creation of chip on blur (sgTransformOnBlur directive)
|
- [web] fixed creation of chip on blur (sgTransformOnBlur directive)
|
||||||
|
|
|
@ -411,17 +411,22 @@
|
||||||
<!-- selected avatar -->
|
<!-- selected avatar -->
|
||||||
<!-- </div> -->
|
<!-- </div> -->
|
||||||
<sg-avatar-image class="md-avatar"
|
<sg-avatar-image class="md-avatar"
|
||||||
ng-if="addressbook.notSelectedComponent(currentCard, 'vcard')"
|
ng-if="currentCard.$isCard()"
|
||||||
sg-email="currentCard.$preferredEmail(addressbook.selectedFolder.constructor.$query.value)"
|
sg-email="currentCard.$preferredEmail(addressbook.selectedFolder.constructor.$query.value)"
|
||||||
sg-src="currentCard.photoURL"
|
sg-src="currentCard.photoURL"
|
||||||
size="40">
|
size="40">
|
||||||
<!-- contact avatar -->
|
<!-- contact avatar -->
|
||||||
</sg-avatar-image>
|
</sg-avatar-image>
|
||||||
<div class="md-avatar sg-avatar-list"
|
<div class="md-avatar sg-avatar-list"
|
||||||
ng-show="addressbook.notSelectedComponent(currentCard, 'vlist')">
|
ng-show="currentCard.$isList()">
|
||||||
<!-- list avatar -->
|
<!-- list avatar -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sg-progress-linear-bottom">
|
||||||
|
<md-progress-linear class="md-accent"
|
||||||
|
md-mode="indeterminate"
|
||||||
|
ng-show="currentCard.$isLoading()"><!-- progress --></md-progress-linear>
|
||||||
|
</div>
|
||||||
</md-list-item>
|
</md-list-item>
|
||||||
</md-list>
|
</md-list>
|
||||||
</md-virtual-repeat-container>
|
</md-virtual-repeat-container>
|
||||||
|
|
|
@ -302,6 +302,11 @@
|
||||||
<md-icon class="ng-hide" ng-show="currentMessage.isforwarded">forward</md-icon>
|
<md-icon class="ng-hide" ng-show="currentMessage.isforwarded">forward</md-icon>
|
||||||
<md-icon class="ng-hide" ng-show="currentMessage.hasattachment">attach_file</md-icon>
|
<md-icon class="ng-hide" ng-show="currentMessage.hasattachment">attach_file</md-icon>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sg-progress-linear-bottom">
|
||||||
|
<md-progress-linear class="md-accent"
|
||||||
|
md-mode="indeterminate"
|
||||||
|
ng-show="currentMessage.$isLoading()"><!-- message loading progress --></md-progress-linear>
|
||||||
|
</div>
|
||||||
</md-list-item>
|
</md-list-item>
|
||||||
</md-list>
|
</md-list>
|
||||||
</md-virtual-repeat-container>
|
</md-virtual-repeat-container>
|
||||||
|
@ -309,7 +314,7 @@
|
||||||
ng-show="mailbox.service.selectedFolder.$isLoading">
|
ng-show="mailbox.service.selectedFolder.$isLoading">
|
||||||
<md-progress-circular class="md-accent"
|
<md-progress-circular class="md-accent"
|
||||||
md-mode="indeterminate"
|
md-mode="indeterminate"
|
||||||
md-diameter="32"><!-- progress --></md-progress-circular>
|
md-diameter="32"><!-- mailbox loading progress --></md-progress-circular>
|
||||||
</div>
|
</div>
|
||||||
<md-button class="md-fab md-fab-bottom-right md-accent"
|
<md-button class="md-fab md-fab-bottom-right md-accent"
|
||||||
label:aria-label="Write a new message"
|
label:aria-label="Write a new message"
|
||||||
|
|
|
@ -483,7 +483,7 @@
|
||||||
// Add new cards matching the search query
|
// Add new cards matching the search query
|
||||||
_.forEach(results, function(cardId, index) {
|
_.forEach(results, function(cardId, index) {
|
||||||
if (_.isUndefined(_.find(cards, _.bind(compareIds, cardId)))) {
|
if (_.isUndefined(_.find(cards, _.bind(compareIds, cardId)))) {
|
||||||
var data = { id: cardId };
|
var data = { pid: addressbookId, id: cardId };
|
||||||
var card = new AddressBook.$Card(data, search);
|
var card = new AddressBook.$Card(data, search);
|
||||||
cards.splice(index, 0, card);
|
cards.splice(index, 0, card);
|
||||||
}
|
}
|
||||||
|
@ -686,7 +686,7 @@
|
||||||
|
|
||||||
// Instanciate Card objects
|
// Instanciate Card objects
|
||||||
_.reduce(_this.ids, function(cards, card, i) {
|
_.reduce(_this.ids, function(cards, card, i) {
|
||||||
var data = { id: card };
|
var data = { pid: _this.id, id: card };
|
||||||
|
|
||||||
// Build map of ID <=> index
|
// Build map of ID <=> index
|
||||||
_this.idsMap[data.id] = i;
|
_this.idsMap[data.id] = i;
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
vm.selectCard = selectCard;
|
vm.selectCard = selectCard;
|
||||||
vm.toggleCardSelection = toggleCardSelection;
|
vm.toggleCardSelection = toggleCardSelection;
|
||||||
vm.newComponent = newComponent;
|
vm.newComponent = newComponent;
|
||||||
vm.notSelectedComponent = notSelectedComponent;
|
|
||||||
vm.unselectCards = unselectCards;
|
vm.unselectCards = unselectCards;
|
||||||
vm.confirmDeleteSelectedCards = confirmDeleteSelectedCards;
|
vm.confirmDeleteSelectedCards = confirmDeleteSelectedCards;
|
||||||
vm.copySelectedCards = copySelectedCards;
|
vm.copySelectedCards = copySelectedCards;
|
||||||
|
@ -79,10 +78,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function notSelectedComponent(currentCard, type) {
|
|
||||||
return (currentCard && currentCard.c_component == type && !currentCard.selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
function unselectCards() {
|
function unselectCards() {
|
||||||
_.forEach(vm.selectedFolder.$cards, function(card) {
|
_.forEach(vm.selectedFolder.$cards, function(card) {
|
||||||
card.selected = false;
|
card.selected = false;
|
||||||
|
@ -180,13 +175,11 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
promises.push(vm.selectedFolder.$getCard(card.id).then(function(card) {
|
promises.push(card.$reload().then(function(card) {
|
||||||
return card.$futureCardData.then(function(data) {
|
_.forEach(card.refs, function(ref) {
|
||||||
_.forEach(data.refs, function(ref) {
|
|
||||||
if (ref.email.length)
|
if (ref.email.length)
|
||||||
recipients.push(ref.$shortFormat());
|
recipients.push(ref.$shortFormat());
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,8 +71,10 @@
|
||||||
angular.module('SOGo.ContactsUI')
|
angular.module('SOGo.ContactsUI')
|
||||||
.constant('sgCard_STATUS', {
|
.constant('sgCard_STATUS', {
|
||||||
NOT_LOADED: 0,
|
NOT_LOADED: 0,
|
||||||
LOADING: 1,
|
DELAYED_LOADING: 1,
|
||||||
LOADED: 2
|
LOADING: 2,
|
||||||
|
LOADED: 3,
|
||||||
|
DELAYED_MS: 300
|
||||||
})
|
})
|
||||||
.factory('Card', Card.$factory);
|
.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
|
* @function $save
|
||||||
* @memberof Card.prototype
|
* @memberof Card.prototype
|
||||||
|
@ -498,7 +527,11 @@
|
||||||
var _this = this;
|
var _this = this;
|
||||||
|
|
||||||
// Card is not loaded yet
|
// 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
|
// Expose the promise
|
||||||
this.$futureCardData = futureCardData.then(function(data) {
|
this.$futureCardData = futureCardData.then(function(data) {
|
||||||
|
@ -523,6 +556,8 @@
|
||||||
|
|
||||||
return _this;
|
return _this;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return this.$futureCardData;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -64,7 +64,9 @@
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
stateCard: stateCard
|
stateCard: stateCard
|
||||||
}
|
},
|
||||||
|
onEnter: onEnterCard,
|
||||||
|
onExit: onExitCard
|
||||||
})
|
})
|
||||||
.state('app.addressbook.card.view', {
|
.state('app.addressbook.card.view', {
|
||||||
url: '/view',
|
url: '/view',
|
||||||
|
@ -129,10 +131,37 @@
|
||||||
/**
|
/**
|
||||||
* @ngInject
|
* @ngInject
|
||||||
*/
|
*/
|
||||||
stateCard.$inject = ['$stateParams', 'stateAddressbook'];
|
stateCard.$inject = ['$state', '$stateParams', 'stateAddressbook'];
|
||||||
function stateCard($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;
|
stateAddressbook.selectedCard = $stateParams.cardId;
|
||||||
return stateAddressbook.$getCard($stateParams.cardId);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngInject
|
||||||
|
*/
|
||||||
|
onExitCard.$inject = ['stateAddressbook'];
|
||||||
|
function onExitCard(stateMailbox) {
|
||||||
|
delete stateAddressbook.selectedCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -600,7 +600,7 @@
|
||||||
* @return the index of the first deleted message
|
* @return the index of the first deleted message
|
||||||
*/
|
*/
|
||||||
Mailbox.prototype.$_deleteMessages = function(uids, messages) {
|
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
|
// Decrement the unseen count
|
||||||
unseen = _.filter(messages, function(message, i) { return !message.isread; });
|
unseen = _.filter(messages, function(message, i) { return !message.isread; });
|
||||||
|
|
|
@ -61,13 +61,13 @@
|
||||||
controllerAs: 'viewer'
|
controllerAs: 'viewer'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onEnter: onEnterMessage,
|
|
||||||
onExit: onExitMessage,
|
|
||||||
resolve: {
|
resolve: {
|
||||||
stateMailbox: stateVirtualMailboxOfMessage,
|
stateMailbox: stateVirtualMailboxOfMessage,
|
||||||
stateMessages: stateMessages,
|
stateMessages: stateMessages,
|
||||||
stateMessage: stateMessage
|
stateMessage: stateMessage
|
||||||
}
|
},
|
||||||
|
onEnter: onEnterMessage,
|
||||||
|
onExit: onExitMessage
|
||||||
})
|
})
|
||||||
.state('mail.account.inbox', {
|
.state('mail.account.inbox', {
|
||||||
url: '/inbox',
|
url: '/inbox',
|
||||||
|
@ -279,7 +279,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
if (message) {
|
if (message) {
|
||||||
return message.$reload();
|
return message.$reload({useCache: true});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Message not found
|
// Message not found
|
||||||
|
|
|
@ -39,8 +39,9 @@
|
||||||
* @desc The factory we'll use to register with Angular
|
* @desc The factory we'll use to register with Angular
|
||||||
* @returns the Message constructor
|
* @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, {
|
angular.extend(Message, {
|
||||||
|
STATUS: Message_STATUS,
|
||||||
$q: $q,
|
$q: $q,
|
||||||
$timeout: $timeout,
|
$timeout: $timeout,
|
||||||
$log: $log,
|
$log: $log,
|
||||||
|
@ -72,6 +73,13 @@
|
||||||
angular.module('SOGo.MailerUI', ['SOGo.Common']);
|
angular.module('SOGo.MailerUI', ['SOGo.Common']);
|
||||||
}
|
}
|
||||||
angular.module('SOGo.MailerUI')
|
angular.module('SOGo.MailerUI')
|
||||||
|
.constant('sgMessage_STATUS', {
|
||||||
|
NOT_LOADED: 0,
|
||||||
|
DELAYED_LOADING: 1,
|
||||||
|
LOADING: 2,
|
||||||
|
LOADED: 3,
|
||||||
|
DELAYED_MS: 300
|
||||||
|
})
|
||||||
.factory('Message', Message.$factory);
|
.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
|
* @function $reload
|
||||||
* @memberof Message.prototype
|
* @memberof Message.prototype
|
||||||
* @desc Fetch the viewable message body along with other metadata such as the list of attachments.
|
* @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
|
* @returns a promise of the HTTP operation
|
||||||
*/
|
*/
|
||||||
Message.prototype.$reload = function(options) {
|
Message.prototype.$reload = function(options) {
|
||||||
var futureMessageData;
|
var futureMessageData;
|
||||||
|
|
||||||
|
if (options && options.useCache && this.$futureMessageData)
|
||||||
|
return this;
|
||||||
|
|
||||||
futureMessageData = Message.$$resource.fetch(this.$absolutePath(options), 'view');
|
futureMessageData = Message.$$resource.fetch(this.$absolutePath(options), 'view');
|
||||||
|
|
||||||
return this.$unwrap(futureMessageData);
|
return this.$unwrap(futureMessageData);
|
||||||
|
@ -638,6 +660,13 @@
|
||||||
Message.prototype.$unwrap = function(futureMessageData) {
|
Message.prototype.$unwrap = function(futureMessageData) {
|
||||||
var _this = this;
|
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
|
// Resolve and expose the promise
|
||||||
this.$futureMessageData = futureMessageData.then(function(data) {
|
this.$futureMessageData = futureMessageData.then(function(data) {
|
||||||
// Calling $timeout will force Angular to refresh the view
|
// Calling $timeout will force Angular to refresh the view
|
||||||
|
@ -653,6 +682,7 @@
|
||||||
angular.extend(_this, data);
|
angular.extend(_this, data);
|
||||||
_this.$formatFullAddresses();
|
_this.$formatFullAddresses();
|
||||||
_this.$loadUnsafeContent = false;
|
_this.$loadUnsafeContent = false;
|
||||||
|
_this.$loaded = Message.STATUS.LOADED;
|
||||||
return _this;
|
return _this;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,2 +1,9 @@
|
||||||
/// progressLinear.scss -*- Mode: scss; indent-tabs-mode: nil; basic-offset: 2 -*-
|
/// progressLinear.scss -*- Mode: scss; indent-tabs-mode: nil; basic-offset: 2 -*-
|
||||||
@import 'extends';
|
@import 'extends';
|
||||||
|
|
||||||
|
.sg-progress-linear-bottom {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
}
|
Loading…
Reference in a new issue