@finema/core 1.4.159 → 1.4.161

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 (62) hide show
  1. package/README.md +60 -60
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +59 -17
  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 +45 -45
  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 +45 -45
  24. package/dist/runtime/components/Form/InputSelectMultiple/index.vue +54 -54
  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 +342 -342
  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/Icon.vue +23 -23
  44. package/dist/runtime/components/Image.vue +36 -36
  45. package/dist/runtime/components/Loader.vue +27 -27
  46. package/dist/runtime/components/Modal/index.vue +146 -146
  47. package/dist/runtime/components/QRCode.vue +22 -22
  48. package/dist/runtime/components/SimplePagination.vue +96 -96
  49. package/dist/runtime/components/Slideover/index.vue +110 -110
  50. package/dist/runtime/components/Table/Base.vue +139 -139
  51. package/dist/runtime/components/Table/ColumnDate.vue +16 -16
  52. package/dist/runtime/components/Table/ColumnDateTime.vue +18 -18
  53. package/dist/runtime/components/Table/ColumnImage.vue +15 -15
  54. package/dist/runtime/components/Table/ColumnNumber.vue +14 -14
  55. package/dist/runtime/components/Table/ColumnText.vue +25 -25
  56. package/dist/runtime/components/Table/Simple.vue +69 -69
  57. package/dist/runtime/components/Table/index.vue +65 -65
  58. package/dist/runtime/components/Tabs/index.vue +64 -64
  59. package/dist/runtime/components/TeleportSafe.vue +40 -40
  60. package/dist/runtime/composables/useNotification.mjs +13 -4
  61. package/dist/runtime/ui.config/notification.mjs +4 -0
  62. package/package.json +95 -95
