@finema/core 3.21.0 → 3.23.2

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 (126) hide show
  1. package/dist/module.d.mts +2 -0
  2. package/dist/module.json +2 -2
  3. package/dist/module.mjs +16 -2
  4. package/dist/runtime/components/App.vue +1 -1
  5. package/dist/runtime/components/DevToolsWindow/index.vue +1 -1
  6. package/dist/runtime/components/Dialog/index.d.vue.ts +1 -4
  7. package/dist/runtime/components/Dialog/index.vue +9 -10
  8. package/dist/runtime/components/Dialog/index.vue.d.ts +1 -4
  9. package/dist/runtime/components/Empty.d.vue.ts +0 -1
  10. package/dist/runtime/components/Empty.vue +6 -2
  11. package/dist/runtime/components/Empty.vue.d.ts +0 -1
  12. package/dist/runtime/components/FlexDeck/index.vue +86 -81
  13. package/dist/runtime/components/Form/FieldWrapper.vue +14 -14
  14. package/dist/runtime/components/Form/Fields.vue +10 -10
  15. package/dist/runtime/components/Form/InputCheckbox/index.vue +20 -19
  16. package/dist/runtime/components/Form/InputCheckboxGroup/index.vue +2 -1
  17. package/dist/runtime/components/Form/InputComponent/index.vue +2 -1
  18. package/dist/runtime/components/Form/InputCurrency/index.vue +51 -50
  19. package/dist/runtime/components/Form/InputDateTime/date_time_field.types.d.ts +1 -1
  20. package/dist/runtime/components/Form/InputDateTime/index.vue +69 -64
  21. package/dist/runtime/components/Form/InputDateTimeRange/date_range_time_field.types.d.ts +1 -1
  22. package/dist/runtime/components/Form/InputDateTimeRange/index.vue +62 -57
  23. package/dist/runtime/components/Form/InputMonth/index.vue +10 -5
  24. package/dist/runtime/components/Form/InputMonth/types.d.ts +1 -1
  25. package/dist/runtime/components/Form/InputNumber/index.vue +22 -21
  26. package/dist/runtime/components/Form/InputRadio/index.d.vue.ts +1 -1
  27. package/dist/runtime/components/Form/InputRadio/index.vue +2 -1
  28. package/dist/runtime/components/Form/InputRadio/index.vue.d.ts +1 -1
  29. package/dist/runtime/components/Form/InputRating/index.vue +2 -1
  30. package/dist/runtime/components/Form/InputSearch/index.vue +7 -2
  31. package/dist/runtime/components/Form/InputSelect/index.vue +112 -111
  32. package/dist/runtime/components/Form/InputSelectMultiple/index.vue +120 -119
  33. package/dist/runtime/components/Form/InputTags/index.vue +56 -55
  34. package/dist/runtime/components/Form/InputText/index.vue +88 -87
  35. package/dist/runtime/components/Form/InputTextarea/index.vue +20 -19
  36. package/dist/runtime/components/Form/InputTime/index.vue +45 -40
  37. package/dist/runtime/components/Form/InputTime/types.d.ts +1 -1
  38. package/dist/runtime/components/Form/InputToggle/index.vue +19 -18
  39. package/dist/runtime/components/Form/InputUploadDropzone/index.d.vue.ts +1 -4
  40. package/dist/runtime/components/Form/InputUploadDropzone/index.vue +40 -35
  41. package/dist/runtime/components/Form/InputUploadDropzone/index.vue.d.ts +1 -4
  42. package/dist/runtime/components/Form/InputUploadDropzone/types.d.ts +2 -2
  43. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.d.vue.ts +0 -5
  44. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +63 -58
  45. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue.d.ts +0 -5
  46. package/dist/runtime/components/Form/InputUploadDropzoneAuto/types.d.ts +3 -3
  47. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.d.vue.ts +0 -5
  48. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue +69 -60
  49. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue.d.ts +0 -5
  50. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/types.d.ts +3 -3
  51. package/dist/runtime/components/Form/InputUploadImageAuto/index.d.vue.ts +1 -6
  52. package/dist/runtime/components/Form/InputUploadImageAuto/index.vue +63 -58
  53. package/dist/runtime/components/Form/InputUploadImageAuto/index.vue.d.ts +1 -6
  54. package/dist/runtime/components/Form/InputUploadImageAuto/types.d.ts +3 -3
  55. package/dist/runtime/components/Form/InputWYSIWYG/EditorImageUploadNode.vue +18 -18
  56. package/dist/runtime/components/Form/InputWYSIWYG/EditorLinkPopover.vue +65 -65
  57. package/dist/runtime/components/Form/InputWYSIWYG/README.md +96 -96
  58. package/dist/runtime/components/Form/InputWYSIWYG/index.vue +43 -42
  59. package/dist/runtime/components/Form/fileState/EmptyState.vue +21 -21
  60. package/dist/runtime/components/Form/fileState/FailedState.vue +37 -33
  61. package/dist/runtime/components/Form/fileState/LoadingState.vue +24 -24
  62. package/dist/runtime/components/Form/fileState/MultipleFilesState.d.vue.ts +4 -4
  63. package/dist/runtime/components/Form/fileState/MultipleFilesState.vue +79 -75
  64. package/dist/runtime/components/Form/fileState/MultipleFilesState.vue.d.ts +4 -4
  65. package/dist/runtime/components/Form/fileState/PreviewModal.vue +23 -23
  66. package/dist/runtime/components/Form/fileState/SuccessState.d.vue.ts +2 -2
  67. package/dist/runtime/components/Form/fileState/SuccessState.vue +8 -4
  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 +40 -120
  73. package/dist/runtime/components/Form/fileState/useUploadImageState.d.ts +7 -8
  74. package/dist/runtime/components/Form/fileState/useUploadImageState.js +28 -300
  75. package/dist/runtime/components/Form/fileState/useUploadState.d.ts +11 -7
  76. package/dist/runtime/components/Form/fileState/useUploadState.js +58 -131
  77. package/dist/runtime/components/Form/fileState/useUploadStateMultiple.d.ts +12 -18
  78. package/dist/runtime/components/Form/fileState/useUploadStateMultiple.js +89 -222
  79. package/dist/runtime/components/Form/index.vue +5 -5
  80. package/dist/runtime/components/Form/types.d.ts +2 -0
  81. package/dist/runtime/components/Image.vue +28 -28
  82. package/dist/runtime/components/Loader.d.vue.ts +1 -0
  83. package/dist/runtime/components/Loader.vue.d.ts +1 -0
  84. package/dist/runtime/components/Table/Base.d.vue.ts +15 -1
  85. package/dist/runtime/components/Table/Base.vue +118 -118
  86. package/dist/runtime/components/Table/Base.vue.d.ts +15 -1
  87. package/dist/runtime/components/Table/ColumnDate.vue +1 -1
  88. package/dist/runtime/components/Table/ColumnDateTime.vue +1 -1
  89. package/dist/runtime/components/Table/ColumnImage.vue +4 -4
  90. package/dist/runtime/components/Table/ColumnText.d.vue.ts +2 -3
  91. package/dist/runtime/components/Table/ColumnText.vue +11 -11
  92. package/dist/runtime/components/Table/ColumnText.vue.d.ts +2 -3
  93. package/dist/runtime/components/Table/Pagination.vue +70 -67
  94. package/dist/runtime/components/Table/Simple.vue +19 -19
  95. package/dist/runtime/components/Table/index.d.vue.ts +1 -1
  96. package/dist/runtime/components/Table/index.vue +37 -33
  97. package/dist/runtime/components/Table/index.vue.d.ts +1 -1
  98. package/dist/runtime/composables/loaderList.d.ts +2 -1
  99. package/dist/runtime/composables/loaderList.js +2 -1
  100. package/dist/runtime/composables/loaderObject.d.ts +2 -1
  101. package/dist/runtime/composables/loaderObject.js +2 -1
  102. package/dist/runtime/composables/loaderPage.js +194 -344
  103. package/dist/runtime/composables/useConfig.js +4 -3
  104. package/dist/runtime/composables/useDialog.js +5 -1
  105. package/dist/runtime/composables/useForm.d.ts +1 -1
  106. package/dist/runtime/composables/useForm.js +1 -1
  107. package/dist/runtime/composables/useMessages.d.ts +10 -0
  108. package/dist/runtime/composables/useMessages.js +10 -0
  109. package/dist/runtime/helpers/apiPageHelper.d.ts +0 -1
  110. package/dist/runtime/helpers/uploadCore.d.ts +54 -0
  111. package/dist/runtime/helpers/uploadCore.js +84 -0
  112. package/dist/runtime/i18n/valibot.d.ts +5 -68
  113. package/dist/runtime/i18n/valibot.js +33 -29
  114. package/dist/runtime/locale/en.d.ts +2 -0
  115. package/dist/runtime/locale/en.js +42 -0
  116. package/dist/runtime/locale/index.d.ts +7 -0
  117. package/dist/runtime/locale/index.js +8 -0
  118. package/dist/runtime/locale/th.d.ts +43 -0
  119. package/dist/runtime/locale/th.js +42 -0
  120. package/dist/runtime/server/tsconfig.json +3 -3
  121. package/dist/runtime/theme/dialog.js +1 -1
  122. package/dist/runtime/utils/ParamHelper.js +1 -1
  123. package/dist/runtime/utils/StringHelper.js +1 -4
  124. package/dist/runtime/utils/TimeHelper.js +2 -2
  125. package/dist/runtime/utils/lodash.js +2 -2
  126. package/package.json +20 -12
