@bagelink/vue 1.2.93 → 1.2.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,144 @@
1
+ import type { BglFile, QueueFile } from './upload.types'
2
+ import { useBagel } from '@bagelink/vue'
3
+ import { ref, computed, onMounted } from 'vue'
4
+ import { files } from './upload'
5
+
6
+ interface UseFileUploadProps {
7
+ multiple?: boolean
8
+ dirPath?: string
9
+ accept?: string
10
+ disabled?: boolean
11
+ }
12
+
13
+ export function useFileUpload(props: UseFileUploadProps = {}) {
14
+ files.setBaseUrl(useBagel().host)
15
+
16
+ const fileQueue = ref<QueueFile[]>([])
17
+ const storageFiles = ref<BglFile[]>([])
18
+ const pk = ref<string[]>([])
19
+
20
+ // Computed
21
+ const pathKeys = computed(() => {
22
+ const storagePathKeys = storageFiles.value.map(file => file.path_key)
23
+ return [...pk.value, ...storagePathKeys]
24
+ })
25
+
26
+ // File handling methods
27
+ const fileToUrl = (file: File) => URL.createObjectURL(file)
28
+
29
+ const addFile = (file?: File | File[] | FileList | null) => {
30
+ if (!file) return
31
+
32
+ let filesToAdd: File[] = []
33
+
34
+ if (file instanceof File) {
35
+ filesToAdd = [file]
36
+ } else if (file instanceof FileList) {
37
+ filesToAdd = Array.from(file)
38
+ } else if (Array.isArray(file)) {
39
+ filesToAdd = file
40
+ }
41
+
42
+ const newQueueFiles = filesToAdd.map(f => ({
43
+ name: f.name,
44
+ file: f,
45
+ progress: 0
46
+ }))
47
+
48
+ fileQueue.value.push(...newQueueFiles)
49
+ }
50
+
51
+ const removeFile = async (pathKeyOrFile: string | File) => {
52
+ if (typeof pathKeyOrFile === 'string') {
53
+ // Remove from both lists
54
+ storageFiles.value = storageFiles.value.filter(file => file.path_key !== pathKeyOrFile)
55
+
56
+ const pathKeyIndex = pk.value.indexOf(pathKeyOrFile)
57
+ if (pathKeyIndex !== -1) {
58
+ pk.value.splice(pathKeyIndex, 1)
59
+ }
60
+
61
+ try {
62
+ await files.delete(pathKeyOrFile)
63
+ } catch (error) {
64
+ console.error('Error deleting file:', error)
65
+ }
66
+ } else if (pathKeyOrFile) {
67
+ const index = fileQueue.value.findIndex(({ file }) => file.name === pathKeyOrFile.name)
68
+ if (index !== -1) {
69
+ fileQueue.value.splice(index, 1)
70
+ }
71
+ }
72
+ }
73
+
74
+ const flushQueue = async () => {
75
+ for (const file of fileQueue.value) {
76
+ file.uploading = true
77
+
78
+ // If not multiple, replace the existing file
79
+ if (!props.multiple) {
80
+ pk.value.splice(0, 1)
81
+ }
82
+
83
+ try {
84
+ const { data } = await files.upload(file.file, {
85
+ onUploadProgress: (e: ProgressEvent) => {
86
+ file.progress = (e.loaded / e.total) * 100 - 1
87
+ },
88
+ dirPath: props.dirPath,
89
+ })
90
+ pk.value.push(data.path_key)
91
+ } catch (error) {
92
+ console.error('Error uploading file:', error)
93
+ }
94
+ }
95
+
96
+ // Clear the queue after processing
97
+ fileQueue.value = []
98
+ }
99
+
100
+ // UI interaction
101
+ const browse = (upload = false) => {
102
+ if (props.disabled) return
103
+
104
+ const input = document.createElement('input')
105
+ input.type = 'file'
106
+ input.multiple = !!props.multiple
107
+ input.accept = props.accept || ''
108
+
109
+ input.onchange = (e: Event) => {
110
+ addFile((e.target as HTMLInputElement).files)
111
+ if (upload) {
112
+ flushQueue()
113
+ }
114
+ }
115
+
116
+ input.click()
117
+ }
118
+
119
+ // Load initial files
120
+ onMounted(() => {
121
+ if (props.dirPath) {
122
+ files.list(props.dirPath)
123
+ .then((response) => {
124
+ const responseData = Array.isArray(response.data)
125
+ ? response.data
126
+ : [response.data]
127
+ storageFiles.value.push(...responseData)
128
+ })
129
+ .catch((error) => { console.error('Error loading files:', error) })
130
+ }
131
+ })
132
+
133
+ return {
134
+ fileQueue,
135
+ storageFiles,
136
+ pk,
137
+ pathKeys,
138
+ fileToUrl,
139
+ removeFile,
140
+ flushQueue,
141
+ addFile,
142
+ browse,
143
+ }
144
+ }
@@ -21,3 +21,5 @@ export { default as TelInput } from './TelInput.vue'
21
21
  export { default as TextInput } from './TextInput.vue'
