From eea002e6f5e3da4c4415d45cffd9cff64fd6c052 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Wed, 8 Jul 2020 10:11:17 +0000
Subject: [PATCH] streamline profile image api, update reset ui for all profile
 images to match avatar, remove unnecessary stuff

---
 CHANGELOG.md                                  |  1 +
 .../settings_modal/tabs/profile_tab.js        | 61 ++++++++++++++++---
 .../settings_modal/tabs/profile_tab.scss      | 39 ++++++++++--
 .../settings_modal/tabs/profile_tab.vue       | 51 +++++++++++-----
 src/components/user_avatar/user_avatar.js     | 16 ++---
 src/components/user_avatar/user_avatar.vue    |  2 +-
 .../who_to_follow_panel.js                    | 17 +++---
 src/i18n/en.json                              |  8 ++-
 src/modules/instance.js                       |  2 +
 src/services/api/api.service.js               | 32 ++--------
 10 files changed, 153 insertions(+), 76 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9f54352d..49ec550c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Autocomplete domains from list of known instances
 - 'Bot' settings option and badge
 - Added profile meta data fields that can be set in profile settings
+- Added option to reset avatar/banner in profile settings
 - Descriptions can be set on uploaded files before posting
 - Added status preview option to preview your statuses before posting
 - When a post is a reply to an unavailable post, the 'Reply to'-text has a strike-through style
diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js
index e6db802d..bd6bef6a 100644
--- a/src/components/settings_modal/tabs/profile_tab.js
+++ b/src/components/settings_modal/tabs/profile_tab.js
@@ -77,6 +77,33 @@ const ProfileTab = {
     },
     maxFields () {
       return this.fieldsLimits ? this.fieldsLimits.maxFields : 0
+    },
+    defaultAvatar () {
+      return this.$store.state.instance.server + this.$store.state.instance.defaultAvatar
+    },
+    defaultBanner () {
+      return this.$store.state.instance.server + this.$store.state.instance.defaultBanner
+    },
+    isDefaultAvatar () {
+      const baseAvatar = this.$store.state.instance.defaultAvatar
+      return !(this.$store.state.users.currentUser.profile_image_url) ||
+      this.$store.state.users.currentUser.profile_image_url.includes(baseAvatar)
+    },
+    isDefaultBanner () {
+      const baseBanner = this.$store.state.instance.defaultBanner
+      return !(this.$store.state.users.currentUser.cover_photo) ||
+      this.$store.state.users.currentUser.cover_photo.includes(baseBanner)
+    },
+    isDefaultBackground () {
+      return !(this.$store.state.users.currentUser.background_image)
+    },
+    avatarImgSrc () {
+      const src = this.$store.state.users.currentUser.profile_image_url_original
+      return (!src) ? this.defaultAvatar : src
+    },
+    bannerImgSrc () {
+      const src = this.$store.state.users.currentUser.cover_photo
+      return (!src) ? this.defaultBanner : src
     }
   },
   methods: {
@@ -150,11 +177,29 @@ const ProfileTab = {
       }
       reader.readAsDataURL(file)
     },
