2121-->
2222
2323<template >
24- <div >
24+ <section >
2525 <HeaderBar :input-id =" inputId"
2626 :readable =" avatar.readable"
2727 :scope.sync =" avatar.scope" />
2828
29- <div v-if =" !cropping" class =" avatar__container" >
29+ <div v-show =" !cropping" class =" avatar__container" >
3030 <div class =" avatar__preview" >
3131 <Avatar v-if =" !loading"
3232 :user =" userId"
3535 :disabled-tooltip =" true"
3636 :show-user-status =" false"
3737 :size =" 180"
38- :key =" avatarKey"
39- />
40- <div v-else class =" icon-loading" ></div >
38+ :key =" avatarKey" />
39+ <div v-else class =" icon-loading" />
4140 </div >
4241 <template v-if =" avatarChangeSupported " >
4342 <div class =" avatar__buttons" >
44- <Button :aria-label =" t('core ', 'Upload profile picture')"
45- @click =" chooseLocalImage " >
43+ <Button :aria-label =" t('settings ', 'Upload profile picture')"
44+ @click =" activateLocalFilePicker " >
4645 <template #icon >
4746 <Upload :size =" 20" />
4847 </template >
4948 </Button >
50- <Button :aria-label =" t('core ', 'Select from files')"
49+ <Button :aria-label =" t('settings ', 'Select profile picture from files')"
5150 @click =" openFilePicker" >
5251 <template #icon >
5352 <Folder :size =" 20" />
5453 </template >
5554 </Button >
5655 <Button v-if =" !isGenerated"
57- :aria-label =" t('core ', 'Remove profile picture')"
56+ :aria-label =" t('settings ', 'Remove profile picture')"
5857 @click =" removeAvatar" >
5958 <template #icon >
6059 <Delete :size =" 20" />
6867 </span >
6968 </div >
7069
71- <template v-else >
70+ <!-- Use v-show to ensure earlier cropper ref availability -->
71+ <div v-show =" cropping" >
7272 <VueCropper ref =" cropper"
7373 class =" avatar__cropper"
74- :aspect-ratio = " 1 / 1 " />
74+ v-bind = " cropperOptions " />
7575 <div class =" avatar__buttons" >
76- <Button @click =" cropping = false " >
76+ <Button @click =" cancel " >
7777 {{ t('settings', 'Cancel') }}
7878 </Button >
7979 <Button type =" primary"
8080 @click =" saveAvatar" >
8181 {{ t('settings', 'Save profile picture') }}
8282 </Button >
8383 </div >
84- </template >
84+ </div >
8585
8686 <input ref =" input"
8787 type =" file"
8888 accept =" image/*"
89- @change =" cropImage " >
90- </div >
89+ @change =" onChange " >
90+ </section >
9191</template >
9292
9393<script >
94- import Avatar from ' @nextcloud/vue/dist/Components/Avatar'
95- import Button from ' @nextcloud/vue/dist/Components/Button'
96- import VueCropper from ' vue-cropperjs'
97-
9894import axios from ' @nextcloud/axios'
99- import { getFilePickerBuilder } from ' @nextcloud/dialogs'
100- import { getCurrentUser } from ' @nextcloud/auth'
101- import { generateUrl } from ' @nextcloud/router'
10295import { loadState } from ' @nextcloud/initial-state'
96+ import { generateUrl } from ' @nextcloud/router'
97+ import { getCurrentUser } from ' @nextcloud/auth'
98+ import { getFilePickerBuilder } from ' @nextcloud/dialogs'
10399import { emit , subscribe } from ' @nextcloud/event-bus'
104100import { showError } from ' @nextcloud/dialogs'
101+
102+ import Avatar from ' @nextcloud/vue/dist/Components/Avatar'
103+ import Button from ' @nextcloud/vue/dist/Components/Button'
104+ import VueCropper from ' vue-cropperjs'
105105import ' cropperjs/dist/cropper.css'
106106
107107import Upload from ' vue-material-design-icons/Upload'
@@ -114,9 +114,9 @@ import { ACCOUNT_PROPERTY_ENUM, NAME_READABLE_ENUM } from '../../constants/Accou
114114const { avatar } = loadState (' settings' , ' personalInfoParameters' , {})
115115const { avatarChangeSupported } = loadState (' settings' , ' accountParameters' , {})
116116
117- const picker = getFilePickerBuilder (t (' settings' , ' Select profile picture' ))
118- .setMimeTypeFilter ([' image/png' , ' image/jpeg' ])
117+ const picker = getFilePickerBuilder (t (' settings' , ' Choose your profile picture' ))
119118 .setMultiSelect (false )
119+ .setMimeTypeFilter ([' image/png' , ' image/jpeg' ])
120120 .setModal (true )
121121 .setType (1 )
122122 .allowDirectories (false )
@@ -141,12 +141,16 @@ export default {
141141 avatarChangeSupported,
142142 cropping: false ,
143143 loading: false ,
144- imgSrc: null ,
145144 userId: getCurrentUser ().uid ,
146145 displayName: getCurrentUser ().displayName ,
147146 avatarKey: oc_userconfig .avatar .version ,
148147 isGenerated: oc_userconfig .avatar .generated ,
149- // tempUrl: generateUrl('/avatar/tmp') + '?requesttoken=' + encodeURIComponent(OC.requestToken) + '#' + Math.floor(Math.random() * 1000),
148+ cropperOptions: {
149+ aspectRatio: 1 / 1 ,
150+ viewMode: 1 ,
151+ guides: false ,
152+ autoCropArea: 1 ,
153+ },
150154 }
151155 },
152156
@@ -166,11 +170,13 @@ export default {
166170 },
167171
168172 methods: {
169- chooseLocalImage () {
173+ activateLocalFilePicker () {
174+ // Set to null so that selecting the same file will trigger the change event
175+ this .$refs .input .value = null
170176 this .$refs .input .click ()
171177 },
172178
173- cropImage (e ) {
179+ onChange (e ) {
174180 this .loading = true
175181 const file = e .target .files [0 ]
176182 if (! file .type .startsWith (' image/' )) {
@@ -187,40 +193,54 @@ export default {
187193 // this.handleAvatarUpdate(false)
188194 },
189195
196+ async openFilePicker () {
197+ const path = await picker .pick ()
198+ try {
199+ const { data } = await axios .post (generateUrl (' /avatar' ), { path })
200+ if (data .status === ' success' ) {
201+ this .handleAvatarUpdate (false )
202+ } else if (data .data === ' notsquare' ) {
203+ const tempImg = generateUrl (' /avatar/tmp' ) + ' ?requesttoken=' + encodeURIComponent (OC .requestToken ) + ' #' + Math .floor (Math .random () * 1000 )
204+ this .$refs .cropper .replace (tempImg)
205+ this .cropping = true
206+ }
207+ } catch (e) {
208+ showError (t (' settings' , ' Error setting profile picture' ))
209+ }
210+ },
211+
190212 saveAvatar () {
191213 this .cropping = false
192214 this .loading = true
193215
194216 this .$refs .cropper .getCroppedCanvas ().toBlob (async (blob ) => {
195217 const formData = new FormData ()
196218 formData .append (' files[]' , blob)
197- await axios .post (generateUrl (' /avatar' ), formData)
219+ try {
220+ await axios .post (generateUrl (' /avatar' ), formData)
221+ } catch (e) {
222+ showError (t (' settings' , ' Error saving profile picture' ))
223+ }
198224 this .loading = false
199225 this .handleAvatarUpdate (false )
200226 })
201227 },
202228
203- async openFilePicker () {
204- const path = await picker .pick ()
205- await axios .post (generateUrl (' /avatar/' ), { path })
206- this .cropping = true
207- // TODO crop
208- // this.$nextTick(() => this.$refs.cropper.replace(event.target.result))
209- // this.imgSrc = generateUrl('/avatar/tmp') + '?requesttoken=' + encodeURIComponent(OC.requestToken) + '#' + Math.floor(Math.random() * 1000)
210- this .handleAvatarUpdate (false )
229+ cancel () {
230+ this .cropping = false
231+ this .loading = false
211232 },
212233
213234 async removeAvatar () {
214- await axios .delete (generateUrl (' /avatar' ))
235+ try {
236+ await axios .delete (generateUrl (' /avatar' ))
237+ } catch (e) {
238+ showError (t (' settings' , ' Error removing profile picture' ))
239+ }
215240 this .handleAvatarUpdate (true )
216241 },
217242
218243 handleDisplayNameUpdate () {
219- // FIXME update the avatar version only when a refresh is needed
220- // If displayName based and displayName updated: refresh
221- // If displayName based and image updated: refresh
222- // If image and image updated: refresh
223- // If image and displayName updated: do not refresh
224244 this .avatarKey = oc_userconfig .avatar .version
225245 },
226246
0 commit comments