From 13725f040bca346a7b35b832f36f4e86c5da11e4 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Thu, 7 Feb 2019 03:05:59 -0500
Subject: Add avatar crop popup

---
 src/components/user_settings/user_settings.vue | 52 +++++++++++++++++++++-----
 1 file changed, 42 insertions(+), 10 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

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 @@
           <div class="setting-item">
             <h2>{{$t('settings.avatar')}}</h2>
             <p class="visibility-notice">{{$t('settings.avatar_size_instruction')}}</p>
-            <p>{{$t('settings.current_avatar')}}</p>
-            <img :src="user.profile_image_url_original" class="old-avatar"></img>
-            <p>{{$t('settings.set_new_avatar')}}</p>
-            <img class="new-avatar" v-bind:src="avatarPreview" v-if="avatarPreview">
-            </img>
-            <div>
-              <input type="file" @change="uploadFile('avatar', $event)" ></input>
+            <div class="avatar-upload-wrapper">
+              <div class="avatar-upload">
+                <img :src="user.profile_image_url_original" class="avatar" />
+                <div class="avatar-upload-loading-wrapper" v-if="avatarUploading">
+                  <i class="icon-spin4 animate-spin"></i>
+                </div>
+              </div>
             </div>
-            <i class="icon-spin4 animate-spin" v-if="avatarUploading"></i>
-            <button class="btn btn-default" v-else-if="avatarPreview" @click="submitAvatar">{{$t('general.submit')}}</button>
-            <div class='alert error' v-if="avatarUploadError">
+            <button class="btn" type="button" id="pick-avatar" :disabled="avatarUploading">{{$t('settings.set_new_avatar')}}</button>
+            <div class="alert error" v-if="avatarUploadError">
               Error: {{ avatarUploadError }}
               <i class="button-icon icon-cancel" @click="clearUploadError('avatar')"></i>
             </div>
+            <image-cropper trigger="#pick-avatar" :title="$t('settings.crop_your_new_avatar')" :saveButtonLabel="$t('settings.set_new_avatar')" @submit="submitAvatar" />
           </div>
           <div class="setting-item">
             <h2>{{$t('settings.profile_banner')}}</h2>
@@ -167,6 +167,8 @@
 </script>
 
 <style lang="scss">
