summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@users.noreply.github.com>2017-08-15 15:47:22 +0200
committerGitHub <noreply@github.com>2017-08-15 15:47:22 +0200
commit2bebe03eab7e5a55570dbb3e0fe9db33d10b63c5 (patch)
tree5d9d4511aa94480a622933e217dd1effdd963f08
parent20455cd24addfd5d855c6864cb5c5b291c8791d1 (diff)
parentcbd0f98b6f39668def754cf9e78f9d953dbe65e7 (diff)
Merge pull request #123 from nextcloud/import-into-custom-ab
Allow user to import contacts into custom addressbook
-rw-r--r--bower.json2
-rw-r--r--css/public/style.scss (renamed from css/public/style.css)143
-rw-r--r--js/components/avatar/avatar_directive.js3
-rw-r--r--js/components/contactImport/contactImport_controller.js28
-rw-r--r--js/components/contactImport/contactImport_directive.js44
-rw-r--r--js/components/contactList/contactList_controller.js9
-rw-r--r--js/components/groupList/groupList_controller.js6
-rw-r--r--js/components/importScreen/importScreen_controller.js18
-rw-r--r--js/components/importScreen/importScreen_directive.js11
-rw-r--r--js/services/contact_service.js23
-rw-r--r--js/services/import_service.js14
-rw-r--r--templates/addressBookList.html4
-rw-r--r--templates/contactImport.html13
-rw-r--r--templates/importScreen.html9
-rw-r--r--templates/main.php8
15 files changed, 301 insertions, 34 deletions
diff --git a/bower.json b/bower.json
index 22419a6e..17054792 100644
--- a/bower.json
+++ b/bower.json
@@ -25,7 +25,7 @@
"angular-sanitize": "1.5.8",
"angular-uuid4": "0.3.1",
"jquery-timepicker": "883bb2cd94",
- "ui-select": "angular-ui/ui-select#0.14.9",
+ "ui-select": "angular-ui/ui-select#0.19.6",
"vcard": "0.2.7",
"angular-click-outside": "^2.10.1",
"ngclipboard": "^1.1.1"
diff --git a/css/public/style.css b/css/public/style.scss
index 274a0809..a8773e1a 100644
--- a/css/public/style.css
+++ b/css/public/style.scss
@@ -1,3 +1,7 @@
+#app-navigation {
+ position: relative;
+}
+
.contactdetails__header {
height: 100px;
padding-left: 20px;
@@ -129,7 +133,7 @@ avatar {
.avatar-options:hover > [class^="icon-"] {
opacity: 0.6;
}
-.avatar-options > [class^="icon-"]:hover {
+.avatar-options > [class^="icon-"]:hover {
opacity: 0.8;
}
@@ -314,7 +318,7 @@ avatar:not(.maximized) .icon-error + img {
opacity: 0.5;
}
-/* Prevent delete for last adr/mail/tel item */
+/* Prevent delete for last adr/mail/tel item */
.last-details > detailsitem.details-item-adr .icon-delete,
.last-details > detailsitem.details-item-email .icon-delete,
.last-details > detailsitem.details-item-tel .icon-delete {
@@ -322,6 +326,10 @@ avatar:not(.maximized) .icon-error + img {
}
/* addressbook settings */
+.settings-section {
+ display: block;
+}
+
ul.addressBookList > li {
padding: 6px 0;
display: flex;
@@ -422,6 +430,11 @@ input.addressBookUrl {
text-overflow: ellipsis;
}
+.select2-drop .select2-search input {
+ width: 100% !important;
+ margin-right: 0;
+}
+
.addressBookList form {
position: relative;
width: 100%;
@@ -463,16 +476,61 @@ li.calendar-share-item span.shareeIdentifier {
opacity: 0.5;
}
+/* Contact import */
#app-settings-content #upload.button {
width: 100%;
padding: 7px 10px;
padding-left: 34px;
background-position: 10px center;
text-align: left;
- margin-bottom: 10px;
+ margin: 0;
display: block;
+ margin-bottom: 0;
+ border-radius: 3px 3px 0 0;
+}
+#app-settings-content #upload.button::after {
+ left: 17px; /* half the padding */
+}
+#app-settings-content #upload.button.no-select {
+ border-radius: 3px;
+}
+contactimport {
+ margin-bottom: 3px;
+}
+contactimport .select2-container {
+ margin-top: 0;
+ width: 100%;
}
+contactimport .select2-container:after {
+ left: 15px;
+}
+
+contactimport .select2-container .select2-choice {
+ height: 100%;
+ line-height: 31px;
+ border-radius: 0 0 3px 3px;
+ border-top: none !important;
+}
+
+contactimport .select2-drop-active {
+ border-top: 1px solid #ddd;
+ box-shadow: 0 -1px 5px rgba(0, 0, 0, .15);
+ border-radius: 3px 3px 0 0;
+ margin-top: initial;
+}
+
+contactimport .ui-select-offscreen {
+ display: none;
+}
+
+contactimport .ui-select-search-hidden {
+ display: none;
+}
+
+contactimport input[type='search']::-webkit-search-cancel-button {
+ -webkit-appearance:none
+}
/* Contacts List */
#new-contact-button {
@@ -643,14 +701,14 @@ detailsitem .select2-container {
/* full width for message list on mobile */
.app-content-list {
width: 100%;
- background: white;
+ background: $color-main-background;
position: relative;
z-index: 100;
}
/* overlay message detail on top of message list */
.app-content-detail {
- background: #fff;
+ background: $color-main-background;
width: 100%;
left: 0;
height: 100%;
@@ -688,7 +746,7 @@ detailsitem .select2-container {
.contact-details-wrapper {
position: relative;
- background: white;
+ background: $color-main-background;
}
.wrapper-show {
z-index: 201;
@@ -700,3 +758,76 @@ detailsitem .select2-container {
.icon-group {
background-image: url('../../img/group.svg');
}
+
+/* Import screen */
+#import-sidebar {
+ position: absolute;
+ width: 250px;
+ height: 100%;
+ z-index: 500;
+ background: rgba(255,255,255,0.6);
+}
+#importscreen-wrapper {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ display: flex;
+ justify-content: center;
+ align-items: start;
+ padding-top: 30vh;
+ background: $color-main-background;
+ z-index: 500;
+}
+#importscreen-content {
+ width: 300px;
+ position: relative;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+}
+#importscreen-title {
+ flex-basis: 100%;
+ text-align: center;
+}
+#importscreen-percent {
+ opacity: 0.5;
+}
+#importscreen-user {
+ font-style: italic;
+ opacity: 0.5;
+ padding-top: 5px;
+}
+/* Copy nextcloud quota bar */
+#importscreen-progress {
+ display: block;
+ width: 100%;
+ padding: 0px;
+ border: 0 none;
+ background-color: #e6e6e6;
+ border-radius: 3px;
+ flex-basis: 100%;
+ height: 5px;
+}
+#importscreen-progress::-webkit-progress-bar {
+ background: transparent;
+}
+#importscreen-progress::-moz-progress-bar {
+ border-radius: 3px;
+ background: $color-primary;
+ transition: 500ms all ease-in-out;
+}
+#importscreen-progress::-webkit-progress-value {
+ border-radius: 3px;
+ background: $color-primary;
+ transition: 500ms all ease-in-out;
+}
+#importscreen-sidebar-block {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background: $color-main-background;
+ z-index: 500;
+ opacity: 0.5;
+}
diff --git a/js/components/avatar/avatar_directive.js b/js/components/avatar/avatar_directive.js
index 66873c8d..542f0702 100644
--- a/js/components/avatar/avatar_directive.js
+++ b/js/components/avatar/avatar_directive.js
@@ -10,9 +10,6 @@ angular.module('contactsApp')
contact: '=data'
},
link: function(scope, element) {
- var importText = t('contacts', 'Import');
- scope.importText = importText;
-
var input = element.find('input');
input.bind('change', function() {
var file = input.get(0).files[0];
diff --git a/js/components/contactImport/contactImport_controller.js b/js/components/contactImport/contactImport_controller.js
index 00063f7f..2d01a254 100644
--- a/js/components/contactImport/contactImport_controller.js
+++ b/js/components/contactImport/contactImport_controller.js
@@ -1,7 +1,33 @@
angular.module('contactsApp')
-.controller('contactimportCtrl', function(ContactService) {
+.controller('contactimportCtrl', function(ContactService, AddressBookService) {
var ctrl = this;
+ ctrl.t = {
+ importText : t('contacts', 'Import into'),
+ importingText : t('contacts', 'Importing...'),
+ selectAddressbook : t('contacts', 'Select your addressbook')
+ };
+
ctrl.import = ContactService.import.bind(ContactService);
+ ctrl.loading = true;
+ ctrl.importText = ctrl.t.importText;
+ ctrl.importing = false;
+ ctrl.loadingClass = 'icon-upload';
+
+ AddressBookService.getAll().then(function(addressBooks) {
+ ctrl.addressBooks = addressBooks;
+ ctrl.loading = false;
+ ctrl.selectedAddressBook = AddressBookService.getDefaultAddressBook();
+ });
+
+ ctrl.stopHideMenu = function(isOpen) {
+ if(isOpen) {
+ // disabling settings bind
+ $('#app-settings-header > button').data('apps-slide-toggle', false);
+ } else {
+ // reenabling it
+ $('#app-settings-header > button').data('apps-slide-toggle', '#app-settings-content');
+ }
+ };
});
diff --git a/js/components/contactImport/contactImport_directive.js b/js/components/contactImport/contactImport_directive.js
index 2280d7f1..d8ea85e5 100644
--- a/js/components/contactImport/contactImport_directive.js
+++ b/js/components/contactImport/contactImport_directive.js
@@ -1,10 +1,7 @@
angular.module('contactsApp')
-.directive('contactimport', function(ContactService) {
+.directive('contactimport', function(ContactService, ImportService, $rootScope) {
return {
- link: function(scope, element) {
- var importText = t('contacts', 'Import');
- scope.importText = importText;
-
+ link: function(scope, element, attrs, ctrl) {
var input = element.find('input');
input.bind('change', function() {
angular.forEach(input.get(0).files, function(file) {
@@ -12,12 +9,39 @@ angular.module('contactsApp')
reader.addEventListener('load', function () {
scope.$apply(function () {
- ContactService.import.call(ContactService, reader.result, file.type, null, function (progress) {
+ // Indicate the user we started something
+ ctrl.importText = ctrl.t.importingText;
+ ctrl.loadingClass = 'icon-loading-small';
+ ctrl.importing = true;
+ $rootScope.importing = true;
+
+ ContactService.import.call(ContactService, reader.result, file.type, ctrl.selectedAddressBook, function (progress, user) {
if (progress === 1) {
- scope.importText = importText;
+ ctrl.importText = ctrl.t.importText;
+ ctrl.loadingClass = 'icon-upload';
+ ctrl.importing = false;
+ $rootScope.importing = false;
+ ImportService.importPercent = 0;
+ ImportService.importing = false;
+ ImportService.importedUser = '';
+ ImportService.selectedAddressBook = '';
} else {
- scope.importText = parseInt(Math.floor(progress * 100)) + '%';
+ // Ugly hack, hide sidebar on import & mobile
+ // Simulate click since we can't directly access snapper
+ if($(window).width() <= 768 && $('body').hasClass('snapjs-left')) {
+ $('#app-navigation-toggle').click();
+ $('body').removeClass('snapjs-left');
+ }
+
+ ImportService.importPercent = parseInt(Math.floor(progress * 100));
+ ImportService.importing = true;
+ ImportService.importedUser = user;
+ ImportService.selectedAddressBook = ctrl.selectedAddressBook.displayName;
}
+ scope.$apply();
+
+ /* Broadcast service update */
+ $rootScope.$broadcast('importing', true);
});
});
}, false);
@@ -29,6 +53,8 @@ angular.module('contactsApp')
input.get(0).value = '';
});
},
- templateUrl: OC.linkTo('contacts', 'templates/contactImport.html')
+ templateUrl: OC.linkTo('contacts', 'templates/contactImport.html'),
+ controller: 'contactimportCtrl',
+ controllerAs: 'ctrl'
};
});
diff --git a/js/components/contactList/contactList_controller.js b/js/components/contactList/contactList_controller.js
index 664df9d6..dfecd34e 100644
--- a/js/components/contactList/contactList_controller.js
+++ b/js/components/contactList/contactList_controller.js
@@ -81,7 +81,14 @@ angular.module('contactsApp')
uid: ev.uid
});
}
- ctrl.contacts = ev.contacts;
+ else if (ev.event === 'importend') {
+ $route.updateParams({
+ gid: t('contacts', 'All contacts')
+ });
+ }
+ if(ev.contacts.length !== 0) {
+ ctrl.contacts = ev.contacts;
+ }
}); });
});
diff --git a/js/components/groupList/groupList_controller.js b/js/components/groupList/groupList_controller.js
index 417f60a7..cf6ec402 100644
--- a/js/components/groupList/groupList_controller.js
+++ b/js/components/groupList/groupList_controller.js
@@ -1,5 +1,5 @@
angular.module('contactsApp')
-.controller('grouplistCtrl', function($scope, ContactService, SearchService, $routeParams) {
+.controller('grouplistCtrl', function($scope, $timeout, ContactService, SearchService, $routeParams) {
var ctrl = this;
ctrl.groups = [];
@@ -15,11 +15,11 @@ angular.module('contactsApp')
// Update groupList on contact add/delete/update
ContactService.registerObserverCallback(function(ev) {
if (ev.event !== 'getFullContacts') {
- $scope.$apply(function() {
+ $timeout(function () { $scope.$apply(function() {
ContactService.getGroupList().then(function(groups) {
ctrl.groups = groups;
});
- });
+ }); });
}
});
diff --git a/js/components/importScreen/importScreen_controller.js b/js/components/importScreen/importScreen_controller.js
new file mode 100644
index 00000000..b4f53d17
--- /dev/null
+++ b/js/components/importScreen/importScreen_controller.js
@@ -0,0 +1,18 @@
+angular.module('contactsApp')
+.controller('importscreenCtrl', function($scope, ImportService) {
+ var ctrl = this;
+
+ ctrl.t = {
+ importingTo : t('contacts', 'Importing into'),
+ selectAddressbook : t('contacts', 'Select your addressbook')
+ };
+
+ // Broadcast update
+ $scope.$on('importing', function () {
+ ctrl.selectedAddressBook = ImportService.selectedAddressBook;
+ ctrl.importedUser = ImportService.importedUser;
+ ctrl.importing = ImportService.importing;
+ ctrl.importPercent = ImportService.importPercent;
+ });
+
+});
diff --git a/js/components/importScreen/importScreen_directive.js b/js/components/importScreen/importScreen_directive.js
new file mode 100644
index 00000000..790c6254
--- /dev/null
+++ b/js/components/importScreen/importScreen_directive.js
@@ -0,0 +1,11 @@
+angular.module('contactsApp')
+.directive('importscreen', function() {
+ return {
+ restrict: 'EA', // has to be an attribute to work with core css
+ scope: {},
+ controller: 'importscreenCtrl',
+ controllerAs: 'ctrl',
+ bindToController: {},
+ templateUrl: OC.linkTo('contacts', 'templates/importScreen.html')
+ };
+});
diff --git a/js/services/contact_service.js b/js/services/contact_service.js
index 26ac4dfe..cd9890b4 100644
--- a/js/services/contact_service.js
+++ b/js/services/contact_service.js
@@ -163,7 +163,7 @@ angular.module('contactsApp')
});
};
- this.create = function(newContact, addressBook, uid) {
+ this.create = function(newContact, addressBook, uid, fromImport) {
addressBook = addressBook || AddressBookService.getDefaultAddressBook();
try {
newContact = newContact || new Contact(addressBook);
@@ -192,11 +192,14 @@ angular.module('contactsApp')
).then(function(xhr) {
newContact.setETag(xhr.getResponseHeader('ETag'));
contacts.put(newUid, newContact);
- notifyObservers('create', newUid);
- $('#details-fullName').select();
+ if (fromImport !== true) {
+ notifyObservers('create', newUid);
+ $('#details-fullName').select();
+ }
return newContact;
}).catch(function() {
OC.Notification.showTemporary(t('contacts', 'Contact could not be created.'));
+ return false;
});
};
@@ -213,6 +216,9 @@ angular.module('contactsApp')
}
return;
}
+
+ notifyObservers('importstart');
+
var num = 1;
for(var i in singleVCards) {
var newContact = new Contact(addressBook, {addressData: singleVCards[i]});
@@ -224,12 +230,19 @@ angular.module('contactsApp')
num++;
continue;
}
- this.create(newContact, addressBook).then(function() {
+ this.create(newContact, addressBook, '', true).then(function(xhrContact) {
+ if (xhrContact !== false) {
+ var xhrContactName = xhrContact.displayName();
+ }
// Update the progress indicator
if (progressCallback) {
- progressCallback(num / singleVCards.length);
+ progressCallback(num / singleVCards.length, xhrContactName);
}
num++;
+ /* Import is over, let's notify */
+ if(num === singleVCards.length) {
+ notifyObservers('importend');
+ }
});
}
};
diff --git a/js/services/import_service.js b/js/services/import_service.js
new file mode 100644
index 00000000..b936108d
--- /dev/null
+++ b/js/services/import_service.js
@@ -0,0 +1,14 @@
+angular.module('contactsApp')
+.service('ImportService', function() {
+
+ this.importing = false;
+ this.selectedAddressBook = t('contacts', 'Import into');
+ this.importedUser = t('contacts', 'Waiting for the server to be ready...');
+ this.importPercent = 0;
+
+ this.t = {
+ importText : t('contacts', 'Import into'),
+ importingText : t('contacts', 'Importing...')
+ };
+
+});
diff --git a/templates/addressBookList.html b/templates/addressBookList.html
index 472584e7..4a9b0738 100644
--- a/templates/addressBookList.html
+++ b/templates/addressBookList.html
@@ -3,7 +3,9 @@
<li ng-repeat="addressBook in ctrl.addressBooks" addressbook data="addressBook" list="ctrl.addressBooks"></li>
<li>
<form ng-submit="ctrl.createAddressBook()">
- <input id="newList" placeholder="{{ctrl.t.addressBookName}}" class="newAddressBookInput" ng-model="ctrl.newAddressBookName" type="text" />
+ <input id="newList" placeholder="{{ctrl.t.addressBookName}}" class="newAddressBookInput"
+ ng-model="ctrl.newAddressBookName" type="text"
+ autocomplete="off" autocorrect="off" spellcheck="false" />
<input type="submit" value="" class="newAddressBookSubmit inline-button icon-confirm action pull-right" />
</form>
</li>
diff --git a/templates/contactImport.html b/templates/contactImport.html
index ead21688..64bf86b2 100644
--- a/templates/contactImport.html
+++ b/templates/contactImport.html
@@ -1,2 +1,13 @@
<input type="file" id="contact-import" class="hidden-visually" multiple/>
-<label for="contact-import" class="icon-upload button" id="upload">{{importText}}</label>
+<label for="contact-import" class="button {{ctrl.loadingClass}}" ng-class="{'no-select': ctrl.addressBooks.length === 1}" id="upload">
+ {{ctrl.importText}}
+ <span ng-if="ctrl.addressBooks.length === 1 && !ctrl.importing" ng-class="{'icon-loading-small': ctrl.loading}">{{ctrl.selectedAddressBook.displayName}}</span>
+</label>
+<ui-select ng-model="ctrl.selectedAddressBook" ng-if="ctrl.addressBooks.length > 1" search-enabled="ctrl.addressBooks.length > 4"
+ theme="select2" class="form-control" title="{{ctrl.t.selectAddressbook}}"
+ uis-open-close="ctrl.stopHideMenu(isOpen)" ng-class="{'icon-loading-small': ctrl.loading}">
+ <ui-select-match placeholder="{{ctrl.t.selectAddressbook}}">{{$select.selected.displayName}}</ui-select-match>
+ <ui-select-choices repeat="addressBook in ctrl.addressBooks | filter: {displayName: $select.search}">
+ {{addressBook.displayName}}
+ </ui-select-choices>
+</ui-select> \ No newline at end of file
diff --git a/templates/importScreen.html b/templates/importScreen.html
new file mode 100644
index 00000000..bcfb79f9
--- /dev/null
+++ b/templates/importScreen.html
@@ -0,0 +1,9 @@
+<div id="importscreen-wrapper" ng-show="ctrl.importing">
+ <div id="importscreen-content">
+ <span class="icon-upload"></span>
+ <h3 id="importscreen-title">{{ctrl.t.importingTo}} {{ctrl.selectedAddressBook}}</h3>
+ <progress id="importscreen-progress" max="100" value="{{ctrl.importPercent}}"></progress>
+ <span id="importscreen-user">{{ctrl.importedUser}}</span>
+ <span id="importscreen-percent">{{ctrl.importPercent}} %</span>
+ </div>
+</div>
diff --git a/templates/main.php b/templates/main.php
index 784cf597..fdd6b581 100644
--- a/templates/main.php
+++ b/templates/main.php
@@ -28,6 +28,7 @@ vendor_style('select2/select2');
<div id="app" ng-app="contactsApp">
<div id="app-navigation">
+ <div id="importscreen-sidebar-block" class="icon-loading" ng-show="$root.importing"></div>
<newContactButton></newContactButton>
<ul groupList></ul>
@@ -39,9 +40,9 @@ vendor_style('select2/select2');
</button>
</div>
<div id="app-settings-content">
- <addressBookList></addressBookList>
- <contactImport></contactImport>
- <sortBy></sortBy>
+ <addressBookList class="settings-section"></addressBookList>
+ <contactImport class="settings-section"></contactImport>
+ <sortBy class="settings-section"></sortBy>
</div>
</div>
</div>
@@ -51,5 +52,6 @@ vendor_style('select2/select2');
<contactlist></contactlist>
</div>
<div class="app-content-detail" ng-view></div>
+ <importscreen class="emptycontent"></importscreen>
</div>
</div>