diff options
-rw-r--r-- | css/importScreen.scss | 17 | ||||
-rw-r--r-- | package-lock.json | 33 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | src/components/ImportScreen.vue | 33 | ||||
-rw-r--r-- | src/services/parseVcf.js | 6 | ||||
-rw-r--r-- | src/store/addressbooks.js | 43 | ||||
-rw-r--r-- | src/store/importState.js | 5 |
7 files changed, 108 insertions, 30 deletions
diff --git a/css/importScreen.scss b/css/importScreen.scss index c57dca90..3d815389 100644 --- a/css/importScreen.scss +++ b/css/importScreen.scss @@ -25,13 +25,16 @@ padding-top: 20px; } &__progress { - width: 50%; - position: absolute; - left: 25%; + width: 50%; + min-width: 300px; + margin: auto; } &__tracker { - padding-top: 10px; - position: absolute; - left: 25%; + display: flex; + justify-content: space-between; + width: 50%; + min-width: 300px; + margin: auto; + padding-top: 10px; } -}
\ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index f5c2f5d8..2efc69c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10869,12 +10869,11 @@ "dev": true }, "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", "requires": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -10884,13 +10883,29 @@ "dev": true, "requires": { "p-limit": "^1.1.0" + }, + "dependencies": { + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } } }, "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" }, "pako": { "version": "1.0.6", diff --git a/package.json b/package.json index ca5646c8..a0e58ba0 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "ical.js": "^1.2.2", "moment": "^2.22.2", "nextcloud-vue": "^0.1.3", + "p-limit": "^2.0.0", "uuid": "^3.3.2", "v-tooltip": "^2.0.0-rc.33", "vue": "^2.5.17", diff --git a/src/components/ImportScreen.vue b/src/components/ImportScreen.vue index bb903442..b58182fa 100644 --- a/src/components/ImportScreen.vue +++ b/src/components/ImportScreen.vue @@ -23,19 +23,46 @@ <template> <div class="emptycontent import-screen"> <p class="icon-upload" /> - <h3 class="import-screen__header">{{ t('contacts', 'Importing into') }} {{ importState.addressbook }}</h3> - <progress :max="importState.total" :value="importState.accepted" class="import-screen__progress" /> - <p class="import-screen__tracker">{{ Math.floor(importState.accepted/(importState.total + 1)) * 100 }} %</p> + <h3 class="import-screen__header">{{ t('contacts', 'Importing {total} contacts into', { total }) }} {{ addressbook }}</h3> + <progress :max="total" :value="progress" class="import-screen__progress" /> + <p class="import-screen__tracker"> + <span>{{ percentage }} %</span> + <span v-tooltip.auto="t('contacts', 'Open your browser console for more details')">{{ denied }} {{ t('contacts', 'failed') }}</span> + </p> </div> </template> <script> +import Vue from 'vue' +import VTooltip from 'v-tooltip' + +Vue.use(VTooltip) export default { name: 'ImportScreen', computed: { importState() { return this.$store.getters.getImportState + }, + addressbook() { + return this.importState.addressbook + }, + total() { + return this.importState.total + }, + accepted() { + return this.importState.accepted + }, + denied() { + return this.importState.denied + }, + progress() { + return this.accepted + this.denied + }, + percentage() { + return this.total <= 0 + ? 0 + : Math.floor(this.progress / this.total * 100) } } } diff --git a/src/services/parseVcf.js b/src/services/parseVcf.js index 65d5f95c..11b1fc49 100644 --- a/src/services/parseVcf.js +++ b/src/services/parseVcf.js @@ -26,14 +26,13 @@ import Store from '../store/index' export default function parseVcf(data = '', addressbook) { let regexp = /BEGIN:VCARD[\s\S]*?END:VCARD/mgi let vCards = data.match(regexp) - let importState = Store.getters.getImportState if (!vCards) { console.debug('Error during the parsing of the following vcf file: ', data) return [] } - importState.total = vCards.length + Store.dispatch('setTotal', vCards.length) // Not using map because we want to only push valid contacts // map force to return at least undefined @@ -41,11 +40,10 @@ export default function parseVcf(data = '', addressbook) { try { // console.log(vCards.indexOf(vCard)) let contact = new Contact(vCard, addressbook) - importState.accepted++ contacts.push(contact) } catch (e) { // Parse error! Do not stop here... - importState.denied++ + Store.dispatch('incrementDenied') console.error(e) } return contacts diff --git a/src/store/addressbooks.js b/src/store/addressbooks.js index 74a47bef..185a6465 100644 --- a/src/store/addressbooks.js +++ b/src/store/addressbooks.js @@ -22,9 +22,11 @@ */ import Vue from 'vue' +import ICAL from 'ical.js' import parseVcf from '../services/parseVcf' import client from '../services/cdav' import Contact from '../models/contact' +import pLimit from 'p-limit' const addressbookModel = { id: '', @@ -327,15 +329,42 @@ const actions = { * @param {Object} context the store mutations * @param {Object} importDetails = { vcf, addressbook } */ - importContactsIntoAddressbook(context, { vcf, addressbook }) { - let contacts = parseVcf(vcf, addressbook) + async importContactsIntoAddressbook(context, { vcf, addressbook }) { + const contacts = parseVcf(vcf, addressbook) context.commit('changeStage', 'importing') - contacts.forEach(contact => { - context.commit('addContact', contact) - context.commit('addContactToAddressbook', contact) - context.commit('appendGroupsFromContacts', [contact]) + + // max simultaneous requests + const limit = pLimit(3) + const requests = [] + + // create the array of requests to send + contacts.map(async contact => { + // Get vcard string + try { + let vData = ICAL.stringify(contact.vCard.jCal) + // push contact to server and use limit + requests.push(limit(() => contact.addressbook.dav.createVCard(vData) + .then((response) => { + // success, update store + context.commit('addContact', contact) + context.commit('addContactToAddressbook', contact) + context.commit('appendGroupsFromContacts', [contact]) + context.commit('incrementAccepted') + }) + .catch((error) => { + // error + context.commit('incrementDenied') + console.error(error) + }) + )) + } catch (e) { + context.commit('incrementDenied') + } + }) + + Promise.all(requests).then(() => { + // context.commit('changeStage', 'default') }) - context.commit('changeStage', 'default') }, /** diff --git a/src/store/importState.js b/src/store/importState.js index 024b0ac0..313c8d6d 100644 --- a/src/store/importState.js +++ b/src/store/importState.js @@ -77,6 +77,11 @@ const mutations = { */ changeStage(state, stage) { state.importState.stage = stage + if (stage === 'default') { + state.accepted = 0 + state.denied = 0 + state.total = 0 + } } } |