@@ -1,306 +1,34 @@
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);
203
- }
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
- });
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
- }
15
+ image.onerror = () => {
16
+ URL.revokeObjectURL(url);
17
+ resolve(void 0);
231
18
  };
232
- fileDialog.onChange((files) => {
233
- const file = files?.[0];
234
- if (file) {
235
- processFile(file);
19
+ image.src = url;
20
+ });
21
+ export const useUploadImageState = (context) => {
22
+ const {
23
+ messages
24
+ } = useCoreMessages();
25
+ return useUploadState(context, {
26
+ defaultAccept: "image/*",
27
+ validateExtra: async (file) => {
28
+ if (!context.props.dimensions) return;
29
+ const size = await readImageSize(file);
30
+ if (!size) return messages.value.upload.imageLoadFailed;
31
+ return validateDimensions(size, context.props.dimensions, messages.value.upload);
236
32
  }
237
33
  });
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
34
  };
@@ -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,94 @@
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 {
12
+ props,
13
+ emits,
14
+ onChange,
15
+ setErrors,
16
+ value
17
+ } = context;
18
+ const {
19
+ messages,
20
+ format
21
+ } = useCoreMessages();
19
22
  const selectedFile = ref();
20
- const fileAllocate = useFileAllocate(selectedFile, props);
21
23
  const {
22
24
  percent,
23
25
  onDownloadProgress,
24
26
  onUploadProgress
25
27
  } = useFileProgress();
26
- const request = {
28
+ const upload = useUploadLoader({
27
29
  requestOptions: {
28
30
  ...props.requestOptions,
29
31
  onDownloadProgress,
30
32
  onUploadProgress
31
33
  },
32
34
  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);
35
+ });
36
+ const currentState = computed(() => {
37
+ if (value.value) return UploadState.SUCCESS;
38
+ if (selectedFile.value && upload.status.value.isLoading) return UploadState.UPLOADING;
39
+ if (selectedFile.value && upload.status.value.isError) return UploadState.ERROR;
40
+ return UploadState.EMPTY;
41
+ });
42
+ const isEmpty = computed(() => currentState.value === UploadState.EMPTY);
43
+ const isUploading = computed(() => currentState.value === UploadState.UPLOADING);
44
+ const isSuccess = computed(() => currentState.value === UploadState.SUCCESS);
45
+ const isError = computed(() => currentState.value === UploadState.ERROR);
46
+ const startUpload = (file) => {
78
47
  upload.run({
79
- data: formData
48
+ data: toUploadFormData(props.bodyKey, file)
80
49
  });
81
50
  };
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
- }
51
+ const processFile = async (file) => {
52
+ const error = validateFile(file, props, messages.value.upload) ?? await options.validateExtra?.(file);
53
+ setErrors(error ?? "");
54
+ if (error) return;
55
+ selectedFile.value = file;
56
+ emits("change", file);
57
+ startUpload(file);
88
58
  };
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,
59
+ const picker = useFilePicker(context, {
97
60
  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]);