+@import '../../_variables.scss';
+
 .profile-edit {
   .bio {
     margin: 0;
@@ -193,5 +195,35 @@
   .bg {
     max-width: 100%;
   }
+
+  .avatar-upload {
+    display: inline-block;
+    position: relative;
+  }
+
+  .avatar-upload-loading-wrapper {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background: rgba(0,0,0,.3);
+
+    i {
+      font-size: 50px;
+      color: #FFF;
+    }
+  }
+
+  .avatar {
+    display: block;
+    width: 150px;
+    height: 150px;
+    border-radius: $fallback--avatarRadius;
+    border-radius: var(--avatarRadius, $fallback--avatarRadius);
+  }
 }
 </style>
-- 
cgit v1.2.3-70-g09d2


From b24db12e1cc318e862169355a22f051d3ea2e809 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Fri, 8 Feb 2019 21:59:33 -0500
Subject: Make embedded image cropper

---
 src/components/image_cropper/image_cropper.js  | 37 ++++++++++++++--------
 src/components/image_cropper/image_cropper.vue | 25 ++++++++-------
 src/components/user_settings/user_settings.js  |  9 ++----
 src/components/user_settings/user_settings.vue | 43 ++++----------------------
 src/i18n/en.json                               |  5 +--
 5 files changed, 50 insertions(+), 69 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/image_cropper/image_cropper.js b/src/components/image_cropper/image_cropper.js
index b2580240..4eaa08d4 100644
--- a/src/components/image_cropper/image_cropper.js
+++ b/src/components/image_cropper/image_cropper.js
@@ -1,5 +1,4 @@
 import Cropper from 'cropperjs'
-import Modal from '../modal/modal.vue'
 import 'cropperjs/dist/cropper.css'
 
 const ImageCropper = {
@@ -8,6 +7,10 @@ const ImageCropper = {
       type: [String, window.Element],
       required: true
     },
+    submitHandler: {
+      type: Function,
+      required: true
+    },
     cropperOptions: {
       type: Object,
       default () {
@@ -25,10 +28,10 @@ const ImageCropper = {
       type: String,
       default: 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon'
     },
-    title: {
+    saveButtonLabel: {
       type: String
     },
-    saveButtonLabel: {
+    cancelButtonLabel: {
       type: String
     }
   },
@@ -36,18 +39,17 @@ const ImageCropper = {
     return {
       cropper: undefined,
       dataUrl: undefined,
-      filename: undefined
+      filename: undefined,
+      submitting: false,
+      submitError: null
     }
   },
-  components: {
-    Modal
-  },
   computed: {
-    modalTitle () {
-      return this.title || this.$t('image_cropper.crop_picture')
-    },
-    modalSaveButtonLabel () {
+    saveText () {
       return this.saveButtonLabel || this.$t('image_cropper.save')
+    },
+    cancelText () {
+      return this.cancelButtonLabel || this.$t('image_cropper.cancel')
     }
   },
   methods: {
@@ -57,10 +59,15 @@ const ImageCropper = {
       }
       this.$refs.input.value = ''
       this.dataUrl = undefined
+      this.$emit('close')
     },
     submit () {
-      this.$emit('submit', this.cropper, this.filename)
-      this.destroy()
+      this.submitting = true
+      this.avatarUploadError = null
+      this.submitHandler(this.cropper, this.filename)
+        .then(() => this.destroy())
+        .catch(err => this.submitError = err)
+        .finally(() => this.submitting = false)
     },
     pickImage () {
       this.$refs.input.click()
@@ -77,11 +84,15 @@ const ImageCropper = {
         let reader = new window.FileReader()
         reader.onload = (e) => {
           this.dataUrl = e.target.result
+          this.$emit('open')
         }
         reader.readAsDataURL(fileInput.files[0])
         this.filename = fileInput.files[0].name || 'unknown'
         this.$emit('changed', fileInput.files[0], reader)
       }
+    },
+    clearError () {
+      this.submitError = null
     }
   },
   mounted () {
diff --git a/src/components/image_cropper/image_cropper.vue b/src/components/image_cropper/image_cropper.vue
index b2367128..aa895863 100644
--- a/src/components/image_cropper/image_cropper.vue
+++ b/src/components/image_cropper/image_cropper.vue
@@ -1,15 +1,19 @@
 <template>
   <div class="image-cropper">
-    <modal :show="dataUrl" :title="modalTitle" @close="destroy">
-      <div class="modal-body">
-        <div class="image-cropper-image-container">
-          <img ref="img" :src="dataUrl" alt="" @load.stop="createCropper" />
-        </div>
+    <div v-if="dataUrl">
+      <div class="image-cropper-image-container">
+        <img ref="img" :src="dataUrl" alt="" @load.stop="createCropper" />
       </div>
-      <div class="modal-footer">
-        <button class="btn image-cropper-btn" type="button" @click="submit" v-text="modalSaveButtonLabel"></button>
+      <div class="image-cropper-buttons-wrapper">
+        <button class="btn" type="button" :disabled="submitting" @click="submit" v-text="saveText"></button>
+        <button class="btn" type="button" :disabled="submitting" @click="destroy" v-text="cancelText"></button>
+        <i class="icon-spin4 animate-spin" v-if="submitting"></i>
       </div>
-    </modal>
+      <div class="alert error" v-if="submitError">
+        Error: {{ submitError }}
+        <i class="button-icon icon-cancel" @click="clearError"></i>
+      </div>
+    </div>
     <input ref="input" type="file" class="image-cropper-img-input" :accept="mimes">
   </div>
 </template>
@@ -31,9 +35,8 @@
     }
   }
 
-  &-btn {
-    display: block;
-    width: 100%;
+  &-buttons-wrapper {
+    margin-top: 15px;
   }
 }
 </style>
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 8987c691..2d521c14 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -21,13 +21,12 @@ const UserSettings = {
       followImportError: false,
       followsImported: false,
       enableFollowsExport: true,
-      avatarUploading: false,
+      pickAvatarBtnVisible: true,
       bannerUploading: false,
       backgroundUploading: false,
       followListUploading: false,
       bannerPreview: null,
       backgroundPreview: null,
-      avatarUploadError: null,
       bannerUploadError: null,
       backgroundUploadError: null,
       deletingAccount: false,
@@ -120,15 +119,13 @@ const UserSettings = {
     },
     submitAvatar (cropper) {
       const img = cropper.getCroppedCanvas({ minWidth: 150, minHeight: 150 }).toDataURL('image/jpeg')
-      this.avatarUploading = true
-      this.$store.state.api.backendInteractor.updateAvatar({ params: { img } }).then((user) => {
+      return this.$store.state.api.backendInteractor.updateAvatar({ params: { img } }).then((user) => {
         if (!user.error) {
           this.$store.commit('addNewUsers', [user])
           this.$store.commit('setCurrentUser', user)
         } else {
-          this.avatarUploadError = this.$t('upload.error.base') + user.error
+          throw this.$t('upload.error.base') + user.error
         }
-        this.avatarUploading = false
       })
     },
     clearUploadError (slot) {
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 9fcd3752..8ab92e95 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -47,20 +47,11 @@
           <div class="setting-item">
             <h2>{{$t('settings.avatar')}}</h2>
             <p class="visibility-notice">{{$t('settings.avatar_size_instruction')}}</p>
-            <div class="avatar-upload-wrapper">
-              <div class="avatar-upload">
-                <img :src="user.profile_image_url_original" class="avatar" />
-                <div class="avatar-upload-loading-wrapper" v-if="avatarUploading">
-                  <i class="icon-spin4 animate-spin"></i>
-                </div>
-              </div>
-            </div>
-            <button class="btn" type="button" id="pick-avatar" :disabled="avatarUploading">{{$t('settings.set_new_avatar')}}</button>
-            <div class="alert error" v-if="avatarUploadError">
-              Error: {{ avatarUploadError }}
-              <i class="button-icon icon-cancel" @click="clearUploadError('avatar')"></i>
-            </div>
-            <image-cropper trigger="#pick-avatar" :title="$t('settings.crop_your_new_avatar')" :saveButtonLabel="$t('settings.set_new_avatar')" @submit="submitAvatar" />
+            <p>{{$t('settings.current_avatar')}}</p>
+            <img :src="user.profile_image_url_original" class="current-avatar"></img>
+            <p>{{$t('settings.set_new_avatar')}}</p>
+            <button class="btn" type="button" id="pick-avatar" v-show="pickAvatarBtnVisible">{{$t('settings.upload_a_photo')}}</button>
+            <image-cropper trigger="#pick-avatar" :submitHandler="submitAvatar" @open="pickAvatarBtnVisible=false" @close="pickAvatarBtnVisible=true" />
           </div>
           <div class="setting-item">
             <h2>{{$t('settings.profile_banner')}}</h2>
@@ -196,29 +187,7 @@
     max-width: 100%;
   }
 
-  .avatar-upload {
-    display: inline-block;
-    position: relative;
-  }
-
-  .avatar-upload-loading-wrapper {
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    background: rgba(0,0,0,.3);
-
-    i {
-      font-size: 50px;
-      color: #FFF;
-    }
-  }
-
-  .avatar {
+  .current-avatar {
     display: block;
     width: 150px;
     height: 150px;
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 3e9ac157..af62acfc 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -23,7 +23,8 @@
   },
   "image_cropper": {
     "crop_picture": "Crop picture",
-    "save": "Save"
+    "save": "Save",
+    "cancel": "Cancel"
   },
   "login": {
     "login": "Log in",
@@ -116,7 +117,6 @@
     "collapse_subject": "Collapse posts with subjects",
     "composing": "Composing",
     "confirm_new_password": "Confirm new password",
-    "crop_your_new_avatar": "Crop your new avatar",
     "current_avatar": "Your current avatar",
     "current_password": "Current password",
     "current_profile_banner": "Your current profile banner",
@@ -211,6 +211,7 @@
     "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
     "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
     "tooltipRadius": "Tooltips/alerts",
+    "upload_a_photo": "Upload a photo",
     "user_settings": "User Settings",
     "values": {
       "false": "no",
-- 
cgit v1.2.3-70-g09d2

