1
+ import { ref , readonly } from 'vue'
2
+ import { useEmitter } from '@/composables/useEmitter'
3
+ import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
4
+ import { handleHTTPError } from '@/utils/http'
5
+ import api from '@/api'
6
+
7
+ /**
8
+ * Composable for handling file uploads
9
+ * @param {Object } options - Configuration options
10
+ * @param {Function } options.onFileUploadSuccess - Callback when file upload succeeds (uploadedFile)
11
+ * @param {Function } options.onUploadError - Optional callback when file upload fails (file, error)
12
+ * @param {string } options.linkedModel - The linked model for the upload
13
+ * @param {Array } options.mediaFiles - Optional external array to manage files (if not provided, internal array is used)
14
+ */
15
+ export function useFileUpload ( options = { } ) {
16
+ const {
17
+ onFileUploadSuccess,
18
+ onUploadError,
19
+ linkedModel,
20
+ mediaFiles : externalMediaFiles
21
+ } = options
22
+
23
+ const emitter = useEmitter ( )
24
+ const uploadingFiles = ref ( [ ] )
25
+ const isUploading = ref ( false )
26
+ const internalMediaFiles = ref ( [ ] )
27
+
28
+ // Use external mediaFiles if provided, otherwise use internal
29
+ const mediaFiles = externalMediaFiles || internalMediaFiles
30
+
31
+ /**
32
+ * Handles the file upload process when files are selected.
33
+ * Uploads each file to the server and adds them to the mediaFiles array.
34
+ * @param {Event } event - The file input change event containing selected files
35
+ */
36
+ const handleFileUpload = ( event ) => {
37
+ const files = Array . from ( event . target . files )
38
+ uploadingFiles . value = files
39
+ isUploading . value = true
40
+
41
+ for ( const file of files ) {
42
+ api
43
+ . uploadMedia ( {
44
+ files : file ,
45
+ inline : false ,
46
+ linked_model : linkedModel
47
+ } )
48
+ . then ( ( resp ) => {
49
+ const uploadedFile = resp . data . data
50
+
51
+ // Add to media files array
52
+ if ( Array . isArray ( mediaFiles . value ) ) {
53
+ mediaFiles . value . push ( uploadedFile )
54
+ } else {
55
+ mediaFiles . push ( uploadedFile )
56
+ }
57
+
58
+ // Remove from uploading list
59
+ uploadingFiles . value = uploadingFiles . value . filter ( ( f ) => f . name !== file . name )
60
+
61
+ // Call success callback
62
+ if ( onFileUploadSuccess ) {
63
+ onFileUploadSuccess ( uploadedFile )
64
+ }
65
+
66
+ // Update uploading state
67
+ if ( uploadingFiles . value . length === 0 ) {
68
+ isUploading . value = false
69
+ }
70
+ } )
71
+ . catch ( ( error ) => {
72
+ uploadingFiles . value = uploadingFiles . value . filter ( ( f ) => f . name !== file . name )
73
+
74
+ // Call error callback or show default toast
75
+ if ( onUploadError ) {
76
+ onUploadError ( file , error )
77
+ } else {
78
+ emitter . emit ( EMITTER_EVENTS . SHOW_TOAST , {
79
+ variant : 'destructive' ,
80
+ description : handleHTTPError ( error ) . message
81
+ } )
82
+ }
83
+
84
+ // Update uploading state
85
+ if ( uploadingFiles . value . length === 0 ) {
86
+ isUploading . value = false
87
+ }
88
+ } )
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Handles the file delete event.
94
+ * Removes the file from the mediaFiles array.
95
+ * @param {String } uuid - The UUID of the file to delete
96
+ */
97
+ const handleFileDelete = ( uuid ) => {
98
+ if ( Array . isArray ( mediaFiles . value ) ) {
99
+ mediaFiles . value = [
100
+ ...mediaFiles . value . filter ( ( item ) => item . uuid !== uuid )
101
+ ]
102
+ } else {
103
+ const index = mediaFiles . findIndex ( ( item ) => item . uuid === uuid )
104
+ if ( index > - 1 ) {
105
+ mediaFiles . splice ( index , 1 )
106
+ }
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Upload files programmatically (without event)
112
+ * @param {File[] } files - Array of files to upload
113
+ */
114
+ const uploadFiles = ( files ) => {
115
+ const mockEvent = { target : { files } }
116
+ handleFileUpload ( mockEvent )
117
+ }
118
+
119
+ /**
120
+ * Clear all media files
121
+ */
122
+ const clearMediaFiles = ( ) => {
123
+ if ( Array . isArray ( mediaFiles . value ) ) {
124
+ mediaFiles . value = [ ]
125
+ } else {
126
+ mediaFiles . length = 0
127
+ }
128
+ }
129
+
130
+ return {
131
+ // State
132
+ uploadingFiles : readonly ( uploadingFiles ) ,
133
+ isUploading : readonly ( isUploading ) ,
134
+ mediaFiles : externalMediaFiles ? readonly ( mediaFiles ) : readonly ( internalMediaFiles ) ,
135
+
136
+ // Methods
137
+ handleFileUpload,
138
+ handleFileDelete,
139
+ uploadFiles,
140
+ clearMediaFiles
141
+ }
142
+ }
0 commit comments