@finema/core 1.4.89 → 1.4.91
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 +1 -1
- package/dist/module.mjs +3 -2
- package/dist/runtime/components/Form/InputUploadDropzone/index.vue +84 -29
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +163 -57
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/types.d.ts +3 -0
- package/dist/runtime/components/Modal/index.vue +2 -2
- package/dist/runtime/helpers/componentHelper.d.ts +9 -0
- package/dist/runtime/helpers/componentHelper.mjs +30 -0
- package/dist/runtime/ui.config/uploadFileDropzone.d.ts +36 -13
- package/dist/runtime/ui.config/uploadFileDropzone.mjs +42 -19
- package/dist/runtime/utils/StringHelper.d.ts +1 -0
- package/dist/runtime/utils/StringHelper.mjs +6 -0
- package/package.json +7 -7
package/dist/module.json
CHANGED
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.
|
|
4
|
+
const version = "1.4.91";
|
|
5
5
|
|
|
6
6
|
const colors = {
|
|
7
7
|
black: "#20243E",
|
|
@@ -341,7 +341,8 @@ const module = defineNuxtModule({
|
|
|
341
341
|
files: [
|
|
342
342
|
...tailwindConfig.content.files || [],
|
|
343
343
|
resolve(runtimeDir, "components/**/*.{vue,mjs,ts}"),
|
|
344
|
-
resolve(runtimeDir, "ui.config/**/*.{mjs,js,ts}")
|
|
344
|
+
resolve(runtimeDir, "ui.config/**/*.{mjs,js,ts}"),
|
|
345
|
+
resolve(runtimeDir, "presets/**/*.{mjs,js,ts}")
|
|
345
346
|
]
|
|
346
347
|
};
|
|
347
348
|
tailwindConfig.theme.extend.colors = {
|
|
@@ -11,12 +11,6 @@
|
|
|
11
11
|
},
|
|
12
12
|
]"
|
|
13
13
|
>
|
|
14
|
-
<Icon
|
|
15
|
-
v-if="selectedFile"
|
|
16
|
-
:name="ui.default.closeIcon"
|
|
17
|
-
:class="[ui.button.delete]"
|
|
18
|
-
@click="handleDeleteFile"
|
|
19
|
-
/>
|
|
20
14
|
<input
|
|
21
15
|
ref="fileInputRef"
|
|
22
16
|
type="file"
|
|
@@ -27,18 +21,6 @@
|
|
|
27
21
|
@change="handleChange"
|
|
28
22
|
/>
|
|
29
23
|
<div :class="[ui.wrapper]">
|
|
30
|
-
<div v-if="selectedFile" :class="[ui.preview.wrapper]">
|
|
31
|
-
<div v-if="isImage(selectedFile)" :class="[ui.preview.image.wrapper]">
|
|
32
|
-
<img :src="generateURL(selectedFile)" :class="[ui.preview.image.img]" alt="file" />
|
|
33
|
-
</div>
|
|
34
|
-
<Icon v-else :name="ui.default.filePreviewIcon" :class="[ui.preview.icon]" />
|
|
35
|
-
<div :class="[ui.preview.filename]">
|
|
36
|
-
<p class="truncate">{{ selectedFile.name }}</p>
|
|
37
|
-
</div>
|
|
38
|
-
<Badge size="xs" variant="outline" class="mt-1">
|
|
39
|
-
{{ isSelectedFileUseMb ? `${selectedFileSizeMb} Mb` : `${selectedFileSizeKb} Kb` }}
|
|
40
|
-
</Badge>
|
|
41
|
-
</div>
|
|
42
24
|
<div v-if="!selectedFile" :class="[ui.placeholderWrapper]">
|
|
43
25
|
<Icon :name="ui.default.uploadIcon" :class="[ui.labelIcon]" />
|
|
44
26
|
<div :class="[ui.labelWrapper]">
|
|
@@ -49,6 +31,67 @@
|
|
|
49
31
|
</div>
|
|
50
32
|
<p v-if="placeholder" :class="[ui.placeholder]">{{ placeholder }}</p>
|
|
51
33
|
</div>
|
|
34
|
+
|
|
35
|
+
<!-- Selected State -->
|
|
36
|
+
<div v-if="selectedFile" :class="[ui.onPreview.wrapper]">
|
|
37
|
+
<div :class="[ui.onPreview.previewImgWrapper]">
|
|
38
|
+
<div v-if="isImage(selectedFile)" class="size-full overflow-hidden">
|
|
39
|
+
<img
|
|
40
|
+
:src="generateURL(selectedFile)"
|
|
41
|
+
:class="[ui.onPreview.previewImgClass]"
|
|
42
|
+
alt="image-preview"
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
<div v-else>
|
|
46
|
+
<Icon
|
|
47
|
+
:name="ui.onPreview.previewFileIcon || ui.default.filePreviewIcon"
|
|
48
|
+
:class="[ui.onPreview.previewFileClass]"
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div :class="[ui.onPreview.textWrapper]">
|
|
53
|
+
<div class="truncate">
|
|
54
|
+
<h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
|
|
55
|
+
<p class="truncate font-light text-gray-400">
|
|
56
|
+
{{
|
|
57
|
+
fileAllocate.isSelectedFileUseMb
|
|
58
|
+
? `${fileAllocate.selectedFileSizeMb} MB`
|
|
59
|
+
: `${fileAllocate.selectedFileSizeKb} KB`
|
|
60
|
+
}}
|
|
61
|
+
</p>
|
|
62
|
+
</div>
|
|
63
|
+
<div :class="[ui.action.wrapper]">
|
|
64
|
+
<PImage
|
|
65
|
+
v-if="isImage(selectedFile)"
|
|
66
|
+
alt="image-popup-preview"
|
|
67
|
+
preview
|
|
68
|
+
:pt="{ button: 'absolute opacity-0 inset-0' }"
|
|
69
|
+
>
|
|
70
|
+
<template #image>
|
|
71
|
+
<Icon :name="ui.action.previewIcon" :class="[ui.action.iconClass]" />
|
|
72
|
+
</template>
|
|
73
|
+
<template #preview="slotProps">
|
|
74
|
+
<img
|
|
75
|
+
:src="generateURL(selectedFile)"
|
|
76
|
+
alt="preview"
|
|
77
|
+
:style="slotProps.style"
|
|
78
|
+
@click="slotProps.previewCallback"
|
|
79
|
+
/>
|
|
80
|
+
</template>
|
|
81
|
+
</PImage>
|
|
82
|
+
<Icon
|
|
83
|
+
:name="ui.action.downloadIcon"
|
|
84
|
+
:class="[ui.action.iconClass]"
|
|
85
|
+
@click="handleDownloadFile"
|
|
86
|
+
/>
|
|
87
|
+
<Icon
|
|
88
|
+
:name="ui.action.deleteIcon"
|
|
89
|
+
:class="[ui.action.iconClass]"
|
|
90
|
+
@click="handleDeleteFile"
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
52
95
|
</div>
|
|
53
96
|
</div>
|
|
54
97
|
</FieldWrapper>
|
|
@@ -57,7 +100,14 @@
|
|
|
57
100
|
<script lang="ts" setup>
|
|
58
101
|
import { useDropZone } from '@vueuse/core'
|
|
59
102
|
import { type IUploadDropzoneProps } from './types'
|
|
60
|
-
import {
|
|
103
|
+
import {
|
|
104
|
+
isImage,
|
|
105
|
+
generateURL,
|
|
106
|
+
checkMaxSize,
|
|
107
|
+
checkFileType,
|
|
108
|
+
getFileAllocate,
|
|
109
|
+
downloadFileFromURL,
|
|
110
|
+
} from '#core/helpers/componentHelper'
|
|
61
111
|
import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
|
|
62
112
|
import { useFieldHOC } from '#core/composables/useForm'
|
|
63
113
|
import { computed, ref, toRef, useUI, useUiConfig } from '#imports'
|
|
@@ -80,13 +130,10 @@ const acceptFile = computed(() =>
|
|
|
80
130
|
typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
|
|
81
131
|
)
|
|
82
132
|
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
133
|
+
const fileAllocate = computed(() =>
|
|
134
|
+
getFileAllocate(props.maxSize ?? 0, selectedFile.value?.size ?? 0)
|
|
135
|
+
)
|
|
86
136
|
|
|
87
|
-
const selectedFileSizeKb = computed(() => ((value?.value.size || 0) / 1000).toFixed(2))
|
|
88
|
-
const selectedFileSizeMb = computed(() => ((value?.value.size || 0) / 1000 / 1000).toFixed(2))
|
|
89
|
-
const isSelectedFileUseMb = computed(() => (value?.value.size || 0) / 1000 > 1024)
|
|
90
137
|
const selectedFile = computed(() => value?.value)
|
|
91
138
|
|
|
92
139
|
const onDrop = (files: File[] | null) => {
|
|
@@ -128,6 +175,10 @@ const handleDeleteFile = () => {
|
|
|
128
175
|
emit('delete')
|
|
129
176
|
}
|
|
130
177
|
|
|
178
|
+
const handleDownloadFile = () => {
|
|
179
|
+
downloadFileFromURL(generateURL(selectedFile.value), selectedFile.value?.name)
|
|
180
|
+
}
|
|
181
|
+
|
|
131
182
|
const handleCheckFileCondition = (file: File | undefined): boolean => {
|
|
132
183
|
if (!file) return false
|
|
133
184
|
|
|
@@ -139,13 +190,17 @@ const handleCheckFileCondition = (file: File | undefined): boolean => {
|
|
|
139
190
|
return false
|
|
140
191
|
}
|
|
141
192
|
|
|
142
|
-
const maxSize = checkMaxSize(file,
|
|
193
|
+
const maxSize = checkMaxSize(file, fileAllocate.value.acceptFileSizeKb ?? 0)
|
|
143
194
|
|
|
144
195
|
if (!maxSize) {
|
|
145
|
-
if (
|
|
146
|
-
setErrors(
|
|
196
|
+
if (fileAllocate.value.isAcceptFileUseMb) {
|
|
197
|
+
setErrors(
|
|
198
|
+
i18next.t('custom:invalid_file_size_mb', { size: fileAllocate.value.acceptFileSizeMb })
|
|
199
|
+
)
|
|
147
200
|
} else {
|
|
148
|
-
setErrors(
|
|
201
|
+
setErrors(
|
|
202
|
+
i18next.t('custom:invalid_file_size_kb', { size: fileAllocate.value.acceptFileSizeKb })
|
|
203
|
+
)
|
|
149
204
|
}
|
|
150
205
|
|
|
151
206
|
return false
|
|
@@ -8,15 +8,10 @@
|
|
|
8
8
|
[ui.disabled]: isDisabled,
|
|
9
9
|
[ui.background.default]: !isOverDropZone && !isDisabled,
|
|
10
10
|
[ui.background.dragover]: isOverDropZone && !isDisabled,
|
|
11
|
+
[ui.failed]: upload.status.value.isError,
|
|
11
12
|
},
|
|
12
13
|
]"
|
|
13
14
|
>
|
|
14
|
-
<Icon
|
|
15
|
-
v-if="selectedFile"
|
|
16
|
-
:name="ui.default.closeIcon"
|
|
17
|
-
:class="[ui.button.delete]"
|
|
18
|
-
@click="handleDeleteFile"
|
|
19
|
-
/>
|
|
20
15
|
<input
|
|
21
16
|
ref="fileInputRef"
|
|
22
17
|
type="file"
|
|
@@ -27,51 +22,141 @@
|
|
|
27
22
|
@change="handleChange"
|
|
28
23
|
/>
|
|
29
24
|
<div :class="[ui.wrapper]">
|
|
30
|
-
<div v-if="selectedFile" :class="[ui.
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
:class="[ui.preview.image.img]"
|
|
38
|
-
alt="file"
|
|
39
|
-
/>
|
|
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>
|
|
40
32
|
</div>
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
/>
|
|
44
47
|
</div>
|
|
45
|
-
<div class="
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
54
|
+
? `${fileAllocate.selectedFileSizeMb} MB`
|
|
55
|
+
: `${fileAllocate.selectedFileSizeKb} KB`
|
|
56
|
+
}}
|
|
57
|
+
- {{ percent }}% {{ uploadingLabel ?? 'กำลังอัพโหลด...' }}
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
49
60
|
<div>
|
|
50
61
|
<Icon
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class="text-success"
|
|
62
|
+
:name="ui.onLoading.loadingIcon || ui.default.loadingIcon"
|
|
63
|
+
:class="[ui.onLoading.loadingIconClass]"
|
|
54
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[responseKey || 'url']"
|
|
75
|
+
:class="[ui.onPreview.previewImgClass]"
|
|
76
|
+
alt="image-preview"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
<div v-else>
|
|
55
80
|
<Icon
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class="text-danger"
|
|
81
|
+
:name="ui.onPreview.previewFileIcon || ui.default.filePreviewIcon"
|
|
82
|
+
:class="[ui.onPreview.previewFileClass]"
|
|
59
83
|
/>
|
|
60
84
|
</div>
|
|
61
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
|
|
92
|
+
? `${fileAllocate.selectedFileSizeMb} MB`
|
|
93
|
+
: `${fileAllocate.selectedFileSizeKb} 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="upload.data.value[responseKey || 'url']" alt="image-preview" />
|
|
119
|
+
</Modal>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
62
122
|
</div>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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>
|
|
73
159
|
</div>
|
|
74
|
-
<p v-if="placeholder" :class="[ui.placeholder]">{{ placeholder }}</p>
|
|
75
160
|
</div>
|
|
76
161
|
</div>
|
|
77
162
|
</div>
|
|
@@ -81,7 +166,13 @@
|
|
|
81
166
|
<script lang="ts" setup>
|
|
82
167
|
import { useDropZone } from '@vueuse/core'
|
|
83
168
|
import { type IUploadDropzoneAutoProps } from './types'
|
|
84
|
-
import {
|
|
169
|
+
import {
|
|
170
|
+
checkMaxSize,
|
|
171
|
+
checkFileType,
|
|
172
|
+
isImage,
|
|
173
|
+
getFileAllocate,
|
|
174
|
+
downloadFileFromURL,
|
|
175
|
+
} from '#core/helpers/componentHelper'
|
|
85
176
|
import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
|
|
86
177
|
import { useFieldHOC } from '#core/composables/useForm'
|
|
87
178
|
import {
|
|
@@ -118,23 +209,17 @@ const { ui } = useUI('uploadFileDropzone', toRef(props, 'ui'), config)
|
|
|
118
209
|
const fileInputRef = ref<HTMLInputElement>()
|
|
119
210
|
const dropzoneRef = ref<HTMLDivElement>()
|
|
120
211
|
const selectedFile = ref<File | undefined>()
|
|
121
|
-
const percent = ref<number>(0)
|
|
212
|
+
const percent = ref<number | string>(0)
|
|
213
|
+
const isPreviewOpen = ref<boolean>(false)
|
|
122
214
|
|
|
123
215
|
const acceptFile = computed(() =>
|
|
124
216
|
typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
|
|
125
217
|
)
|
|
126
218
|
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
const isAcceptFileUseMb = computed(() => acceptFileSizeKb.value && acceptFileSizeKb.value > 1024)
|
|
130
|
-
|
|
131
|
-
const selectedFileSizeKb = computed(() => ((selectedFile.value?.size || 0) / 1000).toFixed(2))
|
|
132
|
-
const selectedFileSizeMb = computed(() =>
|
|
133
|
-
((selectedFile.value?.size || 0) / 1000 / 1000).toFixed(2)
|
|
219
|
+
const fileAllocate = computed(() =>
|
|
220
|
+
getFileAllocate(props.maxSize ?? 0, selectedFile.value?.size ?? 0)
|
|
134
221
|
)
|
|
135
222
|
|
|
136
|
-
const isSelectedFileUseMb = computed(() => (selectedFile.value?.size || 0) / 1000 > 1024)
|
|
137
|
-
|
|
138
223
|
const onDrop = (files: File[] | null) => {
|
|
139
224
|
if (props.isDisabled || files?.length === 0 || !files) return
|
|
140
225
|
|
|
@@ -195,13 +280,17 @@ const handleCheckFileCondition = (file: File | undefined): boolean => {
|
|
|
195
280
|
return false
|
|
196
281
|
}
|
|
197
282
|
|
|
198
|
-
const maxSize = checkMaxSize(file,
|
|
283
|
+
const maxSize = checkMaxSize(file, fileAllocate.value.acceptFileSizeKb ?? 0)
|
|
199
284
|
|
|
200
285
|
if (!maxSize) {
|
|
201
|
-
if (
|
|
202
|
-
setErrors(
|
|
286
|
+
if (fileAllocate.value.isAcceptFileUseMb) {
|
|
287
|
+
setErrors(
|
|
288
|
+
i18next.t('custom:invalid_file_size_mb', { size: fileAllocate.value.acceptFileSizeMb })
|
|
289
|
+
)
|
|
203
290
|
} else {
|
|
204
|
-
setErrors(
|
|
291
|
+
setErrors(
|
|
292
|
+
i18next.t('custom:invalid_file_size_kb', { size: fileAllocate.value.acceptFileSizeKb })
|
|
293
|
+
)
|
|
205
294
|
}
|
|
206
295
|
|
|
207
296
|
return false
|
|
@@ -212,8 +301,23 @@ const handleCheckFileCondition = (file: File | undefined): boolean => {
|
|
|
212
301
|
return true
|
|
213
302
|
}
|
|
214
303
|
|
|
304
|
+
const handleDownloadFile = () => {
|
|
305
|
+
downloadFileFromURL(_get(upload.data.value, props.responseKey || 'url'), selectedFile.value?.name)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const handleRetryUpload = () => {
|
|
309
|
+
if (selectedFile.value) {
|
|
310
|
+
const formData = new FormData()
|
|
311
|
+
|
|
312
|
+
formData.append(props.bodyKey || 'file', selectedFile.value)
|
|
313
|
+
upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
215
317
|
const onUploadProgress = (progressEvent: ProgressEvent) => {
|
|
216
|
-
percent.value =
|
|
318
|
+
percent.value = StringHelper.withFixed(
|
|
319
|
+
(Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.8
|
|
320
|
+
)
|
|
217
321
|
}
|
|
218
322
|
|
|
219
323
|
const onDownloadProgress = (progressEvent: ProgressEvent) => {
|
|
@@ -223,7 +327,9 @@ const onDownloadProgress = (progressEvent: ProgressEvent) => {
|
|
|
223
327
|
return
|
|
224
328
|
}
|
|
225
329
|
|
|
226
|
-
percent.value =
|
|
330
|
+
percent.value = StringHelper.withFixed(
|
|
331
|
+
(Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.2 + 80
|
|
332
|
+
)
|
|
227
333
|
}
|
|
228
334
|
|
|
229
335
|
useWatchTrue(
|
|
@@ -7,6 +7,9 @@ export interface IUploadDropzoneAutoProps extends IFieldProps {
|
|
|
7
7
|
uploadPathURL?: string;
|
|
8
8
|
selectFileLabel?: string;
|
|
9
9
|
selectFileSubLabel?: string;
|
|
10
|
+
uploadingLabel?: string;
|
|
11
|
+
uploadFailedLabel?: string;
|
|
12
|
+
retryLabel?: string;
|
|
10
13
|
accept?: string[] | string;
|
|
11
14
|
bodyKey?: string;
|
|
12
15
|
responseKey?: string;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<Icon
|
|
20
20
|
v-if="!isHideCloseBtn"
|
|
21
21
|
name="i-heroicons-x-mark"
|
|
22
|
-
class="
|
|
22
|
+
class="size-6 cursor-pointer opacity-50"
|
|
23
23
|
@click="close"
|
|
24
24
|
/>
|
|
25
25
|
</div>
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
<Icon
|
|
32
32
|
v-if="!isHideCloseBtn"
|
|
33
33
|
name="i-heroicons-x-mark"
|
|
34
|
-
class="
|
|
34
|
+
class="size-6 cursor-pointer opacity-50"
|
|
35
35
|
@click="close"
|
|
36
36
|
/>
|
|
37
37
|
</div>
|
|
@@ -2,3 +2,12 @@ export declare const checkMaxSize: (file: File, acceptFileSize: number) => boole
|
|
|
2
2
|
export declare const checkFileType: (file: File, acceptFileType: string | string[]) => boolean;
|
|
3
3
|
export declare const generateURL: (file: File) => string;
|
|
4
4
|
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;
|
|
12
|
+
};
|
|
13
|
+
export declare const downloadFileFromURL: (url: string, filename?: string) => Promise<void>;
|
|
@@ -37,3 +37,33 @@ export const generateURL = (file) => {
|
|
|
37
37
|
export const isImage = (file) => {
|
|
38
38
|
return file.type.startsWith("image/");
|
|
39
39
|
};
|
|
40
|
+
export const getFileAllocate = (maxSize, selectedFileSize) => {
|
|
41
|
+
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
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
export const downloadFileFromURL = async (url, filename) => {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const xhr = new XMLHttpRequest();
|
|
53
|
+
xhr.responseType = "blob";
|
|
54
|
+
xhr.onload = () => {
|
|
55
|
+
const a = document.createElement("a");
|
|
56
|
+
const url2 = window.URL.createObjectURL(xhr.response);
|
|
57
|
+
a.href = url2;
|
|
58
|
+
a.download = filename || url2.split("/").pop() || (/* @__PURE__ */ new Date()).toISOString();
|
|
59
|
+
a.click();
|
|
60
|
+
window.URL.revokeObjectURL(url2);
|
|
61
|
+
resolve();
|
|
62
|
+
};
|
|
63
|
+
xhr.onerror = () => {
|
|
64
|
+
reject();
|
|
65
|
+
};
|
|
66
|
+
xhr.open("GET", url);
|
|
67
|
+
xhr.send();
|
|
68
|
+
});
|
|
69
|
+
};
|
|
@@ -2,33 +2,56 @@ export declare const uploadFileDropzone: {
|
|
|
2
2
|
base: string;
|
|
3
3
|
wrapper: string;
|
|
4
4
|
disabled: string;
|
|
5
|
+
failed: string;
|
|
5
6
|
placeholderWrapper: string;
|
|
6
7
|
placeholder: string;
|
|
7
8
|
labelWrapper: string;
|
|
8
|
-
|
|
9
|
+
onLoading: {
|
|
9
10
|
wrapper: string;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
placeholderWrapper: string;
|
|
12
|
+
placeholderImgIcon: string;
|
|
13
|
+
placeholderFileIcon: string;
|
|
14
|
+
placeholderIconClass: string;
|
|
15
|
+
textWrapper: string;
|
|
16
|
+
loadingIcon: string;
|
|
17
|
+
loadingIconClass: string;
|
|
16
18
|
};
|
|
17
|
-
|
|
19
|
+
onPreview: {
|
|
18
20
|
wrapper: string;
|
|
19
|
-
|
|
21
|
+
previewImgWrapper: string;
|
|
22
|
+
previewImgClass: string;
|
|
23
|
+
previewFileIcon: string;
|
|
24
|
+
previewFileClass: string;
|
|
25
|
+
textWrapper: string;
|
|
26
|
+
};
|
|
27
|
+
onFailed: {
|
|
28
|
+
wrapper: string;
|
|
29
|
+
failedImgWrapper: string;
|
|
30
|
+
failedImgIcon: string;
|
|
31
|
+
failedFileIcon: string;
|
|
32
|
+
failedIconClass: string;
|
|
33
|
+
textWrapper: string;
|
|
34
|
+
};
|
|
35
|
+
action: {
|
|
36
|
+
wrapper: string;
|
|
37
|
+
iconClass: string;
|
|
38
|
+
deleteIconClass: string;
|
|
39
|
+
retryBtnClass: string;
|
|
40
|
+
previewIcon: string;
|
|
41
|
+
downloadIcon: string;
|
|
42
|
+
deleteIcon: string;
|
|
43
|
+
retryIcon: string;
|
|
20
44
|
};
|
|
21
45
|
background: {
|
|
22
46
|
default: string;
|
|
23
47
|
dragover: string;
|
|
24
48
|
};
|
|
25
|
-
button: {
|
|
26
|
-
delete: string;
|
|
27
|
-
};
|
|
28
49
|
labelIcon: string;
|
|
29
50
|
default: {
|
|
30
|
-
closeIcon: string;
|
|
31
51
|
filePreviewIcon: string;
|
|
32
52
|
uploadIcon: string;
|
|
53
|
+
placeholderImgIcon: string;
|
|
54
|
+
failedImgIcon: string;
|
|
55
|
+
loadingIcon: string;
|
|
33
56
|
};
|
|
34
57
|
};
|
|
@@ -1,34 +1,57 @@
|
|
|
1
1
|
export const uploadFileDropzone = {
|
|
2
|
-
base: "relative w-full p-
|
|
2
|
+
base: "relative w-full p-4 transition rounded-lg flex items-center justify-center border-[1px] ring-0",
|
|
3
3
|
wrapper: "flex flex-col items-center w-full",
|
|
4
|
-
disabled: "bg-gray-
|
|
5
|
-
|
|
4
|
+
disabled: "bg-gray-100 border-none grayscale cursor-not-allowed",
|
|
5
|
+
failed: "border-danger",
|
|
6
|
+
placeholderWrapper: "py-4 flex flex-col items-center justify-center",
|
|
6
7
|
placeholder: "text-gray-400 text-center font-light text-sm truncate",
|
|
7
8
|
labelWrapper: "flex items-center space-x-2 text-gray-400 text-center",
|
|
8
|
-
|
|
9
|
-
wrapper: "
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
onLoading: {
|
|
10
|
+
wrapper: "w-full flex space-x-4",
|
|
11
|
+
placeholderWrapper: "flex size-12 items-center justify-center rounded-lg bg-gray-100",
|
|
12
|
+
placeholderImgIcon: "",
|
|
13
|
+
placeholderFileIcon: "",
|
|
14
|
+
placeholderIconClass: "size-7 text-gray-400",
|
|
15
|
+
textWrapper: "flex w-full items-center justify-between",
|
|
16
|
+
loadingIcon: "",
|
|
17
|
+
loadingIconClass: "text-primary size-10"
|
|
16
18
|
},
|
|
17
|
-
|
|
18
|
-
wrapper: "w-full
|
|
19
|
-
|
|
19
|
+
onPreview: {
|
|
20
|
+
wrapper: "w-full flex space-x-4",
|
|
21
|
+
previewImgWrapper: "flex size-12 items-center justify-center rounded-lg overflow-hidden bg-gray-100",
|
|
22
|
+
previewImgClass: "w-full h-full object-cover",
|
|
23
|
+
previewFileIcon: "",
|
|
24
|
+
previewFileClass: "size-7 text-gray-400",
|
|
25
|
+
textWrapper: "flex w-full items-center justify-between"
|
|
26
|
+
},
|
|
27
|
+
onFailed: {
|
|
28
|
+
wrapper: "w-full flex space-x-4",
|
|
29
|
+
failedImgWrapper: "flex size-12 items-center justify-center rounded-lg bg-gray-100",
|
|
30
|
+
failedImgIcon: "",
|
|
31
|
+
failedFileIcon: "",
|
|
32
|
+
failedIconClass: "size-7 text-gray-400",
|
|
33
|
+
textWrapper: "flex w-full items-start justify-between"
|
|
34
|
+
},
|
|
35
|
+
action: {
|
|
36
|
+
wrapper: "flex items-center space-x-4",
|
|
37
|
+
iconClass: "size-6 text-gray-400 cursor-pointer",
|
|
38
|
+
deleteIconClass: "size-6 text-danger cursor-pointer",
|
|
39
|
+
retryBtnClass: "px-0",
|
|
40
|
+
previewIcon: "i-ic:outline-remove-red-eye",
|
|
41
|
+
downloadIcon: "i-ic:outline-file-download",
|
|
42
|
+
deleteIcon: "ph:trash",
|
|
43
|
+
retryIcon: "ph:arrow-counter-clockwise"
|
|
20
44
|
},
|
|
21
45
|
background: {
|
|
22
46
|
default: "bg-white border-gray-border",
|
|
23
47
|
dragover: "bg-gray-100 border-primary"
|
|
24
48
|
},
|
|
25
|
-
button: {
|
|
26
|
-
delete: "cursor-pointer w-5 h-5 ml-2 text-gray-400 hover:text-gray-300 absolute top-6 right-6 "
|
|
27
|
-
},
|
|
28
49
|
labelIcon: "w-6 h-6 text-gray-400 text-center mb-4",
|
|
29
50
|
default: {
|
|
30
|
-
closeIcon: "i-heroicons:trash-solid",
|
|
31
51
|
filePreviewIcon: "i-heroicons:document-text-solid",
|
|
32
|
-
uploadIcon: "i-ph:cloud-arrow-up"
|
|
52
|
+
uploadIcon: "i-ph:cloud-arrow-up",
|
|
53
|
+
placeholderImgIcon: "i-material-symbols:imagesmode-outline",
|
|
54
|
+
failedImgIcon: "i-material-symbols:imagesmode-outline",
|
|
55
|
+
loadingIcon: "i-svg-spinners:180-ring-with-bg"
|
|
33
56
|
}
|
|
34
57
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare class StringHelper {
|
|
2
2
|
static genString: (length?: number) => string;
|
|
3
3
|
static withComma: (value?: number | string) => string;
|
|
4
|
+
static withFixed: (value?: number | string) => string;
|
|
4
5
|
static split: (str: string | null | undefined, separator: string | RegExp) => string[];
|
|
5
6
|
static joinURL: (...parts: string[]) => string;
|
|
6
7
|
static truncate: (str: any, num?: number) => any;
|
|
@@ -12,6 +12,12 @@ export class StringHelper {
|
|
|
12
12
|
static withComma = (value = 0) => {
|
|
13
13
|
return (+(value || 0)).toLocaleString();
|
|
14
14
|
};
|
|
15
|
+
static withFixed = (value = 0) => {
|
|
16
|
+
return (+(value || 0)).toLocaleString(void 0, {
|
|
17
|
+
minimumFractionDigits: 0,
|
|
18
|
+
maximumFractionDigits: 2
|
|
19
|
+
});
|
|
20
|
+
};
|
|
15
21
|
static split = (str, separator) => {
|
|
16
22
|
return `${str || ""}`.split(separator).filter((item) => item).map((item) => item.trim());
|
|
17
23
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finema/core",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.91",
|
|
4
4
|
"repository": "https://gitlab.finema.co/finema/ui-kit",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Finema Dev Core Team",
|
|
@@ -34,17 +34,17 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@nuxt/kit": "^3.10.3",
|
|
37
|
-
"@nuxt/ui": "^2.14.
|
|
37
|
+
"@nuxt/ui": "^2.14.2",
|
|
38
38
|
"@pinia/nuxt": "^0.5.1",
|
|
39
39
|
"@vee-validate/nuxt": "^4.12.5",
|
|
40
40
|
"@vee-validate/zod": "^4.12.5",
|
|
41
|
-
"@vuepic/vue-datepicker": "^8.
|
|
41
|
+
"@vuepic/vue-datepicker": "^8.2.0",
|
|
42
42
|
"axios": "^1.6.7",
|
|
43
43
|
"date-fns": "^3.3.1",
|
|
44
44
|
"i18next": "^23.10.0",
|
|
45
45
|
"maska": "^2.1.11",
|
|
46
46
|
"nuxt-lodash": "^2.5.3",
|
|
47
|
-
"nuxt-security": "^1.2.
|
|
47
|
+
"nuxt-security": "^1.2.1",
|
|
48
48
|
"pinia": "^2.1.7",
|
|
49
49
|
"qrcode.vue": "^3.4.1",
|
|
50
50
|
"url-join": "^5.0.0",
|
|
@@ -64,17 +64,17 @@
|
|
|
64
64
|
"changelogen": "^0.5.5",
|
|
65
65
|
"eslint": "^8.56.0",
|
|
66
66
|
"happy-dom": "^13.0.0",
|
|
67
|
-
"husky": "^
|
|
67
|
+
"husky": "^9.0.11",
|
|
68
68
|
"lint-staged": "^15.2.0",
|
|
69
69
|
"nuxt": "^3.10.3",
|
|
70
|
-
"playwright-core": "^1.
|
|
70
|
+
"playwright-core": "^1.42.1",
|
|
71
71
|
"prettier": "^3.1.1",
|
|
72
72
|
"release-it": "^17.0.1",
|
|
73
73
|
"sass": "^1.69.5",
|
|
74
74
|
"stylelint": "^16.1.0",
|
|
75
75
|
"stylelint-config-prettier-scss": "^1.0.0",
|
|
76
76
|
"stylelint-config-standard-scss": "^13.0.0",
|
|
77
|
-
"vitest": "^1.
|
|
77
|
+
"vitest": "^1.3.1",
|
|
78
78
|
"vue": "^3.4.21"
|
|
79
79
|
},
|
|
80
80
|
"lint-staged": {
|