@finema/core 3.22.0 → 3.24.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.
Files changed (130) hide show
  1. package/README.md +22 -2
  2. package/dist/module.d.mts +2 -0
  3. package/dist/module.json +2 -2
  4. package/dist/module.mjs +18 -15
  5. package/dist/runtime/components/App.vue +1 -1
  6. package/dist/runtime/components/DevToolsWindow/index.vue +24 -26
  7. package/dist/runtime/components/Dialog/index.d.vue.ts +1 -4
  8. package/dist/runtime/components/Dialog/index.vue +17 -17
  9. package/dist/runtime/components/Dialog/index.vue.d.ts +1 -4
  10. package/dist/runtime/components/Empty.d.vue.ts +1 -2
  11. package/dist/runtime/components/Empty.vue +19 -11
  12. package/dist/runtime/components/Empty.vue.d.ts +1 -2
  13. package/dist/runtime/components/FlexDeck/index.vue +21 -23
  14. package/dist/runtime/components/Form/FieldWrapper.vue +1 -1
  15. package/dist/runtime/components/Form/Fields.d.vue.ts +1 -1
  16. package/dist/runtime/components/Form/Fields.vue +26 -20
  17. package/dist/runtime/components/Form/Fields.vue.d.ts +1 -1
  18. package/dist/runtime/components/Form/InputCheckbox/index.vue +3 -6
  19. package/dist/runtime/components/Form/InputCheckboxGroup/index.vue +3 -6
  20. package/dist/runtime/components/Form/InputComponent/index.vue +2 -1
  21. package/dist/runtime/components/Form/InputCurrency/index.vue +11 -11
  22. package/dist/runtime/components/Form/InputDateTime/date_time_field.types.d.ts +3 -1
  23. package/dist/runtime/components/Form/InputDateTime/index.vue +12 -17
  24. package/dist/runtime/components/Form/InputDateTimeRange/date_range_time_field.types.d.ts +1 -1
  25. package/dist/runtime/components/Form/InputDateTimeRange/index.vue +28 -20
  26. package/dist/runtime/components/Form/InputMonth/index.vue +14 -12
  27. package/dist/runtime/components/Form/InputMonth/types.d.ts +1 -1
  28. package/dist/runtime/components/Form/InputNumber/index.vue +3 -6
  29. package/dist/runtime/components/Form/InputRadio/index.vue +3 -6
  30. package/dist/runtime/components/Form/InputRating/index.vue +3 -6
  31. package/dist/runtime/components/Form/InputSearch/index.vue +6 -7
  32. package/dist/runtime/components/Form/InputSelect/index.vue +6 -15
  33. package/dist/runtime/components/Form/InputSelectMultiple/index.vue +7 -18
  34. package/dist/runtime/components/Form/InputTags/index.vue +3 -6
  35. package/dist/runtime/components/Form/InputText/index.vue +41 -51
  36. package/dist/runtime/components/Form/InputTextarea/index.vue +3 -6
  37. package/dist/runtime/components/Form/InputTime/index.vue +32 -27
  38. package/dist/runtime/components/Form/InputTime/types.d.ts +1 -1
  39. package/dist/runtime/components/Form/InputToggle/index.vue +3 -5
  40. package/dist/runtime/components/Form/InputUploadDropzone/index.d.vue.ts +1 -4
  41. package/dist/runtime/components/Form/InputUploadDropzone/index.vue +25 -23
  42. package/dist/runtime/components/Form/InputUploadDropzone/index.vue.d.ts +1 -4
  43. package/dist/runtime/components/Form/InputUploadDropzone/types.d.ts +2 -2
  44. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.d.vue.ts +0 -5
  45. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +22 -23
  46. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue.d.ts +0 -5
  47. package/dist/runtime/components/Form/InputUploadDropzoneAuto/types.d.ts +3 -3
  48. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.d.vue.ts +0 -5
  49. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue +26 -24
  50. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue.d.ts +0 -5
  51. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/types.d.ts +3 -3
  52. package/dist/runtime/components/Form/InputUploadImageAuto/index.d.vue.ts +1 -6
  53. package/dist/runtime/components/Form/InputUploadImageAuto/index.vue +22 -23
  54. package/dist/runtime/components/Form/InputUploadImageAuto/index.vue.d.ts +1 -6
  55. package/dist/runtime/components/Form/InputUploadImageAuto/types.d.ts +3 -3
  56. package/dist/runtime/components/Form/InputWYSIWYG/EditorImagePasteExtension.js +7 -6
  57. package/dist/runtime/components/Form/InputWYSIWYG/EditorImageUploadExtension.js +13 -12
  58. package/dist/runtime/components/Form/InputWYSIWYG/EditorImageUploadNode.vue +3 -1
  59. package/dist/runtime/components/Form/InputWYSIWYG/EditorLinkPopover.vue +20 -22
  60. package/dist/runtime/components/Form/InputWYSIWYG/index.vue +140 -118
  61. package/dist/runtime/components/Form/fileState/EmptyState.vue +1 -1
  62. package/dist/runtime/components/Form/fileState/FailedState.vue +4 -2
  63. package/dist/runtime/components/Form/fileState/MultipleFilesState.d.vue.ts +4 -4
  64. package/dist/runtime/components/Form/fileState/MultipleFilesState.vue +5 -5
  65. package/dist/runtime/components/Form/fileState/MultipleFilesState.vue.d.ts +4 -4
  66. package/dist/runtime/components/Form/fileState/SuccessState.d.vue.ts +2 -2
  67. package/dist/runtime/components/Form/fileState/SuccessState.vue +8 -13
  68. package/dist/runtime/components/Form/fileState/SuccessState.vue.d.ts +2 -2
  69. package/dist/runtime/components/Form/fileState/useFilePicker.d.ts +34 -0
  70. package/dist/runtime/components/Form/fileState/useFilePicker.js +48 -0
  71. package/dist/runtime/components/Form/fileState/useFileState.d.ts +7 -9
  72. package/dist/runtime/components/Form/fileState/useFileState.js +32 -120
  73. package/dist/runtime/components/Form/fileState/useUploadImageState.d.ts +7 -8
  74. package/dist/runtime/components/Form/fileState/useUploadImageState.js +27 -301
  75. package/dist/runtime/components/Form/fileState/useUploadState.d.ts +11 -7
  76. package/dist/runtime/components/Form/fileState/useUploadState.js +52 -136
  77. package/dist/runtime/components/Form/fileState/useUploadStateMultiple.d.ts +12 -18
  78. package/dist/runtime/components/Form/fileState/useUploadStateMultiple.js +88 -226
  79. package/dist/runtime/components/Form/types.d.ts +2 -0
  80. package/dist/runtime/components/Image.vue +4 -10
  81. package/dist/runtime/components/Loader.d.vue.ts +2 -1
  82. package/dist/runtime/components/Loader.vue +10 -7
  83. package/dist/runtime/components/Loader.vue.d.ts +2 -1
  84. package/dist/runtime/components/Log/LogItem.vue +1 -3
  85. package/dist/runtime/components/Log/index.vue +1 -3
  86. package/dist/runtime/components/Table/Base.d.vue.ts +15 -1
  87. package/dist/runtime/components/Table/Base.vue +9 -29
  88. package/dist/runtime/components/Table/Base.vue.d.ts +15 -1
  89. package/dist/runtime/components/Table/ColumnText.d.vue.ts +2 -3
  90. package/dist/runtime/components/Table/ColumnText.vue +1 -1
  91. package/dist/runtime/components/Table/ColumnText.vue.d.ts +2 -3
  92. package/dist/runtime/components/Table/Pagination.vue +19 -22
  93. package/dist/runtime/components/Table/index.d.vue.ts +1 -1
  94. package/dist/runtime/components/Table/index.vue +5 -7
  95. package/dist/runtime/components/Table/index.vue.d.ts +1 -1
  96. package/dist/runtime/composables/loaderList.d.ts +2 -1
  97. package/dist/runtime/composables/loaderList.js +3 -4
  98. package/dist/runtime/composables/loaderObject.d.ts +2 -1
  99. package/dist/runtime/composables/loaderObject.js +2 -1
  100. package/dist/runtime/composables/loaderPage.js +223 -350
  101. package/dist/runtime/composables/useConfig.js +4 -3
  102. package/dist/runtime/composables/useDialog.js +3 -1
  103. package/dist/runtime/composables/useForm.d.ts +2 -2
  104. package/dist/runtime/composables/useForm.js +2 -4
  105. package/dist/runtime/composables/useMessages.d.ts +10 -0
  106. package/dist/runtime/composables/useMessages.js +10 -0
  107. package/dist/runtime/composables/useTable.js +2 -14
  108. package/dist/runtime/helpers/apiBaseHelper.js +6 -24
  109. package/dist/runtime/helpers/apiListHelper.js +2 -5
  110. package/dist/runtime/helpers/apiObjectHelper.js +2 -5
  111. package/dist/runtime/helpers/apiPageHelper.d.ts +0 -1
  112. package/dist/runtime/helpers/apiPageHelper.js +19 -38
  113. package/dist/runtime/helpers/componentHelper.js +2 -6
  114. package/dist/runtime/helpers/uploadCore.d.ts +54 -0
  115. package/dist/runtime/helpers/uploadCore.js +81 -0
  116. package/dist/runtime/i18n/valibot.d.ts +5 -68
  117. package/dist/runtime/i18n/valibot.js +33 -29
  118. package/dist/runtime/locale/en.d.ts +2 -0
  119. package/dist/runtime/locale/en.js +42 -0
  120. package/dist/runtime/locale/index.d.ts +7 -0
  121. package/dist/runtime/locale/index.js +5 -0
  122. package/dist/runtime/locale/th.d.ts +43 -0
  123. package/dist/runtime/locale/th.js +42 -0
  124. package/dist/runtime/server/tsconfig.json +1 -1
  125. package/dist/runtime/styles/main.css +1 -1
  126. package/dist/runtime/utils/ParamHelper.js +1 -1
  127. package/dist/runtime/utils/StringHelper.js +1 -4
  128. package/dist/runtime/utils/TimeHelper.js +2 -2
  129. package/dist/runtime/utils/lodash.js +2 -2
  130. package/package.json +57 -36
