2121 hideUserStatus />
2222 <div v-else class =" icon-loading" />
2323 </div >
24- <VueCropper
24+ <component
25+ :is =" cropperComponent"
2526 v-show =" showCropper"
2627 ref =" cropper"
2728 class =" avatar__cropper"
@@ -114,7 +115,7 @@ import { getFilePickerBuilder } from '@nextcloud/dialogs'
114115import { t } from ' @nextcloud/l10n'
115116import { generateUrl } from ' @nextcloud/router'
116117import { useIsDarkTheme } from ' @nextcloud/vue/composables/useIsDarkTheme'
117- import VueCropper from ' vue-cropperjs '
118+ import { markRaw } from ' vue'
118119import NcButton from ' @nextcloud/vue/components/NcButton'
119120import NcColorPicker from ' @nextcloud/vue/components/NcColorPicker'
120121import NcEmojiPicker from ' @nextcloud/vue/components/NcEmojiPicker'
@@ -127,7 +128,15 @@ import ConversationIcon from '../ConversationIcon.vue'
127128import IconFileUpload from ' ../../../img/material-icons/file-upload.svg?raw'
128129import { AVATAR } from ' ../../constants.ts'
129130
130- import ' cropperjs/dist/cropper.css'
131+ // Asynchronous loader for VueCropper component
132+ let vueCropper = null
133+ function getCropperAsyncComponent () {
134+ vueCropper ?? = Promise .all ([
135+ import (' vue-cropperjs' ),
136+ import (' cropperjs/dist/cropper.css' ),
137+ ]).then (([module ]) => module .default )
138+ return vueCropper
139+ }
131140
132141const validMimeTypes = [' image/png' , ' image/jpeg' ]
133142
@@ -140,7 +149,6 @@ export default {
140149 NcColorPicker,
141150 NcEmojiPicker,
142151 NcIconSvgWrapper,
143- VueCropper,
144152 // Icons
145153 IconTrashCanOutline,
146154 IconEmoticonOutline,
@@ -187,6 +195,7 @@ export default {
187195
188196 data () {
189197 return {
198+ cropperComponent: null ,
190199 showCropper: false ,
191200 loading: false ,
192201 cropperOptions: {
@@ -239,7 +248,14 @@ export default {
239248
240249 methods: {
241250 t,
251+
252+ async waitForCropper () {
253+ this .cropperComponent ?? = markRaw (await getCropperAsyncComponent ())
254+ },
255+
242256 activateLocalFilePicker () {
257+ // Async request to load and set up component, while user picks the file
258+ getCropperAsyncComponent ()
243259 // Set to null so that selecting the same file will trigger the change event
244260 this .$refs .input .value = null
245261 this .$refs .input .click ()
@@ -255,14 +271,17 @@ export default {
255271 }
256272
257273 const reader = new FileReader ()
258- reader .onload = (e ) => {
274+ reader .onload = async (e ) => {
275+ await this .waitForCropper ()
259276 this .$refs .cropper .replace (e .target .result )
260277 this .showCropper = true
261278 }
262279 reader .readAsDataURL (file)
263280 },
264281
265282 async showFilePicker () {
283+ // Async request to load and set up component, while user picks the file
284+ getCropperAsyncComponent ()
266285 const filePicker = getFilePickerBuilder (t (' spreed' , ' Choose your conversation picture' ))
267286 .setContainer (' #vue-avatar-section' )
268287 .setMultiSelect (false )
@@ -284,6 +303,7 @@ export default {
284303 }
285304
286305 try {
306+ await this .waitForCropper ()
287307 const tempAvatar = generateUrl (` /core/preview?fileId=${ fileid} &x=512&y=512&a=1` )
288308 this .$refs .cropper .replace (tempAvatar)
289309 this .showCropper = true
@@ -325,6 +345,8 @@ export default {
325345 },
326346
327347 async getPictureFormData () {
348+ // Cropper should be ready by this moment, so just a safeguard
349+ await this .waitForCropper ()
328350 const canvasData = this .$refs .cropper .getCroppedCanvas ()
329351 const scaleFactor = canvasData .width > 512 ? 512 / canvasData .width : 1
330352
0 commit comments