22
22
  export { default as ToggleInput } from './ToggleInput.vue'
23
23
  export { default as UploadInput } from './Upload/UploadInput.vue'
24
+
25
+ export { useFileUpload } from './Upload/useFileUpload'
@@ -63,6 +63,16 @@ function clickOutside() {
63
63
 
64
64
  const upgradeHeaders = (url: string) => url.replace(/http:\/\//, '//')
65
65
 
66
+ function downloadFile() {
67
+ const link = document.createElement('a')
68
+ const src = currentItem.src || ''
69
+ link.target = '_blank'
70
+ link.href = upgradeHeaders(src)
71
+ link.download = src ? src.split('/').pop() || 'download' : 'download'
72
+ document.body.appendChild(link)
73
+ link.click()
74
+ document.body.removeChild(link)
75
+ }
66
76
  defineExpose({ open, close })
67
77
  </script>
68
78
 
@@ -108,8 +118,7 @@ defineExpose({ open, close })
108
118
  <Btn
109
119
  v-if="currentItem?.download && currentItem?.src" class="color-white" round thin flat icon="download"
110
120
  value="Download File"
111
- :href="upgradeHeaders(currentItem?.src)"
112
- download
121
+ @click="downloadFile"
113
122
  />
114
123
  <div v-if="!currentItem?.openFile && !currentItem?.download" />
115
124
  </div>
@@ -128,34 +137,40 @@ defineExpose({ open, close })
128
137
  class="vw90"
129
138
  />
130
139
 
131
- <embed
140
+ <div
132
141
  v-else-if="item?.type === 'pdf' && item?.src"
133
- :src="normalizeURL(item?.src)"
134
- type="application/pdf"
135
- width="100%"
136
- height="1080"
137
- :title="item?.name"
138
142
  class="vw90"
139
143
  >
140
- <div v-else class="file-info txt-white flex m_block align-items-start gap-025">
141
- <Icon class="m-0 m_none" icon="draft" :size="10" weight="12" />
142
- <Icon class="m-0 none m_block m_-mb-1" icon="draft" :size="4" weight="2" />
144
+ <embed
145
+ :src="normalizeURL(item?.src)"
146
+ type="application/pdf"
147
+ width="100%"
148
+ height="1080"
149
+ :title="item?.name"
150
+ class="vw90"
151
+ >
152
+ </div>
153
+ <div v-else class="vw90">
154
+ <div class="file-info txt-white flex m_block align-items-start gap-025">
155
+ <Icon class="m-0 m_none" icon="draft" :size="10" weight="12" />
156
+ <Icon class="m-0 none m_block m_-mb-1" icon="draft" :size="4" weight="2" />
143
157
 
144
- <div class="txt-start">
145
- <p class="mx-0 light">
146
- File:
147
- <span class="semi word-break-all ">
148
- {{ item?.name }}
149
- </span>
150
- </p>
151
- <p class="mx-0 ">
152
- Type:
153
- <span class="semi">
154
- {{ item?.type }}
155
- </span>
156
- </p>
157
- <Btn :href="item?.src" target="_blank" round thin class="mt-1" value="Open file" />
158
- <!-- <a :href="currentItem?.src" target="_blank">Open file</a> -->
158
+ <div class="txt-start">
159
+ <p class="mx-0 light">
160
+ File:
161
+ <span class="semi word-break-all ">
162
+ {{ item?.name }}
163
+ </span>
164
+ </p>
165
+ <p class="mx-0 ">
166
+ Type:
167
+ <span class="semi">
168
+ {{ item?.type }}
169
+ </span>
170
+ </p>
171
+ <Btn :href="item?.src" target="_blank" round thin class="mt-1" value="Open file" />
172
+ <!-- <a :href="currentItem?.src" target="_blank">Open file</a> -->
173
+ </div>
159
174
  </div>
160
175
  </div>
161
176
  </template>