+    resetAvatar () {
+      const confirmed = window.confirm(this.$t('settings.reset_avatar_confirm'))
+      if (confirmed) {
+        this.submitAvatar(undefined, '')
+      }
+    },
+    resetBanner () {
+      const confirmed = window.confirm(this.$t('settings.reset_banner_confirm'))
+      if (confirmed) {
+        this.submitBanner('')
+      }
+    },
+    resetBackground () {
+      const confirmed = window.confirm(this.$t('settings.reset_background_confirm'))
+      if (confirmed) {
+        this.submitBackground('')
+      }
+    },
     submitAvatar (cropper, file) {
       const that = this
       return new Promise((resolve, reject) => {
         function updateAvatar (avatar) {
-          that.$store.state.api.backendInteractor.updateAvatar({ avatar })
+          that.$store.state.api.backendInteractor.updateProfileImages({ avatar })
             .then((user) => {
               that.$store.commit('addNewUsers', [user])
               that.$store.commit('setCurrentUser', user)
@@ -172,11 +217,11 @@ const ProfileTab = {
         }
       })
     },
-    submitBanner () {
-      if (!this.bannerPreview) { return }
+    submitBanner (banner) {
+      if (!this.bannerPreview && banner !== '') { return }
 
       this.bannerUploading = true
-      this.$store.state.api.backendInteractor.updateBanner({ banner: this.banner })
+      this.$store.state.api.backendInteractor.updateProfileImages({ banner })
         .then((user) => {
           this.$store.commit('addNewUsers', [user])
           this.$store.commit('setCurrentUser', user)
@@ -187,11 +232,11 @@ const ProfileTab = {
         })
         .then(() => { this.bannerUploading = false })
     },
-    submitBg () {
-      if (!this.backgroundPreview) { return }
-      let background = this.background
+    submitBackground (background) {
+      if (!this.backgroundPreview && background !== '') { return }
+
       this.backgroundUploading = true
-      this.$store.state.api.backendInteractor.updateBg({ background }).then((data) => {
+      this.$store.state.api.backendInteractor.updateProfileImages({ background }).then((data) => {
         if (!data.error) {
           this.$store.commit('addNewUsers', [data])
           this.$store.commit('setCurrentUser', data)
diff --git a/src/components/settings_modal/tabs/profile_tab.scss b/src/components/settings_modal/tabs/profile_tab.scss
index b3dcf42c..e14cf054 100644
--- a/src/components/settings_modal/tabs/profile_tab.scss
+++ b/src/components/settings_modal/tabs/profile_tab.scss
@@ -13,8 +13,14 @@
     height: auto;
   }
 
-  .banner {
+  .banner-background-preview {
     max-width: 100%;
+    width: 300px;
+    position: relative;
+
+    img {
+      width: 100%;
+    }
   }
 
   .uploading {
@@ -26,18 +32,40 @@
     width: 100%;
   }
 
-  .bg {
-    max-width: 100%;
+  .current-avatar-container {
+    position: relative;
+    width: 150px;
+    height: 150px;
   }
 
   .current-avatar {
     display: block;
-    width: 150px;
-    height: 150px;
+    width: 100%;
+    height: 100%;
     border-radius: $fallback--avatarRadius;
     border-radius: var(--avatarRadius, $fallback--avatarRadius);
   }
 
+  .reset-button {
+    position: absolute;
+    top: 0.2em;
+    right: 0.2em;
+    border-radius: $fallback--tooltipRadius;
+    border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+    background-color: rgba(0, 0, 0, 0.6);
+    opacity: 0.7;
+    color: white;
+    width: 1.5em;
+    height: 1.5em;
+    text-align: center;
+    line-height: 1.5em;
+    font-size: 1.5em;
+    cursor: pointer;
+    &:hover {
+      opacity: 1;
+    }
+  }
+
   .oauth-tokens {
     width: 100%;
 
@@ -86,6 +114,7 @@
     &>.emoji-input {
       flex: 1 1 auto;
       margin: 0 .2em .5em;
+      min-width: 0;
     }
 
     &>.icon-container {
diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue
index 0f9210a6..cf88c4e4 100644
--- a/src/components/settings_modal/tabs/profile_tab.vue
+++ b/src/components/settings_modal/tabs/profile_tab.vue
@@ -161,11 +161,19 @@
       <p class="visibility-notice">
         {{ $t('settings.avatar_size_instruction') }}
       </p>
-      <p>{{ $t('settings.current_avatar') }}</p>
-      <img
-        :src="user.profile_image_url_original"
-        class="current-avatar"
-      >
+      <div class="current-avatar-container">
+        <img
+          :src="user.profile_image_url_original"
+          class="current-avatar"
+        >
+        <i
+          v-if="!isDefaultAvatar && pickAvatarBtnVisible"
+          :title="$t('settings.reset_avatar')"
+          class="reset-button icon-cancel"
+          type="button"
+          @click="resetAvatar"
+        />
+      </div>
       <p>{{ $t('settings.set_new_avatar') }}</p>
       <button
         v-show="pickAvatarBtnVisible"
@@ -184,15 +192,20 @@
     </div>
     <div class="setting-item">
       <h2>{{ $t('settings.profile_banner') }}</h2>
-      <p>{{ $t('settings.current_profile_banner') }}</p>
-      <img
-        :src="user.cover_photo"
-        class="banner"
-      >
+      <div class="banner-background-preview">
+        <img :src="user.cover_photo">
+        <i
+          v-if="!isDefaultBanner"
+          :title="$t('settings.reset_profile_banner')"
+          class="reset-button icon-cancel"
+          type="button"
+          @click="resetBanner"
+        />
+      </div>
       <p>{{ $t('settings.set_new_profile_banner') }}</p>
       <img
         v-if="bannerPreview"
-        class="banner"
+        class="banner-background-preview"
         :src="bannerPreview"
       >
       <div>
@@ -208,7 +221,7 @@
       <button
         v-else-if="bannerPreview"
         class="btn btn-default"
-        @click="submitBanner"
+        @click="submitBanner(banner)"
       >
         {{ $t('general.submit') }}
       </button>
@@ -225,10 +238,20 @@
     </div>
     <div class="setting-item">
       <h2>{{ $t('settings.profile_background') }}</h2>
+      <div class="banner-background-preview">
+        <img :src="user.background_image">
+        <i
+          v-if="!isDefaultBackground"
+          :title="$t('settings.reset_profile_background')"
+          class="reset-button icon-cancel"
+          type="button"
+          @click="resetBackground"
+        />
+      </div>
       <p>{{ $t('settings.set_new_profile_background') }}</p>
       <img
         v-if="backgroundPreview"
-        class="bg"
+        class="banner-background-preview"
         :src="backgroundPreview"
       >
       <div>
@@ -244,7 +267,7 @@
       <button
         v-else-if="backgroundPreview"
         class="btn btn-default"
-        @click="submitBg"
+        @click="submitBackground(background)"
       >
         {{ $t('general.submit') }}
       </button>
diff --git a/src/components/user_avatar/user_avatar.js b/src/components/user_avatar/user_avatar.js
index 4adf8211..94653004 100644
--- a/src/components/user_avatar/user_avatar.js
+++ b/src/components/user_avatar/user_avatar.js
@@ -8,26 +8,20 @@ const UserAvatar = {
   ],
   data () {
     return {
-      showPlaceholder: false
+      showPlaceholder: false,
+      defaultAvatar: `${this.$store.state.instance.server + this.$store.state.instance.defaultAvatar}`
     }
   },
   components: {
     StillImage
   },
-  computed: {
-    imgSrc () {
-      return this.showPlaceholder ? '/images/avi.png' : this.user.profile_image_url_original
-    }
-  },
   methods: {
+    imgSrc (src) {
+      return (!src || this.showPlaceholder) ? this.defaultAvatar : src
+    },
     imageLoadError () {
       this.showPlaceholder = true
     }
-  },
-  watch: {
-    src () {
-      this.showPlaceholder = false
-    }
   }
 }
 
diff --git a/src/components/user_avatar/user_avatar.vue b/src/components/user_avatar/user_avatar.vue
index 9ffb28d8..3545b801 100644
--- a/src/components/user_avatar/user_avatar.vue
+++ b/src/components/user_avatar/user_avatar.vue
@@ -3,7 +3,7 @@
     class="avatar"
     :alt="user.screen_name"
     :title="user.screen_name"
-    :src="imgSrc"
+    :src="imgSrc(user.profile_image_url_original)"
     :class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }"
     :image-load-error="imageLoadError"
   />
diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js
index dcb56106..818e8bd5 100644
--- a/src/components/who_to_follow_panel/who_to_follow_panel.js
+++ b/src/components/who_to_follow_panel/who_to_follow_panel.js
@@ -7,7 +7,7 @@ function showWhoToFollow (panel, reply) {
 
   panel.usersToFollow.forEach((toFollow, index) => {
     let user = shuffled[index]
-    let img = user.avatar || '/images/avi.png'
+    let img = user.avatar || this.$store.state.instance.defaultAvatar
     let name = user.acct
 
     toFollow.img = img
@@ -38,13 +38,7 @@ function getWhoToFollow (panel) {
 
 const WhoToFollowPanel = {
   data: () => ({
-    usersToFollow: new Array(3).fill().map(x => (
-      {
-        img: '/images/avi.png',
-        name: '',
-        id: 0
-      }
-    ))
+    usersToFollow: []
   }),
   computed: {
     user: function () {
@@ -68,6 +62,13 @@ const WhoToFollowPanel = {
   },
   mounted:
     function () {
+      this.usersToFollow = new Array(3).fill().map(x => (
+        {
+          img: this.$store.state.instance.defaultAvatar,
+          name: '',
+          id: 0
+        }
+      ))
       if (this.suggestionsEnabled) {
         getWhoToFollow(this)
       }
diff --git a/src/i18n/en.json b/src/i18n/en.json
index b331a3e0..72c3b1f7 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -290,9 +290,7 @@
     "collapse_subject": "Collapse posts with subjects",
     "composing": "Composing",
     "confirm_new_password": "Confirm new password",
-    "current_avatar": "Your current avatar",
     "current_password": "Current password",
-    "current_profile_banner": "Your current profile banner",
     "mutes_and_blocks": "Mutes and Blocks",
     "data_import_export_tab": "Data Import / Export",
     "default_vis": "Default visibility scope",
@@ -399,6 +397,12 @@
     "set_new_avatar": "Set new avatar",
     "set_new_profile_background": "Set new profile background",
     "set_new_profile_banner": "Set new profile banner",
+    "reset_avatar": "Reset avatar",
+    "reset_profile_background": "Reset profile background",
+    "reset_profile_banner": "Reset profile banner",
+    "reset_avatar_confirm": "Do you really want to reset the avatar?",
+    "reset_banner_confirm": "Do you really want to reset the banner?",
+    "reset_background_confirm": "Do you really want to reset the background?",
     "settings": "Settings",
     "subject_input_always_show": "Always show subject field",
     "subject_line_behavior": "Copy subject when replying",
diff --git a/src/modules/instance.js b/src/modules/instance.js
index ec5f4e54..45a8eeca 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -15,6 +15,8 @@ const defaultState = {
 
   // Stuff from static/config.json
   alwaysShowSubjectInput: true,
+  defaultAvatar: '/images/avi.png',
+  defaultBanner: '/images/banner.png',
   background: '/static/aurora_borealis.jpg',
   collapseMessageWithSubject: false,
   disableChat: false,
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index ad543c6c..14e63e4f 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -141,20 +141,11 @@ const updateNotificationSettings = ({ credentials, settings }) => {
   }).then((data) => data.json())
 }
 
-const updateAvatar = ({ credentials, avatar }) => {
+const updateProfileImages = ({ credentials, avatar = null, banner = null, background = null }) => {
   const form = new FormData()
-  form.append('avatar', avatar)
-  return fetch(MASTODON_PROFILE_UPDATE_URL, {
-    headers: authHeaders(credentials),
-    method: 'PATCH',
-    body: form
-  }).then((data) => data.json())
-    .then((data) => parseUser(data))
-}
-
-const updateBg = ({ credentials, background }) => {
-  const form = new FormData()
-  form.append('pleroma_background_image', background)
+  if (avatar !== null) form.append('avatar', avatar)
+  if (banner !== null) form.append('header', banner)
+  if (background !== null) form.append('pleroma_background_image', background)
   return fetch(MASTODON_PROFILE_UPDATE_URL, {
     headers: authHeaders(credentials),
     method: 'PATCH',
@@ -164,17 +155,6 @@ const updateBg = ({ credentials, background }) => {
     .then((data) => parseUser(data))
 }
 
-const updateBanner = ({ credentials, banner }) => {
-  const form = new FormData()
-  form.append('header', banner)
-  return fetch(MASTODON_PROFILE_UPDATE_URL, {
-    headers: authHeaders(credentials),
-    method: 'PATCH',
-    body: form
-  }).then((data) => data.json())
-    .then((data) => parseUser(data))
-}
-
 const updateProfile = ({ credentials, params }) => {
   return promisedRequest({
     url: MASTODON_PROFILE_UPDATE_URL,
@@ -1206,10 +1186,8 @@ const apiService = {
   deactivateUser,
   register,
   getCaptcha,
-  updateAvatar,
-  updateBg,
+  updateProfileImages,
   updateProfile,
-  updateBanner,
   importBlocks,
   importFollows,
   deleteAccount,