@finema/core 1.4.129 → 1.4.131

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.
Files changed (61) hide show
  1. package/README.md +60 -60
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +1 -1
  4. package/dist/runtime/components/Alert.vue +48 -48
  5. package/dist/runtime/components/Avatar.vue +27 -27
  6. package/dist/runtime/components/Badge.vue +11 -11
  7. package/dist/runtime/components/Breadcrumb.vue +44 -44
  8. package/dist/runtime/components/Button/Group.vue +37 -37
  9. package/dist/runtime/components/Button/index.vue +75 -75
  10. package/dist/runtime/components/Card.vue +38 -38
  11. package/dist/runtime/components/Core.vue +37 -37
  12. package/dist/runtime/components/Dialog/index.vue +108 -108
  13. package/dist/runtime/components/Dropdown/index.vue +70 -70
  14. package/dist/runtime/components/FlexDeck/Base.vue +143 -143
  15. package/dist/runtime/components/FlexDeck/index.vue +68 -68
  16. package/dist/runtime/components/Form/FieldWrapper.vue +23 -23
  17. package/dist/runtime/components/Form/Fields.vue +230 -230
  18. package/dist/runtime/components/Form/InputCheckbox/index.vue +28 -28
  19. package/dist/runtime/components/Form/InputDateTime/index.vue +60 -60
  20. package/dist/runtime/components/Form/InputDateTimeRange/index.vue +83 -83
  21. package/dist/runtime/components/Form/InputNumber/index.vue +27 -27
  22. package/dist/runtime/components/Form/InputRadio/index.vue +27 -27
  23. package/dist/runtime/components/Form/InputSelect/index.vue +43 -43
  24. package/dist/runtime/components/Form/InputSelectMultiple/index.vue +52 -52
  25. package/dist/runtime/components/Form/InputStatic/index.vue +16 -16
  26. package/dist/runtime/components/Form/InputTags/index.vue +145 -145
  27. package/dist/runtime/components/Form/InputText/index.vue +67 -67
  28. package/dist/runtime/components/Form/InputTextarea/index.vue +25 -25
  29. package/dist/runtime/components/Form/InputToggle/index.vue +14 -14
  30. package/dist/runtime/components/Form/InputUploadDropzone/index.vue +206 -206
  31. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +341 -341
  32. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/ItemUpload.vue +241 -241
  33. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/ItemView.vue +89 -89
  34. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue +164 -164
  35. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/ItemUpload.vue +161 -161
  36. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/ItemView.vue +64 -64
  37. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/index.vue +172 -172
  38. package/dist/runtime/components/Form/InputUploadFileClassic/index.vue +95 -95
  39. package/dist/runtime/components/Form/InputUploadFileClassicAuto/index.vue +151 -151
  40. package/dist/runtime/components/Form/InputUploadImageAuto/index.vue +219 -219
  41. package/dist/runtime/components/Form/InputWYSIWYG/index.vue +53 -53
  42. package/dist/runtime/components/Form/index.vue +6 -6
  43. package/dist/runtime/components/Form/types.d.ts +0 -1
  44. package/dist/runtime/components/Icon.vue +23 -23
  45. package/dist/runtime/components/Image.vue +36 -36
  46. package/dist/runtime/components/Loader.vue +27 -27
  47. package/dist/runtime/components/Modal/index.vue +146 -146
  48. package/dist/runtime/components/QRCode.vue +22 -22
  49. package/dist/runtime/components/SimplePagination.vue +96 -96
  50. package/dist/runtime/components/Slideover/index.vue +110 -110
  51. package/dist/runtime/components/Table/Base.vue +139 -139
  52. package/dist/runtime/components/Table/ColumnDate.vue +16 -16
  53. package/dist/runtime/components/Table/ColumnDateTime.vue +18 -18
  54. package/dist/runtime/components/Table/ColumnImage.vue +15 -15
  55. package/dist/runtime/components/Table/ColumnNumber.vue +14 -14
  56. package/dist/runtime/components/Table/ColumnText.vue +25 -25
  57. package/dist/runtime/components/Table/Simple.vue +69 -69
  58. package/dist/runtime/components/Table/index.vue +65 -65
  59. package/dist/runtime/components/Tabs/index.vue +64 -64
  60. package/dist/runtime/components/TeleportSafe.vue +40 -40
  61. package/package.json +95 -95
