@finema/core 1.4.98 → 1.4.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.
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finema/core",
3
- "version": "1.4.98",
3
+ "version": "1.4.99",
4
4
  "configKey": "core",
5
5
  "compatibility": {
6
6
  "nuxt": "^3.7.4"
package/dist/module.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { defineNuxtModule, createResolver, installModule, addPlugin, addComponentsDir, addImportsDir } from '@nuxt/kit';
2
2
 
3
3
  const name = "@finema/core";
4
- const version = "1.4.98";
4
+ const version = "1.4.99";
5
5
 
6
6
  const colors = {
7
7
  black: "#20243E",
@@ -54,9 +54,9 @@
54
54
  <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
55
55
  <p class="truncate font-light text-gray-400">
56
56
  {{
57
- fileAllocate.isSelectedFileUseMb
58
- ? `${fileAllocate.selectedFileSizeMb} MB`
59
- : `${fileAllocate.selectedFileSizeKb} KB`
57
+ fileAllocate.isSelectedFileUseMb.value
58
+ ? `${fileAllocate.selectedFileSizeMb.value} MB`
59
+ : `${fileAllocate.selectedFileSizeKb.value} KB`
60
60
  }}
61
61
  </p>
62
62
  </div>
@@ -96,8 +96,8 @@ import {
96
96
  generateURL,
97
97
  checkMaxSize,
98
98
  checkFileType,
99
- getFileAllocate,
100
99
  downloadFileFromURL,
100
+ useFileAllocate,
101
101
  } from '#core/helpers/componentHelper'
102
102
  import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
103
103
  import { useFieldHOC } from '#core/composables/useForm'
@@ -122,11 +122,8 @@ const acceptFile = computed(() =>
122
122
  typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
123
123
  )
124
124
 
125
- const fileAllocate = computed(() =>
126
- getFileAllocate(props.maxSize ?? 0, selectedFile.value?.size ?? 0)
127
- )
128
-
129
125
  const selectedFile = computed(() => value?.value)
126
+ const fileAllocate = useFileAllocate(selectedFile, props)
130
127
 
131
128
  const onDrop = (files: File[] | null) => {
132
129
  if (props.isDisabled || files?.length === 0 || !files) return
@@ -182,16 +179,16 @@ const handleCheckFileCondition = (file: File | undefined): boolean => {
182
179
  return false
183
180
  }
184
181
 
185
- const maxSize = checkMaxSize(file, fileAllocate.value.acceptFileSizeKb ?? 0)
182
+ const maxSize = checkMaxSize(file, fileAllocate.acceptFileSizeKb.value)
186
183
 
187
184
  if (!maxSize) {
188
- if (fileAllocate.value.isAcceptFileUseMb) {
185
+ if (fileAllocate.isAcceptFileUseMb.value) {
189
186
  setErrors(
190
- i18next.t('custom:invalid_file_size_mb', { size: fileAllocate.value.acceptFileSizeMb })
187
+ i18next.t('custom:invalid_file_size_mb', { size: fileAllocate.acceptFileSizeMb.value })
191
188
  )
192
189
  } else {
193
190
  setErrors(
194
- i18next.t('custom:invalid_file_size_kb', { size: fileAllocate.value.acceptFileSizeKb })
191
+ i18next.t('custom:invalid_file_size_kb', { size: fileAllocate.acceptFileSizeKb.value })
195
192
  )
196
193
  }
197
194
 
@@ -50,9 +50,9 @@
50
50
  <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
51
51
  <p class="truncate font-light text-gray-400">
52
52
  {{
53
- fileAllocate.isSelectedFileUseMb
54
- ? `${fileAllocate.selectedFileSizeMb} MB`
55
- : `${fileAllocate.selectedFileSizeKb} KB`
53
+ fileAllocate.isSelectedFileUseMb.value
54
+ ? `${fileAllocate.selectedFileSizeMb.value} MB`
55
+ : `${fileAllocate.selectedFileSizeKb.value} KB`
56
56
  }}
57
57
  - {{ percent }}% {{ uploadingLabel ?? 'กำลังอัพโหลด...' }}
58
58
  </p>
@@ -88,9 +88,9 @@
88
88
  <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
89
89
  <p class="truncate text-sm font-light text-gray-400">
90
90
  {{
91
- fileAllocate.isSelectedFileUseMb
92
- ? `${fileAllocate.selectedFileSizeMb} MB`
93
- : `${fileAllocate.selectedFileSizeKb} KB`
91
+ fileAllocate.isSelectedFileUseMb.value
92
+ ? `${fileAllocate.selectedFileSizeMb.value} MB`
93
+ : `${fileAllocate.selectedFileSizeKb.value} KB`
94
94
  }}
95
95
  </p>
96
96
  </div>
@@ -170,8 +170,9 @@ import {
170
170
  checkMaxSize,
171
171
  checkFileType,
172
172
  isImage,
173
- getFileAllocate,
174
173
  downloadFileFromURL,
174
+ useFileAllocate,
175
+ useFileProgress,
175
176
  } from '#core/helpers/componentHelper'
176
177
  import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
177
178
  import { useFieldHOC } from '#core/composables/useForm'
@@ -215,16 +216,14 @@ const { ui } = useUI('uploadFileDropzone', toRef(props, 'ui'), config)
215
216
  const fileInputRef = ref<HTMLInputElement>()
216
217
  const dropzoneRef = ref<HTMLDivElement>()
217
218
  const selectedFile = ref<File | undefined>()
218
- const percent = ref<number | string>(0)
219
219
  const isPreviewOpen = ref<boolean>(false)
220
220
 
221
221
  const acceptFile = computed(() =>
222
222
  typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
223
223
  )
224
224
 
225
- const fileAllocate = computed(() =>
226
- getFileAllocate(props.maxSize ?? 0, selectedFile.value?.size ?? 0)
227
- )
225
+ const { onUploadProgress, onDownloadProgress, percent } = useFileProgress()
226
+ const fileAllocate = useFileAllocate(selectedFile, props)
228
227
 
229
228
  const onDrop = (files: File[] | null) => {
230
229
  if (props.isDisabled || files?.length === 0 || !files) return
@@ -286,16 +285,16 @@ const handleCheckFileCondition = (file: File | undefined): boolean => {
286
285
  return false
287
286
  }
288
287
 
289
- const maxSize = checkMaxSize(file, fileAllocate.value.acceptFileSizeKb ?? 0)
288
+ const maxSize = checkMaxSize(file, fileAllocate.acceptFileSizeKb.value)
290
289
 
291
290
  if (!maxSize) {
292
- if (fileAllocate.value.isAcceptFileUseMb) {
291
+ if (fileAllocate.isAcceptFileUseMb.value) {
293
292
  setErrors(
294
- i18next.t('custom:invalid_file_size_mb', { size: fileAllocate.value.acceptFileSizeMb })
293
+ i18next.t('custom:invalid_file_size_mb', { size: fileAllocate.acceptFileSizeMb.value })
295
294
  )
296
295
  } else {
297
296
  setErrors(
298
- i18next.t('custom:invalid_file_size_kb', { size: fileAllocate.value.acceptFileSizeKb })
297
+ i18next.t('custom:invalid_file_size_kb', { size: fileAllocate.acceptFileSizeKb.value })
299
298
  )
300
299
  }
301
300
 
@@ -320,24 +319,6 @@ const handleRetryUpload = () => {
320
319
  }
321
320
  }
322
321
 
323
- const onUploadProgress = (progressEvent: ProgressEvent) => {
324
- percent.value = StringHelper.withFixed(
325
- (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.8
326
- )
327
- }
328
-
329
- const onDownloadProgress = (progressEvent: ProgressEvent) => {
330
- if (progressEvent.total === 0) {
331
- percent.value = 100
332
-
333
- return
334
- }
335
-
336
- percent.value = StringHelper.withFixed(
337
- (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.2 + 80
338
- )
339
- }
340
-
341
322
  useWatchTrue(
342
323
  () => upload.status.value.isSuccess,
343
324
  () => {
@@ -25,9 +25,9 @@
25
25
  <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
26
26
  <p class="truncate font-light text-gray-400">
27
27
  {{
28
- fileAllocate.isSelectedFileUseMb
29
- ? `${fileAllocate.selectedFileSizeMb} MB`
30
- : `${fileAllocate.selectedFileSizeKb} KB`
28
+ fileAllocate.isSelectedFileUseMb.value
29
+ ? `${fileAllocate.selectedFileSizeMb.value} MB`
30
+ : `${fileAllocate.selectedFileSizeKb.value} KB`
31
31
  }}
32
32
  - {{ percent }}% {{ uploadingLabel }}
33
33
  </p>
@@ -65,9 +65,9 @@
65
65
  </h1>
66
66
  <p class="truncate text-sm font-light text-gray-400">
67
67
  {{
68
- fileAllocate.isSelectedFileUseMb
69
- ? `${fileAllocate.selectedFileSizeMb} MB`
70
- : `${fileAllocate.selectedFileSizeKb} KB`
68
+ fileAllocate.isSelectedFileUseMb.value
69
+ ? `${fileAllocate.selectedFileSizeMb.value} MB`
70
+ : `${fileAllocate.selectedFileSizeKb.value} KB`
71
71
  }}
72
72
  </p>
73
73
  </div>
@@ -148,9 +148,10 @@ import {
148
148
  checkFileType,
149
149
  checkMaxSize,
150
150
  downloadFileFromURL,
151
- getFileAllocate,
152
151
  generateURL,
153
152
  isImage,
153
+ useFileAllocate,
154
+ useFileProgress,
154
155
  } from '#core/helpers/componentHelper'
155
156
  import { type uploadFileDropzone } from '#core/ui.config'
156
157
  import type { IUploadDropzoneAutoMultipleProps } from '#core/components/Form/InputUploadDropzoneAutoMultiple/types'
@@ -159,6 +160,7 @@ import {
159
160
  type IUploadRequest,
160
161
  onMounted,
161
162
  ref,
163
+ toRef,
162
164
  StringHelper,
163
165
  useUploadLoader,
164
166
  useWatchTrue,
@@ -179,7 +181,6 @@ const request: IUploadRequest = {
179
181
  }
180
182
 
181
183
  const isPreviewOpen = ref<boolean>(false)
182
- const percent = ref<number | string>(0)
183
184
  const upload = useUploadLoader(request)
184
185
  const errMsg = ref<string>('')
185
186
 
@@ -187,9 +188,8 @@ const acceptFile = computed(() =>
187
188
  typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
188
189
  )
189
190
 
190
- const fileAllocate = computed(() =>
191
- getFileAllocate(props.maxSize ?? 0, props.selectedFile?.size ?? 0)
192
- )
191
+ const { onUploadProgress, onDownloadProgress, percent } = useFileProgress()
192
+ const fileAllocate = useFileAllocate(toRef(props.selectedFile), props)
193
193
 
194
194
  const handleDeleteFile = () => {
195
195
  emits('delete')
@@ -206,24 +206,6 @@ const handleDownloadFile = () => {
206
206
  downloadFileFromURL(URL.createObjectURL(props.selectedFile), props.selectedFile.name)
207
207
  }
208
208
 
209
- const onUploadProgress = (progressEvent: ProgressEvent) => {
210
- percent.value = StringHelper.withFixed(
211
- (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.8
212
- )
213
- }
214
-
215
- const onDownloadProgress = (progressEvent: ProgressEvent) => {
216
- if (progressEvent.total === 0) {
217
- percent.value = 100
218
-
219
- return
220
- }
221
-
222
- percent.value = StringHelper.withFixed(
223
- (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.2 + 80
224
- )
225
- }
226
-
227
209
  const handleCheckFileCondition = (file: File | undefined): boolean => {
228
210
  if (!file) return false
229
211
 
@@ -235,16 +217,16 @@ const handleCheckFileCondition = (file: File | undefined): boolean => {
235
217
  return false
236
218
  }
237
219
 
238
- const maxSize = checkMaxSize(file, fileAllocate.value.acceptFileSizeKb ?? 0)
220
+ const maxSize = checkMaxSize(file, fileAllocate.acceptFileSizeKb.value)
239
221
 
240
222
  if (!maxSize) {
241
- if (fileAllocate.value.isAcceptFileUseMb) {
223
+ if (fileAllocate.isAcceptFileUseMb.value) {
242
224
  errMsg.value = i18next.t('custom:invalid_file_size_mb', {
243
- size: fileAllocate.value.acceptFileSizeMb,
225
+ size: fileAllocate.acceptFileSizeMb.value,
244
226
  })
245
227
  } else {
246
228
  errMsg.value = i18next.t('custom:invalid_file_size_kb', {
247
- size: fileAllocate.value.acceptFileSizeKb,
229
+ size: fileAllocate.acceptFileSizeKb.value,
248
230
  })
249
231
  }
250
232
 
@@ -37,10 +37,10 @@
37
37
  </div>
38
38
  <Item
39
39
  v-for="(file, index) in selectedFiles"
40
- :key="file.name + index"
40
+ :key="file.key"
41
41
  v-bind="$props"
42
42
  :ui="ui"
43
- :selected-file="file"
43
+ :selected-file="file.file"
44
44
  @success="handleSuccess(index, $event)"
45
45
  @delete="handleDeleteFile(index)"
46
46
  @error="handleError(index, $event)"
@@ -54,12 +54,11 @@ import { useDropZone } from '@vueuse/core'
54
54
  import { type IUploadDropzoneAutoMultipleProps } from './types'
55
55
  import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
56
56
  import { useFieldHOC } from '#core/composables/useForm'
57
- import { _get, computed, ref, toRef, useUI, useUiConfig } from '#imports'
57
+ import { _get, computed, ref, toRef, useUI, useUiConfig, StringHelper } from '#imports'
58
58
  import { uploadFileDropzone } from '#core/ui.config'
59
59
  import Item from './Item.vue'
60
60
 
61
61
  const config = useUiConfig<typeof uploadFileDropzone>(uploadFileDropzone, 'uploadFileDropzone')
62
-
63
62
  const props = withDefaults(defineProps<IUploadDropzoneAutoMultipleProps>(), {
64
63
  bodyKey: 'file',
65
64
  responseKey: 'url',
@@ -71,13 +70,12 @@ const props = withDefaults(defineProps<IUploadDropzoneAutoMultipleProps>(), {
71
70
  })
72
71
 
73
72
  const emits = defineEmits(['change', 'success', 'delete'])
74
- const selectedFiles = ref<File[]>([])
75
-
73
+ const selectedFiles = ref<Array<{ file: File; key: string }>>([])
76
74
  const { wrapperProps, handleChange: onChange, setErrors, value } = useFieldHOC<File[]>(props)
77
75
 
78
76
  const { ui } = useUI('uploadFileDropzone', toRef(props, 'ui'), config)
79
77
 
80
- const fileInputRef = ref<HTMLInputElement>()
78
+ const fileInputRef = ref<HTMLInputElement | null>()
81
79
  const dropzoneRef = ref<HTMLDivElement>()
82
80
 
83
81
  const acceptFile = computed(() =>
@@ -88,7 +86,14 @@ const onDrop = (files: File[] | null) => {
88
86
  if (props.isDisabled || files?.length === 0 || !files) return
89
87
 
90
88
  for (const file of files) {
91
- selectedFiles.value = [file, ...selectedFiles.value]
89
+ selectedFiles.value = [
90
+ {
91
+ file,
92
+ key: StringHelper.genString(),
93
+ },
94
+ ...selectedFiles.value,
95
+ ]
96
+
92
97
  emits('change', value.value)
93
98
  }
94
99
  }
@@ -101,9 +106,20 @@ const handleChange = (e: Event) => {
101
106
  if (props.isDisabled) return
102
107
 
103
108
  for (const file of (e.target as HTMLInputElement).files ?? []) {
104
- selectedFiles.value = [file, ...selectedFiles.value]
109
+ selectedFiles.value = [
110
+ {
111
+ file,
112
+ key: StringHelper.genString(),
113
+ },
114
+ ...selectedFiles.value,
115
+ ]
116
+
105
117
  emits('change', value.value)
106
118
  }
119
+
120
+ fileInputRef.value!.type = 'text'
121
+ fileInputRef.value!.type = 'file'
122
+ fileInputRef.value = null
107
123
  }
108
124
 
109
125
  const handleOpenFile = () => {
@@ -37,15 +37,14 @@
37
37
  <div v-else :class="ui.imageItemWrapper">
38
38
  <Item
39
39
  v-for="(file, index) in selectedFiles"
40
- :key="file.name + index"
40
+ :key="file.key"
41
41
  v-bind="$props"
42
42
  :ui="ui"
43
- :selected-file="file"
43
+ :selected-file="file.file"
44
44
  @success="handleSuccess(index, $event)"
45
45
  @delete="handleDeleteFile(index)"
46
46
  @error="handleError(index, $event)"
47
47
  />
48
-
49
48
  <Item v-bind="$props" is-adding-btn :ui="ui" @add="handleOpenFile" />
50
49
  </div>
51
50
  </div>
@@ -59,7 +58,7 @@ import { useDropZone } from '@vueuse/core'
59
58
  import { type IUploadDropzoneImageAutoMultipleProps } from './types'
60
59
  import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
61
60
  import { useFieldHOC } from '#core/composables/useForm'
62
- import { _get, computed, ref, toRef, useUI, useUiConfig } from '#imports'
61
+ import { _get, computed, ref, toRef, StringHelper, useUI, useUiConfig } from '#imports'
63
62
  import { uploadFileDropzoneImage } from '#core/ui.config'
64
63
  import Item from './item.vue'
65
64
 
@@ -78,13 +77,13 @@ const props = withDefaults(defineProps<IUploadDropzoneImageAutoMultipleProps>(),
78
77
  })
79
78
 
80
79
  const emits = defineEmits(['change', 'success', 'delete'])
81
- const selectedFiles = ref<File[]>([])
80
+ const selectedFiles = ref<Array<{ file: File; key: string }>>([])
82
81
 
83
82
  const { wrapperProps, handleChange: onChange, value } = useFieldHOC<File[]>(props)
84
83
 
85
84
  const { ui } = useUI('uploadFileDropzoneImage', toRef(props, 'ui'), config)
86
85
 
87
- const fileInputRef = ref<HTMLInputElement>()
86
+ const fileInputRef = ref<HTMLInputElement | null>()
88
87
  const dropzoneRef = ref<HTMLDivElement>()
89
88
 
90
89
  const acceptFile = computed(() =>
@@ -95,7 +94,13 @@ const onDrop = (files: File[] | null) => {
95
94
  if (props.isDisabled || files?.length === 0 || !files) return
96
95
 
97
96
  for (const file of files) {
98
- selectedFiles.value = [...selectedFiles.value, file]
97
+ selectedFiles.value = [
98
+ ...selectedFiles.value,
99
+ {
100
+ file,
101
+ key: StringHelper.genString(),
102
+ },
103
+ ]
99
104
 
100
105
  emits('change', value.value)
101
106
  }
@@ -109,9 +114,20 @@ const handleChange = (e: Event) => {
109
114
  if (props.isDisabled) return
110
115
 
111
116
  for (const file of (e.target as HTMLInputElement).files ?? []) {
112
- selectedFiles.value = [...selectedFiles.value, file]
117
+ selectedFiles.value = [
118
+ ...selectedFiles.value,
119
+ {
120
+ file,
121
+ key: StringHelper.genString(),
122
+ },
123
+ ]
124
+
113
125
  emits('change', value.value)
114
126
  }
127
+
128
+ fileInputRef.value!.type = 'text'
129
+ fileInputRef.value!.type = 'file'
130
+ fileInputRef.value = null
115
131
  }
116
132
 
117
133
  const handleOpenFile = () => {
@@ -73,7 +73,8 @@ import {
73
73
  checkFileType,
74
74
  checkMaxSize,
75
75
  generateURL,
76
- getFileAllocate,
76
+ useFileAllocate,
77
+ useFileProgress,
77
78
  } from '#core/helpers/componentHelper'
78
79
  import { type uploadFileDropzoneImage } from '#core/ui.config'
79
80
  import type { IUploadDropzoneImageAutoMultipleProps } from '#core/components/Form/InputUploadDropzoneImageAutoMultiple/types'
@@ -82,7 +83,7 @@ import {
82
83
  type IUploadRequest,
83
84
  onMounted,
84
85
  ref,
85
- StringHelper,
86
+ toRef,
86
87
  useUploadLoader,
87
88
  useWatchTrue,
88
89
  } from '#imports'
@@ -103,7 +104,6 @@ const request: IUploadRequest = {
103
104
  }
104
105
 
105
106
  const isPreviewOpen = ref<boolean>(false)
106
- const percent = ref<number | string>(0)
107
107
  const upload = useUploadLoader(request)
108
108
  const errMsg = ref<string>('')
109
109
 
@@ -111,32 +111,13 @@ const acceptFile = computed(() =>
111
111
  typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
112
112
  )
113
113
 
114
- const fileAllocate = computed(() =>
115
- getFileAllocate(props.maxSize ?? 0, props.selectedFile?.size ?? 0)
116
- )
114
+ const { onUploadProgress, onDownloadProgress, percent } = useFileProgress()
115
+ const fileAllocate = useFileAllocate(toRef(props.selectedFile), props)
117
116
 
118
117
  const handleDeleteFile = () => {
119
118
  emits('delete')
120
119
  }
121
120
 
122
- const onUploadProgress = (progressEvent: ProgressEvent) => {
123
- percent.value = StringHelper.withFixed(
124
- (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.8
125
- )
126
- }
127
-
128
- const onDownloadProgress = (progressEvent: ProgressEvent) => {
129
- if (progressEvent.total === 0) {
130
- percent.value = 100
131
-
132
- return
133
- }
134
-
135
- percent.value = StringHelper.withFixed(
136
- (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.2 + 80
137
- )
138
- }
139
-
140
121
  const handleCheckFileCondition = (file: File | undefined): boolean => {
141
122
  if (!file) return false
142
123
 
@@ -148,16 +129,16 @@ const handleCheckFileCondition = (file: File | undefined): boolean => {
148
129
  return false
149
130
  }
150
131
 
151
- const maxSize = checkMaxSize(file, fileAllocate.value.acceptFileSizeKb ?? 0)
132
+ const maxSize = checkMaxSize(file, fileAllocate.acceptFileSizeKb.value)
152
133
 
153
134
  if (!maxSize) {
154
- if (fileAllocate.value.isAcceptFileUseMb) {
135
+ if (fileAllocate.isAcceptFileUseMb.value) {
155
136
  errMsg.value = i18next.t('custom:invalid_file_size_mb', {
156
- size: fileAllocate.value.acceptFileSizeMb,
137
+ size: fileAllocate.acceptFileSizeMb.value,
157
138
  })
158
139
  } else {
159
140
  errMsg.value = i18next.t('custom:invalid_file_size_kb', {
160
- size: fileAllocate.value.acceptFileSizeKb,
141
+ size: fileAllocate.acceptFileSizeKb.value,
161
142
  })
162
143
  }
163
144
 
@@ -32,6 +32,7 @@ import { useUiConfig } from '#core/composables/useConfig'
32
32
  import { uploadFileInputClassicAuto } from '#core/ui.config'
33
33
  import { computed, ref, toRef, useUI } from '#imports'
34
34
  import i18next from 'i18next'
35
+ import { checkMaxSize } from '#core/helpers/componentHelper'
35
36
 
36
37
  const config = useUiConfig<typeof uploadFileInputClassicAuto>(
37
38
  uploadFileInputClassicAuto,
@@ -63,18 +64,10 @@ const handleOpenFile = () => {
63
64
 
64
65
  const { ui } = useUI('uploadFileInputClassicAuto', toRef(props, 'ui'), config)
65
66
 
66
- const checkMaxSize = (file: File): boolean => {
67
- if (acceptFileSizeKb.value) {
68
- return file.size / 1000 <= acceptFileSizeKb.value
69
- }
70
-
71
- return true
72
- }
73
-
74
67
  const handleCheckFileCondition = (file: File | undefined): boolean => {
75
68
  if (!file) return false
76
69
 
77
- const maxSize = checkMaxSize(file)
70
+ const maxSize = checkMaxSize(file, props.maxSize)
78
71
 
79
72
  if (!maxSize) {
80
73
  if (isAcceptFileUseMb.value) {
@@ -6,7 +6,7 @@
6
6
  type="file"
7
7
  class="hidden"
8
8
  :name="name"
9
- :accept="acceptFile"
9
+ :accept="fileAllocate.acceptFile.value"
10
10
  :disabled="isDisabled"
11
11
  @change="handleChange"
12
12
  />
@@ -17,7 +17,11 @@
17
17
  {{ selectedFile?.name ?? placeholder ?? 'ยังไม่ได้เลือกไฟล์' }}
18
18
  </p>
19
19
  <Badge v-if="selectedFile" size="xs" variant="outline">
20
- {{ isSelectedFileUseMb ? `${selectedFileSizeMb} MB` : `${selectedFileSizeKb} KB` }}
20
+ {{
21
+ fileAllocate.isSelectedFileUseMb.value
22
+ ? `${fileAllocate.selectedFileSizeMb.value} MB`
23
+ : `${fileAllocate.selectedFileSizeKb.value} KB`
24
+ }}
21
25
  </Badge>
22
26
  </div>
23
27
  <div v-if="selectedFile">
@@ -44,22 +48,14 @@
44
48
  </template>
45
49
 
46
50
  <script lang="tsx" setup>
47
- import {
48
- _get,
49
- computed,
50
- ref,
51
- StringHelper,
52
- toRef,
53
- useUI,
54
- useUiConfig,
55
- useWatchTrue,
56
- } from '#imports'
51
+ import { _get, ref, StringHelper, toRef, useUI, useUiConfig, useWatchTrue } from '#imports'
57
52
  import { type IUploadFileProps } from './types'
58
53
  import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
59
54
  import { useFieldHOC } from '#core/composables/useForm'
60
55
  import { type IUploadRequest, useUploadLoader } from '#core/composables/useUpload'
61
56
  import { uploadFileInputClassicAuto } from '#core/ui.config'
62
57
  import i18next from 'i18next'
58
+ import { checkMaxSize, useFileAllocate, useFileProgress } from '#core/helpers/componentHelper'
63
59
 
64
60
  const config = useUiConfig<typeof uploadFileInputClassicAuto>(
65
61
  uploadFileInputClassicAuto,
@@ -84,22 +80,9 @@ const upload = useUploadLoader(request)
84
80
 
85
81
  const fileInput = ref<HTMLInputElement>()
86
82
  const selectedFile = ref<File | undefined>()
87
- const percent = ref<number>(0)
88
-
89
- const selectedFileSizeKb = computed(() => ((selectedFile.value?.size || 0) / 1000).toFixed(2))
90
- const selectedFileSizeMb = computed(() =>
91
- ((selectedFile.value?.size || 0) / 1000 / 1000).toFixed(2)
92
- )
93
-
94
- const isSelectedFileUseMb = computed(() => (selectedFile.value?.size || 0) / 1000 > 1024)
95
-
96
- const acceptFileSizeKb = computed(() => props.maxSize)
97
- const acceptFileSizeMb = computed(() => ((acceptFileSizeKb.value || 0) / 1024).toFixed(2))
98
- const isAcceptFileUseMb = computed(() => acceptFileSizeKb.value && acceptFileSizeKb.value > 1024)
99
- const acceptFile = computed(() =>
100
- typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
101
- )
102
83
 
84
+ const fileAllocate = useFileAllocate(selectedFile, props)
85
+ const { onUploadProgress, onDownloadProgress, percent } = useFileProgress()
103
86
  const { ui } = useUI('uploadFileInputClassicAuto', toRef(props, 'ui'), config)
104
87
 
105
88
  const handleOpenFile = () => {
@@ -122,13 +105,17 @@ const handleChange = (e: Event) => {
122
105
  const handleCheckFileCondition = (file: File | undefined): boolean => {
123
106
  if (!file) return false
124
107
 
125
- const maxSize = checkMaxSize(file)
108
+ const maxSize = checkMaxSize(file, fileAllocate.acceptFileSizeKb.value)
126
109
 
127
110
  if (!maxSize) {
128
- if (isAcceptFileUseMb.value) {
129
- setErrors(i18next.t('custom:invalid_file_size_mb', { size: acceptFileSizeMb.value }))
111
+ if (fileAllocate.isAcceptFileUseMb.value) {
112
+ setErrors(
113
+ i18next.t('custom:invalid_file_size_mb', { size: fileAllocate.acceptFileSizeMb.value })
114
+ )
130
115
  } else {
131
- setErrors(i18next.t('custom:invalid_file_size_kb', { size: acceptFileSizeKb.value }))
116
+ setErrors(
117
+ i18next.t('custom:invalid_file_size_kb', { size: fileAllocate.acceptFileSizeKb.value })
118
+ )
132
119
  }
133
120
 
134
121
  return false
@@ -139,28 +126,6 @@ const handleCheckFileCondition = (file: File | undefined): boolean => {
139
126
  return true
140
127
  }
141
128
 
142
- const checkMaxSize = (file: File): boolean => {
143
- if (acceptFileSizeKb.value) {
144
- return file.size / 1000 <= acceptFileSizeKb.value
145
- }
146
-
147
- return true
148
- }
149
-
150
- const onUploadProgress = (progressEvent: ProgressEvent) => {
151
- percent.value = (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.8
152
- }
153
-
154
- const onDownloadProgress = (progressEvent: ProgressEvent) => {
155
- if (progressEvent.total === 0) {
156
- percent.value = 100
157
-
158
- return
159
- }
160
-
161
- percent.value = (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.2 + 80
162
- }
163
-
164
129
  useWatchTrue(
165
130
  () => upload.status.value.isSuccess,
166
131
  () => {
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <QrcodeVue :value="value" :level="level" :render-as="renderAs" class="h-[350px] w-[350px]" />
2
+ <QrcodeVue :value="value" :level="level" :render-as="renderAs" class="size-[350px]" />
3
3
  </template>
4
4
  <script lang="ts" setup>
5
5
  import QrcodeVue from 'qrcode.vue'
@@ -15,7 +15,7 @@
15
15
  <Icon
16
16
  v-if="!isHideCloseBtn"
17
17
  name="i-heroicons-x-mark"
18
- class="h-6 w-6 cursor-pointer"
18
+ class="size-6 cursor-pointer"
19
19
  @click="close"
20
20
  />
21
21
  </div>
@@ -1,13 +1,23 @@
1
- export declare const checkMaxSize: (file: File, acceptFileSize: number) => boolean;
1
+ import type { Ref } from 'vue';
2
+ export declare const checkMaxSize: (file: File, acceptFileSize?: number) => boolean;
2
3
  export declare const checkFileType: (file: File, acceptFileType: string | string[]) => boolean;
3
4
  export declare const generateURL: (file: File) => string;
4
5
  export declare const isImage: (file: File) => boolean;
5
- export declare const getFileAllocate: (maxSize: number, selectedFileSize: Blob['size']) => {
6
- acceptFileSizeKb: number;
7
- acceptFileSizeMb: string;
8
- isAcceptFileUseMb: boolean;
9
- selectedFileSizeKb: string;
10
- selectedFileSizeMb: string;
11
- isSelectedFileUseMb: boolean;
6
+ export declare const useFileAllocate: (selectedFile: Ref<File | undefined | null>, props: {
7
+ accept?: string | string[];
8
+ maxSize?: number;
9
+ }) => {
10
+ selectedFileSizeKb: any;
11
+ selectedFileSizeMb: any;
12
+ isSelectedFileUseMb: any;
13
+ acceptFileSizeKb: any;
14
+ acceptFileSizeMb: any;
15
+ isAcceptFileUseMb: any;
16
+ acceptFile: any;
17
+ };
18
+ export declare const useFileProgress: () => {
19
+ percent: any;
20
+ onUploadProgress: (progressEvent: ProgressEvent) => void;
21
+ onDownloadProgress: (progressEvent: ProgressEvent) => void;
12
22
  };
13
23
  export declare const downloadFileFromURL: (url: string, filename?: string) => Promise<void>;
@@ -1,4 +1,5 @@
1
- export const checkMaxSize = (file, acceptFileSize) => {
1
+ import { computed, ref, StringHelper } from "#imports";
2
+ export const checkMaxSize = (file, acceptFileSize = 0) => {
2
3
  if (acceptFileSize) {
3
4
  return file.size / 1e3 <= acceptFileSize;
4
5
  }
@@ -37,14 +38,52 @@ export const generateURL = (file) => {
37
38
  export const isImage = (file) => {
38
39
  return file.type.startsWith("image/");
39
40
  };
40
- export const getFileAllocate = (maxSize, selectedFileSize) => {
41
+ export const useFileAllocate = (selectedFile, props) => {
42
+ const selectedFileSizeKb = computed(() => ((selectedFile.value?.size || 0) / 1e3).toFixed(2));
43
+ const selectedFileSizeMb = computed(
44
+ () => ((selectedFile.value?.size || 0) / 1e3 / 1e3).toFixed(2)
45
+ );
46
+ const isSelectedFileUseMb = computed(() => (selectedFile.value?.size || 0) / 1e3 > 1024);
47
+ const acceptFileSizeKb = computed(() => props.maxSize || 0);
48
+ const acceptFileSizeMb = computed(() => ((acceptFileSizeKb.value || 0) / 1024).toFixed(2));
49
+ const isAcceptFileUseMb = computed(() => acceptFileSizeKb.value && acceptFileSizeKb.value > 1024);
50
+ const acceptFile = computed(
51
+ () => typeof props.accept === "string" ? props.accept : props.accept?.join(",")
52
+ );
41
53
  return {
42
- acceptFileSizeKb: maxSize,
43
- acceptFileSizeMb: (maxSize / 1024).toFixed(2),
44
- isAcceptFileUseMb: maxSize > 1024,
45
- selectedFileSizeKb: (selectedFileSize / 1e3).toFixed(2),
46
- selectedFileSizeMb: (selectedFileSize / 1e3 / 1e3).toFixed(2),
47
- isSelectedFileUseMb: selectedFileSize / 1e3 > 1024
54
+ selectedFileSizeKb,
55
+ selectedFileSizeMb,
56
+ isSelectedFileUseMb,
57
+ acceptFileSizeKb,
58
+ acceptFileSizeMb,
59
+ isAcceptFileUseMb,
60
+ acceptFile
61
+ };
62
+ };
63
+ export const useFileProgress = () => {
64
+ const percent = ref(0);
65
+ const onUploadProgress = (progressEvent) => {
66
+ percent.value = parseFloat(
67
+ StringHelper.withFixed(
68
+ (Math.floor(progressEvent.loaded * 100 / progressEvent.total) || 0) * 0.8
69
+ )
70
+ );
71
+ };
72
+ const onDownloadProgress = (progressEvent) => {
73
+ if (progressEvent.total === 0) {
74
+ percent.value = 100;
75
+ return;
76
+ }
77
+ percent.value = parseFloat(
78
+ StringHelper.withFixed(
79
+ (Math.floor(progressEvent.loaded * 100 / progressEvent.total) || 0) * 0.2 + 80
80
+ )
81
+ );
82
+ };
83
+ return {
84
+ percent,
85
+ onUploadProgress,
86
+ onDownloadProgress
48
87
  };
49
88
  };
50
89
  export const downloadFileFromURL = async (url, filename) => {
@@ -1,9 +1,5 @@
1
- import { get } from "lodash";
2
- import { describe, test, expect, vi } from "vitest";
1
+ import { describe, test, expect } from "vitest";
3
2
  import { ArrayHelper } from "./ArrayHelper.mjs";
4
- vi.mock("#build/imports", () => ({
5
- _get: get
6
- }));
7
3
  describe("ArrayHelper", () => {
8
4
  test("toOptions default attr", () => {
9
5
  const data = [
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { FileHelper } from "./FileHelper.mjs";
3
+ describe("FileHelper", () => {
4
+ describe("dataURLtoFile", () => {
5
+ it("should convert a data URL to a File", async () => {
6
+ const dataUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==";
7
+ const filename = "test.png";
8
+ const file = await FileHelper.dataURLtoFile(dataUrl, filename);
9
+ expect(file).toBeInstanceOf(File);
10
+ expect(file.name).toBe(filename);
11
+ expect(file.type).toBe("image/png");
12
+ });
13
+ });
14
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { ObjectHelper } from "./ObjectHelper.mjs";
3
+ describe("ObjectHelper", () => {
4
+ describe("createOption", () => {
5
+ it("should create an option object with the provided value and label", () => {
6
+ const value = "value1";
7
+ const label = "Label 1";
8
+ const option = ObjectHelper.createOption(value, label);
9
+ expect(option).toEqual({ value, label });
10
+ });
11
+ it("should create an option object with an empty label if not provided", () => {
12
+ const value = "value1";
13
+ const option = ObjectHelper.createOption(value);
14
+ expect(option).toEqual({ value, label: "" });
15
+ });
16
+ });
17
+ describe("toOption", () => {
18
+ it("should create an option object from the provided data", () => {
19
+ const data = { id: 1, name: "Option 1" };
20
+ const option = ObjectHelper.toOption(data);
21
+ expect(option).toEqual({ value: 1, label: "Option 1" });
22
+ });
23
+ it("should use the provided value and label attributes", () => {
24
+ const data = { code: "CODE1", description: "Description 1" };
25
+ const option = ObjectHelper.toOption(data, "code", "description");
26
+ expect(option).toEqual({ value: "CODE1", label: "Description 1" });
27
+ });
28
+ });
29
+ describe("toErrorStatus", () => {
30
+ it("should create an error status object from the provided error", () => {
31
+ const error = {
32
+ response: {
33
+ status: 400,
34
+ request: {
35
+ response: JSON.stringify({ code: "INVALID_REQUEST", message: "Invalid request" })
36
+ }
37
+ }
38
+ };
39
+ const status = ObjectHelper.toErrorStatus({}, error);
40
+ expect(status.isError).toBe(true);
41
+ expect(status.isSuccess).toBe(false);
42
+ expect(status.errorData).toEqual({ code: "INVALID_REQUEST", message: "Invalid request" });
43
+ });
44
+ it("should create a network error status if the error response status is missing", () => {
45
+ const error = {};
46
+ const status = ObjectHelper.toErrorStatus({}, error);
47
+ expect(status.isError).toBe(true);
48
+ expect(status.isSuccess).toBe(false);
49
+ expect(status.errorData).toEqual({ code: "NETWORK_ERROR", message: "Network error" });
50
+ });
51
+ });
52
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,78 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { ParamHelper } from "./ParamHelper.mjs";
3
+ describe("ParamHelper", () => {
4
+ describe("getParams", () => {
5
+ it("should merge params from opts and reqOptions", () => {
6
+ const opts = { params: { page: 1, limit: 10 } };
7
+ const reqOptions = { params: { sort: "name" } };
8
+ const result = ParamHelper.getParams(opts, reqOptions);
9
+ expect(result).toEqual({ page: 1, limit: 10, sort: "name" });
10
+ });
11
+ it("should return reqOptions.params if opts.params is not provided", () => {
12
+ const opts = {};
13
+ const reqOptions = { params: { sort: "name" } };
14
+ const result = ParamHelper.getParams(opts, reqOptions);
15
+ expect(result).toEqual({ sort: "name" });
16
+ });
17
+ });
18
+ describe("getBoolTrue", () => {
19
+ it("should return true for truthy values", () => {
20
+ expect(ParamHelper.getBoolTrue(true)).toBe(true);
21
+ expect(ParamHelper.getBoolTrue(1)).toBe(true);
22
+ expect(ParamHelper.getBoolTrue("true")).toBe(true);
23
+ });
24
+ it("should return false for falsy values", () => {
25
+ expect(ParamHelper.getBoolTrue(false)).toBe(false);
26
+ expect(ParamHelper.getBoolTrue(0)).toBe(false);
27
+ expect(ParamHelper.getBoolTrue("false")).toBe(false);
28
+ });
29
+ it("should return true for null value", () => {
30
+ expect(ParamHelper.getBoolTrue(null)).toBe(true);
31
+ });
32
+ });
33
+ describe("getBoolFalse", () => {
34
+ it("should return false for falsy values", () => {
35
+ expect(ParamHelper.getBoolFalse(false)).toBe(false);
36
+ expect(ParamHelper.getBoolFalse(0)).toBe(false);
37
+ expect(ParamHelper.getBoolFalse("false")).toBe(false);
38
+ });
39
+ it("should return true for truthy values", () => {
40
+ expect(ParamHelper.getBoolFalse(true)).toBe(true);
41
+ expect(ParamHelper.getBoolFalse(1)).toBe(true);
42
+ expect(ParamHelper.getBoolFalse("true")).toBe(true);
43
+ });
44
+ it("should return false for null value", () => {
45
+ expect(ParamHelper.getBoolFalse(null)).toBe(false);
46
+ });
47
+ });
48
+ describe("isNotFoundError", () => {
49
+ it('should return true if the error code is "NOT_FOUND"', () => {
50
+ const error = { code: "NOT_FOUND" };
51
+ expect(ParamHelper.isNotFoundError(error)).toBe(true);
52
+ });
53
+ it('should return false if the error code is not "NOT_FOUND"', () => {
54
+ const error = { code: "INTERNAL_SERVER_ERROR" };
55
+ expect(ParamHelper.isNotFoundError(error)).toBe(false);
56
+ });
57
+ });
58
+ describe("isChangeWithFalse", () => {
59
+ it("should return true if the value changes from true to false", () => {
60
+ expect(ParamHelper.isChangeWithFalse(false, true)).toBe(true);
61
+ });
62
+ it("should return false if the value does not change or changes to true", () => {
63
+ expect(ParamHelper.isChangeWithFalse(true, true)).toBe(false);
64
+ expect(ParamHelper.isChangeWithFalse(false, false)).toBe(false);
65
+ expect(ParamHelper.isChangeWithFalse(true, false)).toBe(false);
66
+ });
67
+ });
68
+ describe("isChangeWithTrue", () => {
69
+ it("should return true if the value changes from false to true", () => {
70
+ expect(ParamHelper.isChangeWithTrue(true, false)).toBe(true);
71
+ });
72
+ it("should return false if the value does not change or changes to false", () => {
73
+ expect(ParamHelper.isChangeWithTrue(false, false)).toBe(false);
74
+ expect(ParamHelper.isChangeWithTrue(true, true)).toBe(false);
75
+ expect(ParamHelper.isChangeWithTrue(false, true)).toBe(false);
76
+ });
77
+ });
78
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,76 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { StringHelper } from "./StringHelper.mjs";
3
+ describe("StringHelper", () => {
4
+ describe("genString", () => {
5
+ it("should generate a random string of the specified length", () => {
6
+ const length = 10;
7
+ const result = StringHelper.genString(length);
8
+ expect(result).toHaveLength(length);
9
+ expect(typeof result).toBe("string");
10
+ });
11
+ it("should generate a random string of default length if not specified", () => {
12
+ const result = StringHelper.genString();
13
+ expect(result).toHaveLength(5);
14
+ expect(typeof result).toBe("string");
15
+ });
16
+ });
17
+ describe("withComma", () => {
18
+ it("should format a number with comma separator", () => {
19
+ expect(StringHelper.withComma(1e3)).toBe("1,000");
20
+ expect(StringHelper.withComma(1234.56)).toBe("1,234.56");
21
+ });
22
+ it('should return "0" for null or undefined values', () => {
23
+ expect(StringHelper.withComma(void 0)).toBe("0");
24
+ });
25
+ });
26
+ describe("withFixed", () => {
27
+ it("should format a number with comma separator and fixed decimal places", () => {
28
+ expect(StringHelper.withFixed(1e3)).toBe("1,000");
29
+ expect(StringHelper.withFixed(1234.56)).toBe("1,234.56");
30
+ });
31
+ it('should return "0" for null or undefined values', () => {
32
+ expect(StringHelper.withFixed(void 0)).toBe("0");
33
+ });
34
+ });
35
+ describe("split", () => {
36
+ it("should split a string into an array based on the specified separator", () => {
37
+ const str = "apple,banana,orange";
38
+ const separator = ",";
39
+ const result = StringHelper.split(str, separator);
40
+ expect(result).toEqual(["apple", "banana", "orange"]);
41
+ });
42
+ it("should return an empty array for null or undefined string", () => {
43
+ expect(StringHelper.split(null, ",")).toEqual([]);
44
+ expect(StringHelper.split(void 0, ",")).toEqual([]);
45
+ });
46
+ });
47
+ describe("joinURL", () => {
48
+ it("should join the provided URL parts", () => {
49
+ const result = StringHelper.joinURL("https://example.com", "path", "to", "resource");
50
+ expect(result).toBe("https://example.com/path/to/resource");
51
+ });
52
+ });
53
+ describe("truncate", () => {
54
+ it("should truncate a string if it exceeds the specified length", () => {
55
+ const str = "This is a long string that needs to be truncated";
56
+ const result = StringHelper.truncate(str, 20);
57
+ expect(result).toBe("This is a long strin...");
58
+ });
59
+ it("should return the original string if it does not exceed the specified length", () => {
60
+ const str = "Short string";
61
+ const result = StringHelper.truncate(str, 20);
62
+ expect(result).toBe("Short string");
63
+ });
64
+ });
65
+ describe("getError", () => {
66
+ it("should return the error message from the errorData", () => {
67
+ const errorData = { code: "ERROR_CODE", message: "Error message" };
68
+ const result = StringHelper.getError(errorData);
69
+ expect(result).toBe("Error message");
70
+ });
71
+ it("should return the default error message if the errorData is empty", () => {
72
+ const result = StringHelper.getError({}, "Default error message");
73
+ expect(result).toBe("Default error message");
74
+ });
75
+ });
76
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finema/core",
3
- "version": "1.4.98",
3
+ "version": "1.4.99",
4
4
  "repository": "https://gitlab.finema.co/finema/ui-kit",
5
5
  "license": "MIT",
6
6
  "author": "Finema Dev Core Team",