diff options
author | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2020-01-24 18:27:35 +0100 |
---|---|---|
committer | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2020-01-29 17:26:05 +0100 |
commit | 0ff05f50d458b5875707bea33ccf2b8976254edb (patch) | |
tree | d7e0c03b52eee2935508ceb848d7734d3e8b0f62 /src | |
parent | 503be8085b4e02e22bd69dbea959bbea7534cc76 (diff) |
Allow importing from files
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/components/ContactDetails/ContactDetailsAvatar.vue | 7 | ||||
-rw-r--r-- | src/components/Properties/PropertyDateTime.vue | 2 | ||||
-rw-r--r-- | src/components/Properties/PropertyMultipleText.vue | 4 | ||||
-rw-r--r-- | src/components/Settings/SettingsImportContacts.vue | 194 | ||||
-rw-r--r-- | src/components/SettingsSection.vue | 2 | ||||
-rw-r--r-- | src/main.js | 4 | ||||
-rw-r--r-- | src/models/rfcProps.js | 2 | ||||
-rw-r--r-- | src/router/index.js | 2 | ||||
-rw-r--r-- | src/services/cdav.js | 4 |
9 files changed, 191 insertions, 30 deletions
diff --git a/src/components/ContactDetails/ContactDetailsAvatar.vue b/src/components/ContactDetails/ContactDetailsAvatar.vue index 703a3d4f..76433d41 100644 --- a/src/components/ContactDetails/ContactDetailsAvatar.vue +++ b/src/components/ContactDetails/ContactDetailsAvatar.vue @@ -88,8 +88,9 @@ import debounce from 'debounce' import { ActionLink, ActionButton } from '@nextcloud/vue' -import { getFilePickerBuilder } from 'nextcloud-dialogs' -import { generateRemoteUrl } from 'nextcloud-router' +import { getFilePickerBuilder } from '@nextcloud/dialogs' +import { generateRemoteUrl } from '@nextcloud/router' +import { getCurrentUser } from '@nextcloud/auth' const axios = () => import('axios') @@ -113,7 +114,7 @@ export default { maximizeAvatar: false, opened: false, loading: false, - root: generateRemoteUrl(`dav/files/${OC.getCurrentUser().uid}`), + root: generateRemoteUrl(`dav/files/${getCurrentUser().uid}`), width: 0, height: 0, } diff --git a/src/components/Properties/PropertyDateTime.vue b/src/components/Properties/PropertyDateTime.vue index 03144509..cd0af107 100644 --- a/src/components/Properties/PropertyDateTime.vue +++ b/src/components/Properties/PropertyDateTime.vue @@ -85,7 +85,7 @@ import debounce from 'debounce' import moment from 'moment' import { DatetimePicker } from '@nextcloud/vue' -import { getLocale } from 'nextcloud-l10n' +import { getLocale } from '@nextcloud/l10n' import { VCardTime } from 'ical.js' import PropertyMixin from '../../mixins/PropertyMixin' diff --git a/src/components/Properties/PropertyMultipleText.vue b/src/components/Properties/PropertyMultipleText.vue index cd4107e9..896ff661 100644 --- a/src/components/Properties/PropertyMultipleText.vue +++ b/src/components/Properties/PropertyMultipleText.vue @@ -63,8 +63,8 @@ @input="updateValue"> <!-- props actions --> - <PropertyActions class="property__actions--floating" - v-if="!isReadOnly" + <PropertyActions v-if="!isReadOnly" + class="property__actions--floating" :actions="actions" :property-component="this" @delete="deleteProperty" /> diff --git a/src/components/Settings/SettingsImportContacts.vue b/src/components/Settings/SettingsImportContacts.vue index d7583a3a..f0b6f04c 100644 --- a/src/components/Settings/SettingsImportContacts.vue +++ b/src/components/Settings/SettingsImportContacts.vue @@ -23,21 +23,56 @@ <template> <div class="import-contact"> <template v-if="!isNoAddressbookAvailable"> - <input id="contact-import" - :disabled="isImporting" - type="file" - class="hidden-visually" - @change="processFile"> - <label id="upload" for="contact-import" class="button import-contact__multiselect-label icon-upload"> - {{ isImporting ? t('contacts', 'Importing into') : t('contacts', 'Import into') }} - </label> - <multiselect - v-model="selectedAddressbook" - :options="options" - :disabled="isSingleAddressbook || isImporting" - :placeholder="t('contacts', 'Contacts')" - label="displayName" - class="import-contact__multiselect" /> + <button class="import-contact__button-main" @click="toggleModal"> + <span class="icon-upload" /> + {{ t('contacts', 'Import contacts') }} + </button> + <Modal v-if="isOpened" + ref="modal" + class="import-contact__modal" + :title="t('contacts', 'Import contacts')" + @close="toggleModal"> + <section class="import-contact__modal-addressbook"> + <h3>{{ t('contacts', 'Import contacts') }}</h3> + <multiselect + v-if="!isSingleAddressbook" + id="select-addressbook" + v-model="selectedAddressbook" + :allow-empty="false" + :options="options" + :disabled="isSingleAddressbook || isImporting" + :placeholder="t('contacts', 'Contacts')" + label="displayName" + class="import-contact__multiselect"> + <template slot="singleLabel" slot-scope="{ option }"> + {{ t('contacts', `Import into the '{addressbookName}' addressbook`, { addressbookName: option.displayName }) }} + </template> + </multiselect> + </section> + <section class="import-contact__modal-pick"> + <input id="contact-import" + ref="contact-import-input" + :disabled="loading || isImporting" + type="file" + class="hidden-visually" + @change="processFile"> + <button + :disabled="loading" + class="button import-contact__button import-contact__button--local" + @click="clickImportInput"> + <span class="import-contact__button-icon icon-upload" /> + {{ t('contacts', 'Select local file') }} + </button> + <button + :class="{'icon-loading': loading}" + :disabled="loading" + class="button primary import-contact__button import-contact__button--files" + @click="openPicker"> + <span class="import-contact__button-icon icon-folder-white" /> + {{ t('contacts', 'Import from files') }} + </button> + </section> + </Modal> </template> <button v-else id="upload" @@ -49,12 +84,31 @@ </template> <script> +import { encodePath } from '@nextcloud/paths' +import { getCurrentUser } from '@nextcloud/auth' +import { generateRemoteUrl } from '@nextcloud/router' +import { getFilePickerBuilder } from '@nextcloud/dialogs' +import axios from 'axios' + +const CancelToken = axios.CancelToken + +const picker = getFilePickerBuilder(t('contacts', 'Choose a vcard file to import')) + .setMultiSelect(false) + .setModal(true) + .setType(1) + .allowDirectories(false) + .setMimeTypeFilter('text/vcard') + .build() + export default { name: 'SettingsImportContacts', data() { return { + cancelRequest: () => {}, importDestination: false, + isOpened: false, + loading: false, } }, @@ -110,20 +164,126 @@ export default { }, }, methods: { + /** + * Process input type file change + * + * @param {Event} event the input change event + */ processFile(event) { + this.loading = true + this.$store.dispatch('changeStage', 'parsing') + const file = event.target.files[0] const reader = new FileReader() const selectedAddressbook = this.selectedAddressbook - this.$store.dispatch('changeStage', 'parsing') - this.$store.dispatch('setAddressbook', selectedAddressbook.displayName) + const self = this reader.onload = function(e) { + self.isOpened = false self.$store.dispatch('importContactsIntoAddressbook', { vcf: reader.result, addressbook: selectedAddressbook }) + // reset input event.target.value = '' + self.resetState() } reader.readAsText(file) }, + + toggleModal() { + this.isOpened = !this.isOpened + // cancel any ongoing request if closed + if (!this.isOpened) { + this.cancelRequest() + } + }, + + clickImportInput() { + this.$refs['contact-import-input'].click() + }, + + /** + * Open nextcloud file picker + */ + async openPicker() { + try { + this.loading = true + // unlikely, but let's cancel any previous request + this.cancelRequest() + + // prepare cancel token for axios request + const source = CancelToken.source() + this.cancelRequest = source.cancel + + // pick and retrieve file + const path = await picker.pick() + const file = await axios.get(generateRemoteUrl(`dav/files/${getCurrentUser().uid}`) + encodePath(path), { + cancelToken: source.token, + }) + + this.$store.dispatch('changeStage', 'parsing') + this.$store.dispatch('setAddressbook', this.selectedAddressbook.displayName) + + if (file.data) { + await this.$store.dispatch('importContactsIntoAddressbook', { vcf: file.data, addressbook: this.selectedAddressbook }) + } + } catch (error) { + console.error('Something wrong happened while picking a file', error) + } finally { + this.resetState() + } + }, + + /** + * Reset default component state + */ + resetState() { + this.cancelRequest = () => {} + this.importDestination = false + this.isOpened = false + this.loading = false + }, }, } </script> + +<style lang="scss" scoped> +.import-contact { + &__modal { + section { + padding: 22px; + // only one padding bewteen sections + &:not(:last-child) { + padding-bottom: 0; + } + } + &-pick { + display: flex; + align-items: center; + flex-wrap: wrap; + justify-content: space-evenly; + } + } + &__button { + display: flex; + align-items: center; + flex: 0 1 150px; + width: 150px; + // spread evenly + margin: 10px; + padding: 10px; + &-icon { + width: 32px; + height: 32px; + margin-right: 5px; + } + &-main { + width: 100%; + } + &--cancel:not(:focus):not(:hover) { + border-color: transparent; + background-color: transparent; + } + } +} + +</style> diff --git a/src/components/SettingsSection.vue b/src/components/SettingsSection.vue index 7053356b..e0757716 100644 --- a/src/components/SettingsSection.vue +++ b/src/components/SettingsSection.vue @@ -26,11 +26,11 @@ <SettingsAddressbook v-for="addressbook in addressbooks" :key="addressbook.id" :addressbook="addressbook" /> </ul> <SettingsNewAddressbook :addressbooks="addressbooks" /> + <SettingsSortContacts class="settings-section" /> <SettingsImportContacts :addressbooks="addressbooks" class="settings-section" @clicked="onClickImport" @fileLoaded="onLoad" /> - <SettingsSortContacts class="settings-section" /> </div> </template> diff --git a/src/main.js b/src/main.js index 87e8c2db..47b4353e 100644 --- a/src/main.js +++ b/src/main.js @@ -28,8 +28,8 @@ import App from './ContactsRoot' import router from './router' import store from './store' import { sync } from 'vuex-router-sync' -import { generateFilePath } from 'nextcloud-router' -import { getRequestToken } from 'nextcloud-auth' +import { generateFilePath } from '@nextcloud/router' +import { getRequestToken } from '@nextcloud/auth' /** GLOBAL COMPONENTS AND DIRECTIVE */ import { Actions, DatetimePicker, Multiselect, PopoverMenu, Modal } from '@nextcloud/vue' diff --git a/src/models/rfcProps.js b/src/models/rfcProps.js index ca9b4e33..311f61f5 100644 --- a/src/models/rfcProps.js +++ b/src/models/rfcProps.js @@ -20,7 +20,7 @@ * */ import { VCardTime } from 'ical.js' -// import { loadState } from 'nextcloud-initial-state' +// import { loadState } from '@nextcloud/initial-state' import { loadState } from '../services/initialstate' import ActionCopyNtoFN from '../components/Actions/ActionCopyNtoFN' diff --git a/src/router/index.js b/src/router/index.js index 1f8fd221..eb2c9758 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -22,7 +22,7 @@ import Vue from 'vue' import Router from 'vue-router' -import { generateUrl } from 'nextcloud-router' +import { generateUrl } from '@nextcloud/router' import Contacts from '../views/Contacts' Vue.use(Router) diff --git a/src/services/cdav.js b/src/services/cdav.js index da5c6549..82ba3759 100644 --- a/src/services/cdav.js +++ b/src/services/cdav.js @@ -21,8 +21,8 @@ */ import DavClient from 'cdav-library' -import { generateRemoteUrl } from 'nextcloud-router' -import { getRequestToken } from 'nextcloud-auth' +import { generateRemoteUrl } from '@nextcloud/router' +import { getRequestToken } from '@nextcloud/auth' function xhrProvider() { const headers = { |