@@ -1,341 +1,341 @@
1
- <template>
2
- <FieldWrapper v-bind="wrapperProps">
3
- <div
4
- ref="dropzoneRef"
5
- :class="[
6
- ui.base,
7
- {
8
- [ui.disabled]: isDisabled,
9
- [ui.background.default]: !isOverDropZone && !isDisabled,
10
- [ui.background.dragover]: isOverDropZone && !isDisabled,
11
- [ui.failed]: upload.status.value.isError,
12
- },
13
- ]"
14
- >
15
- <input
16
- ref="fileInputRef"
17
- type="file"
18
- class="hidden"
19
- :name="name"
20
- :accept="fileAllocate.acceptFile.value"
21
- :disabled="isDisabled"
22
- @change="handleChange"
23
- />
24
- <div :class="[ui.wrapper]">
25
- <div v-if="!selectedFile" :class="[ui.placeholderWrapper]">
26
- <Icon :name="ui.default.uploadIcon" :class="[ui.labelIcon]" />
27
- <div :class="[ui.labelWrapper]">
28
- <p class="text-primary cursor-pointer" @click="handleOpenFile">
29
- {{ selectFileLabel }}
30
- </p>
31
- <p>{{ selectFileSubLabel }}</p>
32
- </div>
33
- <p v-if="placeholder" :class="[ui.placeholder]">{{ placeholder }}</p>
34
- </div>
35
-
36
- <!-- Loading State -->
37
- <div v-if="selectedFile && upload.status.value.isLoading" :class="[ui.onLoading.wrapper]">
38
- <div :class="[ui.onLoading.placeholderWrapper]">
39
- <Icon
40
- :name="
41
- isImage(selectedFile)
42
- ? ui.onLoading.placeholderImgIcon || ui.default.placeholderImgIcon
43
- : ui.onLoading.placeholderFileIcon || ui.default.filePreviewIcon
44
- "
45
- :class="[ui.onLoading.placeholderIconClass]"
46
- />
47
- </div>
48
- <div :class="[ui.onLoading.textWrapper]">
49
- <div class="truncate">
50
- <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
51
- <p class="truncate font-light text-gray-400">
52
- {{
53
- fileAllocate.isSelectedFileUseMb.value
54
- ? `${fileAllocate.selectedFileSizeMb.value} MB`
55
- : `${fileAllocate.selectedFileSizeKb.value} KB`
56
- }}
57
- - {{ percent }}% {{ uploadingLabel }}
58
- </p>
59
- </div>
60
- <div>
61
- <Icon
62
- :name="ui.onLoading.loadingIcon || ui.default.loadingIcon"
63
- :class="[ui.onLoading.loadingIconClass]"
64
- />
65
- </div>
66
- </div>
67
- </div>
68
-
69
- <!-- Success State -->
70
- <div v-if="selectedFile && upload.status.value.isSuccess" :class="[ui.onPreview.wrapper]">
71
- <div :class="[ui.onPreview.previewImgWrapper]">
72
- <div v-if="isImage(selectedFile)" class="size-full overflow-hidden">
73
- <img
74
- :src="upload.data.value[responseURL || 'url']"
75
- :class="[ui.onPreview.previewImgClass]"
76
- alt="img-preview"
77
- />
78
- </div>
79
- <div v-else>
80
- <Icon
81
- :name="ui.onPreview.previewFileIcon || ui.default.filePreviewIcon"
82
- :class="[ui.onPreview.previewFileClass]"
83
- />
84
- </div>
85
- </div>
86
- <div :class="[ui.onPreview.textWrapper]">
87
- <div class="truncate">
88
- <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
89
- <p class="truncate text-sm font-light text-gray-400">
90
- {{
91
- fileAllocate.isSelectedFileUseMb.value
92
- ? `${fileAllocate.selectedFileSizeMb.value} MB`
93
- : `${fileAllocate.selectedFileSizeKb.value} KB`
94
- }}
95
- </p>
96
- </div>
97
- <div :class="[ui.action.wrapper]">
98
- <Icon
99
- v-if="isImage(selectedFile)"
100
- :name="ui.action.previewIcon"
101
- :class="[ui.action.iconClass]"
102
- title="ดูตัวอย่าง"
103
- @click="() => (isPreviewOpen = true)"
104
- />
105
- <Icon
106
- :name="ui.action.downloadIcon"
107
- :class="[ui.action.iconClass]"
108
- title="ดาวน์โหลดไฟล์"
109
- @click="handleDownloadFile"
110
- />
111
- <Icon
112
- :name="ui.action.deleteIcon"
113
- :class="[ui.action.iconClass]"
114
- title="ลบไฟล์"
115
- @click="handleDeleteFile"
116
- />
117
- <Modal v-model="isPreviewOpen" :title="selectedFile?.name">
118
- <img :src="_get(upload.data.value, responseURL)" alt="img-preview" />
119
- </Modal>
120
- </div>
121
- </div>
122
- </div>
123
-
124
- <!-- Failed State -->
125
- <div v-if="selectedFile && upload.status.value.isError" :class="[ui.onFailed.wrapper]">
126
- <div :class="[ui.onFailed.failedImgWrapper]">
127
- <Icon
128
- :name="
129
- isImage(selectedFile)
130
- ? ui.onFailed.failedImgIcon || ui.default.placeholderImgIcon
131
- : ui.onFailed.failedFileIcon || ui.default.filePreviewIcon
132
- "
133
- :class="[ui.onFailed.failedIconClass]"
134
- />
135
- </div>
136
- <div :class="[ui.onFailed.textWrapper]">
137
- <div class="truncate">
138
- <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
139
- <p class="text-danger truncate font-light">
140
- {{ uploadFailedLabel }}
141
- </p>
142
- <Button
143
- variant="ghost"
144
- :label="retryLabel"
145
- :leading-icon="ui.action.retryIcon"
146
- :class="[ui.action.retryBtnClass]"
147
- size="sm"
148
- @click="handleRetryUpload"
149
- />
150
- </div>
151
- <div :class="[ui.action.wrapper]">
152
- <Icon
153
- title="ลบไฟล์"
154
- :name="ui.action.deleteIcon"
155
- :class="[ui.action.deleteIconClass]"
156
- @click="handleDeleteFile"
157
- />
158
- </div>
159
- </div>
160
- </div>
161
- </div>
162
- </div>
163
- </FieldWrapper>
164
- </template>
165
-
166
- <script lang="ts" setup>
167
- import { useDropZone } from '@vueuse/core'
168
- import { type IUploadDropzoneAutoProps } from './types'
169
- import {
170
- checkMaxSize,
171
- checkFileType,
172
- isImage,
173
- downloadFileFromURL,
174
- useFileAllocate,
175
- useFileProgress,
176
- } from '#core/helpers/componentHelper'
177
- import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
178
- import { useFieldHOC } from '#core/composables/useForm'
179
- import {
180
- type IUploadRequest,
181
- ref,
182
- toRef,
183
- useUI,
184
- useUiConfig,
185
- useUploadLoader,
186
- useWatchTrue,
187
- StringHelper,
188
- _get,
189
- } from '#imports'
190
- import { uploadFileDropzone } from '#core/ui.config'
191
- import i18next from 'i18next'
192
- import type { IFileValue } from '#core/components/Form/types'
193
-
194
- const config = useUiConfig<typeof uploadFileDropzone>(uploadFileDropzone, 'uploadFileDropzone')
195
-
196
- const props = withDefaults(defineProps<IUploadDropzoneAutoProps>(), {
197
- bodyKey: 'file',
198
- responseURL: 'url',
199
- responsePath: 'path',
200
- selectFileLabel: 'คลิกเพื่อเลือกไฟล์',
201
- selectFileSubLabel: 'หรือ ลากและวางที่นี่',
202
- uploadingLabel: 'กำลังอัพโหลด...',
203
- uploadFailedLabel: 'อัพโหลดล้มเหลว, กรุณาลองอีกครั้ง',
204
- })
205
-
206
- const emits = defineEmits(['change', 'success', 'delete'])
207
-
208
- const { wrapperProps, handleChange: onChange, setErrors, value } = useFieldHOC<IFileValue>(props)
209
-
210
- const request: IUploadRequest = {
211
- pathURL: props.uploadPathURL,
212
- requestOptions: props.requestOptions,
213
- }
214
-
215
- const upload = useUploadLoader(request)
216
-
217
- const { ui } = useUI('uploadFileDropzone', toRef(props, 'ui'), config)
218
-
219
- const fileInputRef = ref<HTMLInputElement>()
220
- const dropzoneRef = ref<HTMLDivElement>()
221
- const selectedFile = ref<File | undefined>()
222
- const isPreviewOpen = ref<boolean>(false)
223
-
224
- const { onUploadProgress, onDownloadProgress, percent } = useFileProgress()
225
- const fileAllocate = useFileAllocate(selectedFile, props)
226
-
227
- const onDrop = (files: File[] | null) => {
228
- if (props.isDisabled || files?.length === 0 || !files) return
229
-
230
- const file = files[0]
231
- const result = handleCheckFileCondition(file)
232
-
233
- if (result && file) {
234
- selectedFile.value = file
235
- const formData = new FormData()
236
-
237
- emits('change', file)
238
-
239
- formData.append(props.bodyKey, file)
240
- upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
241
- }
242
- }
243
-
244
- const { isOverDropZone } = useDropZone(dropzoneRef as unknown as HTMLElement, {
245
- onDrop,
246
- })
247
-
248
- const handleChange = (e: Event) => {
249
- if (props.isDisabled) return
250
-
251
- const file = (e.target as HTMLInputElement).files?.[0]
252
- const result = handleCheckFileCondition(file)
253
-
254
- if (result && file) {
255
- selectedFile.value = file
256
- const formData = new FormData()
257
-
258
- emits('change', file)
259
-
260
- formData.append(props.bodyKey, file)
261
- upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
262
- }
263
- }
264
-
265
- const handleOpenFile = () => {
266
- fileInputRef.value?.click()
267
- }
268
-
269
- const handleDeleteFile = () => {
270
- fileInputRef.value?.value && (fileInputRef.value.value = '')
271
- selectedFile.value = undefined
272
- onChange(undefined)
273
- emits('delete')
274
- }
275
-
276
- const handleCheckFileCondition = (file: File | undefined): boolean => {
277
- if (!file) return false
278
-
279
- const fileType = checkFileType(file, fileAllocate.acceptFile.value ?? '')
280
-
281
- if (!fileType) {
282
- setErrors(i18next.t('custom:invalid_file_type'))
283
-
284
- return false
285
- }
286
-
287
- const maxSize = checkMaxSize(file, fileAllocate.acceptFileSizeKb.value)
288
-
289
- if (!maxSize) {
290
- if (fileAllocate.isAcceptFileUseMb.value) {
291
- setErrors(
292
- i18next.t('custom:invalid_file_size_mb', { size: fileAllocate.acceptFileSizeMb.value })
293
- )
294
- } else {
295
- setErrors(
296
- i18next.t('custom:invalid_file_size_kb', { size: fileAllocate.acceptFileSizeKb.value })
297
- )
298
- }
299
-
300
- return false
301
- }
302
-
303
- setErrors('')
304
-
305
- return true
306
- }
307
-
308
- const handleDownloadFile = () => {
309
- downloadFileFromURL(_get(upload.data.value, props.responseURL), selectedFile.value?.name)
310
- }
311
-
312
- const handleRetryUpload = () => {
313
- if (selectedFile.value) {
314
- const formData = new FormData()
315
-
316
- formData.append(props.bodyKey, selectedFile.value)
317
- upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
318
- }
319
- }
320
-
321
- useWatchTrue(
322
- () => upload.status.value.isSuccess,
323
- () => {
324
- value.value = {
325
- url: _get(upload.data.value, props.responseURL),
326
- path: _get(upload.data.value, props.responsePath),
327
- name: upload.data.value.name,
328
- size: upload.data.value.size,
329
- }
330
-
331
- emits('success', value.value)
332
- }
333
- )
334
-
335
- useWatchTrue(
336
- () => upload.status.value.isError,
337
- () => {
338
- setErrors(StringHelper.getError(upload.status.value.errorData))
339
- }
340
- )
341
- </script>
1
+ <template>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <div
4
+ ref="dropzoneRef"
5
+ :class="[
6
+ ui.base,
7
+ {
8
+ [ui.disabled]: isDisabled,
9
+ [ui.background.default]: !isOverDropZone && !isDisabled,
10
+ [ui.background.dragover]: isOverDropZone && !isDisabled,
11
+ [ui.failed]: upload.status.value.isError,
12
+ },
13
+ ]"
14
+ >
15
+ <input
16
+ ref="fileInputRef"
17
+ type="file"
18
+ class="hidden"
19
+ :name="name"
20
+ :accept="fileAllocate.acceptFile.value"
21
+ :disabled="isDisabled"
22
+ @change="handleChange"
23
+ />
24
+ <div :class="[ui.wrapper]">
25
+ <div v-if="!selectedFile" :class="[ui.placeholderWrapper]">
26
+ <Icon :name="ui.default.uploadIcon" :class="[ui.labelIcon]" />
27
+ <div :class="[ui.labelWrapper]">
28
+ <p class="text-primary cursor-pointer" @click="handleOpenFile">
29
+ {{ selectFileLabel }}
30
+ </p>
31
+ <p>{{ selectFileSubLabel }}</p>
32
+ </div>
33
+ <p v-if="placeholder" :class="[ui.placeholder]">{{ placeholder }}</p>
34
+ </div>
35
+
36
+ <!-- Loading State -->
37
+ <div v-if="selectedFile && upload.status.value.isLoading" :class="[ui.onLoading.wrapper]">
38
+ <div :class="[ui.onLoading.placeholderWrapper]">
39
+ <Icon
40
+ :name="
41
+ isImage(selectedFile)
42
+ ? ui.onLoading.placeholderImgIcon || ui.default.placeholderImgIcon
43
+ : ui.onLoading.placeholderFileIcon || ui.default.filePreviewIcon
44
+ "
45
+ :class="[ui.onLoading.placeholderIconClass]"
46
+ />
47
+ </div>
48
+ <div :class="[ui.onLoading.textWrapper]">
49
+ <div class="truncate">
50
+ <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
51
+ <p class="truncate font-light text-gray-400">
52
+ {{
53
+ fileAllocate.isSelectedFileUseMb.value
54
+ ? `${fileAllocate.selectedFileSizeMb.value} MB`
55
+ : `${fileAllocate.selectedFileSizeKb.value} KB`
56
+ }}
57
+ - {{ percent }}% {{ uploadingLabel }}
58
+ </p>
59
+ </div>
60
+ <div>
61
+ <Icon
62
+ :name="ui.onLoading.loadingIcon || ui.default.loadingIcon"
63
+ :class="[ui.onLoading.loadingIconClass]"
64
+ />
65
+ </div>
66
+ </div>
67
+ </div>
68
+
69
+ <!-- Success State -->
70
+ <div v-if="selectedFile && upload.status.value.isSuccess" :class="[ui.onPreview.wrapper]">
71
+ <div :class="[ui.onPreview.previewImgWrapper]">
72
+ <div v-if="isImage(selectedFile)" class="size-full overflow-hidden">
73
+ <img
74
+ :src="upload.data.value[responseURL || 'url']"
75
+ :class="[ui.onPreview.previewImgClass]"
76
+ alt="img-preview"
77
+ />
78
+ </div>
79
+ <div v-else>
80
+ <Icon
81
+ :name="ui.onPreview.previewFileIcon || ui.default.filePreviewIcon"
82
+ :class="[ui.onPreview.previewFileClass]"
83
+ />
84
+ </div>
85
+ </div>
86
+ <div :class="[ui.onPreview.textWrapper]">
87
+ <div class="truncate">
88
+ <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
89
+ <p class="truncate text-sm font-light text-gray-400">
90
+ {{
91
+ fileAllocate.isSelectedFileUseMb.value
92
+ ? `${fileAllocate.selectedFileSizeMb.value} MB`
93
+ : `${fileAllocate.selectedFileSizeKb.value} KB`
94
+ }}
95
+ </p>
96
+ </div>
97
+ <div :class="[ui.action.wrapper]">
98
+ <Icon
99
+ v-if="isImage(selectedFile)"
100
+ :name="ui.action.previewIcon"
101
+ :class="[ui.action.iconClass]"
102
+ title="ดูตัวอย่าง"
103
+ @click="() => (isPreviewOpen = true)"
104
+ />
105
+ <Icon
106
+ :name="ui.action.downloadIcon"
107
+ :class="[ui.action.iconClass]"
108
+ title="ดาวน์โหลดไฟล์"
109
+ @click="handleDownloadFile"
110
+ />
111
+ <Icon
112
+ :name="ui.action.deleteIcon"
113
+ :class="[ui.action.iconClass]"
114
+ title="ลบไฟล์"
115
+ @click="handleDeleteFile"
116
+ />
117
+ <Modal v-model="isPreviewOpen" :title="selectedFile?.name">
118
+ <img :src="_get(upload.data.value, responseURL)" alt="img-preview" />
119
+ </Modal>
120
+ </div>
121
+ </div>
122
+ </div>
123
+
124
+ <!-- Failed State -->
125
+ <div v-if="selectedFile && upload.status.value.isError" :class="[ui.onFailed.wrapper]">
126
+ <div :class="[ui.onFailed.failedImgWrapper]">
127
+ <Icon
128
+ :name="
129
+ isImage(selectedFile)
130
+ ? ui.onFailed.failedImgIcon || ui.default.placeholderImgIcon
131
+ : ui.onFailed.failedFileIcon || ui.default.filePreviewIcon
132
+ "
133
+ :class="[ui.onFailed.failedIconClass]"
134
+ />
135
+ </div>
136
+ <div :class="[ui.onFailed.textWrapper]">
137
+ <div class="truncate">
138
+ <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
139
+ <p class="text-danger truncate font-light">
140
+ {{ uploadFailedLabel }}
141
+ </p>
142
+ <Button
143
+ variant="ghost"
144
+ :label="retryLabel"
145
+ :leading-icon="ui.action.retryIcon"
146
+ :class="[ui.action.retryBtnClass]"
147
+ size="sm"
148
+ @click="handleRetryUpload"
149
+ />
150
+ </div>
151
+ <div :class="[ui.action.wrapper]">
152
+ <Icon
153
+ title="ลบไฟล์"
154
+ :name="ui.action.deleteIcon"
155
+ :class="[ui.action.deleteIconClass]"
156
+ @click="handleDeleteFile"
157
+ />
158
+ </div>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ </FieldWrapper>
164
+ </template>
165
+
166
+ <script lang="ts" setup>
167
+ import { useDropZone } from '@vueuse/core'
168
+ import { type IUploadDropzoneAutoProps } from './types'
169
+ import {
170
+ checkMaxSize,
171
+ checkFileType,
172
+ isImage,
173
+ downloadFileFromURL,
174
+ useFileAllocate,
175
+ useFileProgress,
176
+ } from '#core/helpers/componentHelper'
177
+ import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
178
+ import { useFieldHOC } from '#core/composables/useForm'
179
+ import {
180
+ type IUploadRequest,
181
+ ref,
182
+ toRef,
183
+ useUI,
184
+ useUiConfig,
185
+ useUploadLoader,
186
+ useWatchTrue,
187
+ StringHelper,
188
+ _get,
189
+ } from '#imports'
190
+ import { uploadFileDropzone } from '#core/ui.config'
191
+ import i18next from 'i18next'
192
+ import type { IFileValue } from '#core/components/Form/types'
193
+
194
+ const config = useUiConfig<typeof uploadFileDropzone>(uploadFileDropzone, 'uploadFileDropzone')
195
+
196
+ const props = withDefaults(defineProps<IUploadDropzoneAutoProps>(), {
197
+ bodyKey: 'file',
198
+ responseURL: 'url',
199
+ responsePath: 'path',
200
+ selectFileLabel: 'คลิกเพื่อเลือกไฟล์',
201
+ selectFileSubLabel: 'หรือ ลากและวางที่นี่',
202
+ uploadingLabel: 'กำลังอัพโหลด...',
203
+ uploadFailedLabel: 'อัพโหลดล้มเหลว, กรุณาลองอีกครั้ง',
204
+ })
205
+
206
+ const emits = defineEmits(['change', 'success', 'delete'])
207
+
208
+ const { wrapperProps, handleChange: onChange, setErrors, value } = useFieldHOC<IFileValue>(props)
209
+
210
+ const request: IUploadRequest = {
211
+ pathURL: props.uploadPathURL,
212
+ requestOptions: props.requestOptions,
213
+ }
214
+
215
+ const upload = useUploadLoader(request)
216
+
217
+ const { ui } = useUI('uploadFileDropzone', toRef(props, 'ui'), config)
218
+
219
+ const fileInputRef = ref<HTMLInputElement>()
220
+ const dropzoneRef = ref<HTMLDivElement>()
221
+ const selectedFile = ref<File | undefined>()
222
+ const isPreviewOpen = ref<boolean>(false)
223
+
224
+ const { onUploadProgress, onDownloadProgress, percent } = useFileProgress()
225
+ const fileAllocate = useFileAllocate(selectedFile, props)
226
+
227
+ const onDrop = (files: File[] | null) => {
228
+ if (props.isDisabled || files?.length === 0 || !files) return
229
+
230
+ const file = files[0]
231
+ const result = handleCheckFileCondition(file)
232
+
233
+ if (result && file) {
234
+ selectedFile.value = file
235
+ const formData = new FormData()
236
+
237
+ emits('change', file)
238
+
239
+ formData.append(props.bodyKey, file)
240
+ upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
241
+ }
242
+ }
243
+
244
+ const { isOverDropZone } = useDropZone(dropzoneRef as unknown as HTMLElement, {
245
+ onDrop,
246
+ })
247
+
248
+ const handleChange = (e: Event) => {
249
+ if (props.isDisabled) return
250
+
251
+ const file = (e.target as HTMLInputElement).files?.[0]
252
+ const result = handleCheckFileCondition(file)
253
+
254
+ if (result && file) {
255
+ selectedFile.value = file
256
+ const formData = new FormData()
257
+
258
+ emits('change', file)
259
+
260
+ formData.append(props.bodyKey, file)
261
+ upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
262
+ }
263
+ }
264
+
265
+ const handleOpenFile = () => {
266
+ fileInputRef.value?.click()
267
+ }
268
+
269
+ const handleDeleteFile = () => {
270
+ fileInputRef.value?.value && (fileInputRef.value.value = '')
271
+ selectedFile.value = undefined
272
+ onChange(undefined)
273
+ emits('delete')
274
+ }
275
+
276
+ const handleCheckFileCondition = (file: File | undefined): boolean => {
277
+ if (!file) return false
278
+
279
+ const fileType = checkFileType(file, fileAllocate.acceptFile.value ?? '')
280
+
281
+ if (!fileType) {
282
+ setErrors(i18next.t('custom:invalid_file_type'))
283
+
284
+ return false
285
+ }
286
+
287
+ const maxSize = checkMaxSize(file, fileAllocate.acceptFileSizeKb.value)
288
+
289
+ if (!maxSize) {
290
+ if (fileAllocate.isAcceptFileUseMb.value) {
291
+ setErrors(
292
+ i18next.t('custom:invalid_file_size_mb', { size: fileAllocate.acceptFileSizeMb.value })
293
+ )
294
+ } else {
295
+ setErrors(
296
+ i18next.t('custom:invalid_file_size_kb', { size: fileAllocate.acceptFileSizeKb.value })
297
+ )
298
+ }
299
+
300
+ return false
301
+ }
302
+
303
+ setErrors('')
304
+
305
+ return true
306
+ }
307
+
308
+ const handleDownloadFile = () => {
309
+ downloadFileFromURL(_get(upload.data.value, props.responseURL), selectedFile.value?.name)
310
+ }
311
+
312
+ const handleRetryUpload = () => {
313
+ if (selectedFile.value) {
314
+ const formData = new FormData()
315
+
316
+ formData.append(props.bodyKey, selectedFile.value)
317
+ upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
318
+ }
319
+ }
320
+
321
+ useWatchTrue(
322
+ () => upload.status.value.isSuccess,
323
+ () => {
324
+ value.value = {
325
+ url: _get(upload.data.value, props.responseURL),
326
+ path: _get(upload.data.value, props.responsePath),
327
+ name: upload.data.value.name,
328
+ size: upload.data.value.size,
329
+ }
330
+
331
+ emits('success', value.value)
332
+ }
333
+ )
334
+
335
+ useWatchTrue(
336
+ () => upload.status.value.isError,
337
+ () => {
338
+ setErrors(StringHelper.getError(upload.status.value.errorData))
339
+ }
340
+ )
341
+ </script>