diff options
author | John Molakvoæ <skjnldsv@users.noreply.github.com> | 2017-08-15 15:47:22 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-15 15:47:22 +0200 |
commit | 2bebe03eab7e5a55570dbb3e0fe9db33d10b63c5 (patch) | |
tree | 5d9d4511aa94480a622933e217dd1effdd963f08 | |
parent | 20455cd24addfd5d855c6864cb5c5b291c8791d1 (diff) | |
parent | cbd0f98b6f39668def754cf9e78f9d953dbe65e7 (diff) |
Merge pull request #123 from nextcloud/import-into-custom-ab
Allow user to import contacts into custom addressbook
-rw-r--r-- | bower.json | 2 | ||||
-rw-r--r-- | css/public/style.scss (renamed from css/public/style.css) | 143 | ||||
-rw-r--r-- | js/components/avatar/avatar_directive.js | 3 | ||||
-rw-r--r-- | js/components/contactImport/contactImport_controller.js | 28 | ||||
-rw-r--r-- | js/components/contactImport/contactImport_directive.js | 44 | ||||
-rw-r--r-- | js/components/contactList/contactList_controller.js | 9 | ||||
-rw-r--r-- | js/components/groupList/groupList_controller.js | 6 | ||||
-rw-r--r-- | js/components/importScreen/importScreen_controller.js | 18 | ||||
-rw-r--r-- | js/components/importScreen/importScreen_directive.js | 11 | ||||
-rw-r--r-- | js/services/contact_service.js | 23 | ||||
-rw-r--r-- | js/services/import_service.js | 14 | ||||
-rw-r--r-- | templates/addressBookList.html | 4 | ||||
-rw-r--r-- | templates/contactImport.html | 13 | ||||
-rw-r--r-- | templates/importScreen.html | 9 | ||||
-rw-r--r-- | templates/main.php | 8 |
15 files changed, 301 insertions, 34 deletions
@@ -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> |