61
+ defaultAccept: options.defaultAccept,
62
+ canDrop: () => isEmpty.value,
63
+ onFiles: async ([file]) => {
64
+ if (file) await processFile(file);
120
65
  }
121
66
  });
122
- const handleOpenFile = () => {
123
- if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
124
- fileDialog.open();
125
- };
126
67
  const handleDeleteFile = () => {
127
- fileDialog.reset();
68
+ picker.fileDialog.reset();
128
69
  upload.clear();
129
70
  selectedFile.value = void 0;
130
71
  onChange(void 0);
131
72
  emits("delete");
132
73
  };
133
74
  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
- });
75
+ if (selectedFile.value) startUpload(selectedFile.value);
144
76
  };
145
77
  useWatchTrue(
146
78
  () => upload.status.value.isSuccess,
147
79
  () => {
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
- }
80
+ if (!upload.data.value) return;
81
+ const fileValue = toFileValue(upload.data.value, props, selectedFile.value);
82
+ value.value = fileValue;
83
+ emits("success", fileValue);
159
84
  }
160
85
  );
161
86
  useWatchTrue(
162
87
  () => upload.status.value.isError,
163
88
  () => {
164
- setErrors("\u0E1E\u0E1A\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14: " + StringHelper.getError(upload.status.value.errorData));
89
+ setErrors(format(messages.value.upload.error, {
90
+ message: StringHelper.getError(upload.status.value.errorData, messages.value.common.somethingWentWrong)
91
+ }));
165
92
  }
166
93
  );
167
94
  return {
@@ -174,13 +101,13 @@ export const useUploadState = (props, emits, onChange, setErrors, value, accepte
174
101
  selectedFile,
175
102
  // Upload utilities
176
103
  upload,
177
- dropzone,
104
+ dropzone: picker.dropzone,
178
105
  percent,
179
106
  // Handlers
180
- handleInputChange,
181
- handleOpenFile,
107
+ handleInputChange: picker.handleInputChange,
108
+ handleOpenFile: picker.handleOpenFile,
182
109
  handleDeleteFile,
183
110
  handleRetryUpload,
184
- handlePreview
111
+ handlePreview: () => picker.openPreview(value.value)
185
112
  };
186
113
  };
@@ -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
  };