@finema/core 1.4.152 → 1.4.153

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 (168) hide show
  1. package/README.md +60 -60
  2. package/dist/module.json +1 -5
  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/date_range_time_field.types.d.ts +1 -1
  21. package/dist/runtime/components/Form/InputDateTimeRange/index.vue +83 -83
  22. package/dist/runtime/components/Form/InputNumber/index.vue +27 -27
  23. package/dist/runtime/components/Form/InputRadio/index.vue +27 -27
  24. package/dist/runtime/components/Form/InputSelect/index.vue +45 -45
  25. package/dist/runtime/components/Form/InputSelectMultiple/index.vue +54 -54
  26. package/dist/runtime/components/Form/InputStatic/index.vue +16 -16
  27. package/dist/runtime/components/Form/InputTags/index.vue +145 -145
  28. package/dist/runtime/components/Form/InputText/index.vue +67 -67
  29. package/dist/runtime/components/Form/InputTextarea/index.vue +25 -25
  30. package/dist/runtime/components/Form/InputToggle/index.vue +14 -14
  31. package/dist/runtime/components/Form/InputUploadDropzone/index.vue +206 -206
  32. package/dist/runtime/components/Form/InputUploadDropzone/types.d.ts +1 -1
  33. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +342 -342
  34. package/dist/runtime/components/Form/InputUploadDropzoneAuto/types.d.ts +1 -1
  35. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/ItemUpload.vue +241 -241
  36. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/ItemView.vue +89 -89
  37. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue +164 -164
  38. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/types.d.ts +1 -1
  39. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/ItemUpload.vue +161 -161
  40. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/ItemView.vue +64 -64
  41. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/index.vue +172 -172
  42. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/types.d.ts +1 -1
  43. package/dist/runtime/components/Form/InputUploadFileClassic/index.vue +95 -95
  44. package/dist/runtime/components/Form/InputUploadFileClassicAuto/index.vue +151 -151
  45. package/dist/runtime/components/Form/InputUploadFileClassicAuto/types.d.ts +1 -1
  46. package/dist/runtime/components/Form/InputUploadImageAuto/index.vue +219 -219
  47. package/dist/runtime/components/Form/InputUploadImageAuto/types.d.ts +1 -1
  48. package/dist/runtime/components/Form/InputWYSIWYG/index.vue +53 -53
  49. package/dist/runtime/components/Form/index.vue +6 -6
  50. package/dist/runtime/components/Icon.vue +23 -23
  51. package/dist/runtime/components/Image.vue +36 -36
  52. package/dist/runtime/components/Loader.vue +27 -27
  53. package/dist/runtime/components/Modal/index.vue +146 -146
  54. package/dist/runtime/components/QRCode.vue +22 -22
  55. package/dist/runtime/components/SimplePagination.vue +96 -96
  56. package/dist/runtime/components/Slideover/index.vue +110 -110
  57. package/dist/runtime/components/Table/Base.vue +139 -139
  58. package/dist/runtime/components/Table/ColumnDate.vue +16 -16
  59. package/dist/runtime/components/Table/ColumnDateTime.vue +18 -18
  60. package/dist/runtime/components/Table/ColumnImage.vue +15 -15
  61. package/dist/runtime/components/Table/ColumnNumber.vue +14 -14
  62. package/dist/runtime/components/Table/ColumnText.vue +25 -25
  63. package/dist/runtime/components/Table/Simple.vue +69 -69
  64. package/dist/runtime/components/Table/index.vue +65 -65
  65. package/dist/runtime/components/Tabs/index.vue +64 -64
  66. package/dist/runtime/components/TeleportSafe.vue +40 -40
  67. package/dist/runtime/composables/loaderList.d.ts +2 -2
  68. package/dist/runtime/composables/{loaderList.js → loaderList.mjs} +2 -2
  69. package/dist/runtime/composables/loaderObject.d.ts +1 -1
  70. package/dist/runtime/composables/{loaderObject.js → loaderObject.mjs} +2 -2
  71. package/dist/runtime/composables/loaderPage.d.ts +1 -1
  72. package/dist/runtime/composables/{loaderPage.js → loaderPage.mjs} +3 -3
  73. package/dist/runtime/composables/useConfig.d.ts +1 -1
  74. package/dist/runtime/composables/useForm.d.ts +1 -1
  75. package/dist/runtime/composables/useTable.d.ts +2 -2
  76. package/dist/runtime/composables/{useTable.js → useTable.mjs} +1 -1
  77. package/dist/runtime/composables/{useUpload.js → useUpload.mjs} +1 -1
  78. package/dist/runtime/helpers/{apiListHelper.js → apiListHelper.mjs} +1 -1
  79. package/dist/runtime/helpers/{apiObjectHelper.js → apiObjectHelper.mjs} +1 -1
  80. package/dist/runtime/helpers/apiPageHelper.d.ts +1 -1
  81. package/dist/runtime/helpers/{apiPageHelper.js → apiPageHelper.mjs} +4 -4
  82. package/dist/runtime/ui.config/index.d.ts +16 -16
  83. package/dist/runtime/ui.config/index.mjs +16 -0
  84. package/dist/runtime/ui.config/{uploadDropzoneImage.js → uploadDropzoneImage.mjs} +1 -1
  85. package/dist/runtime/utils/ArrayHelper.d.ts +1 -1
  86. package/dist/runtime/utils/{ArrayHelper.spec.js → ArrayHelper.spec.mjs} +1 -1
  87. package/dist/runtime/utils/{FileHelper.spec.js → FileHelper.spec.mjs} +1 -1
  88. package/dist/runtime/utils/ObjectHelper.d.ts +2 -2
  89. package/dist/runtime/utils/{ObjectHelper.js → ObjectHelper.mjs} +2 -2
  90. package/dist/runtime/utils/{ObjectHelper.spec.js → ObjectHelper.spec.mjs} +1 -1
  91. package/dist/runtime/utils/ParamHelper.d.ts +1 -1
  92. package/dist/runtime/utils/{ParamHelper.spec.js → ParamHelper.spec.mjs} +1 -1
  93. package/dist/runtime/utils/{StringHelper.spec.js → StringHelper.spec.mjs} +1 -1
  94. package/dist/runtime/utils/{TimeHelper.spec.js → TimeHelper.spec.mjs} +1 -1
  95. package/dist/runtime/utils/{TimeHelper.thai.spec.js → TimeHelper.thai.spec.mjs} +1 -1
  96. package/dist/types.d.mts +16 -1
  97. package/dist/types.d.ts +16 -1
  98. package/package.json +92 -93
  99. package/dist/runtime/ui.config/index.js +0 -16
  100. /package/dist/runtime/components/Dropdown/{types.js → types.mjs} +0 -0
  101. /package/dist/runtime/components/FlexDeck/{types.js → types.mjs} +0 -0
  102. /package/dist/runtime/components/Form/InputCheckbox/{types.js → types.mjs} +0 -0
  103. /package/dist/runtime/components/Form/InputDateTime/{date_time_field.types.js → date_time_field.types.mjs} +0 -0
  104. /package/dist/runtime/components/Form/InputDateTimeRange/{date_range_time_field.types.js → date_range_time_field.types.mjs} +0 -0
  105. /package/dist/runtime/components/Form/InputNumber/{types.js → types.mjs} +0 -0
  106. /package/dist/runtime/components/Form/InputRadio/{types.js → types.mjs} +0 -0
  107. /package/dist/runtime/components/Form/InputSelect/{types.js → types.mjs} +0 -0
  108. /package/dist/runtime/components/Form/InputSelectMultiple/{types.js → types.mjs} +0 -0
  109. /package/dist/runtime/components/Form/InputStatic/{types.js → types.mjs} +0 -0
  110. /package/dist/runtime/components/Form/InputTags/{types.js → types.mjs} +0 -0
  111. /package/dist/runtime/components/Form/InputText/{types.js → types.mjs} +0 -0
  112. /package/dist/runtime/components/Form/InputTextarea/{types.js → types.mjs} +0 -0
  113. /package/dist/runtime/components/Form/InputToggle/{types.js → types.mjs} +0 -0
  114. /package/dist/runtime/components/Form/InputUploadDropzone/{types.js → types.mjs} +0 -0
  115. /package/dist/runtime/components/Form/InputUploadDropzoneAuto/{types.js → types.mjs} +0 -0
  116. /package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/{types.js → types.mjs} +0 -0
  117. /package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/{types.js → types.mjs} +0 -0
  118. /package/dist/runtime/components/Form/InputUploadFileClassic/{types.js → types.mjs} +0 -0
  119. /package/dist/runtime/components/Form/InputUploadFileClassicAuto/{types.js → types.mjs} +0 -0
  120. /package/dist/runtime/components/Form/InputUploadImageAuto/{types.js → types.mjs} +0 -0
  121. /package/dist/runtime/components/Form/InputWYSIWYG/{types.js → types.mjs} +0 -0
  122. /package/dist/runtime/components/Form/{types.js → types.mjs} +0 -0
  123. /package/dist/runtime/components/Table/{types.js → types.mjs} +0 -0
  124. /package/dist/runtime/composables/{useApp.js → useApp.mjs} +0 -0
  125. /package/dist/runtime/composables/{useConfig.js → useConfig.mjs} +0 -0
  126. /package/dist/runtime/composables/{useDialog.js → useDialog.mjs} +0 -0
  127. /package/dist/runtime/composables/{useFlexDeck.js → useFlexDeck.mjs} +0 -0
  128. /package/dist/runtime/composables/{useForm.js → useForm.mjs} +0 -0
  129. /package/dist/runtime/composables/{useNotification.js → useNotification.mjs} +0 -0
  130. /package/dist/runtime/composables/{useWatch.js → useWatch.mjs} +0 -0
  131. /package/dist/runtime/{core.config.js → core.config.mjs} +0 -0
  132. /package/dist/runtime/helpers/{componentHelper.js → componentHelper.mjs} +0 -0
  133. /package/dist/runtime/lib/{Requester.js → Requester.mjs} +0 -0
  134. /package/dist/runtime/{plugin.js → plugin.mjs} +0 -0
  135. /package/dist/runtime/{quill.plugin.js → quill.plugin.mjs} +0 -0
  136. /package/dist/runtime/types/{common.js → common.mjs} +0 -0
  137. /package/dist/runtime/types/{config.js → config.mjs} +0 -0
  138. /package/dist/runtime/types/{lib.js → lib.mjs} +0 -0
  139. /package/dist/runtime/ui.config/{alert.js → alert.mjs} +0 -0
  140. /package/dist/runtime/ui.config/{badge.js → badge.mjs} +0 -0
  141. /package/dist/runtime/ui.config/{breadcrumb.js → breadcrumb.mjs} +0 -0
  142. /package/dist/runtime/ui.config/{button.js → button.mjs} +0 -0
  143. /package/dist/runtime/ui.config/{buttonGroup.js → buttonGroup.mjs} +0 -0
  144. /package/dist/runtime/ui.config/{card.js → card.mjs} +0 -0
  145. /package/dist/runtime/ui.config/{checkbox.js → checkbox.mjs} +0 -0
  146. /package/dist/runtime/ui.config/{formGroup.js → formGroup.mjs} +0 -0
  147. /package/dist/runtime/ui.config/{icon.js → icon.mjs} +0 -0
  148. /package/dist/runtime/ui.config/{input.js → input.mjs} +0 -0
  149. /package/dist/runtime/ui.config/{modal.js → modal.mjs} +0 -0
  150. /package/dist/runtime/ui.config/{notification.js → notification.mjs} +0 -0
  151. /package/dist/runtime/ui.config/{pagination.js → pagination.mjs} +0 -0
  152. /package/dist/runtime/ui.config/{select.js → select.mjs} +0 -0
  153. /package/dist/runtime/ui.config/{selectMenu.js → selectMenu.mjs} +0 -0
  154. /package/dist/runtime/ui.config/{slideover.js → slideover.mjs} +0 -0
  155. /package/dist/runtime/ui.config/{table.js → table.mjs} +0 -0
  156. /package/dist/runtime/ui.config/{tabs.js → tabs.mjs} +0 -0
  157. /package/dist/runtime/ui.config/{tags.js → tags.mjs} +0 -0
  158. /package/dist/runtime/ui.config/{textarea.js → textarea.mjs} +0 -0
  159. /package/dist/runtime/ui.config/{toggle.js → toggle.mjs} +0 -0
  160. /package/dist/runtime/ui.config/{uploadFileDropzone.js → uploadFileDropzone.mjs} +0 -0
  161. /package/dist/runtime/ui.config/{uploadFileInputClassicAuto.js → uploadFileInputClassicAuto.mjs} +0 -0
  162. /package/dist/runtime/ui.config/{uploadImage.js → uploadImage.mjs} +0 -0
  163. /package/dist/runtime/utils/{ArrayHelper.js → ArrayHelper.mjs} +0 -0
  164. /package/dist/runtime/utils/{FileHelper.js → FileHelper.mjs} +0 -0
  165. /package/dist/runtime/utils/{ParamHelper.js → ParamHelper.mjs} +0 -0
  166. /package/dist/runtime/utils/{StringHelper.js → StringHelper.mjs} +0 -0
  167. /package/dist/runtime/utils/{TimeHelper.js → TimeHelper.mjs} +0 -0
  168. /package/dist/runtime/utils/{lodash.js → lodash.mjs} +0 -0
@@ -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>