@finema/core 2.12.3 → 2.13.0
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 +1 -1
- package/dist/runtime/components/FlexDeck/Base.vue +1 -1
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/EmptyState.vue +35 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/EmptyState.vue.d.ts +12 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/FailedState.vue +54 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/FailedState.vue.d.ts +14 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/LoadingState.vue +54 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/LoadingState.vue.d.ts +8 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/PreviewModal.vue +35 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/PreviewModal.vue.d.ts +10 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/SuccessState.vue +88 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/SuccessState.vue.d.ts +17 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +91 -76
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue.d.ts +10 -6
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/types.d.ts +7 -5
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/useUploadState.d.ts +25 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/useUploadState.js +187 -0
- package/dist/runtime/components/Image.vue +1 -1
- package/dist/runtime/composables/useUpload.js +2 -4
- package/dist/runtime/helpers/componentHelper.d.ts +3 -2
- package/dist/runtime/helpers/componentHelper.js +6 -2
- package/dist/runtime/theme/uploadFileDropzone.d.ts +32 -0
- package/dist/runtime/theme/uploadFileDropzone.js +41 -5
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="theme.placeholderWrapper()">
|
|
3
|
+
<Icon
|
|
4
|
+
:name="themeStatic.uploadIcon"
|
|
5
|
+
:class="theme.labelIcon()"
|
|
6
|
+
/>
|
|
7
|
+
<div :class="theme.labelWrapper()">
|
|
8
|
+
<p
|
|
9
|
+
class="text-primary cursor-pointer font-bold"
|
|
10
|
+
@click="$emit('openFile')"
|
|
11
|
+
>
|
|
12
|
+
{{ selectFileLabel }}
|
|
13
|
+
</p>
|
|
14
|
+
<p>{{ selectFileSubLabel }}</p>
|
|
15
|
+
</div>
|
|
16
|
+
<p
|
|
17
|
+
v-if="placeholder"
|
|
18
|
+
:class="theme.placeholder()"
|
|
19
|
+
>
|
|
20
|
+
{{ placeholder }}
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup>
|
|
26
|
+
import { useUiConfigStatic } from "#imports";
|
|
27
|
+
defineProps({
|
|
28
|
+
theme: { type: null, required: true },
|
|
29
|
+
selectFileLabel: { type: String, required: true },
|
|
30
|
+
selectFileSubLabel: { type: String, required: true },
|
|
31
|
+
placeholder: { type: String, required: false }
|
|
32
|
+
});
|
|
33
|
+
defineEmits(["openFile"]);
|
|
34
|
+
const themeStatic = useUiConfigStatic("uploadFileDropzone");
|
|
35
|
+
</script>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
theme: any;
|
|
3
|
+
selectFileLabel: string;
|
|
4
|
+
selectFileSubLabel: string;
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
}
|
|
7
|
+
declare const _default: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
8
|
+
openFile: () => any;
|
|
9
|
+
}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
|
|
10
|
+
onOpenFile?: (() => any) | undefined;
|
|
11
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
12
|
+
export default _default;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="theme.onFailedWrapper()">
|
|
3
|
+
<div :class="theme.onFailedFailedImgWrapper()">
|
|
4
|
+
<Icon
|
|
5
|
+
:name="getFileIcon(selectedFile)"
|
|
6
|
+
:class="theme.onFailedFailedIconClass()"
|
|
7
|
+
/>
|
|
8
|
+
</div>
|
|
9
|
+
<div :class="theme.onFailedTextWrapper()">
|
|
10
|
+
<div class="truncate">
|
|
11
|
+
<h1 class="truncate font-bold">
|
|
12
|
+
{{ selectedFile.name }}
|
|
13
|
+
</h1>
|
|
14
|
+
<p class="text-error truncate font-light">
|
|
15
|
+
{{ uploadFailedLabel }}
|
|
16
|
+
</p>
|
|
17
|
+
<Button
|
|
18
|
+
variant="link"
|
|
19
|
+
:icon="themeStatic.actionRetryIcon"
|
|
20
|
+
:class="theme.actionRetryBtnClass()"
|
|
21
|
+
color="primary"
|
|
22
|
+
@click="$emit('retry')"
|
|
23
|
+
>
|
|
24
|
+
{{ retryLabel }}
|
|
25
|
+
</Button>
|
|
26
|
+
</div>
|
|
27
|
+
<Icon
|
|
28
|
+
:name="themeStatic.actionDeleteIcon"
|
|
29
|
+
:class="theme.actionDeleteIconClass()"
|
|
30
|
+
title="ลบไฟล์"
|
|
31
|
+
@click="$emit('delete')"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<script setup>
|
|
38
|
+
import { isImage } from "#core/helpers/componentHelper";
|
|
39
|
+
import { useUiConfigStatic } from "#imports";
|
|
40
|
+
const props = defineProps({
|
|
41
|
+
theme: { type: null, required: true },
|
|
42
|
+
selectedFile: { type: null, required: true },
|
|
43
|
+
uploadFailedLabel: { type: String, required: true },
|
|
44
|
+
retryLabel: { type: String, required: true }
|
|
45
|
+
});
|
|
46
|
+
defineEmits(["retry", "delete"]);
|
|
47
|
+
const themeStatic = useUiConfigStatic("uploadFileDropzone");
|
|
48
|
+
const getFileIcon = (file) => {
|
|
49
|
+
if (isImage(file)) {
|
|
50
|
+
return themeStatic.placeholderImgIcon;
|
|
51
|
+
}
|
|
52
|
+
return themeStatic.filePreviewIcon;
|
|
53
|
+
};
|
|
54
|
+
</script>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
theme: any;
|
|
3
|
+
selectedFile: File;
|
|
4
|
+
uploadFailedLabel: string;
|
|
5
|
+
retryLabel: string;
|
|
6
|
+
}
|
|
7
|
+
declare const _default: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
8
|
+
delete: () => any;
|
|
9
|
+
retry: () => any;
|
|
10
|
+
}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
|
|
11
|
+
onDelete?: (() => any) | undefined;
|
|
12
|
+
onRetry?: (() => any) | undefined;
|
|
13
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
14
|
+
export default _default;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="theme.onLoadingWrapper()">
|
|
3
|
+
<div :class="theme.onLoadingPlaceholderWrapper()">
|
|
4
|
+
<Icon
|
|
5
|
+
:name="getFileIcon(selectedFile)"
|
|
6
|
+
:class="theme.onLoadingPlaceholderIconClass()"
|
|
7
|
+
/>
|
|
8
|
+
</div>
|
|
9
|
+
<div :class="theme.onLoadingTextWrapper()">
|
|
10
|
+
<div class="truncate">
|
|
11
|
+
<h1 class="truncate font-bold">
|
|
12
|
+
{{ selectedFile.name }}
|
|
13
|
+
</h1>
|
|
14
|
+
<p class="truncate font-light text-gray-400">
|
|
15
|
+
{{ getFileSize(selectedFile) }} - {{ percent }}% {{ uploadingLabel }}
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
<div>
|
|
19
|
+
<Icon
|
|
20
|
+
:name="themeStatic.loadingIcon"
|
|
21
|
+
:class="theme.onLoadingLoadingIconClass()"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup>
|
|
29
|
+
import { isImage } from "#core/helpers/componentHelper";
|
|
30
|
+
import { useUiConfigStatic } from "#imports";
|
|
31
|
+
const props = defineProps({
|
|
32
|
+
theme: { type: null, required: true },
|
|
33
|
+
selectedFile: { type: null, required: true },
|
|
34
|
+
percent: { type: Number, required: true },
|
|
35
|
+
uploadingLabel: { type: String, required: true }
|
|
36
|
+
});
|
|
37
|
+
const themeStatic = useUiConfigStatic("uploadFileDropzone");
|
|
38
|
+
const getFileIcon = (file) => {
|
|
39
|
+
if (isImage(file)) {
|
|
40
|
+
return themeStatic.placeholderImgIcon;
|
|
41
|
+
}
|
|
42
|
+
return themeStatic.filePreviewIcon;
|
|
43
|
+
};
|
|
44
|
+
const getFileSize = (file) => {
|
|
45
|
+
const size = file.size;
|
|
46
|
+
const useMb = size > 1024 * 1024;
|
|
47
|
+
if (useMb) {
|
|
48
|
+
const sizeMb = (size / (1024 * 1024)).toFixed(2);
|
|
49
|
+
return `${sizeMb} MB`;
|
|
50
|
+
}
|
|
51
|
+
const sizeKb = (size / 1024).toFixed(2);
|
|
52
|
+
return `${sizeKb} KB`;
|
|
53
|
+
};
|
|
54
|
+
</script>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
theme: any;
|
|
3
|
+
selectedFile: File;
|
|
4
|
+
percent: number;
|
|
5
|
+
uploadingLabel: string;
|
|
6
|
+
}
|
|
7
|
+
declare const _default: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Modal
|
|
3
|
+
:close="{ onClick: () => emits('close', false) }"
|
|
4
|
+
:dismissible="false"
|
|
5
|
+
:title="value?.name"
|
|
6
|
+
:ui="{
|
|
7
|
+
content: 'max-w-3xl'
|
|
8
|
+
}"
|
|
9
|
+
>
|
|
10
|
+
<template #body>
|
|
11
|
+
<div class="flex justify-center">
|
|
12
|
+
<img
|
|
13
|
+
v-if="value && isImageFromPath(value.path)"
|
|
14
|
+
:src="value.url"
|
|
15
|
+
alt="img-preview"
|
|
16
|
+
class="max-h-96 max-w-full rounded-lg"
|
|
17
|
+
/>
|
|
18
|
+
<video
|
|
19
|
+
v-else-if="value && isVideoFromPath(value.path)"
|
|
20
|
+
:src="value.url"
|
|
21
|
+
controls
|
|
22
|
+
class="max-h-96 max-w-full"
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
</Modal>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup>
|
|
30
|
+
import { isImageFromPath, isVideoFromPath } from "#core/helpers/componentHelper";
|
|
31
|
+
defineProps({
|
|
32
|
+
value: { type: Object, required: false }
|
|
33
|
+
});
|
|
34
|
+
const emits = defineEmits(["close"]);
|
|
35
|
+
</script>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IFileValue } from '#core/components/Form/types';
|
|
2
|
+
interface Props {
|
|
3
|
+
value?: IFileValue;
|
|
4
|
+
}
|
|
5
|
+
declare const _default: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
+
close: (args_0: boolean) => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
|
|
8
|
+
onClose?: ((args_0: boolean) => any) | undefined;
|
|
9
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="theme.onPreviewWrapper()">
|
|
3
|
+
<div :class="theme.onPreviewPreviewImgWrapper()">
|
|
4
|
+
<div
|
|
5
|
+
v-if="isImageFromPath(value.path)"
|
|
6
|
+
class="size-full overflow-hidden"
|
|
7
|
+
>
|
|
8
|
+
<img
|
|
9
|
+
:src="value.url"
|
|
10
|
+
:class="theme.onPreviewPreviewImgClass()"
|
|
11
|
+
alt="img-preview"
|
|
12
|
+
/>
|
|
13
|
+
</div>
|
|
14
|
+
<div v-else>
|
|
15
|
+
<Icon
|
|
16
|
+
:name="themeStatic.filePreviewIcon"
|
|
17
|
+
:class="theme.onPreviewPreviewFileClass()"
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<div :class="theme.onPreviewTextWrapper()">
|
|
22
|
+
<div class="truncate">
|
|
23
|
+
<h1 class="truncate font-bold">
|
|
24
|
+
{{ value.name }}
|
|
25
|
+
</h1>
|
|
26
|
+
<p class="truncate text-sm font-light text-gray-400">
|
|
27
|
+
{{ getFileSizeFromValue(value) }}
|
|
28
|
+
</p>
|
|
29
|
+
</div>
|
|
30
|
+
<div :class="theme.actionWrapper()">
|
|
31
|
+
<a
|
|
32
|
+
v-if="isPDFFromPath(value.path)"
|
|
33
|
+
:href="value.url"
|
|
34
|
+
target="_blank"
|
|
35
|
+
class="flex"
|
|
36
|
+
>
|
|
37
|
+
<Icon
|
|
38
|
+
:name="themeStatic.actionPreviewIcon"
|
|
39
|
+
:class="theme.actionIconClass()"
|
|
40
|
+
title="ดูตัวอย่าง"
|
|
41
|
+
/>
|
|
42
|
+
</a>
|
|
43
|
+
<Icon
|
|
44
|
+
v-if="isImageFromPath(value.path) || isVideoFromPath(value.path)"
|
|
45
|
+
:name="themeStatic.actionPreviewIcon"
|
|
46
|
+
:class="theme.actionIconClass()"
|
|
47
|
+
title="ดูตัวอย่าง"
|
|
48
|
+
@click="$emit('preview')"
|
|
49
|
+
/>
|
|
50
|
+
<Icon
|
|
51
|
+
:name="themeStatic.actionDownloadIcon"
|
|
52
|
+
:class="theme.actionIconClass()"
|
|
53
|
+
title="ดาวน์โหลดไฟล์"
|
|
54
|
+
@click="$emit('download')"
|
|
55
|
+
/>
|
|
56
|
+
<Icon
|
|
57
|
+
v-if="!disabled && !readonly"
|
|
58
|
+
:name="themeStatic.actionDeleteIcon"
|
|
59
|
+
:class="theme.actionIconClass()"
|
|
60
|
+
title="ลบไฟล์"
|
|
61
|
+
@click="$emit('delete')"
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</template>
|
|
67
|
+
|
|
68
|
+
<script setup>
|
|
69
|
+
import {
|
|
70
|
+
isImageFromPath,
|
|
71
|
+
isPDFFromPath,
|
|
72
|
+
isVideoFromPath,
|
|
73
|
+
useFileSize
|
|
74
|
+
} from "#core/helpers/componentHelper";
|
|
75
|
+
import { useUiConfigStatic } from "#imports";
|
|
76
|
+
defineProps({
|
|
77
|
+
theme: { type: null, required: true },
|
|
78
|
+
value: { type: Object, required: true },
|
|
79
|
+
disabled: { type: Boolean, required: false },
|
|
80
|
+
readonly: { type: Boolean, required: false }
|
|
81
|
+
});
|
|
82
|
+
defineEmits(["preview", "download", "delete"]);
|
|
83
|
+
const themeStatic = useUiConfigStatic("uploadFileDropzone");
|
|
84
|
+
const getFileSizeFromValue = (fileValue) => {
|
|
85
|
+
const allocate = useFileSize(fileValue.size || 0);
|
|
86
|
+
return allocate.isSelectedFileUseMb.value ? `${allocate.selectedFileSizeMb.value} MB` : `${allocate.selectedFileSizeKb.value} KB`;
|
|
87
|
+
};
|
|
88
|
+
</script>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IFileValue } from '#core/components/Form/types';
|
|
2
|
+
interface Props {
|
|
3
|
+
theme: any;
|
|
4
|
+
value: IFileValue;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
readonly?: boolean;
|
|
7
|
+
}
|
|
8
|
+
declare const _default: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
delete: () => any;
|
|
10
|
+
preview: () => any;
|
|
11
|
+
download: () => any;
|
|
12
|
+
}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
|
|
13
|
+
onDelete?: (() => any) | undefined;
|
|
14
|
+
onPreview?: (() => any) | undefined;
|
|
15
|
+
onDownload?: (() => any) | undefined;
|
|
16
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
17
|
+
export default _default;
|
|
@@ -1,60 +1,83 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<FieldWrapper v-bind="wrapperProps">
|
|
3
|
-
<div
|
|
4
|
-
ref="
|
|
5
|
-
:class="theme.base()"
|
|
6
|
-
>
|
|
7
|
-
<div :class="theme.wrapper()">
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
2
|
+
<FieldWrapper v-bind="wrapperProps">
|
|
3
|
+
<div
|
|
4
|
+
ref="dropzoneRef"
|
|
5
|
+
:class="theme.base()"
|
|
6
|
+
>
|
|
7
|
+
<div :class="theme.wrapper()">
|
|
8
|
+
<!-- Empty State -->
|
|
9
|
+
<EmptyState
|
|
10
|
+
v-if="uploadState.isEmpty.value"
|
|
11
|
+
:theme="theme"
|
|
12
|
+
:select-file-label="selectFileLabel"
|
|
13
|
+
:select-file-sub-label="selectFileSubLabel"
|
|
14
|
+
:placeholder="placeholder"
|
|
15
|
+
@open-file="uploadState.handleOpenFile"
|
|
16
|
+
/>
|
|
17
|
+
|
|
18
|
+
<!-- Loading State -->
|
|
19
|
+
<LoadingState
|
|
20
|
+
v-if="uploadState.isUploading.value"
|
|
21
|
+
:theme="theme"
|
|
22
|
+
:selected-file="uploadState.selectedFile.value"
|
|
23
|
+
:percent="uploadState.percent.value"
|
|
24
|
+
:uploading-label="uploadingLabel"
|
|
25
|
+
/>
|
|
26
|
+
|
|
27
|
+
<!-- Success State -->
|
|
28
|
+
<SuccessState
|
|
29
|
+
v-if="uploadState.isSuccess.value"
|
|
30
|
+
:theme="theme"
|
|
31
|
+
:value="value"
|
|
32
|
+
:disabled="wrapperProps.disabled"
|
|
33
|
+
:readonly="wrapperProps.readonly"
|
|
34
|
+
@preview="uploadState.handlePreview"
|
|
35
|
+
@download="handleDownloadFile"
|
|
36
|
+
@delete="uploadState.handleDeleteFile"
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<!-- Failed State -->
|
|
40
|
+
<FailedState
|
|
41
|
+
v-if="uploadState.isError.value"
|
|
42
|
+
:theme="theme"
|
|
43
|
+
:selected-file="uploadState.selectedFile.value"
|
|
44
|
+
:upload-failed-label="uploadFailedLabel"
|
|
45
|
+
:retry-label="retryLabel"
|
|
46
|
+
@retry="uploadState.handleRetryUpload"
|
|
47
|
+
@delete="uploadState.handleDeleteFile"
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</FieldWrapper>
|
|
35
52
|
</template>
|
|
36
53
|
|
|
37
54
|
<script setup>
|
|
38
|
-
import
|
|
39
|
-
import
|
|
55
|
+
import EmptyState from "./EmptyState.vue";
|
|
56
|
+
import LoadingState from "./LoadingState.vue";
|
|
57
|
+
import SuccessState from "./SuccessState.vue";
|
|
58
|
+
import FailedState from "./FailedState.vue";
|
|
59
|
+
import { useUploadState } from "./useUploadState";
|
|
60
|
+
import { computed, useTemplateRef } from "#imports";
|
|
40
61
|
import FieldWrapper from "#core/components/Form/FieldWrapper.vue";
|
|
41
62
|
import { useFieldHOC } from "#core/composables/useForm";
|
|
42
63
|
import { uploadFileDropzoneTheme } from "#core/theme/uploadFileDropzone";
|
|
43
|
-
import { useUiConfig
|
|
44
|
-
import {
|
|
64
|
+
import { useUiConfig } from "#core/composables/useConfig";
|
|
65
|
+
import { downloadFileFromURL } from "#core/helpers/componentHelper";
|
|
45
66
|
const props = defineProps({
|
|
46
67
|
requestOptions: { type: Object, required: true },
|
|
47
68
|
uploadPathURL: { type: String, required: false },
|
|
48
|
-
selectFileLabel: { type: String, required: false, default: "\u0E04\u0E25\u0E34\u0E01\u0E40\u0E1E\u0E37\u0E48\u0E2D\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E44\u0E1F\u0E25\u0E4C" },
|
|
49
|
-
selectFileSubLabel: { type: String, required: false, default: "\u0E2B\u0E23\u0E37\u0E2D \u0E25\u0E32\u0E01\u0E41\u0E25\u0E30\u0E27\u0E32\u0E07\u0E17\u0E35\u0E48\u0E19\u0E35\u0E48" },
|
|
50
|
-
uploadingLabel: { type: String, required: false, default: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E2D\u0E31\u0E1E\u0E42\u0E2B\u0E25\u0E14..." },
|
|
51
|
-
uploadFailedLabel: { type: String, required: false, default: "\u0E2D\u0E31\u0E1E\u0E42\u0E2B\u0E25\u0E14\u0E25\u0E49\u0E21\u0E40\u0E2B\u0E25\u0E27, \u0E01\u0E23\u0E38\u0E13\u0E32\u0E25\u0E2D\u0E07\u0E2D\u0E35\u0E01\u0E04\u0E23\u0E31\u0E49\u0E07" },
|
|
52
|
-
retryLabel: { type: String, required: false },
|
|
53
|
-
accept: { type: [Array, String], required: false },
|
|
54
69
|
bodyKey: { type: String, required: false, default: "file" },
|
|
55
70
|
responseURL: { type: String, required: false, default: "url" },
|
|
56
71
|
responsePath: { type: String, required: false, default: "path" },
|
|
72
|
+
responseName: { type: String, required: false, default: "name" },
|
|
73
|
+
responseSize: { type: String, required: false, default: "size" },
|
|
74
|
+
accept: { type: [Array, String], required: false },
|
|
57
75
|
maxSize: { type: Number, required: false },
|
|
76
|
+
selectFileLabel: { type: String, required: false, default: "\u0E04\u0E25\u0E34\u0E01\u0E40\u0E1E\u0E37\u0E48\u0E2D\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E44\u0E1F\u0E25\u0E4C" },
|
|
77
|
+
selectFileSubLabel: { type: String, required: false, default: "\u0E2B\u0E23\u0E37\u0E2D \u0E25\u0E32\u0E01\u0E41\u0E25\u0E30\u0E27\u0E32\u0E07\u0E17\u0E35\u0E48\u0E19\u0E35\u0E48" },
|
|
78
|
+
uploadingLabel: { type: String, required: false, default: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E2D\u0E31\u0E1E\u0E42\u0E2B\u0E25\u0E14..." },
|
|
79
|
+
uploadFailedLabel: { type: String, required: false, default: "\u0E2D\u0E31\u0E1E\u0E42\u0E2B\u0E25\u0E14\u0E25\u0E49\u0E21\u0E40\u0E2B\u0E25\u0E27, \u0E01\u0E23\u0E38\u0E13\u0E32\u0E25\u0E2D\u0E07\u0E2D\u0E35\u0E01\u0E04\u0E23\u0E31\u0E49\u0E07" },
|
|
80
|
+
retryLabel: { type: String, required: false, default: "\u0E25\u0E2D\u0E07\u0E2D\u0E35\u0E01\u0E04\u0E23\u0E31\u0E49\u0E07" },
|
|
58
81
|
form: { type: Object, required: false },
|
|
59
82
|
name: { type: String, required: true },
|
|
60
83
|
errorMessage: { type: String, required: false },
|
|
@@ -77,38 +100,30 @@ const {
|
|
|
77
100
|
setErrors,
|
|
78
101
|
value
|
|
79
102
|
} = useFieldHOC(props);
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
const acceptedFileTypes = computed(
|
|
104
|
+
() => typeof props.accept === "string" ? props.accept : props.accept?.join(",")
|
|
105
|
+
);
|
|
106
|
+
const dropzoneRef = useTemplateRef("dropzoneRef");
|
|
107
|
+
const uploadState = useUploadState(
|
|
108
|
+
props,
|
|
109
|
+
emits,
|
|
110
|
+
onChange,
|
|
111
|
+
setErrors,
|
|
112
|
+
value,
|
|
113
|
+
acceptedFileTypes,
|
|
114
|
+
wrapperProps,
|
|
115
|
+
dropzoneRef
|
|
116
|
+
);
|
|
117
|
+
const theme = computed(
|
|
118
|
+
() => useUiConfig(uploadFileDropzoneTheme, "uploadFileDropzone")({
|
|
119
|
+
dragover: uploadState.dropzone.isOverDropZone.value && uploadState.isEmpty.value,
|
|
120
|
+
disabled: wrapperProps.value.disabled,
|
|
121
|
+
failed: uploadState.upload.status.value.isError
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
const handleDownloadFile = () => {
|
|
125
|
+
if (value.value?.url && value.value?.name) {
|
|
126
|
+
downloadFileFromURL(value.value.url, value.value.name);
|
|
127
|
+
}
|
|
104
128
|
};
|
|
105
|
-
const dropzone = useDropZone(dropZoneRef, {
|
|
106
|
-
onDrop,
|
|
107
|
-
// specify the types of data to be received.
|
|
108
|
-
dataTypes: typeof props.accept === "string" ? [props.accept] : props.accept,
|
|
109
|
-
// control multi-file drop
|
|
110
|
-
multiple: false,
|
|
111
|
-
// whether to prevent default behavior for unhandled events
|
|
112
|
-
preventDefaultForUnhandled: false
|
|
113
|
-
});
|
|
114
129
|
</script>
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import type { IUploadDropzoneAutoProps } from './types.js';
|
|
2
|
+
import type { IFileValue } from '#core/components/Form/types';
|
|
2
3
|
declare const _default: import("vue").DefineComponent<IUploadDropzoneAutoProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
3
|
-
success: (
|
|
4
|
-
delete: (
|
|
5
|
-
change: (
|
|
4
|
+
success: (res: IFileValue) => any;
|
|
5
|
+
delete: () => any;
|
|
6
|
+
change: (value: File | undefined) => any;
|
|
6
7
|
}, string, import("vue").PublicProps, Readonly<IUploadDropzoneAutoProps> & Readonly<{
|
|
7
|
-
onSuccess?: ((
|
|
8
|
-
onDelete?: ((
|
|
9
|
-
onChange?: ((
|
|
8
|
+
onSuccess?: ((res: IFileValue) => any) | undefined;
|
|
9
|
+
onDelete?: (() => any) | undefined;
|
|
10
|
+
onChange?: ((value: File | undefined) => any) | undefined;
|
|
10
11
|
}>, {
|
|
11
12
|
selectFileLabel: string;
|
|
12
13
|
selectFileSubLabel: string;
|
|
13
14
|
uploadingLabel: string;
|
|
14
15
|
uploadFailedLabel: string;
|
|
16
|
+
retryLabel: string;
|
|
15
17
|
bodyKey: string;
|
|
16
18
|
responseURL: string;
|
|
17
19
|
responsePath: string;
|
|
20
|
+
responseName: string;
|
|
21
|
+
responseSize: string;
|
|
18
22
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
19
23
|
export default _default;
|
|
@@ -5,16 +5,18 @@ export interface IUploadDropzoneAutoProps extends IFieldProps {
|
|
|
5
5
|
baseURL: string;
|
|
6
6
|
};
|
|
7
7
|
uploadPathURL?: string;
|
|
8
|
+
bodyKey?: string;
|
|
9
|
+
responseURL?: string;
|
|
10
|
+
responsePath?: string;
|
|
11
|
+
responseName?: string;
|
|
12
|
+
responseSize?: string;
|
|
13
|
+
accept?: string[] | string;
|
|
14
|
+
maxSize?: number;
|
|
8
15
|
selectFileLabel?: string;
|
|
9
16
|
selectFileSubLabel?: string;
|
|
10
17
|
uploadingLabel?: string;
|
|
11
18
|
uploadFailedLabel?: string;
|
|
12
19
|
retryLabel?: string;
|
|
13
|
-
accept?: string[] | string;
|
|
14
|
-
bodyKey?: string;
|
|
15
|
-
responseURL?: string;
|
|
16
|
-
responsePath?: string;
|
|
17
|
-
maxSize?: number;
|
|
18
20
|
}
|
|
19
21
|
export type IUploadDropzoneAutoField = IFormFieldBase<INPUT_TYPES.UPLOAD_DROPZONE_AUTO, IUploadDropzoneAutoProps, {
|
|
20
22
|
change: (value: File | undefined) => void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TemplateRef } from 'vue';
|
|
2
|
+
import type { IUploadDropzoneAutoProps } from './types.js';
|
|
3
|
+
import type { IFileValue } from '#core/components/Form/types';
|
|
4
|
+
export declare enum UploadState {
|
|
5
|
+
EMPTY = "empty",
|
|
6
|
+
UPLOADING = "uploading",
|
|
7
|
+
SUCCESS = "success",
|
|
8
|
+
ERROR = "error"
|
|
9
|
+
}
|
|
10
|
+
export declare const useUploadState: (props: IUploadDropzoneAutoProps, emits: any, onChange: (value: IFileValue | undefined) => void, setErrors: (error: string) => void, value: any, acceptedFileTypes: any, wrapperProps: any, dropzoneRef: TemplateRef<HTMLDivElement | undefined>) => {
|
|
11
|
+
currentState: import("vue").ComputedRef<UploadState>;
|
|
12
|
+
isEmpty: import("vue").ComputedRef<boolean>;
|
|
13
|
+
isUploading: import("vue").ComputedRef<boolean>;
|
|
14
|
+
isSuccess: import("vue").ComputedRef<boolean>;
|
|
15
|
+
isError: import("vue").ComputedRef<boolean>;
|
|
16
|
+
selectedFile: import("vue").Ref<File | undefined, File | undefined>;
|
|
17
|
+
upload: import("../../../helpers/apiObjectHelper.js").IUseObjectLoader<any, any, Record<string, any>>;
|
|
18
|
+
dropzone: import("@vueuse/core").UseDropZoneReturn;
|
|
19
|
+
percent: import("vue").Ref<number, number>;
|
|
20
|
+
handleInputChange: (event: Event) => void;
|
|
21
|
+
handleOpenFile: () => void;
|
|
22
|
+
handleDeleteFile: () => void;
|
|
23
|
+
handleRetryUpload: () => void;
|
|
24
|
+
handlePreview: () => void;
|
|
25
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { useDropZone, useFileDialog } from "@vueuse/core";
|
|
2
|
+
import PreviewModal from "./PreviewModal.vue";
|
|
3
|
+
import { computed, ref, useOverlay, watch } from "#imports";
|
|
4
|
+
import { useUploadLoader } from "#core/composables/useUpload";
|
|
5
|
+
import { useFileAllocate, useFileProgress } from "#core/helpers/componentHelper";
|
|
6
|
+
import { StringHelper } from "#core/utils/StringHelper";
|
|
7
|
+
import { _get } from "#core/utils/lodash";
|
|
8
|
+
const overlay = useOverlay();
|
|
9
|
+
const previewModal = overlay.create(PreviewModal);
|
|
10
|
+
export var UploadState = /* @__PURE__ */ ((UploadState2) => {
|
|
11
|
+
UploadState2["EMPTY"] = "empty";
|
|
12
|
+
UploadState2["UPLOADING"] = "uploading";
|
|
13
|
+
UploadState2["SUCCESS"] = "success";
|
|
14
|
+
UploadState2["ERROR"] = "error";
|
|
15
|
+
return UploadState2;
|
|
16
|
+
})(UploadState || {});
|
|
17
|
+
export const useUploadState = (props, emits, onChange, setErrors, value, acceptedFileTypes, wrapperProps, dropzoneRef) => {
|
|
18
|
+
const selectedFile = ref();
|
|
19
|
+
const fileAllocate = useFileAllocate(selectedFile, props);
|
|
20
|
+
const {
|
|
21
|
+
percent,
|
|
22
|
+
onDownloadProgress,
|
|
23
|
+
onUploadProgress
|
|
24
|
+
} = useFileProgress();
|
|
25
|
+
const request = {
|
|
26
|
+
requestOptions: {
|
|
27
|
+
...props.requestOptions,
|
|
28
|
+
onDownloadProgress,
|
|
29
|
+
onUploadProgress
|
|
30
|
+
},
|
|
31
|
+
pathURL: props.uploadPathURL
|
|
32
|
+
};
|
|
33
|
+
const upload = useUploadLoader(request);
|
|
34
|
+
const validateFile = (file) => {
|
|
35
|
+
const acceptedTypes = fileAllocate.acceptFile.value;
|
|
36
|
+
if (acceptedTypes) {
|
|
37
|
+
const acceptedTypesList = acceptedTypes.split(",").map((type) => type.trim());
|
|
38
|
+
const fileExtension = file.name.toLowerCase().split(".").pop();
|
|
39
|
+
const isValidFileType = acceptedTypesList.some((acceptedType) => {
|
|
40
|
+
if (acceptedType.startsWith(".")) {
|
|
41
|
+
const extension = acceptedType.slice(1).toLowerCase();
|
|
42
|
+
return fileExtension === extension;
|
|
43
|
+
} else if (!acceptedType.includes("/") && !acceptedType.includes("*")) {
|
|
44
|
+
return fileExtension === acceptedType.toLowerCase();
|
|
45
|
+
}
|
|
46
|
+
if (acceptedType.endsWith("/*")) {
|
|
47
|
+
const baseType = acceptedType.slice(0, -2);
|
|
48
|
+
return file.type.startsWith(baseType + "/");
|
|
49
|
+
}
|
|
50
|
+
return file.type === acceptedType;
|
|
51
|
+
});
|
|
52
|
+
if (!isValidFileType) {
|
|
53
|
+
setErrors("\u0E1B\u0E23\u0E30\u0E40\u0E20\u0E17\u0E44\u0E1F\u0E25\u0E4C\u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 (\u0E23\u0E2D\u0E07\u0E23\u0E31\u0E1A\u0E40\u0E09\u0E1E\u0E32\u0E30 " + acceptedTypesList.join(", ") + ")");
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const maxSizeBytes = (fileAllocate.acceptFileSizeKb.value || 0) * 1024;
|
|
58
|
+
if (file.size > maxSizeBytes) {
|
|
59
|
+
if (fileAllocate.isAcceptFileUseMb.value) {
|
|
60
|
+
setErrors(`\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${fileAllocate.acceptFileSizeMb.value} MB`);
|
|
61
|
+
} else {
|
|
62
|
+
setErrors(`\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${fileAllocate.acceptFileSizeKb.value} KB`);
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
setErrors("");
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
const processFile = (file) => {
|
|
70
|
+
if (!validateFile(file)) return;
|
|
71
|
+
selectedFile.value = file;
|
|
72
|
+
emits("change", file);
|
|
73
|
+
const formData = new FormData();
|
|
74
|
+
formData.append(props.bodyKey || "file", file);
|
|
75
|
+
upload.run({
|
|
76
|
+
data: formData
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
const handleFileDrop = (files) => {
|
|
80
|
+
if (wrapperProps.value.disabled || wrapperProps.value.readonly || !files?.length || !isEmpty.value) return;
|
|
81
|
+
const file = files[0];
|
|
82
|
+
if (file) {
|
|
83
|
+
processFile(file);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const fileDialog = useFileDialog({
|
|
87
|
+
accept: acceptedFileTypes.value || "",
|
|
88
|
+
directory: false,
|
|
89
|
+
multiple: false
|
|
90
|
+
});
|
|
91
|
+
const dropzone = useDropZone(dropzoneRef, {
|
|
92
|
+
onDrop: handleFileDrop,
|
|
93
|
+
// dataTypes: typeof props.accept === 'string' ? [props.accept] : props.accept,
|
|
94
|
+
multiple: false,
|
|
95
|
+
preventDefaultForUnhandled: false
|
|
96
|
+
});
|
|
97
|
+
const currentState = computed(() => {
|
|
98
|
+
if (value.value) return "success" /* SUCCESS */;
|
|
99
|
+
if (selectedFile.value && upload.status.value.isLoading) return "uploading" /* UPLOADING */;
|
|
100
|
+
if (selectedFile.value && upload.status.value.isError) return "error" /* ERROR */;
|
|
101
|
+
return "empty" /* EMPTY */;
|
|
102
|
+
});
|
|
103
|
+
const isEmpty = computed(() => currentState.value === "empty" /* EMPTY */);
|
|
104
|
+
const isUploading = computed(() => currentState.value === "uploading" /* UPLOADING */);
|
|
105
|
+
const isSuccess = computed(() => currentState.value === "success" /* SUCCESS */);
|
|
106
|
+
const isError = computed(() => currentState.value === "error" /* ERROR */);
|
|
107
|
+
const handleInputChange = (event) => {
|
|
108
|
+
if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
|
|
109
|
+
const file = event.target.files?.[0];
|
|
110
|
+
if (file) {
|
|
111
|
+
processFile(file);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const handleOpenFile = () => {
|
|
115
|
+
if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
|
|
116
|
+
fileDialog.open();
|
|
117
|
+
};
|
|
118
|
+
const handleDeleteFile = () => {
|
|
119
|
+
fileDialog.reset();
|
|
120
|
+
upload.clear();
|
|
121
|
+
selectedFile.value = void 0;
|
|
122
|
+
onChange(void 0);
|
|
123
|
+
emits("delete");
|
|
124
|
+
};
|
|
125
|
+
const handleRetryUpload = () => {
|
|
126
|
+
if (selectedFile.value) {
|
|
127
|
+
const formData = new FormData();
|
|
128
|
+
formData.append(props.bodyKey || "file", selectedFile.value);
|
|
129
|
+
upload.run(formData);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const handlePreview = () => {
|
|
133
|
+
previewModal.open({
|
|
134
|
+
value: value.value
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
watch(
|
|
138
|
+
() => fileDialog.files.value,
|
|
139
|
+
(files) => {
|
|
140
|
+
if (files?.length) {
|
|
141
|
+
processFile(files[0]);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
watch(
|
|
146
|
+
() => upload.status.value.isSuccess,
|
|
147
|
+
(isSuccess2) => {
|
|
148
|
+
if (isSuccess2 && upload.data.value) {
|
|
149
|
+
const fileValue = {
|
|
150
|
+
url: _get(upload.data.value, props.responseURL),
|
|
151
|
+
path: _get(upload.data.value, props.responsePath),
|
|
152
|
+
name: _get(upload.data.value, props.responseName) || selectedFile.value?.name,
|
|
153
|
+
size: Number(_get(upload.data.value, props.responseSize) || selectedFile.value?.size)
|
|
154
|
+
};
|
|
155
|
+
value.value = fileValue;
|
|
156
|
+
emits("success", fileValue);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
watch(
|
|
161
|
+
() => upload.status.value.isError,
|
|
162
|
+
(isError2) => {
|
|
163
|
+
if (isError2) {
|
|
164
|
+
setErrors(StringHelper.getError(upload.status.value.errorData));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
return {
|
|
169
|
+
// State
|
|
170
|
+
currentState,
|
|
171
|
+
isEmpty,
|
|
172
|
+
isUploading,
|
|
173
|
+
isSuccess,
|
|
174
|
+
isError,
|
|
175
|
+
selectedFile,
|
|
176
|
+
// Upload utilities
|
|
177
|
+
upload,
|
|
178
|
+
dropzone,
|
|
179
|
+
percent,
|
|
180
|
+
// Handlers
|
|
181
|
+
handleInputChange,
|
|
182
|
+
handleOpenFile,
|
|
183
|
+
handleDeleteFile,
|
|
184
|
+
handleRetryUpload,
|
|
185
|
+
handlePreview
|
|
186
|
+
};
|
|
187
|
+
};
|
|
@@ -3,14 +3,12 @@ export const useUploadLoader = (request) => {
|
|
|
3
3
|
return useObjectLoader({
|
|
4
4
|
method: "post",
|
|
5
5
|
url: request.pathURL || "",
|
|
6
|
-
getRequestOptions: (
|
|
6
|
+
getRequestOptions: () => ({
|
|
7
7
|
...request.requestOptions,
|
|
8
8
|
headers: {
|
|
9
9
|
...request.requestOptions.headers,
|
|
10
10
|
"Content-Type": "multipart/form-data"
|
|
11
|
-
}
|
|
12
|
-
onDownloadProgress: opts.data?.onDownloadProgress,
|
|
13
|
-
onUploadProgress: opts.data?.onUploadProgress
|
|
11
|
+
}
|
|
14
12
|
})
|
|
15
13
|
});
|
|
16
14
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Ref } from 'vue';
|
|
2
|
+
import type { AxiosProgressEvent } from 'axios';
|
|
2
3
|
export declare const checkMaxSize: (file: File, acceptFileSize?: number) => boolean;
|
|
3
4
|
export declare const checkFileType: (file: File, acceptFileType: string | string[]) => boolean;
|
|
4
5
|
export declare const generateURL: (file: File) => string;
|
|
@@ -25,7 +26,7 @@ export declare const useFileSize: (size?: number) => {
|
|
|
25
26
|
};
|
|
26
27
|
export declare const useFileProgress: () => {
|
|
27
28
|
percent: Ref<number, number>;
|
|
28
|
-
onUploadProgress: (progressEvent:
|
|
29
|
-
onDownloadProgress: (progressEvent:
|
|
29
|
+
onUploadProgress: (progressEvent: AxiosProgressEvent) => void;
|
|
30
|
+
onDownloadProgress: (progressEvent: AxiosProgressEvent) => void;
|
|
30
31
|
};
|
|
31
32
|
export declare const downloadFileFromURL: (url: string, filename?: string) => Promise<void>;
|
|
@@ -86,6 +86,10 @@ export const useFileSize = (size = 0) => {
|
|
|
86
86
|
export const useFileProgress = () => {
|
|
87
87
|
const percent = ref(0);
|
|
88
88
|
const onUploadProgress = (progressEvent) => {
|
|
89
|
+
if (!progressEvent.total || progressEvent.total === 0) {
|
|
90
|
+
percent.value = 100;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
89
93
|
percent.value = Number.parseFloat(
|
|
90
94
|
StringHelper.withFixed(
|
|
91
95
|
(Math.floor(progressEvent.loaded * 100 / progressEvent.total) || 0) * 0.8
|
|
@@ -93,13 +97,13 @@ export const useFileProgress = () => {
|
|
|
93
97
|
);
|
|
94
98
|
};
|
|
95
99
|
const onDownloadProgress = (progressEvent) => {
|
|
96
|
-
if (progressEvent.total === 0) {
|
|
100
|
+
if (!progressEvent.total || progressEvent.total === 0) {
|
|
97
101
|
percent.value = 100;
|
|
98
102
|
return;
|
|
99
103
|
}
|
|
100
104
|
percent.value = Number.parseFloat(
|
|
101
105
|
StringHelper.withFixed(
|
|
102
|
-
|
|
106
|
+
Math.floor(progressEvent.loaded * 100 / progressEvent.total) * 0.2 + 80
|
|
103
107
|
)
|
|
104
108
|
);
|
|
105
109
|
};
|
|
@@ -13,6 +13,28 @@ export declare const uploadFileDropzoneTheme: {
|
|
|
13
13
|
failedImgIcon: string;
|
|
14
14
|
loadingIcon: string;
|
|
15
15
|
labelIcon: string;
|
|
16
|
+
onLoadingWrapper: string;
|
|
17
|
+
onLoadingPlaceholderWrapper: string;
|
|
18
|
+
onLoadingPlaceholderIconClass: string;
|
|
19
|
+
onLoadingTextWrapper: string;
|
|
20
|
+
onLoadingLoadingIconClass: string;
|
|
21
|
+
onPreviewWrapper: string;
|
|
22
|
+
onPreviewPreviewImgWrapper: string;
|
|
23
|
+
onPreviewPreviewImgClass: string;
|
|
24
|
+
onPreviewPreviewFileClass: string;
|
|
25
|
+
onPreviewTextWrapper: string;
|
|
26
|
+
onFailedWrapper: string;
|
|
27
|
+
onFailedFailedImgWrapper: string;
|
|
28
|
+
onFailedFailedIconClass: string;
|
|
29
|
+
onFailedTextWrapper: string;
|
|
30
|
+
actionWrapper: string;
|
|
31
|
+
actionIconClass: string;
|
|
32
|
+
actionDeleteIconClass: string;
|
|
33
|
+
actionPreviewIcon: string;
|
|
34
|
+
actionDownloadIcon: string;
|
|
35
|
+
actionDeleteIcon: string;
|
|
36
|
+
actionRetryIcon: string;
|
|
37
|
+
actionRetryBtnClass: string;
|
|
16
38
|
};
|
|
17
39
|
variants: {
|
|
18
40
|
dragover: {
|
|
@@ -20,5 +42,15 @@ export declare const uploadFileDropzoneTheme: {
|
|
|
20
42
|
base: string;
|
|
21
43
|
};
|
|
22
44
|
};
|
|
45
|
+
disabled: {
|
|
46
|
+
true: {
|
|
47
|
+
base: string;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
failed: {
|
|
51
|
+
true: {
|
|
52
|
+
base: string;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
23
55
|
};
|
|
24
56
|
};
|
|
@@ -1,23 +1,59 @@
|
|
|
1
1
|
export const uploadFileDropzoneTheme = {
|
|
2
2
|
slots: {
|
|
3
|
-
base: "relative w-full p-4 transition rounded-lg flex items-center justify-center ring-1 bg-white ring-gray-300",
|
|
3
|
+
base: "relative w-full text-base p-4 transition rounded-lg flex items-center justify-center ring-1 bg-white ring-gray-300",
|
|
4
4
|
wrapper: "flex flex-col items-center w-full",
|
|
5
5
|
disabled: "bg-gray-100 border-none grayscale cursor-not-allowed",
|
|
6
|
-
failed: "border-
|
|
6
|
+
failed: "border-error",
|
|
7
7
|
placeholderWrapper: "py-4 flex flex-col items-center justify-center",
|
|
8
8
|
placeholder: "text-gray-400 text-center font-light text-sm truncate",
|
|
9
9
|
labelWrapper: "flex items-center space-x-2 text-gray-400 text-center",
|
|
10
10
|
filePreviewIcon: "i-heroicons:document-text-solid",
|
|
11
|
-
uploadIcon: "
|
|
11
|
+
uploadIcon: "ri:upload-cloud-2-line",
|
|
12
12
|
placeholderImgIcon: "i-material-symbols:imagesmode-outline",
|
|
13
13
|
failedImgIcon: "i-material-symbols:imagesmode-outline",
|
|
14
14
|
loadingIcon: "i-svg-spinners:180-ring-with-bg",
|
|
15
|
-
labelIcon: "
|
|
15
|
+
labelIcon: "size-6 text-gray-400 text-center mb-3",
|
|
16
|
+
// Loading state
|
|
17
|
+
onLoadingWrapper: "flex items-center space-x-4 w-full",
|
|
18
|
+
onLoadingPlaceholderWrapper: "flex-shrink-0",
|
|
19
|
+
onLoadingPlaceholderIconClass: "size-12 text-gray-400",
|
|
20
|
+
onLoadingTextWrapper: "flex-1 min-w-0 flex items-center justify-between",
|
|
21
|
+
onLoadingLoadingIconClass: "size-10 text-primary animate-spin",
|
|
22
|
+
// Preview state
|
|
23
|
+
onPreviewWrapper: "flex items-center space-x-4 rounded-lg w-full",
|
|
24
|
+
onPreviewPreviewImgWrapper: "flex-shrink-0 w-16 h-16 rounded-lg overflow-hidden bg-gray-100",
|
|
25
|
+
onPreviewPreviewImgClass: "w-full h-full object-cover",
|
|
26
|
+
onPreviewPreviewFileClass: "size-8 text-gray-400 m-auto mt-4",
|
|
27
|
+
onPreviewTextWrapper: "flex-1 min-w-0 flex items-center justify-between",
|
|
28
|
+
// Failed state
|
|
29
|
+
onFailedWrapper: "flex items-start space-x-4 w-full rounded-lg",
|
|
30
|
+
onFailedFailedImgWrapper: "flex-shrink-0",
|
|
31
|
+
onFailedFailedIconClass: "size-12",
|
|
32
|
+
onFailedTextWrapper: "flex-1 min-w-0 flex items-start justify-between",
|
|
33
|
+
// Actions
|
|
34
|
+
actionWrapper: "flex items-center space-x-2",
|
|
35
|
+
actionIconClass: "size-6 text-dimmed hover:text-dimmed-600 cursor-pointer transition-colors",
|
|
36
|
+
actionDeleteIconClass: "size-6 text-(--ui-color-error-500) hover:text-(--ui-color-error-600) cursor-pointer transition-colors",
|
|
37
|
+
actionPreviewIcon: "ic:outline-remove-red-eye",
|
|
38
|
+
actionDownloadIcon: "material-symbols:download",
|
|
39
|
+
actionDeleteIcon: "material-symbols:delete",
|
|
40
|
+
actionRetryIcon: "stash:arrow-retry",
|
|
41
|
+
actionRetryBtnClass: "px-0"
|
|
16
42
|
},
|
|
17
43
|
variants: {
|
|
18
44
|
dragover: {
|
|
19
45
|
true: {
|
|
20
|
-
base: "ring-primary bg-primary-50"
|
|
46
|
+
base: "ring-primary bg-(--ui-color-primary-50)"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
disabled: {
|
|
50
|
+
true: {
|
|
51
|
+
base: "bg-gray-100 border-none grayscale cursor-not-allowed"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
failed: {
|
|
55
|
+
true: {
|
|
56
|
+
base: "ring-(--ui-color-error-500) bg-(--ui-color-error-50)"
|
|
21
57
|
}
|
|
22
58
|
}
|
|
23
59
|
}
|