diff options
author | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2019-03-29 10:03:03 +0100 |
---|---|---|
committer | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2019-03-29 10:03:03 +0100 |
commit | 80ea98fdab10d08b3d31d3b00129cb49512b24fe (patch) | |
tree | ebf69fcfd18efe52e4d1b4b302915f7114b596e3 /src | |
parent | 0444744e0b4432406bf32dcb7ab7492dfe97cd70 (diff) |
Allow to pick avatar from files + use modal
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/components/ContactDetails.vue | 5 | ||||
-rw-r--r-- | src/components/ContactDetails/ContactDetailsAvatar.vue | 151 | ||||
-rw-r--r-- | src/main.js | 6 |
3 files changed, 138 insertions, 24 deletions
diff --git a/src/components/ContactDetails.vue b/src/components/ContactDetails.vue index 75014703..ac1ff0b1 100644 --- a/src/components/ContactDetails.vue +++ b/src/components/ContactDetails.vue @@ -134,8 +134,6 @@ import PQueue from 'p-queue' import qr from 'qr-image' import { stringify } from 'ical.js' -import { Modal } from 'nextcloud-vue' - import rfcProps from 'Models/rfcProps' import validate from 'Services/validate' @@ -157,8 +155,7 @@ export default { PropertyGroups, PropertyRev, AddNewProp, - ContactAvatar, - Modal + ContactAvatar }, props: { diff --git a/src/components/ContactDetails/ContactDetailsAvatar.vue b/src/components/ContactDetails/ContactDetailsAvatar.vue index 54636f17..7557cec2 100644 --- a/src/components/ContactDetails/ContactDetailsAvatar.vue +++ b/src/components/ContactDetails/ContactDetailsAvatar.vue @@ -22,25 +22,39 @@ --> <template> - <div :class="{'maximised':maximizeAvatar }" class="contact-header-avatar"> + <div class="contact-header-avatar"> <div class="contact-header-avatar__wrapper"> - <div class="contact-header-avatar__background" @click="toggleSize" /> + <div class="contact-header-avatar__background" @click="toggleModal" /> <div v-if="contact.photo" :style="{ 'backgroundImage': `url(${photo})` }" class="contact-header-avatar__photo" - @click="toggleSize" /> - <div class="contact-header-avatar__options"> - <input id="contact-avatar-upload" type="file" class="hidden" - accept="image/*" @change="processFile"> - <label v-if="!contact.addressbook.readOnly" v-tooltip.auto="t('contacts', 'Upload a new picture')" - for="contact-avatar-upload" class="icon-upload-force-white" @click="processFile" /> - <div v-if="maximizeAvatar && !contact.addressbook.readOnly" class="icon-delete-force-white" @click="removePhoto" /> - <a v-if="maximizeAvatar" :href="contact.url + '?photo'" class="icon-download-force-white" /> + @click="toggleModal" /> + + <div v-click-outside="closeMenu" class="contact-header-avatar__options"> + <a v-tooltip.bottom="t('contacts', 'Add a new picture')" href="#" class="contact-avatar-options" + :class="loading ? 'icon-loading-small' : 'icon-picture-force-white'" + @click.prevent="toggleMenu" /> + <input id="contact-avatar-upload" ref="uploadInput" type="file" + class="hidden" accept="image/*" @change="processFile"> + </div> + + <modal v-if="maximizeAvatar" class="contact-header-modal__photo" :actions="modalActions" + @close="toggleModal"> + <img :src="photo" class="contact-header-modal__photo"> + </modal> + + <!-- out of the avatar__options because of the overflow hidden --> + <div :class="{ 'open': opened }" class="contact-avatar-options__popovermenu popovermenu"> + <popover-menu :menu="actions" /> </div> </div> </div> </template> <script> +import { pickFileOrDirectory } from 'nextcloud-server/dist/files' +import { generateRemoteUrl } from 'nextcloud-server/dist/router' + +const axios = () => import('axios') export default { name: 'ContactAvatar', @@ -51,9 +65,13 @@ export default { required: true } }, + data() { return { - maximizeAvatar: false + maximizeAvatar: false, + opened: false, + loading: false, + root: generateRemoteUrl(`dav/files/${OC.getCurrentUser().uid}`) } }, computed: { @@ -65,6 +83,35 @@ export default { return `data:image;base64,${this.contact.photo.split(',').pop()}` } return this.contact.photo + }, + actions() { + return [ + { + icon: 'icon-upload', + text: t('contacts', 'Upload a new picture'), + action: this.selectFileInput + }, + { + icon: 'icon-picture', + text: t('contacts', 'Choose from files'), + action: this.selectFilePicker + } + ] + }, + modalActions() { + return [...this.actions, ...[ + { + icon: 'icon-delete', + text: t('contacts', 'Delete picture'), + action: this.removePhoto + }, + { + icon: 'icon-download', + text: t('contacts', 'Download picture'), + href: this.contact.url + '?photo', + target: '_blank' + } + ]] } }, methods: { @@ -74,32 +121,47 @@ export default { * @param {Object} event the event object containing the image */ processFile(event) { - if (event.target.files) { + if (event.target.files && !this.loading) { + this.closeMenu() + let file = event.target.files[0] if (file && file.size && file.size <= 1 * 1024 * 1024) { let reader = new FileReader() let self = this - // check if photo property exists to decide whether to add/update it - reader.onload = function(e) { - self.contact.photo - ? self.contact.photo = reader.result - : self.contact.vCard.addPropertyWithValue('photo', reader.result) - self.$store.dispatch('updateContact', self.contact) + reader.onload = function(e) { + self.setPhoto(reader.result) } + reader.readAsDataURL(file) } else { OC.Notification.showTemporary(t('contacts', 'Image is too big (max 1MB).')) // reset input event.target.value = '' + this.loading = false } } }, /** + * Update the contact photo + * + * @param {String} value the photo as base64 + */ + setPhoto(value) { + // check if photo property exists to decide whether to add/update it + this.contact.photo + ? this.contact.photo = value + : this.contact.vCard.addPropertyWithValue('photo', value) + + this.$store.dispatch('updateContact', this.contact) + this.loading = false + }, + + /** * Toggle the full image preview */ - toggleSize() { + toggleModal() { // maximise or minimise avatar photo this.maximizeAvatar = !this.maximizeAvatar }, @@ -111,6 +173,57 @@ export default { this.contact.vCard.removeProperty('photo') this.maximizeAvatar = !this.maximizeAvatar this.$store.dispatch('updateContact', this.contact) + }, + + /** + * Picker handlers + */ + selectFileInput() { + if (!this.loading) { + this.$refs.uploadInput.click() + } + }, + async selectFilePicker() { + if (!this.loading) { + const file = await pickFileOrDirectory( + t('contacts', 'Pick an avatar'), + false, + [ + 'image/png', + 'image/jpeg', + 'image/gif', + 'image/x-xbitmap', + 'image/bmp', + 'image/svg+xml' + ] + ) + if (file) { + this.loading = true + try { + const { get } = await axios() + const response = await get(`${this.root}${file}`, { + responseType: 'arraybuffer' + }) + const data = `data:${response.headers['content-type']};base64,${Buffer.from(response.data, 'binary').toString('base64')}` + this.setPhoto(data) + } catch (error) { + OC.Notification.showTemporary(t('contacts', 'Error while processing the picture.')) + console.error(error) + this.loading = false + } + } + } + }, + + /** + * Menu handling + */ + toggleMenu() { + // only open if not loading + this.opened = !this.opened ? !this.opened && !this.loading : false + }, + closeMenu() { + this.opened = false } } diff --git a/src/main.js b/src/main.js index bc6e37a3..6b74fd5e 100644 --- a/src/main.js +++ b/src/main.js @@ -27,7 +27,7 @@ import { sync } from 'vuex-router-sync' import { generateFilePath } from 'nextcloud-server/dist/router' /** GLOBAL COMPONENTS AND DIRECTIVE */ -import { Action, DatetimePicker, Multiselect, PopoverMenu } from 'nextcloud-vue' +import { Action, DatetimePicker, Multiselect, PopoverMenu, Modal } from 'nextcloud-vue' import ClickOutside from 'vue-click-outside' import { VTooltip } from 'v-tooltip' import VueClipboard from 'vue-clipboard2' @@ -43,12 +43,16 @@ __webpack_nonce__ = btoa(OC.requestToken) // eslint-disable-next-line __webpack_public_path__ = generateFilePath('contacts', '', 'js/') +// Register global components Vue.component('Action', Action) Vue.component('DatetimePicker', DatetimePicker) +Vue.component('Modal', Modal) Vue.component('Multiselect', Multiselect) Vue.component('PopoverMenu', PopoverMenu) + Vue.directive('ClickOutside', ClickOutside) Vue.directive('Tooltip', VTooltip) + Vue.use(VueClipboard) sync(store, router) |