(js) Drag'n'drop of cards in AddressBook module
Required to add the option to move multiple cards to another addressbook. Fixed the possibility to copy cards to a subscribed calendar.pull/27/merge
parent
e963981460
commit
907024d2c4
1
NEWS
1
NEWS
|
@ -3,6 +3,7 @@
|
|||
|
||||
New features
|
||||
- [web] drag'n'drop of messages in the Mail module (#3497, #3586, #3734, #3788)
|
||||
- [web] drag'n'drop of cards in the AddressBook module
|
||||
- [eas] added folder merging capabilities
|
||||
|
||||
Enhancements
|
||||
|
|
|
@ -41,20 +41,28 @@
|
|||
"Move To" = "Move To";
|
||||
"Copy To" = "Copy To";
|
||||
"Add to" = "Add to";
|
||||
|
||||
/* Subheader of empty addressbook */
|
||||
"No contact" = "No contact";
|
||||
|
||||
/* Subheader of system addressbook */
|
||||
"Start a search to browse this address book" = "Start a search to browse this address book";
|
||||
|
||||
/* Number of contacts in addressbook; string is prefixed by number */
|
||||
"contacts" = "contacts";
|
||||
|
||||
/* No contact matching search criteria */
|
||||
"No matching contact" = "No matching contact";
|
||||
|
||||
/* Number of contacts matching search criteria; string is prefixed by number */
|
||||
"matching contacts" = "matching contacts";
|
||||
|
||||
/* Number of selected contacts in list */
|
||||
"selected" = "selected";
|
||||
|
||||
/* Empty right pane */
|
||||
"No contact selected" = "No contact selected";
|
||||
|
||||
/* Tooltips */
|
||||
"Create a new address book card" = "Create a new address book card";
|
||||
"Create a new list" = "Create a new list";
|
||||
|
@ -95,9 +103,11 @@
|
|||
"Work" = "Work";
|
||||
"Mobile" = "Mobile";
|
||||
"Pager" = "Pager";
|
||||
|
||||
/* categories */
|
||||
"contacts_category_labels" = "Colleague, Competitor, Customer, Friend, Family, Business Partner, Provider, Press, VIP";
|
||||
"New category" = "New category";
|
||||
|
||||
/* adresses */
|
||||
"Title" = "Title";
|
||||
"Service" = "Service";
|
||||
|
@ -141,6 +151,7 @@
|
|||
= "You cannot subscribe to a folder that you own.";
|
||||
"Unable to subscribe to that folder!"
|
||||
= "Unable to subscribe to that folder.";
|
||||
|
||||
/* acls */
|
||||
"Access rights to" = "Access rights to";
|
||||
"For user" = "For user";
|
||||
|
@ -159,11 +170,15 @@
|
|||
"The selected contact has no email address."
|
||||
= "The selected contact has no email address.";
|
||||
"Please select a contact." = "Please select a contact.";
|
||||
/* Error messages for move and copy */
|
||||
|
||||
/* Messages for move and copy */
|
||||
"%{0} card(s) copied" = "%{0} card(s) copied";
|
||||
"%{0} card(s) moved" = "%{0} card(s) moved";
|
||||
"SoAccessDeniedException" = "You cannot write to this address book.";
|
||||
"Forbidden" = "You cannot write to this address book.";
|
||||
"Invalid Contact" = "The selected contact no longer exists.";
|
||||
"Unknown Destination Folder" = "The chosen destination address book no longer exists.";
|
||||
|
||||
/* Lists */
|
||||
"List details" = "List details";
|
||||
"List name" = "List name";
|
||||
|
@ -176,6 +191,8 @@
|
|||
"Export" = "Export";
|
||||
"Export Address Book..." = "Export Address Book...";
|
||||
"View Raw Source" = "View Raw Source";
|
||||
|
||||
/* Import */
|
||||
"Import Cards" = "Import Cards";
|
||||
"Select a vCard or LDIF file." = "Select a vCard or LDIF file.";
|
||||
"Upload" = "Upload";
|
||||
|
@ -185,6 +202,7 @@
|
|||
"No card was imported." = "No card was imported.";
|
||||
"A total of %{0} cards were imported in the addressbook." = "A total of %{0} cards were imported in the addressbook.";
|
||||
"Reload" = "Reload";
|
||||
|
||||
/* Properties window */
|
||||
"Address Book Name" = "Address Book Name";
|
||||
"Links to this Address Book" = "Links to this Address Book";
|
||||
|
|
|
@ -19,7 +19,11 @@
|
|||
ui-view="addressbooks"
|
||||
ng-controller="navController"><!-- addressbooks list --></main>
|
||||
|
||||
<!-- TEMPLATE SCRIPT WRAPPER -->
|
||||
<sg-draggable-helper>
|
||||
<md-icon>person</md-icon>
|
||||
<sg-draggable-helper-counter class="md-default-theme md-warn md-hue-1 md-bg"><!-- count --></sg-draggable-helper-counter>
|
||||
</sg-draggable-helper>
|
||||
|
||||
<script type="text/ng-template" id="UIxContactFoldersView">
|
||||
|
||||
<!-- Sidenav -->
|
||||
|
@ -47,7 +51,9 @@
|
|||
ng-click="app.select($event, folder)"
|
||||
ng-dblclick="app.edit(folder)"
|
||||
ui-sref="app.addressbook({addressbookId: folder.id})"
|
||||
ui-sref-active="md-default-theme md-background md-bg md-hue-1">
|
||||
ui-sref-active="md-default-theme md-background md-bg md-hue-1"
|
||||
sg-droppable="app.isDroppableFolder(dragFolder, folder)"
|
||||
sg-drop="app.dragSelectedCards(dragFolder, folder, dragMode)">
|
||||
<md-icon>contacts</md-icon>
|
||||
<p class="sg-item-name"
|
||||
ng-show="app.editMode!=folder.id">
|
||||
|
@ -133,7 +139,9 @@
|
|||
ng-click="app.select($event, folder)"
|
||||
ng-dblclick="app.edit(folder)"
|
||||
ui-sref="app.addressbook({addressbookId: folder.id})"
|
||||
ui-sref-active="md-default-theme md-background md-bg md-hue-1">
|
||||
ui-sref-active="md-default-theme md-background md-bg md-hue-1"
|
||||
sg-droppable="app.isDroppableFolder(dragFolder, folder)"
|
||||
sg-drop="app.dragSelectedCards(dragFolder, folder, dragMode)">
|
||||
<md-icon>contacts</md-icon>
|
||||
<p class="sg-item-name"
|
||||
ng-show="app.editMode!=folder.id">
|
||||
|
@ -333,29 +341,11 @@
|
|||
ng-click="addressbook.confirmDeleteSelectedCards()">
|
||||
<md-icon>delete</md-icon>
|
||||
</md-button>
|
||||
<md-menu>
|
||||
<md-button class="sg-icon-button" label:aria-label="Copy contacts" ng-click="$mdOpenMenu()">
|
||||
<md-tooltip md-direction="left"><var:string label:value="Copy To"/></md-tooltip>
|
||||
<md-icon>content_copy</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content width="4">
|
||||
<md-menu-item>
|
||||
<md-button class="md-primary" ng-disabled="true"><var:string label:value="Copy To"/></md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-divider><!-- divider --></md-menu-divider>
|
||||
<md-menu-item ng-repeat="folder in app.service.$addressbooks track by folder.id"
|
||||
ng-hide="addressbook.selectedFolder.id == folder.id">
|
||||
<md-button ng-click="addressbook.copySelectedCards(folder.id)">
|
||||
<span ng-class="'sg-child-level-' + folder.level">{{folder.name}}</span>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
<md-menu>
|
||||
<md-button class="sg-icon-button" label:aria-label="More messages options" ng-click="$mdOpenMenu()">
|
||||
<md-icon>more_vert</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content width="2">
|
||||
<md-menu-content width="3">
|
||||
<md-menu-item>
|
||||
<md-button ng-click="addressbook.newMessageWithSelectedCards($event)">
|
||||
<var:string label:value="Write"/>
|
||||
|
@ -366,6 +356,42 @@
|
|||
<var:string label:value="Export"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-menu>
|
||||
<md-button label:aria-label="Copy To" ng-click="$mdOpenMenu($event)">
|
||||
<var:string label:value="Copy To"/>
|
||||
</md-button>
|
||||
<md-menu-content class="md-dense" width="4">
|
||||
<md-menu-item ng-repeat="folder in app.service.$addressbooks track by folder.id"
|
||||
ng-hide="addressbook.selectedFolder.id == folder.id">
|
||||
<md-button ng-click="addressbook.copySelectedCards(folder.id)">{{folder.name}}</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item ng-repeat="folder in app.service.$subscriptions track by folder.id"
|
||||
ng-hide="addressbook.selectedFolder.id == folder.id">
|
||||
<md-button ng-click="addressbook.copySelectedCards(folder.id)"
|
||||
ng-disabled="!folder.acls.objectCreator">{{folder.name}}</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
</md-menu-item>
|
||||
<md-menu-item ng-show="addressbook.selectedFolder.acls.objectEraser">
|
||||
<md-menu>
|
||||
<md-button label:aria-label="Move To" ng-click="$mdOpenMenu($event)">
|
||||
<var:string label:value="Move To"/>
|
||||
</md-button>
|
||||
<md-menu-content class="md-dense" width="4">
|
||||
<md-menu-item ng-repeat="folder in app.service.$addressbooks track by folder.id"
|
||||
ng-hide="addressbook.selectedFolder.id == folder.id">
|
||||
<md-button ng-click="addressbook.moveSelectedCards(folder.id)">{{folder.name}}</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item ng-repeat="folder in app.service.$subscriptions track by folder.id"
|
||||
ng-hide="addressbook.selectedFolder.id == folder.id">
|
||||
<md-button ng-click="addressbook.moveSelectedCards(folder.id)"
|
||||
ng-disabled="!folder.acls.objectCreator">{{folder.name}}</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
</div>
|
||||
|
@ -395,7 +421,11 @@
|
|||
</md-subheader>
|
||||
<md-virtual-repeat-container class="md-flex" md-top-index="addressbook.selectedFolder.$topIndex">
|
||||
<md-list class="sg-section-list"
|
||||
ng-class="{ 'sg-list-selectable': addressbook.mode.multiple }">
|
||||
ng-class="{ 'sg-list-selectable': addressbook.mode.multiple }"
|
||||
sg-draggable="addressbook.selectedFolder"
|
||||
sg-drag-start="addressbook.selectedFolder.hasSelectedCard() ||
|
||||
addressbook.selectedFolder.$selectedCount()"
|
||||
sg-drag-count="addressbook.selectedFolder.$selectedCount()">
|
||||
<md-list-item
|
||||
class="md-default-theme md-accent md-hue-2"
|
||||
ng-class="{'md-bg': addressbook.selectedFolder.isSelectedCard(currentCard.id)}"
|
||||
|
|
|
@ -337,6 +337,16 @@
|
|||
return loaded;
|
||||
};
|
||||
|
||||
/**
|
||||
* @function hasSelectedMessage
|
||||
* @memberof AddressBook.prototype
|
||||
* @desc Check if a card is selected.
|
||||
* @returns true if the a card is selected
|
||||
*/
|
||||
AddressBook.prototype.hasSelectedCard = function() {
|
||||
return angular.isDefined(this.selectedCard);
|
||||
};
|
||||
|
||||
/**
|
||||
* @function isSelectedCard
|
||||
* @memberof AddressBook.prototype
|
||||
|
@ -345,7 +355,29 @@
|
|||
* @returns true if the specified card is selected
|
||||
*/
|
||||
AddressBook.prototype.isSelectedCard = function(cardId) {
|
||||
return this.selectedCard == cardId;
|
||||
return this.hasSelectedCard() && this.selectedCard == cardId;
|
||||
};
|
||||
|
||||
/**
|
||||
* @function $selectedCard
|
||||
* @memberof AddressBook.prototype
|
||||
* @desc Return the currently visible card.
|
||||
* @returns a Card instance or undefined if no card is displayed
|
||||
*/
|
||||
AddressBook.prototype.$selectedCard = function() {
|
||||
var _this = this;
|
||||
|
||||
return _.find(this.$cards, function(card) { return card.id == _this.selectedCard; });
|
||||
};
|
||||
|
||||
/**
|
||||
* @function $selectedCards
|
||||
* @memberof AddressBook.prototype
|
||||
* @desc Return the cards selected by the user.
|
||||
* @returns Card instances
|
||||
*/
|
||||
AddressBook.prototype.$selectedCards = function() {
|
||||
return _.filter(this.$cards, function(card) { return card.selected; });
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -554,18 +586,16 @@
|
|||
};
|
||||
|
||||
/**
|
||||
* @function $deleteCards
|
||||
* @function $_deleteCards
|
||||
* @memberof AddressBook.prototype
|
||||
* @desc Delete multiple cards from addressbook.
|
||||
* @return a promise of the HTTP operation
|
||||
* @desc Delete multiple cards from AddressBook object.
|
||||
* @param {string[]} ids - the cards ids
|
||||
*/
|
||||
AddressBook.prototype.$deleteCards = function(cards) {
|
||||
var _this = this,
|
||||
ids = _.map(cards, function(card) { return card.id; });
|
||||
AddressBook.prototype.$_deleteCards = function(ids) {
|
||||
var _this = this;
|
||||
|
||||
return AddressBook.$$resource.post(this.id, 'batchDelete', {uids: ids}).then(function() {
|
||||
// Remove cards from $cards and idsMap
|
||||
_.forEachRight(_this.$cards, function(card, index) {
|
||||
_.forEachRight(this.$cards, function(card, index) {
|
||||
var selectedIndex = _.findIndex(ids, function(id) {
|
||||
return card.id == id;
|
||||
});
|
||||
|
@ -580,6 +610,20 @@
|
|||
_this.idsMap[card.id] -= ids.length;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @function $deleteCards
|
||||
* @memberof AddressBook.prototype
|
||||
* @desc Delete multiple cards from addressbook.
|
||||
* @return a promise of the HTTP operation
|
||||
*/
|
||||
AddressBook.prototype.$deleteCards = function(cards) {
|
||||
var _this = this,
|
||||
ids = _.map(cards, 'id');
|
||||
|
||||
return AddressBook.$$resource.post(this.id, 'batchDelete', {uids: ids}).then(function() {
|
||||
_this.$_deleteCards(ids);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -590,10 +634,28 @@
|
|||
* @return a promise of the HTTP operation
|
||||
*/
|
||||
AddressBook.prototype.$copyCards = function(cards, folder) {
|
||||
var uids = _.map(cards, function(card) { return card.id; });
|
||||
var uids = _.map(cards, 'id');
|
||||
return AddressBook.$$resource.post(this.id, 'copy', {uids: uids, folder: folder});
|
||||
};
|
||||
|
||||
/**
|
||||
* @function $moveCards
|
||||
* @memberof AddressBook.prototype
|
||||
* @desc Move multiple cards from the current addressbook to a target one
|
||||
* @param {object[]} cards - instances of Card object
|
||||
* @param {string} folder - the destination folder id
|
||||
* @return a promise of the HTTP operation
|
||||
*/
|
||||
AddressBook.prototype.$moveCards = function(cards, folder) {
|
||||
var _this = this, uids;
|
||||
|
||||
uids = _.map(cards, 'id');
|
||||
return AddressBook.$$resource.post(this.id, 'move', {uids: uids, folder: folder})
|
||||
.then(function() {
|
||||
return _this.$_deleteCards(uids);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @function $save
|
||||
* @memberof AddressBook.prototype
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
/**
|
||||
* @ngInject
|
||||
*/
|
||||
AddressBookController.$inject = ['$scope', '$q', '$window', '$state', '$timeout', '$mdDialog', 'Account', 'Card', 'AddressBook', 'Dialog', 'sgSettings', 'stateAddressbooks', 'stateAddressbook'];
|
||||
function AddressBookController($scope, $q, $window, $state, $timeout, $mdDialog, Account, Card, AddressBook, Dialog, Settings, stateAddressbooks, stateAddressbook) {
|
||||
AddressBookController.$inject = ['$scope', '$q', '$window', '$state', '$timeout', '$mdDialog', '$mdToast', 'Account', 'Card', 'AddressBook', 'Dialog', 'sgSettings', 'stateAddressbooks', 'stateAddressbook'];
|
||||
function AddressBookController($scope, $q, $window, $state, $timeout, $mdDialog, $mdToast, Account, Card, AddressBook, Dialog, Settings, stateAddressbooks, stateAddressbook) {
|
||||
var vm = this;
|
||||
|
||||
AddressBook.selectedFolder = stateAddressbook;
|
||||
|
@ -20,6 +20,7 @@
|
|||
vm.unselectCards = unselectCards;
|
||||
vm.confirmDeleteSelectedCards = confirmDeleteSelectedCards;
|
||||
vm.copySelectedCards = copySelectedCards;
|
||||
vm.moveSelectedCards = moveSelectedCards;
|
||||
vm.selectAll = selectAll;
|
||||
vm.sort = sort;
|
||||
vm.sortedBy = sortedBy;
|
||||
|
@ -66,11 +67,58 @@
|
|||
});
|
||||
}
|
||||
|
||||
function copySelectedCards(folder) {
|
||||
var selectedCards = _.filter(vm.selectedFolder.$cards, function(card) { return card.selected; });
|
||||
vm.selectedFolder.$copyCards(selectedCards, folder).then(function() {
|
||||
// TODO: refresh target addressbook?
|
||||
/**
|
||||
* @see AddressBooksController.dragSelectedCards
|
||||
*/
|
||||
function _selectedCardsOperation(operation, dstId) {
|
||||
var srcFolder, allCards, cards, ids, clearCardView, promise, success;
|
||||
|
||||
srcFolder = vm.selectedFolder;
|
||||
clearCardView = false;
|
||||
allCards = srcFolder.$selectedCards();
|
||||
cards = _.filter(allCards, function(card) {
|
||||
return card.$isCard();
|
||||
});
|
||||
|
||||
if (cards.length != allCards.length)
|
||||
$mdToast.show(
|
||||
$mdToast.simple()
|
||||
.content(l("Lists can't be moved or copied."))
|
||||
.position('top right')
|
||||
.hideDelay(2000));
|
||||
|
||||
if (cards.length) {
|
||||
if (operation == 'copy') {
|
||||
promise = srcFolder.$copyCards(cards, dstId);
|
||||
success = l('%{0} card(s) copied', cards.length);
|
||||
}
|
||||
else {
|
||||
promise = srcFolder.$moveCards(cards, dstId);
|
||||
success = l('%{0} card(s) moved', cards.length);
|
||||
// Check if currently displayed card will be moved
|
||||
ids = _.map(cards, 'id');
|
||||
clearCardView = (srcFolder.selectedCard && ids.indexOf(srcFolder.selectedCard) >= 0);
|
||||
}
|
||||
|
||||
// Show success toast when action succeeds
|
||||
promise.then(function() {
|
||||
if (clearCardView)
|
||||
$state.go('app.addressbook');
|
||||
$mdToast.show(
|
||||
$mdToast.simple()
|
||||
.content(success)
|
||||
.position('top right')
|
||||
.hideDelay(2000));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function copySelectedCards(folder) {
|
||||
_selectedCardsOperation('copy', folder);
|
||||
}
|
||||
|
||||
function moveSelectedCards(folder) {
|
||||
_selectedCardsOperation('move', folder);
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
vm.showProperties = showProperties;
|
||||
vm.share = share;
|
||||
vm.subscribeToFolder = subscribeToFolder;
|
||||
vm.isDroppableFolder = isDroppableFolder;
|
||||
vm.dragSelectedCards = dragSelectedCards;
|
||||
|
||||
function select($event, folder) {
|
||||
if ($state.params.addressbookId != folder.id &&
|
||||
|
@ -301,6 +303,58 @@
|
|||
.hideDelay(3000));
|
||||
});
|
||||
}
|
||||
|
||||
function isDroppableFolder(srcFolder, dstFolder) {
|
||||
return (dstFolder.id != srcFolder.id) && (dstFolder.isOwned || dstFolder.acls.objectCreator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AddressBookController._selectedCardsOperation
|
||||
*/
|
||||
function dragSelectedCards(srcFolder, dstFolder, mode) {
|
||||
var dstId, cards, ids, clearCardView, promise, success;
|
||||
|
||||
dstId = dstFolder.id;
|
||||
clearCardView = false;
|
||||
cards = srcFolder.$selectedCards();
|
||||
if (cards.length === 0)
|
||||
cards = [srcFolder.$selectedCard()];
|
||||
|
||||
if (_.find(cards, function(card) {
|
||||
return card.$isList();
|
||||
})) {
|
||||
$mdToast.show(
|
||||
$mdToast.simple()
|
||||
.content(l("Lists can't be moved or copied."))
|
||||
.position('top right')
|
||||
.hideDelay(2000));
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == 'copy') {
|
||||
promise = srcFolder.$copyCards(cards, dstId);
|
||||
success = l('%{0} card(s) copied', cards.length);
|
||||
}
|
||||
else {
|
||||
promise = srcFolder.$moveCards(cards, dstId);
|
||||
success = l('%{0} card(s) moved', cards.length);
|
||||
// Check if currently displayed card will be moved
|
||||
ids = _.map(cards, 'id');
|
||||
clearCardView = (srcFolder.selectedCard && ids.indexOf(srcFolder.selectedCard) >= 0);
|
||||
}
|
||||
|
||||
// Show success toast when action succeeds
|
||||
promise.then(function() {
|
||||
if (clearCardView)
|
||||
$state.go('app.addressbook');
|
||||
$mdToast.show(
|
||||
$mdToast.simple()
|
||||
.content(success)
|
||||
.position('top right')
|
||||
.hideDelay(2000));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
angular
|
||||
|
|
|
@ -110,7 +110,7 @@
|
|||
return addressbook.id == $stateParams.addressbookId;
|
||||
});
|
||||
if (addressbook) {
|
||||
addressbook.selectedCard = false;
|
||||
delete addressbook.selectedCard;
|
||||
addressbook.$reload();
|
||||
return addressbook;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue