From 13725f040bca346a7b35b832f36f4e86c5da11e4 Mon Sep 17 00:00:00 2001 From: taehoon Date: Thu, 7 Feb 2019 03:05:59 -0500 Subject: Add avatar crop popup --- src/components/image_cropper/image_cropper.js | 88 +++++++++++++++++++++ src/components/image_cropper/image_cropper.vue | 39 ++++++++++ src/components/modal/modal.js | 17 +++++ src/components/modal/modal.vue | 101 +++++++++++++++++++++++++ src/components/settings/settings.vue | 14 ---- src/components/user_settings/user_settings.js | 29 ++----- src/components/user_settings/user_settings.vue | 52 ++++++++++--- src/i18n/en.json | 2 +- 8 files changed, 294 insertions(+), 48 deletions(-) create mode 100644 src/components/image_cropper/image_cropper.js create mode 100644 src/components/image_cropper/image_cropper.vue create mode 100644 src/components/modal/modal.js create mode 100644 src/components/modal/modal.vue (limited to 'src') diff --git a/src/components/image_cropper/image_cropper.js b/src/components/image_cropper/image_cropper.js new file mode 100644 index 00000000..e4bf5ea2 --- /dev/null +++ b/src/components/image_cropper/image_cropper.js @@ -0,0 +1,88 @@ +import Cropper from 'cropperjs' +import Modal from '../modal/modal.vue' +import 'cropperjs/dist/cropper.css' + +const ImageCropper = { + props: { + trigger: { + type: [String, Element], + required: true + }, + cropperOptions: { + type: Object, + default () { + return { + aspectRatio: 1, + autoCropArea: 1, + viewMode: 1, + movable: false, + zoomable: false, + guides: false + } + } + }, + mimes: { + type: String, + default: 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon' + }, + title: { + type: String, + default: 'Crop picture' + }, + saveButtonLabel: { + type: String, + default: 'Save' + } + }, + data () { + return { + cropper: undefined, + dataUrl: undefined, + filename: undefined + } + }, + components: { + Modal + }, + methods: { + destroy () { + this.cropper.destroy() + this.$refs.input.value = '' + this.dataUrl = undefined + }, + submit () { + this.$emit('submit', this.cropper, this.filename) + this.destroy() + }, + pickImage () { + this.$refs.input.click() + }, + createCropper () { + this.cropper = new Cropper(this.$refs.img, this.cropperOptions) + } + }, + mounted () { + // listen for click event on trigger + let trigger = typeof this.trigger === 'object' ? this.trigger : document.querySelector(this.trigger) + if (!trigger) { + this.$emit('error', 'No image make trigger found.', 'user') + } else { + trigger.addEventListener('click', this.pickImage) + } + // listen for input file changes + let fileInput = this.$refs.input + fileInput.addEventListener('change', () => { + if (fileInput.files != null && fileInput.files[0] != null) { + let reader = new FileReader() + reader.onload = (e) => { + this.dataUrl = e.target.result + } + reader.readAsDataURL(fileInput.files[0]) + this.filename = fileInput.files[0].name || 'unknown' + this.$emit('changed', fileInput.files[0], reader) + } + }) + } +} + +export default ImageCropper diff --git a/src/components/image_cropper/image_cropper.vue b/src/components/image_cropper/image_cropper.vue new file mode 100644 index 00000000..1c52842c --- /dev/null +++ b/src/components/image_cropper/image_cropper.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/src/components/modal/modal.js b/src/components/modal/modal.js new file mode 100644 index 00000000..36cd7f4a --- /dev/null +++ b/src/components/modal/modal.js @@ -0,0 +1,17 @@ +const Modal = { + props: ['show', 'title'], + methods: { + close: function () { + this.$emit('close') + } + }, + mounted: function () { + document.addEventListener('keydown', (e) => { + if (this.show && e.keyCode === 27) { + this.close() + } + }) + } +} + +export default Modal diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue new file mode 100644 index 00000000..2e3cbe75 --- /dev/null +++ b/src/components/modal/modal.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index dfb2e49d..91232382 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -311,20 +311,6 @@ color: $fallback--cRed; } - .old-avatar { - width: 128px; - border-radius: $fallback--avatarRadius; - border-radius: var(--avatarRadius, $fallback--avatarRadius); - } - - .new-avatar { - object-fit: cover; - width: 128px; - height: 128px; - border-radius: $fallback--avatarRadius; - border-radius: var(--avatarRadius, $fallback--avatarRadius); - } - .btn { min-height: 28px; min-width: 10em; diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index d20bf308..b3d31d67 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -1,6 +1,7 @@ import { unescape } from 'lodash' import TabSwitcher from '../tab_switcher/tab_switcher.js' +import ImageCropper from '../image_cropper/image_cropper.vue' import StyleSwitcher from '../style_switcher/style_switcher.vue' import fileSizeFormatService from '../../services/file_size_format/file_size_format.js' @@ -24,7 +25,6 @@ const UserSettings = { bannerUploading: false, backgroundUploading: false, followListUploading: false, - avatarPreview: null, bannerPreview: null, backgroundPreview: null, avatarUploadError: null, @@ -41,7 +41,8 @@ const UserSettings = { }, components: { StyleSwitcher, - TabSwitcher + TabSwitcher, + ImageCropper }, computed: { user () { @@ -117,31 +118,13 @@ const UserSettings = { } reader.readAsDataURL(file) }, - submitAvatar () { - if (!this.avatarPreview) { return } - - let img = this.avatarPreview - // eslint-disable-next-line no-undef - let imginfo = new Image() - let cropX, cropY, cropW, cropH - imginfo.src = img - if (imginfo.height > imginfo.width) { - cropX = 0 - cropW = imginfo.width - cropY = Math.floor((imginfo.height - imginfo.width) / 2) - cropH = imginfo.width - } else { - cropY = 0 - cropH = imginfo.height - cropX = Math.floor((imginfo.width - imginfo.height) / 2) - cropW = imginfo.height - } + submitAvatar (cropper) { + const img = cropper.getCroppedCanvas({ width: 150, height: 150 }).toDataURL('image/jpeg') this.avatarUploading = true - this.$store.state.api.backendInteractor.updateAvatar({params: {img, cropX, cropY, cropW, cropH}}).then((user) => { + this.$store.state.api.backendInteractor.updateAvatar({ params: { img } }).then((user) => { if (!user.error) { this.$store.commit('addNewUsers', [user]) this.$store.commit('setCurrentUser', user) - this.avatarPreview = null } else { this.avatarUploadError = this.$t('upload.error.base') + user.error } diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index d2381da2..9fcd3752 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -47,20 +47,20 @@

{{$t('settings.avatar')}}

{{$t('settings.avatar_size_instruction')}}

-

{{$t('settings.current_avatar')}}

- -

{{$t('settings.set_new_avatar')}}

- - -
- +
+
+ +
+ +
+
- - -
+ +
Error: {{ avatarUploadError }}
+

{{$t('settings.profile_banner')}}

@@ -167,6 +167,8 @@ diff --git a/src/i18n/en.json b/src/i18n/en.json index c664fbfa..90132e3e 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -112,7 +112,7 @@ "collapse_subject": "Collapse posts with subjects", "composing": "Composing", "confirm_new_password": "Confirm new password", - "current_avatar": "Your current avatar", + "crop_your_new_avatar": "Crop your new avatar", "current_password": "Current password", "current_profile_banner": "Your current profile banner", "data_import_export_tab": "Data Import / Export", -- cgit v1.2.3-70-g09d2