@@ -1,342 +1,342 @@
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 || !isReadonly),
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 || isReadonly"
22
- @change="handleChange"
23
- />
24
- <div :class="[ui.wrapper]">
25
- <div v-if="!selectedFile && !value" :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="value" :class="[ui.onPreview.wrapper]">
71
- <div :class="[ui.onPreview.previewImgWrapper]">
72
- <div v-if="isImageFromPath(value.path)" class="size-full overflow-hidden">
73
- <img :src="value.url" :class="[ui.onPreview.previewImgClass]" alt="img-preview" />
74
- </div>
75
- <div v-else>
76
- <Icon
77
- :name="ui.onPreview.previewFileIcon || ui.default.filePreviewIcon"
78
- :class="[ui.onPreview.previewFileClass]"
79
- />
80
- </div>
81
- </div>
82
- <div :class="[ui.onPreview.textWrapper]">
83
- <div class="truncate">
84
- <h1 class="truncate font-bold">{{ value.name }}</h1>
85
- <p class="truncate text-sm font-light text-gray-400">
86
- {{
87
- fileAllocateFromPath.isSelectedFileUseMb.value
88
- ? `${fileAllocate.selectedFileSizeMb.value} MB`
89
- : `${fileAllocateFromPath.selectedFileSizeKb.value} KB`
90
- }}
91
- </p>
92
- </div>
93
- <div :class="[ui.action.wrapper]">
94
- <Icon
95
- v-if="isImageFromPath(value.path)"
96
- :name="ui.action.previewIcon"
97
- :class="[ui.action.iconClass]"
98
- title="ดูตัวอย่าง"
99
- @click="() => (isPreviewOpen = true)"
100
- />
101
- <Icon
102
- :name="ui.action.downloadIcon"
103
- :class="[ui.action.iconClass]"
104
- title="ดาวน์โหลดไฟล์"
105
- @click="handleDownloadFile"
106
- />
107
- <Icon
108
- v-if="!(isDisabled || isReadonly)"
109
- :name="ui.action.deleteIcon"
110
- :class="[ui.action.iconClass]"
111
- title="ลบไฟล์"
112
- @click="handleDeleteFile"
113
- />
114
- <Modal v-model="isPreviewOpen" :title="value.name">
115
- <img :src="value.url" alt="img-preview" />
116
- </Modal>
117
- </div>
118
- </div>
119
- </div>
120
-
121
- <!-- Failed State -->
122
- <div v-if="selectedFile && upload.status.value.isError" :class="[ui.onFailed.wrapper]">
123
- <div :class="[ui.onFailed.failedImgWrapper]">
124
- <Icon
125
- :name="
126
- isImage(selectedFile)
127
- ? ui.onFailed.failedImgIcon || ui.default.placeholderImgIcon
128
- : ui.onFailed.failedFileIcon || ui.default.filePreviewIcon
129
- "
130
- :class="[ui.onFailed.failedIconClass]"
131
- />
132
- </div>
133
- <div :class="[ui.onFailed.textWrapper]">
134
- <div class="truncate">
135
- <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
136
- <p class="text-danger truncate font-light">
137
- {{ uploadFailedLabel }}
138
- </p>
139
- <Button
140
- variant="ghost"
141
- :label="retryLabel"
142
- :leading-icon="ui.action.retryIcon"
143
- :class="[ui.action.retryBtnClass]"
144
- size="sm"
145
- @click="handleRetryUpload"
146
- />
147
- </div>
148
- <div :class="[ui.action.wrapper]">
149
- <Icon
150
- title="ลบไฟล์"
151
- :name="ui.action.deleteIcon"
152
- :class="[ui.action.deleteIconClass]"
153
- @click="handleDeleteFile"
154
- />
155
- </div>
156
- </div>
157
- </div>
158
- </div>
159
- </div>
160
- </FieldWrapper>
161
- </template>
162
-
163
- <script lang="ts" setup>
164
- import { useDropZone } from '@vueuse/core'
165
- import { type IUploadDropzoneAutoProps } from './types'
166
- import {
167
- checkMaxSize,
168
- checkFileType,
169
- isImage,
170
- downloadFileFromURL,
171
- useFileAllocate,
172
- useFileProgress,
173
- isImageFromPath,
174
- useFileSize,
175
- } from '#core/helpers/componentHelper'
176
- import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
177
- import { useFieldHOC } from '#core/composables/useForm'
178
- import {
179
- type IUploadRequest,
180
- ref,
181
- computed,
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
- const fileAllocateFromPath = computed(() => useFileSize(value.value?.size))
227
-
228
- const onDrop = (files: File[] | null) => {
229
- if (props.isDisabled || files?.length === 0 || !files) return
230
-
231
- const file = files[0]
232
- const result = handleCheckFileCondition(file)
233
-
234
- if (result && file) {
235
- selectedFile.value = file
236
- const formData = new FormData()
237
-
238
- emits('change', file)
239
-
240
- formData.append(props.bodyKey, file)
241
- upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
242
- }
243
- }
244
-
245
- const { isOverDropZone } = useDropZone(dropzoneRef as unknown as HTMLElement, {
246
- onDrop,
247
- })
248
-
249
- const handleChange = (e: Event) => {
250
- if (props.isDisabled || props.isReadonly) return
251
-
252
- const file = (e.target as HTMLInputElement).files?.[0]
253
- const result = handleCheckFileCondition(file)
254
-
255
- if (result && file) {
256
- selectedFile.value = file
257
- const formData = new FormData()
258
-
259
- emits('change', file)
260
-
261
- formData.append(props.bodyKey, file)
262
- upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
263
- }
264
- }
265
-
266
- const handleOpenFile = () => {
267
- fileInputRef.value?.click()
268
- }
269
-
270
- const handleDeleteFile = () => {
271
- fileInputRef.value?.value && (fileInputRef.value.value = '')
272
- selectedFile.value = undefined
273
- onChange(undefined)
274
- emits('delete')
275
- }
276
-
277
- const handleCheckFileCondition = (file: File | undefined): boolean => {
278
- if (!file) return false
279
-
280
- const fileType = checkFileType(file, fileAllocate.acceptFile.value ?? '')
281
-
282
- if (!fileType) {
283
- setErrors(i18next.t('custom:invalid_file_type'))
284
-
285
- return false
286
- }
287
-
288
- const maxSize = checkMaxSize(file, fileAllocate.acceptFileSizeKb.value)
289
-
290
- if (!maxSize) {
291
- if (fileAllocate.isAcceptFileUseMb.value) {
292
- setErrors(
293
- i18next.t('custom:invalid_file_size_mb', { size: fileAllocate.acceptFileSizeMb.value })
294
- )
295
- } else {
296
- setErrors(
297
- i18next.t('custom:invalid_file_size_kb', { size: fileAllocate.acceptFileSizeKb.value })
298
- )
299
- }
300
-
301
- return false
302
- }
303
-
304
- setErrors('')
305
-
306
- return true
307
- }
308
-
309
- const handleDownloadFile = () => {
310
- downloadFileFromURL(value.value?.url, value.value?.name)
311
- }
312
-
313
- const handleRetryUpload = () => {
314
- if (selectedFile.value) {
315
- const formData = new FormData()
316
-
317
- formData.append(props.bodyKey, selectedFile.value)
318
- upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
319
- }
320
- }
321
-
322
- useWatchTrue(
323
- () => upload.status.value.isSuccess,
324
- () => {
325
- value.value = {
326
- url: _get(upload.data.value, props.responseURL),
327
- path: _get(upload.data.value, props.responsePath),
328
- name: upload.data.value.file_name,
329
- size: upload.data.value.size,
330
- }
331
-
332
- emits('success', value.value)
333
- }
334
- )
335
-
336
- useWatchTrue(
337
- () => upload.status.value.isError,
338
- () => {
339
- setErrors(StringHelper.getError(upload.status.value.errorData))
340
- }
341
- )
342
- </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 || !isReadonly),
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 || isReadonly"
22
+ @change="handleChange"
23
+ />
24
+ <div :class="[ui.wrapper]">
25
+ <div v-if="!selectedFile && !value" :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="value" :class="[ui.onPreview.wrapper]">
71
+ <div :class="[ui.onPreview.previewImgWrapper]">
72
+ <div v-if="isImageFromPath(value.path)" class="size-full overflow-hidden">
73
+ <img :src="value.url" :class="[ui.onPreview.previewImgClass]" alt="img-preview" />
74
+ </div>
75
+ <div v-else>
76
+ <Icon
77
+ :name="ui.onPreview.previewFileIcon || ui.default.filePreviewIcon"
78
+ :class="[ui.onPreview.previewFileClass]"
79
+ />
80
+ </div>
81
+ </div>
82
+ <div :class="[ui.onPreview.textWrapper]">
83
+ <div class="truncate">
84
+ <h1 class="truncate font-bold">{{ value.name }}</h1>
85
+ <p class="truncate text-sm font-light text-gray-400">
86
+ {{
87
+ fileAllocateFromPath.isSelectedFileUseMb.value
88
+ ? `${fileAllocate.selectedFileSizeMb.value} MB`
89
+ : `${fileAllocateFromPath.selectedFileSizeKb.value} KB`
90
+ }}
91
+ </p>
92
+ </div>
93
+ <div :class="[ui.action.wrapper]">
94
+ <Icon
95
+ v-if="isImageFromPath(value.path)"
96
+ :name="ui.action.previewIcon"
97
+ :class="[ui.action.iconClass]"
98
+ title="ดูตัวอย่าง"
99
+ @click="() => (isPreviewOpen = true)"
100
+ />
101
+ <Icon
102
+ :name="ui.action.downloadIcon"
103
+ :class="[ui.action.iconClass]"
104
+ title="ดาวน์โหลดไฟล์"
105
+ @click="handleDownloadFile"
106
+ />
107
+ <Icon
108
+ v-if="!(isDisabled || isReadonly)"
109
+ :name="ui.action.deleteIcon"
110
+ :class="[ui.action.iconClass]"
111
+ title="ลบไฟล์"
112
+ @click="handleDeleteFile"
113
+ />
114
+ <Modal v-model="isPreviewOpen" :title="value.name">
115
+ <img :src="value.url" alt="img-preview" />
116
+ </Modal>
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- Failed State -->
122
+ <div v-if="selectedFile && upload.status.value.isError" :class="[ui.onFailed.wrapper]">
123
+ <div :class="[ui.onFailed.failedImgWrapper]">
124
+ <Icon
125
+ :name="
126
+ isImage(selectedFile)
127
+ ? ui.onFailed.failedImgIcon || ui.default.placeholderImgIcon
128
+ : ui.onFailed.failedFileIcon || ui.default.filePreviewIcon
129
+ "
130
+ :class="[ui.onFailed.failedIconClass]"
131
+ />
132
+ </div>
133
+ <div :class="[ui.onFailed.textWrapper]">
134
+ <div class="truncate">
135
+ <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
136
+ <p class="text-danger truncate font-light">
137
+ {{ uploadFailedLabel }}
138
+ </p>
139
+ <Button
140
+ variant="ghost"
141
+ :label="retryLabel"
142
+ :leading-icon="ui.action.retryIcon"
143
+ :class="[ui.action.retryBtnClass]"
144
+ size="sm"
145
+ @click="handleRetryUpload"
146
+ />
147
+ </div>
148
+ <div :class="[ui.action.wrapper]">
149
+ <Icon
150
+ title="ลบไฟล์"
151
+ :name="ui.action.deleteIcon"
152
+ :class="[ui.action.deleteIconClass]"
153
+ @click="handleDeleteFile"
154
+ />
155
+ </div>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ </FieldWrapper>
161
+ </template>
162
+
163
+ <script lang="ts" setup>
164
+ import { useDropZone } from '@vueuse/core'
165
+ import { type IUploadDropzoneAutoProps } from './types'
166
+ import {
167
+ checkMaxSize,
168
+ checkFileType,
169
+ isImage,
170
+ downloadFileFromURL,
171
+ useFileAllocate,
172
+ useFileProgress,
173
+ isImageFromPath,
174
+ useFileSize,
175
+ } from '#core/helpers/componentHelper'
176
+ import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
177
+ import { useFieldHOC } from '#core/composables/useForm'
178
+ import {
179
+ type IUploadRequest,
180
+ ref,
181
+ computed,
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
+ const fileAllocateFromPath = computed(() => useFileSize(value.value?.size))
227
+
228
+ const onDrop = (files: File[] | null) => {
229
+ if (props.isDisabled || files?.length === 0 || !files) return
230
+
231
+ const file = files[0]
232
+ const result = handleCheckFileCondition(file)
233
+
234
+ if (result && file) {
235
+ selectedFile.value = file
236
+ const formData = new FormData()
237
+
238
+ emits('change', file)
239
+
240
+ formData.append(props.bodyKey, file)
241
+ upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
242
+ }
243
+ }
244
+
245
+ const { isOverDropZone } = useDropZone(dropzoneRef as unknown as HTMLElement, {
246
+ onDrop,
247
+ })
248
+
249
+ const handleChange = (e: Event) => {
250
+ if (props.isDisabled || props.isReadonly) return
251
+
252
+ const file = (e.target as HTMLInputElement).files?.[0]
253
+ const result = handleCheckFileCondition(file)
254
+
255
+ if (result && file) {
256
+ selectedFile.value = file
257
+ const formData = new FormData()
258
+
259
+ emits('change', file)
260
+
261
+ formData.append(props.bodyKey, file)
262
+ upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
263
+ }
264
+ }
265
+
266
+ const handleOpenFile = () => {
267
+ fileInputRef.value?.click()
268
+ }
269
+
270
+ const handleDeleteFile = () => {
271
+ fileInputRef.value?.value && (fileInputRef.value.value = '')
272
+ selectedFile.value = undefined
273
+ onChange(undefined)
274
+ emits('delete')
275
+ }
276
+
277
+ const handleCheckFileCondition = (file: File | undefined): boolean => {
278
+ if (!file) return false
279
+
280
+ const fileType = checkFileType(file, fileAllocate.acceptFile.value ?? '')
281
+
282
+ if (!fileType) {
283
+ setErrors(i18next.t('custom:invalid_file_type'))
284
+
285
+ return false
286
+ }
287
+
288
+ const maxSize = checkMaxSize(file, fileAllocate.acceptFileSizeKb.value)
289
+
290
+ if (!maxSize) {
291
+ if (fileAllocate.isAcceptFileUseMb.value) {
292
+ setErrors(
293
+ i18next.t('custom:invalid_file_size_mb', { size: fileAllocate.acceptFileSizeMb.value })
294
+ )
295
+ } else {
296
+ setErrors(
297
+ i18next.t('custom:invalid_file_size_kb', { size: fileAllocate.acceptFileSizeKb.value })
298
+ )
299
+ }
300
+
301
+ return false
302
+ }
303
+
304
+ setErrors('')
305
+
306
+ return true
307
+ }
308
+
309
+ const handleDownloadFile = () => {
310
+ downloadFileFromURL(value.value?.url, value.value?.name)
311
+ }
312
+
313
+ const handleRetryUpload = () => {
314
+ if (selectedFile.value) {
315
+ const formData = new FormData()
316
+
317
+ formData.append(props.bodyKey, selectedFile.value)
318
+ upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
319
+ }
320
+ }
321
+
322
+ useWatchTrue(
323
+ () => upload.status.value.isSuccess,
324
+ () => {
325
+ value.value = {
326
+ url: _get(upload.data.value, props.responseURL),
327
+ path: _get(upload.data.value, props.responsePath),
328
+ name: upload.data.value.file_name,
329
+ size: upload.data.value.size,
330
+ }
331
+
332
+ emits('success', value.value)
333
+ }
334
+ )
335
+
336
+ useWatchTrue(
337
+ () => upload.status.value.isError,
338
+ () => {
339
+ setErrors(StringHelper.getError(upload.status.value.errorData))
340
+ }
341
+ )
342
+ </script>