@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usssa/component-library",
3
- "version": "1.0.0-alpha.227",
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
- files.forEach((file, index) => {
148
- files[index]['displayName'] = files[index]['name']
149
- fileDisplayName.value[file['__key']] = files[index]['displayName']
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
- return emit('getFilesOnAdded', uploaderRef.value.files)
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="autoUpload"
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="scope.canAddFiles && !uploadedFiles.length"
243
- class="q-py-md q-px-xl bg-neutral-2 uploader-content"
319
+ v-if="filesConverting.some((f) => f.converting)"
320
+ class="row items-center justify-between uploaded-container"
244
321
  >
245
- <div class="text-center">
246
- <img
247
- alt="Upload Files"
248
- src="../../assets/upload-illustration.svg"
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
- <div class="list-items row">
255
- <UBtnStd
256
- class="q-mt-md selectFileBtn"
257
- :aria-label="selectFileBtnLabel"
258
- color="primary"
259
- dataTestId="select-file-btn"
260
- :disable="selectFileBtnDisable"
261
- :full-width="true"
262
- :label="selectFileBtnLabel"
263
- size="lg"
264
- @click="scope.pickFiles"
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
- </UBtnStd>
279
- <UBtnStd
280
- v-if="scope.canUpload && showUploadBtn"
281
- class="q-mt-md q-mb-md"
282
- color="primary"
283
- dataTestId="upload-btn"
284
- flat
285
- icon="cloud_upload"
286
- size="sm"
287
- @click="scope.upload"
288
- >
289
- <UTooltip description="Upload Files" />
290
- </UBtnStd>
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.padEnd(length, ' ')
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',