@bagelink/vue 1.2.89 → 1.2.97
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.
- package/dist/components/Pagination.vue.d.ts +35 -0
- package/dist/components/Pagination.vue.d.ts.map +1 -0
- package/dist/components/Pill.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts +7 -8
- package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/Upload/useFileUpload.d.ts +76 -0
- package/dist/components/form/inputs/Upload/useFileUpload.d.ts.map +1 -0
- package/dist/components/form/inputs/index.d.ts +1 -0
- package/dist/components/form/inputs/index.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
- package/dist/index.cjs +850 -599
- package/dist/index.mjs +850 -599
- package/dist/style.css +88 -67
- package/package.json +1 -1
- package/src/components/Pagination.vue +252 -0
- package/src/components/Pill.vue +1 -1
- package/src/components/form/inputs/Upload/UploadInput.vue +26 -101
- package/src/components/form/inputs/Upload/useFileUpload.ts +144 -0
- package/src/components/form/inputs/index.ts +2 -0
- package/src/components/index.ts +1 -0
- package/src/components/lightbox/Lightbox.vue +41 -26
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type {
|
|
3
|
-
import { Btn, IMAGE_FORMATS_REGEXP, Icon,
|
|
4
|
-
import {
|
|
5
|
-
import { files } from './upload'
|
|
2
|
+
import type { UploadInputProps } from './upload.types'
|
|
3
|
+
import { Btn, IMAGE_FORMATS_REGEXP, Icon, Card, Image, pathKeyToURL } from '@bagelink/vue'
|
|
4
|
+
import { useFileUpload } from './useFileUpload'
|
|
6
5
|
|
|
7
6
|
const props = withDefaults(defineProps<UploadInputProps>(), {
|
|
8
7
|
height: '215px',
|
|
@@ -11,122 +10,48 @@ const props = withDefaults(defineProps<UploadInputProps>(), {
|
|
|
11
10
|
})
|
|
12
11
|
|
|
13
12
|
const emit = defineEmits(['update:modelValue', 'addFileStart'])
|
|
14
|
-
const bagel = useBagel(bagelInjectionKey)
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
const {
|
|
15
|
+
fileQueue,
|
|
16
|
+
pathKeys,
|
|
17
|
+
removeFile,
|
|
18
|
+
flushQueue,
|
|
19
|
+
fileToUrl,
|
|
20
|
+
addFile,
|
|
21
|
+
browse,
|
|
22
|
+
} = useFileUpload({
|
|
23
|
+
disabled: props.disabled,
|
|
24
|
+
dirPath: props.dirPath,
|
|
25
|
+
multiple: props.multiple,
|
|
26
|
+
accept: props.accept
|
|
25
27
|
})
|
|
26
28
|
|
|
27
|
-
watch(() => pk, (value) => {
|
|
28
|
-
if (props.multiple) emit('update:modelValue', value)
|
|
29
|
-
else emit('update:modelValue', value[0] || undefined)
|
|
30
|
-
}, { deep: true })
|
|
31
|
-
|
|
32
29
|
const isImage = (str: string) => IMAGE_FORMATS_REGEXP.test(str)
|
|
33
|
-
const fileToUrl = (file: File) => URL.createObjectURL(file)
|
|
34
30
|
|
|
35
31
|
let isDragOver = $ref(false)
|
|
36
32
|
|
|
37
|
-
// File handling methods
|
|
38
|
-
async function removeFile(path_key: string) {
|
|
39
|
-
storageFiles = storageFiles.filter(file => file.path_key !== path_key)
|
|
40
|
-
const pki = pk.indexOf(path_key)
|
|
41
|
-
if (pki !== -1) pk.splice(pki, 1)
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
await files.delete(path_key)
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.error(error)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function flushQueue() {
|
|
51
|
-
emit('addFileStart')
|
|
52
|
-
for (const file of fileQueue) {
|
|
53
|
-
file.uploading = true
|
|
54
|
-
if (!props.multiple) pk.splice(0, 1)
|
|
55
|
-
try {
|
|
56
|
-
const { data } = await files.upload(file.file, {
|
|
57
|
-
onUploadProgress: (e: ProgressEvent) => {
|
|
58
|
-
file.progress = (e.loaded / e.total) * 100 - 1
|
|
59
|
-
},
|
|
60
|
-
dirPath: props.dirPath,
|
|
61
|
-
})
|
|
62
|
-
pk.push(data.path_key)
|
|
63
|
-
} catch (error) {
|
|
64
|
-
console.error('error flushing queue', error)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
fileQueue.splice(0, fileQueue.length)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Event handlers
|
|
71
|
-
function browse() {
|
|
72
|
-
if (props.disabled) return
|
|
73
|
-
const input = document.createElement('input')
|
|
74
|
-
input.type = 'file'
|
|
75
|
-
input.multiple = props.multiple
|
|
76
|
-
input.accept = props.accept
|
|
77
|
-
input.onchange = (e: Event) => {
|
|
78
|
-
const files = Array.from((e.target as HTMLInputElement).files || [])
|
|
79
|
-
fileQueue.push(...files.map(file => ({ name: file.name, file, progress: 0 })))
|
|
80
|
-
flushQueue()
|
|
81
|
-
}
|
|
82
|
-
input.click()
|
|
83
|
-
}
|
|
84
|
-
|
|
85
33
|
function handleDrag(e: DragEvent, isDragging = false) {
|
|
86
34
|
e.preventDefault()
|
|
87
35
|
e.stopPropagation()
|
|
88
36
|
if (!props.disabled) isDragOver = isDragging
|
|
89
37
|
}
|
|
90
38
|
|
|
91
|
-
function handleDrop(e: DragEvent) {
|
|
39
|
+
async function handleDrop(e: DragEvent) {
|
|
92
40
|
if (props.disabled) return
|
|
93
41
|
e.preventDefault()
|
|
94
42
|
e.stopPropagation()
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
flushQueue()
|
|
100
|
-
}
|
|
43
|
+
emit('addFileStart')
|
|
44
|
+
addFile(e.dataTransfer?.files)
|
|
45
|
+
await flushQueue()
|
|
46
|
+
emit('update:modelValue', pathKeys.value)
|
|
101
47
|
isDragOver = false
|
|
102
48
|
}
|
|
103
|
-
|
|
104
|
-
if (props.dirPath) {
|
|
105
|
-
files.list(props.dirPath)
|
|
106
|
-
.then(response => storageFiles.push(...([response.data].flat())))
|
|
107
|
-
.catch(console.error)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
watch(() => props.dirPath, () => {
|
|
111
|
-
if (props.dirPath) {
|
|
112
|
-
files.list(props.dirPath)
|
|
113
|
-
.then(response => storageFiles.push(...([response.data].flat())))
|
|
114
|
-
.catch(console.error)
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
49
|
</script>
|
|
118
50
|
|
|
119
51
|
<template>
|
|
120
52
|
<div class="bagel-input">
|
|
121
53
|
<label v-if="label">{{ label }}</label>
|
|
122
|
-
<input
|
|
123
|
-
v-if="required && !pathKeys.length"
|
|
124
|
-
placeholder="required"
|
|
125
|
-
type="text"
|
|
126
|
-
required
|
|
127
|
-
class="pixel"
|
|
128
|
-
>
|
|
129
|
-
|
|
54
|
+
<input v-if="required && !pathKeys.length" placeholder="required" type="text" required class="pixel">
|
|
130
55
|
<Card
|
|
131
56
|
v-if="theme === 'basic'"
|
|
132
57
|
outline
|
|
@@ -140,7 +65,7 @@ watch(() => props.dirPath, () => {
|
|
|
140
65
|
icon="upload"
|
|
141
66
|
outline
|
|
142
67
|
:value="btnPlaceholder || 'Upload'"
|
|
143
|
-
@click="browse"
|
|
68
|
+
@click="browse(true)"
|
|
144
69
|
/>
|
|
145
70
|
|
|
146
71
|
<template v-for="path_key in pathKeys" :key="path_key">
|
|
@@ -158,7 +83,7 @@ watch(() => props.dirPath, () => {
|
|
|
158
83
|
color="gray"
|
|
159
84
|
thin
|
|
160
85
|
icon="autorenew"
|
|
161
|
-
@click="browse"
|
|
86
|
+
@click="browse(true)"
|
|
162
87
|
/>
|
|
163
88
|
<Btn
|
|
164
89
|
icon="download"
|
|
@@ -197,7 +122,7 @@ watch(() => props.dirPath, () => {
|
|
|
197
122
|
'bgl_oval-upload': oval,
|
|
198
123
|
}"
|
|
199
124
|
:style="{ width, height }"
|
|
200
|
-
@click="browse"
|
|
125
|
+
@click="browse(true)"
|
|
201
126
|
@dragover="(e) => handleDrag(e, true)"
|
|
202
127
|
@drop="handleDrop"
|
|
203
128
|
@dragleave="(e) => handleDrag(e)"
|
|
@@ -281,7 +206,7 @@ watch(() => props.dirPath, () => {
|
|
|
281
206
|
color="gray"
|
|
282
207
|
thin
|
|
283
208
|
icon="autorenew"
|
|
284
|
-
@click="browse"
|
|
209
|
+
@click="browse(true)"
|
|
285
210
|
/>
|
|
286
211
|
<Btn
|
|
287
212
|
v-tooltip="'Download'"
|
|
@@ -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'
|
package/src/components/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ export { default as ModalConfirm } from './ModalConfirm.vue'
|
|
|
32
32
|
export { default as ModalForm } from './ModalForm.vue'
|
|
33
33
|
export { default as NavBar } from './NavBar.vue'
|
|
34
34
|
export { default as PageTitle } from './PageTitle.vue'
|
|
35
|
+
export { default as Pagination } from './Pagination.vue'
|
|
35
36
|
export { default as Pill } from './Pill.vue'
|
|
36
37
|
export { default as RouterWrapper } from './RouterWrapper.vue'
|
|
37
38
|
export { default as Slider } from './Slider.vue'
|
|
@@ -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
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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>
|