@@ -1,306 +1,32 @@
1
- import { useDropZone, useFileDialog } from "@vueuse/core";
2
- import { useWatchTrue } from "#core/composables/useWatch";
3
- import PreviewModal from "./PreviewModal.vue";
4
- import { computed, ref, useOverlay, useAppConfig } from "#imports";
5
- import { useUploadLoader } from "#core/composables/useUpload";
6
- import { useFileAllocate, useFileProgress } from "#core/helpers/componentHelper";
7
- import { StringHelper } from "#core/utils/StringHelper";
8
- import { _get } from "#core/utils/lodash";
9
- export var UploadState = /* @__PURE__ */ ((UploadState2) => {
10
- UploadState2["EMPTY"] = "empty";
11
- UploadState2["UPLOADING"] = "uploading";
12
- UploadState2["SUCCESS"] = "success";
13
- UploadState2["ERROR"] = "error";
14
- return UploadState2;
15
- })(UploadState || {});
16
- export const useUploadImageState = (props, emits, onChange, setErrors, value, acceptedFileTypes, wrapperProps, dropzoneRef) => {
17
- const overlay = useOverlay();
18
- const previewModal = overlay.create(PreviewModal);
19
- const selectedFile = ref();
20
- const fileAllocate = useFileAllocate(selectedFile, props);
21
- const appConfig = useAppConfig();
22
- const {
23
- percent,
24
- onDownloadProgress,
25
- onUploadProgress
26
- } = useFileProgress();
27
- const request = {
28
- requestOptions: {
29
- ...props.requestOptions,
30
- onDownloadProgress,
31
- onUploadProgress
32
- },
33
- pathURL: props.uploadPathURL
34
- };
35
- const upload = useUploadLoader(request);
36
- const validateImageDimensions = (file) => {
37
- return new Promise((resolve) => {
38
- if (!props.dimensions) {
39
- resolve(true);
40
- return;
41
- }
42
- const img = new Image();
43
- const url = URL.createObjectURL(file);
44
- img.onload = () => {
45
- URL.revokeObjectURL(url);
46
- const {
47
- width,
48
- height
49
- } = img;
50
- const dimensions = props.dimensions;
51
- if (dimensions.width !== void 0 && width !== dimensions.width) {
52
- if (appConfig.core?.locale === "th") {
53
- setErrors(`\u0E04\u0E27\u0E32\u0E21\u0E01\u0E27\u0E49\u0E32\u0E07\u0E02\u0E2D\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19 ${dimensions.width}px (\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19: ${width}px)`);
54
- } else {
55
- setErrors(`Image width must be ${dimensions.width}px (current: ${width}px)`);
56
- }
57
- resolve(false);
58
- return;
59
- }
60
- if (dimensions.height !== void 0 && height !== dimensions.height) {
61
- if (appConfig.core?.locale === "th") {
62
- setErrors(`\u0E04\u0E27\u0E32\u0E21\u0E2A\u0E39\u0E07\u0E02\u0E2D\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19 ${dimensions.height}px (\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19: ${height}px)`);
63
- } else {
64
- setErrors(`Image height must be ${dimensions.height}px (current: ${height}px)`);
65
- }
66
- resolve(false);
67
- return;
68
- }
69
- if (dimensions.minWidth !== void 0 && width < dimensions.minWidth) {
70
- if (appConfig.core?.locale === "th") {
71
- setErrors(`\u0E04\u0E27\u0E32\u0E21\u0E01\u0E27\u0E49\u0E32\u0E07\u0E02\u0E2D\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E19\u0E49\u0E2D\u0E22\u0E01\u0E27\u0E48\u0E32 ${dimensions.minWidth}px (\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19: ${width}px)`);
72
- } else {
73
- setErrors(`Image width must be at least ${dimensions.minWidth}px (current: ${width}px)`);
74
- }
75
- resolve(false);
76
- return;
77
- }
78
- if (dimensions.minHeight !== void 0 && height < dimensions.minHeight) {
79
- if (appConfig.core?.locale === "th") {
80
- setErrors(`\u0E04\u0E27\u0E32\u0E21\u0E2A\u0E39\u0E07\u0E02\u0E2D\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E19\u0E49\u0E2D\u0E22\u0E01\u0E27\u0E48\u0E32 ${dimensions.minHeight}px (\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19: ${height}px)`);
81
- } else {
82
- setErrors(`Image height must be at least ${dimensions.minHeight}px (current: ${height}px)`);
83
- }
84
- resolve(false);
85
- return;
86
- }
87
- if (dimensions.maxWidth !== void 0 && width > dimensions.maxWidth) {
88
- if (appConfig.core?.locale === "th") {
89
- setErrors(`\u0E04\u0E27\u0E32\u0E21\u0E01\u0E27\u0E49\u0E32\u0E07\u0E02\u0E2D\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${dimensions.maxWidth}px (\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19: ${width}px)`);
90
- } else {
91
- setErrors(`Image width must not exceed ${dimensions.maxWidth}px (current: ${width}px)`);
92
- }
93
- resolve(false);
94
- return;
95
- }
96
- if (dimensions.maxHeight !== void 0 && height > dimensions.maxHeight) {
97
- if (appConfig.core?.locale === "th") {
98
- setErrors(`\u0E04\u0E27\u0E32\u0E21\u0E2A\u0E39\u0E07\u0E02\u0E2D\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${dimensions.maxHeight}px (\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19: ${height}px)`);
99
- } else {
100
- setErrors(`Image height must not exceed ${dimensions.maxHeight}px (current: ${height}px)`);
101
- }
102
- resolve(false);
103
- return;
104
- }
105
- if (dimensions.aspectRatio !== void 0) {
106
- const actualRatio = width / height;
107
- const tolerance = dimensions.aspectRatioTolerance ?? 0.01;
108
- const expectedRatio = dimensions.aspectRatio;
109
- if (Math.abs(actualRatio - expectedRatio) > tolerance) {
110
- if (appConfig.core?.locale === "th") {
111
- setErrors(`\u0E2D\u0E31\u0E15\u0E23\u0E32\u0E2A\u0E48\u0E27\u0E19\u0E02\u0E2D\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 (\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23: ${expectedRatio.toFixed(2)}, \u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19: ${actualRatio.toFixed(2)})`);
112
- } else {
113
- setErrors(`Image aspect ratio is incorrect (expected: ${expectedRatio.toFixed(2)}, current: ${actualRatio.toFixed(2)})`);
114
- }
115
- resolve(false);
116
- return;
117
- }
118
- }
119
- setErrors("");
120
- resolve(true);
121
- };
122
- img.onerror = () => {
123
- URL.revokeObjectURL(url);
124
- if (appConfig.core?.locale === "th") {
125
- setErrors("\u0E44\u0E21\u0E48\u0E2A\u0E32\u0E21\u0E32\u0E23\u0E16\u0E42\u0E2B\u0E25\u0E14\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E44\u0E14\u0E49");
126
- } else {
127
- setErrors("Failed to load image");
128
- }
129
- resolve(false);
130
- };
131
- img.src = url;
132
- });
133
- };
134
- const validateFile = async (file) => {
135
- if (props.accept && fileAllocate.acceptFile.value) {
136
- const acceptedTypes = fileAllocate.acceptFile.value;
137
- const acceptedTypesList = acceptedTypes.split(",").map((type) => type.trim());
138
- const fileExtension = file.name.toLowerCase().split(".").pop();
139
- const isValidFileType = acceptedTypesList.some((acceptedType) => {
140
- if (acceptedType.startsWith(".")) {
141
- const extension = acceptedType.slice(1).toLowerCase();
142
- return fileExtension === extension;
143
- } else if (!acceptedType.includes("/") && !acceptedType.includes("*")) {
144
- return fileExtension === acceptedType.toLowerCase();
145
- }
146
- if (acceptedType.endsWith("/*")) {
147
- const baseType = acceptedType.slice(0, -2);
148
- return file.type.startsWith(baseType + "/");
149
- }
150
- return file.type === acceptedType;
151
- });
152
- if (!isValidFileType) {
153
- if (appConfig.core?.locale === "th") {
154
- 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(", ") + ")");
155
- } else {
156
- setErrors("Invalid file type (only " + acceptedTypesList.join(", ") + " are allowed)");
157
- }
158
- return false;
159
- }
160
- }
161
- if (props.maxSize) {
162
- const maxSizeBytes = (fileAllocate.acceptFileSizeKb.value || 0) * 1024;
163
- if (file.size > maxSizeBytes) {
164
- if (fileAllocate.isAcceptFileUseMb.value) {
165
- if (appConfig.core?.locale === "th") {
166
- setErrors(`\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${fileAllocate.acceptFileSizeMb.value} MB`);
167
- } else {
168
- setErrors(`File size must not exceed ${fileAllocate.acceptFileSizeMb.value} MB`);
169
- }
170
- } else {
171
- if (appConfig.core?.locale === "th") {
172
- setErrors(`\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${fileAllocate.acceptFileSizeKb.value} KB`);
173
- } else {
174
- setErrors(`File size must not exceed ${fileAllocate.acceptFileSizeKb.value} KB`);
175
- }
176
- }
177
- return false;
178
- }
179
- }
180
- const isDimensionsValid = await validateImageDimensions(file);
181
- if (!isDimensionsValid) {
182
- return false;
183
- }
184
- setErrors("");
185
- return true;
186
- };
187
- const processFile = async (file) => {
188
- const isValid = await validateFile(file);
189
- if (!isValid) return;
190
- selectedFile.value = file;
191
- emits("change", file);
192
- const formData = new FormData();
193
- formData.append(props.bodyKey, file);
194
- upload.run({
195
- data: formData
1
+ import { UploadState, useUploadState } from "./useUploadState.js";
2
+ import { validateDimensions } from "#core/helpers/uploadCore";
3
+ import { useCoreMessages } from "#core/composables/useMessages";
4
+ export { UploadState };
5
+ const readImageSize = (file) => new Promise((resolve) => {
6
+ const image = new Image();
7
+ const url = URL.createObjectURL(file);
8
+ image.onload = () => {
9
+ URL.revokeObjectURL(url);
10
+ resolve({
11
+ width: image.width,
12
+ height: image.height
196
13
  });
197
14
  };
198
- const handleFileDrop = (files) => {
199
- if (wrapperProps.value.disabled || wrapperProps.value.readonly || !files?.length || !isEmpty.value) return;
200
- const file = files[0];
201
- if (file) {
202
- processFile(file);
15
+ image.onerror = () => {
16
+ URL.revokeObjectURL(url);
17
+ resolve(void 0);
18
+ };
19
+ image.src = url;
20
+ });
21
+ export const useUploadImageState = (context) => {
22
+ const { messages } = useCoreMessages();
23
+ return useUploadState(context, {
24
+ defaultAccept: "image/*",
25
+ validateExtra: async (file) => {
26
+ if (!context.props.dimensions) return;
27
+ const size = await readImageSize(file);
28
+ if (!size) return messages.value.upload.imageLoadFailed;
29
+ return validateDimensions(size, context.props.dimensions, messages.value.upload);
203
30
  }
204
- };
205
- const fileDialog = useFileDialog({
206
- accept: acceptedFileTypes.value || "image/*",
207
- directory: false,
208
- multiple: false
209
- });
210
- const dropzone = useDropZone(dropzoneRef, {
211
- onDrop: handleFileDrop,
212
- multiple: false,
213
- preventDefaultForUnhandled: false
214
31
  });
215
- const currentState = computed(() => {
216
- if (value.value) return "success" /* SUCCESS */;
217
- if (selectedFile.value && upload.status.value.isLoading) return "uploading" /* UPLOADING */;
218
- if (selectedFile.value && upload.status.value.isError) return "error" /* ERROR */;
219
- return "empty" /* EMPTY */;
220
- });
221
- const isEmpty = computed(() => currentState.value === "empty" /* EMPTY */);
222
- const isUploading = computed(() => currentState.value === "uploading" /* UPLOADING */);
223
- const isSuccess = computed(() => currentState.value === "success" /* SUCCESS */);
224
- const isError = computed(() => currentState.value === "error" /* ERROR */);
225
- const handleInputChange = (event) => {
226
- if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
227
- const file = event.target.files?.[0];
228
- if (file) {
229
- processFile(file);
230
- }
231
- };
232
- fileDialog.onChange((files) => {
233
- const file = files?.[0];
234
- if (file) {
235
- processFile(file);
236
- }
237
- });
238
- const handleOpenFile = () => {
239
- if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
240
- fileDialog.open();
241
- };
242
- const handleDeleteFile = () => {
243
- fileDialog.reset();
244
- upload.clear();
245
- selectedFile.value = void 0;
246
- onChange(void 0);
247
- emits("delete");
248
- };
249
- const handleRetryUpload = () => {
250
- if (selectedFile.value) {
251
- const formData = new FormData();
252
- formData.append(props.bodyKey, selectedFile.value);
253
- upload.run(formData);
254
- }
255
- };
256
- const handlePreview = () => {
257
- previewModal.open({
258
- value: value.value
259
- });
260
- };
261
- useWatchTrue(
262
- () => upload.status.value.isSuccess,
263
- () => {
264
- if (upload.data.value) {
265
- const fileValue = {
266
- url: _get(upload.data.value, props.responseURL),
267
- path: _get(upload.data.value, props.responsePath),
268
- name: _get(upload.data.value, props.responseName) || selectedFile.value?.name,
269
- size: Number(_get(upload.data.value, props.responseSize) || selectedFile.value?.size),
270
- id: _get(upload.data.value, props.responseID)
271
- };
272
- value.value = fileValue;
273
- emits("success", fileValue);
274
- }
275
- }
276
- );
277
- useWatchTrue(
278
- () => upload.status.value.isError,
279
- () => {
280
- if (appConfig.core?.locale === "th") {
281
- setErrors("\u0E1E\u0E1A\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14: " + StringHelper.getError(upload.status.value.errorData));
282
- } else {
283
- setErrors("An error occurred: " + StringHelper.getError(upload.status.value.errorData));
284
- }
285
- }
286
- );
287
- return {
288
- // State
289
- currentState,
290
- isEmpty,
291
- isUploading,
292
- isSuccess,
293
- isError,
294
- selectedFile,
295
- // Upload utilities
296
- upload,
297
- dropzone,
298
- percent,
299
- // Handlers
300
- handleInputChange,
301
- handleOpenFile,
302
- handleDeleteFile,
303
- handleRetryUpload,
304
- handlePreview
305
- };
306
32
  };
@@ -1,13 +1,17 @@
1
- import type { TemplateRef } from 'vue';
2
1
  import type { IUploadDropzoneAutoProps } from '../InputUploadDropzoneAuto/types.js';
2
+ import { type IUploadFieldContext } from './useFilePicker.js';
3
+ import { UploadState } from '#core/helpers/uploadCore';
3
4
  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"
5
+ export { UploadState };
6
+ interface IUploadStateOptions {
7
+ defaultAccept?: string;
8
+ /** Extra check after type and size pass; resolves to an error message, or undefined when the file is fine */
9
+ validateExtra?: (file: File) => Promise<string | undefined>;
9
10
  }
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
+ /**
12
+ * Uploads the picked file right away and stores the file reference returned by the API as the field value
13
+ */
14
+ export declare const useUploadState: (context: IUploadFieldContext<IUploadDropzoneAutoProps, IFileValue>, options?: IUploadStateOptions) => {
11
15
  currentState: import("vue").ComputedRef<UploadState>;
12
16
  isEmpty: import("vue").ComputedRef<boolean>;
13
17
  isUploading: import("vue").ComputedRef<boolean>;
@@ -1,167 +1,83 @@
1
- import { useDropZone, useFileDialog } from "@vueuse/core";
2
- import { useWatchTrue } from "./../../../../../dist/runtime/composables/useWatch";
3
- import PreviewModal from "./PreviewModal.vue";
4
- import { computed, ref, useOverlay } from "#imports";
1
+ import { useFilePicker } from "./useFilePicker.js";
2
+ import { toFileValue, toUploadFormData, UploadState, validateFile } from "#core/helpers/uploadCore";
3
+ import { useWatchTrue } from "#core/composables/useWatch";
4
+ import { useCoreMessages } from "#core/composables/useMessages";
5
5
  import { useUploadLoader } from "#core/composables/useUpload";
6
- import { useFileAllocate, useFileProgress } from "#core/helpers/componentHelper";
6
+ import { useFileProgress } from "#core/helpers/componentHelper";
7
7
  import { StringHelper } from "#core/utils/StringHelper";
8
- import { _get } from "#core/utils/lodash";
9
- export var UploadState = /* @__PURE__ */ ((UploadState2) => {
10
- UploadState2["EMPTY"] = "empty";
11
- UploadState2["UPLOADING"] = "uploading";
12
- UploadState2["SUCCESS"] = "success";
13
- UploadState2["ERROR"] = "error";
14
- return UploadState2;
15
- })(UploadState || {});
16
- export const useUploadState = (props, emits, onChange, setErrors, value, acceptedFileTypes, wrapperProps, dropzoneRef) => {
17
- const overlay = useOverlay();
18
- const previewModal = overlay.create(PreviewModal);
8
+ import { computed, ref } from "#imports";
9
+ export { UploadState };
10
+ export const useUploadState = (context, options = {}) => {
11
+ const { props, emits, onChange, setErrors, value } = context;
12
+ const { messages, format } = useCoreMessages();
19
13
  const selectedFile = ref();
20
- const fileAllocate = useFileAllocate(selectedFile, props);
21
- const {
22
- percent,
23
- onDownloadProgress,
24
- onUploadProgress
25
- } = useFileProgress();
26
- const request = {
14
+ const { percent, onDownloadProgress, onUploadProgress } = useFileProgress();
15
+ const upload = useUploadLoader({
27
16
  requestOptions: {
28
17
  ...props.requestOptions,
29
18
  onDownloadProgress,
30
19
  onUploadProgress
31
20
  },
32
21
  pathURL: props.uploadPathURL
33
- };
34
- const upload = useUploadLoader(request);
35
- const validateFile = (file) => {
36
- if (props.accept && fileAllocate.acceptFile.value) {
37
- const acceptedTypes = fileAllocate.acceptFile.value;
38
- const acceptedTypesList = acceptedTypes.split(",").map((type) => type.trim());
39
- const fileExtension = file.name.toLowerCase().split(".").pop();
40
- const isValidFileType = acceptedTypesList.some((acceptedType) => {
41
- if (acceptedType.startsWith(".")) {
42
- const extension = acceptedType.slice(1).toLowerCase();
43
- return fileExtension === extension;
44
- } else if (!acceptedType.includes("/") && !acceptedType.includes("*")) {
45
- return fileExtension === acceptedType.toLowerCase();
46
- }
47
- if (acceptedType.endsWith("/*")) {
48
- const baseType = acceptedType.slice(0, -2);
49
- return file.type.startsWith(baseType + "/");
50
- }
51
- return file.type === acceptedType;
52
- });
53
- if (!isValidFileType) {
54
- 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(", ") + ")");
55
- return false;
56
- }
57
- }
58
- if (props.maxSize) {
59
- const maxSizeBytes = (fileAllocate.acceptFileSizeKb.value || 0) * 1024;
60
- if (file.size > maxSizeBytes) {
61
- if (fileAllocate.isAcceptFileUseMb.value) {
62
- setErrors(`\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${fileAllocate.acceptFileSizeMb.value} MB`);
63
- } else {
64
- setErrors(`\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${fileAllocate.acceptFileSizeKb.value} KB`);
65
- }
66
- return false;
67
- }
68
- }
69
- setErrors("");
70
- return true;
71
- };
72
- const processFile = (file) => {
73
- if (!validateFile(file)) return;
74
- selectedFile.value = file;
75
- emits("change", file);
76
- const formData = new FormData();
77
- formData.append(props.bodyKey, file);
22
+ });
23
+ const currentState = computed(() => {
24
+ if (value.value) return UploadState.SUCCESS;
25
+ if (selectedFile.value && upload.status.value.isLoading) return UploadState.UPLOADING;
26
+ if (selectedFile.value && upload.status.value.isError) return UploadState.ERROR;
27
+ return UploadState.EMPTY;
28
+ });
29
+ const isEmpty = computed(() => currentState.value === UploadState.EMPTY);
30
+ const isUploading = computed(() => currentState.value === UploadState.UPLOADING);
31
+ const isSuccess = computed(() => currentState.value === UploadState.SUCCESS);
32
+ const isError = computed(() => currentState.value === UploadState.ERROR);
33
+ const startUpload = (file) => {
78
34
  upload.run({
79
- data: formData
35
+ data: toUploadFormData(props.bodyKey, file)
80
36
  });
81
37
  };
82
- const handleFileDrop = (files) => {
83
- if (wrapperProps.value.disabled || wrapperProps.value.readonly || !files?.length || !isEmpty.value) return;
84
- const file = files[0];
85
- if (file) {
86
- processFile(file);
87
- }
38
+ const processFile = async (file) => {
39
+ const error = validateFile(file, props, messages.value.upload) ?? await options.validateExtra?.(file);
40
+ setErrors(error ?? "");
41
+ if (error) return;
42
+ selectedFile.value = file;
43
+ emits("change", file);
44
+ startUpload(file);
88
45
  };
89
- const fileDialog = useFileDialog({
90
- accept: acceptedFileTypes.value || "",
91
- directory: false,
92
- multiple: false
93
- });
94
- const dropzone = useDropZone(dropzoneRef, {
95
- onDrop: handleFileDrop,
96
- // dataTypes: typeof props.accept === 'string' ? [props.accept] : props.accept,
46
+ const picker = useFilePicker(context, {
97
47
  multiple: false,
98
- preventDefaultForUnhandled: false
99
- });
100
- const currentState = computed(() => {
101
- if (value.value) return "success" /* SUCCESS */;
102
- if (selectedFile.value && upload.status.value.isLoading) return "uploading" /* UPLOADING */;
103
- if (selectedFile.value && upload.status.value.isError) return "error" /* ERROR */;
104
- return "empty" /* EMPTY */;
105
- });
106
- const isEmpty = computed(() => currentState.value === "empty" /* EMPTY */);
107
- const isUploading = computed(() => currentState.value === "uploading" /* UPLOADING */);
108
- const isSuccess = computed(() => currentState.value === "success" /* SUCCESS */);
109
- const isError = computed(() => currentState.value === "error" /* ERROR */);
110
- const handleInputChange = (event) => {
111
- if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
112
- const file = event.target.files?.[0];
113
- if (file) {
114
- processFile(file);
115
- }
116
- };
117
- fileDialog.onChange((files) => {
118
- if (files?.length) {
119
- processFile(files[0]);
48
+ defaultAccept: options.defaultAccept,
49
+ canDrop: () => isEmpty.value,
50
+ onFiles: async ([file]) => {
51
+ if (file) await processFile(file);
120
52
  }
121
53
  });
122
- const handleOpenFile = () => {
123
- if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
124
- fileDialog.open();
125
- };
126
54
  const handleDeleteFile = () => {
127
- fileDialog.reset();
55
+ picker.fileDialog.reset();
128
56
  upload.clear();
129
57
  selectedFile.value = void 0;
130
58
  onChange(void 0);
131
59
  emits("delete");
132
60
  };
133
61
  const handleRetryUpload = () => {
134
- if (selectedFile.value) {
135
- const formData = new FormData();
136
- formData.append(props.bodyKey, selectedFile.value);
137
- upload.run(formData);
138
- }
139
- };
140
- const handlePreview = () => {
141
- previewModal.open({
142
- value: value.value
143
- });
62
+ if (selectedFile.value) startUpload(selectedFile.value);
144
63
  };
145
64
  useWatchTrue(
146
65
  () => upload.status.value.isSuccess,
147
66
  () => {
148
- if (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
- id: _get(upload.data.value, props.responseID)
155
- };
156
- value.value = fileValue;
157
- emits("success", fileValue);
158
- }
67
+ if (!upload.data.value) return;
68
+ const fileValue = toFileValue(upload.data.value, props, selectedFile.value);
69
+ value.value = fileValue;
70
+ emits("success", fileValue);
159
71
  }
160
72
  );
161
73
  useWatchTrue(
162
74
  () => upload.status.value.isError,
163
75
  () => {
164
- setErrors("\u0E1E\u0E1A\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14: " + StringHelper.getError(upload.status.value.errorData));
76
+ setErrors(
77
+ format(messages.value.upload.error, {
78
+ message: StringHelper.getError(upload.status.value.errorData, messages.value.common.somethingWentWrong)
79
+ })
80
+ );
165
81
  }
166
82
  );
167
83
  return {
@@ -174,13 +90,13 @@ export const useUploadState = (props, emits, onChange, setErrors, value, accepte
174
90
  selectedFile,
175
91
  // Upload utilities
176
92
  upload,
177
- dropzone,
93
+ dropzone: picker.dropzone,
178
94
  percent,
179
95
  // Handlers
180
- handleInputChange,
181
- handleOpenFile,
96
+ handleInputChange: picker.handleInputChange,
97
+ handleOpenFile: picker.handleOpenFile,
182
98
  handleDeleteFile,
183
99
  handleRetryUpload,
184
- handlePreview
100
+ handlePreview: () => picker.openPreview(value.value)
185
101
  };
186
102
  };
@@ -1,24 +1,23 @@
1
- import type { TemplateRef } from 'vue';
2
1
  import type { IUploadDropzoneAutoMultipleProps } from '../InputUploadDropzoneAutoMultiple/types.js';
2
+ import { type IUploadFieldContext } from './useFilePicker.js';
3
+ import { UploadState } from '#core/helpers/uploadCore';
3
4
  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
- }
5
+ export { UploadState };
10
6
  export interface FileUploadItem {
7
+ id: number;
11
8
  file: File;
12
9
  state: UploadState;
13
10
  progress: number;
14
11
  value?: IFileValue;
15
12
  error?: string;
16
- uploadLoader?: any;
17
- percentRef?: any;
18
- cleanupWatchers?: (() => void)[];
19
13
  }
20
- export declare const useUploadStateMultiple: (props: IUploadDropzoneAutoMultipleProps, emits: any, onChange: (value: IFileValue[] | undefined) => void, setErrors: (error: string) => void, value: any, acceptedFileTypes: any, wrapperProps: any, dropzoneRef: TemplateRef<HTMLDivElement | undefined>) => {
14
+ /**
15
+ * Uploads every picked file right away. Finished files move into the field value (newest first);
16
+ * `fileItems` only holds the ones still uploading or failed.
17
+ */
18
+ export declare const useUploadStateMultiple: (context: IUploadFieldContext<IUploadDropzoneAutoMultipleProps, IFileValue[]>) => {
21
19
  fileItems: import("vue").Ref<{
20
+ id: number;
22
21
  file: {
23
22
  readonly lastModified: number;
24
23
  readonly name: string;
@@ -56,10 +55,8 @@ export declare const useUploadStateMultiple: (props: IUploadDropzoneAutoMultiple
56
55
  id?: string | undefined;
57
56
  } | undefined;
58
57
  error?: string | undefined;
59
- uploadLoader?: any;
60
- percentRef?: any;
61
- cleanupWatchers?: (() => void)[] | undefined;
62
58
  }[], FileUploadItem[] | {
59
+ id: number;
63
60
  file: {
64
61
  readonly lastModified: number;
65
62
  readonly name: string;
@@ -97,9 +94,6 @@ export declare const useUploadStateMultiple: (props: IUploadDropzoneAutoMultiple
97
94
  id?: string | undefined;
98
95
  } | undefined;
99
96
  error?: string | undefined;
100
- uploadLoader?: any;
101
- percentRef?: any;
102
- cleanupWatchers?: (() => void)[] | undefined;
103
97
  }[]>;
104
98
  isEmpty: import("vue").ComputedRef<boolean>;
105
99
  hasUploading: import("vue").ComputedRef<boolean>;
@@ -108,5 +102,5 @@ export declare const useUploadStateMultiple: (props: IUploadDropzoneAutoMultiple
108
102
  handleOpenFile: () => void;
109
103
  handleDeleteFile: (index: number) => void;
110
104
  handleRetryUpload: (index: number) => Promise<void>;
111
- handlePreview: (fileValue: IFileValue) => void;
105
+ handlePreview: (fileValue: IFileValue | File) => void;
112
106
  };