@usssa/component-library 1.0.0-alpha.227 → 1.0.0-alpha.228
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/package.json +2 -1
- package/src/components/core/UUploader.vue +146 -55
- package/src/utils/data.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usssa/component-library",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.228",
|
|
4
4
|
"description": "A Quasar component library project",
|
|
5
5
|
"productName": "Quasar component library App",
|
|
6
6
|
"author": "Engineering Team <engineering@usssa.com>",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@usssa/core-client": "^0.0.19",
|
|
40
40
|
"algoliasearch": "4",
|
|
41
41
|
"flag-icons": "^7.2.3",
|
|
42
|
+
"heic2any": "^0.0.4",
|
|
42
43
|
"quasar": "^2.16.0",
|
|
43
44
|
"vue": "^3.4.18",
|
|
44
45
|
"vue-router": "^4.0.12",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { useQuasar } from 'quasar'
|
|
3
|
-
import { computed, ref } from 'vue'
|
|
3
|
+
import { computed, ref, watch } from 'vue'
|
|
4
|
+
import heic2any from 'heic2any'
|
|
4
5
|
import { fixStringLength, formatDate, getFileCategory } from '../../utils/data'
|
|
5
6
|
import UBtnIcon from './UBtnIcon.vue'
|
|
6
7
|
import UBtnStd from './UBtnStd.vue'
|
|
@@ -8,6 +9,7 @@ import UInputTextStd from './UInputTextStd.vue'
|
|
|
8
9
|
import UTooltip from './UTooltip.vue'
|
|
9
10
|
|
|
10
11
|
const emit = defineEmits([
|
|
12
|
+
'conversionStatus',
|
|
11
13
|
'onUploadFactory',
|
|
12
14
|
'onViewFile',
|
|
13
15
|
'getFilesOnAdded',
|
|
@@ -29,6 +31,10 @@ const props = defineProps({
|
|
|
29
31
|
type: String,
|
|
30
32
|
default: 'Cancel',
|
|
31
33
|
},
|
|
34
|
+
conversionText: {
|
|
35
|
+
type: String,
|
|
36
|
+
default: 'Converting into jpeg image',
|
|
37
|
+
},
|
|
32
38
|
dataTestId: {
|
|
33
39
|
type: String,
|
|
34
40
|
default: 'uploader',
|
|
@@ -116,9 +122,18 @@ const props = defineProps({
|
|
|
116
122
|
|
|
117
123
|
const $q = useQuasar()
|
|
118
124
|
|
|
125
|
+
const filesConverting = ref([]) // track converting state per file
|
|
119
126
|
const fileDisplayName = ref([])
|
|
120
127
|
const isEditing = ref([])
|
|
121
128
|
const uploaderRef = ref(null)
|
|
129
|
+
const autoUploadRef = computed({
|
|
130
|
+
get() {
|
|
131
|
+
return props.autoUpload
|
|
132
|
+
},
|
|
133
|
+
set(val) {
|
|
134
|
+
return val
|
|
135
|
+
},
|
|
136
|
+
})
|
|
122
137
|
const isSmallWidthDevices = computed(() => $q.screen.width < 350)
|
|
123
138
|
const uploadedFiles = computed({
|
|
124
139
|
get() {
|
|
@@ -143,13 +158,69 @@ const handleViewClick = (file) => {
|
|
|
143
158
|
return emit('onViewFile', file)
|
|
144
159
|
}
|
|
145
160
|
|
|
146
|
-
const onFileAdded = (files) => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
161
|
+
const onFileAdded = async (files) => {
|
|
162
|
+
try {
|
|
163
|
+
const uploader = uploaderRef.value
|
|
164
|
+
if (!uploader) return // uploader destroyed
|
|
165
|
+
|
|
166
|
+
const convertedFiles = []
|
|
167
|
+
|
|
168
|
+
const heicConversions = files.map(async (file) => {
|
|
169
|
+
const key = file.__key
|
|
170
|
+
const isHeic =
|
|
171
|
+
/\.(heic|heif)$/i.test(file.name) ||
|
|
172
|
+
file.type === 'image/heic' ||
|
|
173
|
+
file.type === 'image/heif'
|
|
174
|
+
|
|
175
|
+
if (!isHeic) return
|
|
176
|
+
|
|
177
|
+
// Add to converting array
|
|
178
|
+
filesConverting.value.push({ key, name: file.name, converting: true })
|
|
179
|
+
|
|
180
|
+
// Safely remove HEIC file
|
|
181
|
+
if (uploaderRef.value) {
|
|
182
|
+
const idx = uploader.files.findIndex((f) => f.__key === key)
|
|
183
|
+
if (idx !== -1) uploader.removeFile(uploader.files[idx])
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const convertedBlob = await heic2any({
|
|
188
|
+
blob: file,
|
|
189
|
+
toType: 'image/jpeg',
|
|
190
|
+
quality: 0.8,
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
// Skip if uploader closed while converting
|
|
194
|
+
if (!uploaderRef.value) return
|
|
195
|
+
|
|
196
|
+
const newFile = new File(
|
|
197
|
+
[convertedBlob],
|
|
198
|
+
file.name.replace(/\.(heic|heif)$/i, '.jpeg'),
|
|
199
|
+
{ type: 'image/jpeg' }
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
newFile.__key = key
|
|
203
|
+
newFile.displayName = newFile.name
|
|
204
|
+
convertedFiles.push(newFile)
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.error('HEIC conversion failed:', err)
|
|
207
|
+
} finally {
|
|
208
|
+
const index = filesConverting.value.findIndex((f) => f.key === key)
|
|
209
|
+
if (index !== -1) filesConverting.value.splice(index, 1)
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
await Promise.allSettled(heicConversions)
|
|
214
|
+
|
|
215
|
+
// Safely add converted files
|
|
216
|
+
if (convertedFiles.length > 0 && uploaderRef.value) {
|
|
217
|
+
uploaderRef.value.addFiles(convertedFiles)
|
|
218
|
+
}
|
|
151
219
|
|
|
152
|
-
|
|
220
|
+
emit('getFilesOnAdded', uploaderRef.value?.files || [])
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.warn('error during conversion:', err.message || err)
|
|
223
|
+
}
|
|
153
224
|
}
|
|
154
225
|
|
|
155
226
|
const onFileUploaded = ({ files, xhr }) => {
|
|
@@ -176,7 +247,6 @@ const removeFile = (payload) => {
|
|
|
176
247
|
if (uploadedIndex !== -1) {
|
|
177
248
|
const updated = [...uploadedFiles.value]
|
|
178
249
|
updated.splice(uploadedIndex, 1)
|
|
179
|
-
|
|
180
250
|
emit('update:uploadedFiles', updated)
|
|
181
251
|
}
|
|
182
252
|
|
|
@@ -195,6 +265,8 @@ const scopedFiles = (files) => {
|
|
|
195
265
|
__status: 'uploaded',
|
|
196
266
|
uploaded: true,
|
|
197
267
|
__key: uploadedFile.__key || uploadedFile.name,
|
|
268
|
+
displayName: uploadedFile.displayName || uploadedFile.name,
|
|
269
|
+
name: uploadedFile.displayName || uploadedFile.name,
|
|
198
270
|
})
|
|
199
271
|
}
|
|
200
272
|
})
|
|
@@ -217,6 +289,11 @@ const upload = () => {
|
|
|
217
289
|
uploaderRef.value.upload()
|
|
218
290
|
}
|
|
219
291
|
|
|
292
|
+
watch(
|
|
293
|
+
() => filesConverting.value.length,
|
|
294
|
+
(newVal) => emit('conversionStatus', newVal > 0)
|
|
295
|
+
)
|
|
296
|
+
|
|
220
297
|
defineExpose({ upload })
|
|
221
298
|
</script>
|
|
222
299
|
<template>
|
|
@@ -224,7 +301,7 @@ defineExpose({ upload })
|
|
|
224
301
|
<q-uploader
|
|
225
302
|
v-bind="$attrs"
|
|
226
303
|
class="u-uploader"
|
|
227
|
-
:auto-upload="
|
|
304
|
+
:auto-upload="autoUploadRef"
|
|
228
305
|
:batch="batch"
|
|
229
306
|
:dataTestId="dataTestId"
|
|
230
307
|
label="Drag and drop your file or select choose file."
|
|
@@ -239,55 +316,69 @@ defineExpose({ upload })
|
|
|
239
316
|
>
|
|
240
317
|
<template v-slot:header="scope">
|
|
241
318
|
<div
|
|
242
|
-
v-if="
|
|
243
|
-
class="
|
|
319
|
+
v-if="filesConverting.some((f) => f.converting)"
|
|
320
|
+
class="row items-center justify-between uploaded-container"
|
|
244
321
|
>
|
|
245
|
-
<div class="
|
|
246
|
-
<
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
<div class="text-body-md q-mt-ba" dataTestId="description">
|
|
251
|
-
{{ description }}
|
|
252
|
-
</div>
|
|
322
|
+
<div class="row items-center">
|
|
323
|
+
<q-spinner class="q-mr-sm" color="black" size="20px" />
|
|
324
|
+
<span class="text-black">
|
|
325
|
+
{{ conversionText }}
|
|
326
|
+
</span>
|
|
253
327
|
</div>
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
>
|
|
266
|
-
{{ selectFileBtnLabel }}
|
|
267
|
-
<q-uploader-add-trigger
|
|
268
|
-
v-if="!selectFileBtnDisable"
|
|
269
|
-
:aria-label="selectFileBtnLabel"
|
|
270
|
-
/>
|
|
271
|
-
<UTooltip
|
|
272
|
-
v-if="selectFileBtnTooltip?.length"
|
|
273
|
-
anchor="bottom middle"
|
|
274
|
-
:description="selectFileBtnTooltip"
|
|
275
|
-
:offset="[4, 4]"
|
|
276
|
-
self="top middle"
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
<div v-else>
|
|
331
|
+
<div
|
|
332
|
+
v-if="scope.canAddFiles && !uploadedFiles.length"
|
|
333
|
+
class="q-py-md q-px-xl bg-neutral-2 uploader-content"
|
|
334
|
+
>
|
|
335
|
+
<div class="text-center">
|
|
336
|
+
<img
|
|
337
|
+
alt="Upload Files"
|
|
338
|
+
src="../../assets/upload-illustration.svg"
|
|
277
339
|
/>
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
340
|
+
<div class="text-body-md q-mt-ba" dataTestId="description">
|
|
341
|
+
{{ description }}
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
<div class="list-items row">
|
|
345
|
+
<UBtnStd
|
|
346
|
+
class="q-mt-md selectFileBtn"
|
|
347
|
+
:aria-label="selectFileBtnLabel"
|
|
348
|
+
color="primary"
|
|
349
|
+
dataTestId="select-file-btn"
|
|
350
|
+
:disable="selectFileBtnDisable"
|
|
351
|
+
:full-width="true"
|
|
352
|
+
:label="selectFileBtnLabel"
|
|
353
|
+
size="lg"
|
|
354
|
+
@click="scope.pickFiles"
|
|
355
|
+
>
|
|
356
|
+
{{ selectFileBtnLabel }}
|
|
357
|
+
<q-uploader-add-trigger
|
|
358
|
+
v-if="!selectFileBtnDisable"
|
|
359
|
+
:aria-label="selectFileBtnLabel"
|
|
360
|
+
/>
|
|
361
|
+
<UTooltip
|
|
362
|
+
v-if="selectFileBtnTooltip?.length"
|
|
363
|
+
anchor="bottom middle"
|
|
364
|
+
:description="selectFileBtnTooltip"
|
|
365
|
+
:offset="[4, 4]"
|
|
366
|
+
self="top middle"
|
|
367
|
+
/>
|
|
368
|
+
</UBtnStd>
|
|
369
|
+
<UBtnStd
|
|
370
|
+
v-if="scope.canUpload && showUploadBtn"
|
|
371
|
+
class="q-mt-md q-mb-md"
|
|
372
|
+
color="primary"
|
|
373
|
+
dataTestId="upload-btn"
|
|
374
|
+
flat
|
|
375
|
+
icon="cloud_upload"
|
|
376
|
+
size="sm"
|
|
377
|
+
@click="scope.upload"
|
|
378
|
+
>
|
|
379
|
+
<UTooltip description="Upload Files" />
|
|
380
|
+
</UBtnStd>
|
|
381
|
+
</div>
|
|
291
382
|
</div>
|
|
292
383
|
</div>
|
|
293
384
|
</template>
|
package/src/utils/data.ts
CHANGED
|
@@ -24,7 +24,7 @@ export const fixStringLength = (str: string, length: number) => {
|
|
|
24
24
|
if (str?.length > length) {
|
|
25
25
|
return str.substring(0, length) + '....'
|
|
26
26
|
} else {
|
|
27
|
-
return str
|
|
27
|
+
return str?.padEnd(length, ' ')
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -55,6 +55,8 @@ export const getFileCategory = (mimeType) => {
|
|
|
55
55
|
'image/bmp': 'image',
|
|
56
56
|
'image/svg+xml': 'image',
|
|
57
57
|
'image/webp': 'image',
|
|
58
|
+
'image/heic': 'image',
|
|
59
|
+
'image/heif': 'image',
|
|
58
60
|
|
|
59
61
|
// Video MIME types
|
|
60
62
|
'video/mp4': 'video',
|