sprintify-ui 0.11.27 → 0.11.28
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/sprintify-ui.es.js +4463 -4401
- package/dist/types/components/BaseFileUploader.vue.d.ts +2 -0
- package/dist/types/components/BaseMediaLibrary.vue.d.ts +9 -0
- package/package.json +1 -1
- package/src/components/BaseFileUploader.vue +133 -3
- package/src/components/BaseMediaLibrary.vue +5 -0
- package/src/components/BaseModalSide.vue +1 -1
|
@@ -7,6 +7,7 @@ type __VLS_Props = {
|
|
|
7
7
|
beforeUpload?: () => boolean;
|
|
8
8
|
twButton?: string;
|
|
9
9
|
maxSize?: number;
|
|
10
|
+
maxImageSizeBeforeResize?: number;
|
|
10
11
|
accept?: string;
|
|
11
12
|
acceptedExtensions?: string[];
|
|
12
13
|
cropper?: BaseCropperConfig | Record<string, any> | boolean | null;
|
|
@@ -64,6 +65,7 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}
|
|
|
64
65
|
accept: string;
|
|
65
66
|
acceptedExtensions: string[];
|
|
66
67
|
beforeUpload: () => boolean;
|
|
68
|
+
maxImageSizeBeforeResize: number;
|
|
67
69
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
68
70
|
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
69
71
|
export default _default;
|
|
@@ -52,6 +52,10 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
|
|
|
52
52
|
default: number;
|
|
53
53
|
type: NumberConstructor;
|
|
54
54
|
};
|
|
55
|
+
maxImageSizeBeforeResize: {
|
|
56
|
+
default: undefined;
|
|
57
|
+
type: NumberConstructor;
|
|
58
|
+
};
|
|
55
59
|
accept: {
|
|
56
60
|
default: undefined;
|
|
57
61
|
type: StringConstructor;
|
|
@@ -127,6 +131,10 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
|
|
|
127
131
|
default: number;
|
|
128
132
|
type: NumberConstructor;
|
|
129
133
|
};
|
|
134
|
+
maxImageSizeBeforeResize: {
|
|
135
|
+
default: undefined;
|
|
136
|
+
type: NumberConstructor;
|
|
137
|
+
};
|
|
130
138
|
accept: {
|
|
131
139
|
default: undefined;
|
|
132
140
|
type: StringConstructor;
|
|
@@ -195,6 +203,7 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
|
|
|
195
203
|
maxSize: number;
|
|
196
204
|
accept: string;
|
|
197
205
|
acceptedExtensions: string[];
|
|
206
|
+
maxImageSizeBeforeResize: number;
|
|
198
207
|
currentMedia: Media[];
|
|
199
208
|
uploadUrl: string;
|
|
200
209
|
pickerComponent: "BaseFilePicker" | "BaseFilePickerCrop";
|
package/package.json
CHANGED
|
@@ -35,6 +35,7 @@ import { UploadedFile } from '@/types/UploadedFile';
|
|
|
35
35
|
import { useSnackbarsStore } from '@/stores/snackbars';
|
|
36
36
|
import BaseLoadingCover from '@/components/BaseLoadingCover.vue';
|
|
37
37
|
import { BaseCropperConfig } from '@/types';
|
|
38
|
+
import { base64ToBlob, blobToBase64, resizeImageFromURI } from '@/utils';
|
|
38
39
|
import BaseFilePicker from './BaseFilePicker.vue';
|
|
39
40
|
import BaseFilePickerCrop from './BaseFilePickerCrop.vue';
|
|
40
41
|
import { t } from '@/i18n';
|
|
@@ -51,6 +52,7 @@ const props = withDefaults(
|
|
|
51
52
|
beforeUpload?: () => boolean;
|
|
52
53
|
twButton?: string;
|
|
53
54
|
maxSize?: number;
|
|
55
|
+
maxImageSizeBeforeResize?: number;
|
|
54
56
|
accept?: string;
|
|
55
57
|
acceptedExtensions?: string[];
|
|
56
58
|
cropper?: BaseCropperConfig | Record<string, any> | boolean | null;
|
|
@@ -66,6 +68,7 @@ const props = withDefaults(
|
|
|
66
68
|
},
|
|
67
69
|
twButton: '',
|
|
68
70
|
maxSize: undefined,
|
|
71
|
+
maxImageSizeBeforeResize: undefined,
|
|
69
72
|
accept: undefined,
|
|
70
73
|
acceptedExtensions: undefined,
|
|
71
74
|
cropper: true,
|
|
@@ -149,15 +152,16 @@ async function onFileSelect(files: File | File[]) {
|
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
async function processFileUpload(file: File): Promise<UploadedFile> {
|
|
155
|
+
const preparedFile = await processFileBeforeUpload(file);
|
|
152
156
|
|
|
153
157
|
const formData = new FormData();
|
|
154
158
|
|
|
155
|
-
formData.append('file',
|
|
159
|
+
formData.append('file', preparedFile);
|
|
156
160
|
|
|
157
161
|
const response = await http.post(props.url ?? config.upload_url, formData);
|
|
158
162
|
|
|
159
163
|
const payload = response.data as UploadedFile;
|
|
160
|
-
payload.original_file =
|
|
164
|
+
payload.original_file = preparedFile;
|
|
161
165
|
|
|
162
166
|
// Read file if image, add add data_url to payload
|
|
163
167
|
|
|
@@ -175,11 +179,137 @@ async function processFileUpload(file: File): Promise<UploadedFile> {
|
|
|
175
179
|
};
|
|
176
180
|
|
|
177
181
|
if (payload.mime_type.includes('image')) {
|
|
178
|
-
reader.readAsDataURL(
|
|
182
|
+
reader.readAsDataURL(preparedFile);
|
|
179
183
|
} else {
|
|
180
184
|
resolve(payload);
|
|
181
185
|
}
|
|
182
186
|
});
|
|
183
187
|
}
|
|
184
188
|
|
|
189
|
+
async function processFileBeforeUpload(file: File): Promise<File> {
|
|
190
|
+
if (!shouldAutoResize(file)) {
|
|
191
|
+
return file;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return await autoResizeImage(file);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function shouldAutoResize(file: File): boolean {
|
|
198
|
+
if (!props.maxImageSizeBeforeResize || props.maxImageSizeBeforeResize <= 0) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!file.type.includes('image')) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (['image/gif', 'image/svg+xml'].includes(file.type)) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (file.size <= props.maxImageSizeBeforeResize) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function autoResizeImage(file: File): Promise<File> {
|
|
218
|
+
if (!props.maxImageSizeBeforeResize) {
|
|
219
|
+
return file;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const source = await blobToBase64(file);
|
|
224
|
+
const dimensions = await getImageDimensions(source);
|
|
225
|
+
|
|
226
|
+
const resizeRatio = Math.sqrt(props.maxImageSizeBeforeResize / file.size);
|
|
227
|
+
|
|
228
|
+
let targetWidth = Math.max(1, Math.floor(dimensions.width * resizeRatio));
|
|
229
|
+
let targetHeight = Math.max(1, Math.floor(dimensions.height * resizeRatio));
|
|
230
|
+
|
|
231
|
+
let resizedBlob = await createResizedBlob(
|
|
232
|
+
source,
|
|
233
|
+
targetHeight,
|
|
234
|
+
targetWidth,
|
|
235
|
+
file.type
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
let attempts = 0;
|
|
239
|
+
|
|
240
|
+
while (
|
|
241
|
+
resizedBlob.size > props.maxImageSizeBeforeResize &&
|
|
242
|
+
attempts < 5 &&
|
|
243
|
+
targetWidth > 1 &&
|
|
244
|
+
targetHeight > 1
|
|
245
|
+
) {
|
|
246
|
+
targetWidth = Math.max(1, Math.floor(targetWidth * 0.8));
|
|
247
|
+
targetHeight = Math.max(1, Math.floor(targetHeight * 0.8));
|
|
248
|
+
resizedBlob = await createResizedBlob(
|
|
249
|
+
source,
|
|
250
|
+
targetHeight,
|
|
251
|
+
targetWidth,
|
|
252
|
+
file.type
|
|
253
|
+
);
|
|
254
|
+
attempts++;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (resizedBlob.size >= file.size) {
|
|
258
|
+
return file;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return new File(
|
|
262
|
+
[resizedBlob],
|
|
263
|
+
getResizedFileName(file.name, resizedBlob.type),
|
|
264
|
+
{
|
|
265
|
+
type: resizedBlob.type || file.type,
|
|
266
|
+
lastModified: file.lastModified,
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error(error);
|
|
271
|
+
return file;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function createResizedBlob(
|
|
276
|
+
source: string,
|
|
277
|
+
height: number,
|
|
278
|
+
width: number,
|
|
279
|
+
mimeType: string
|
|
280
|
+
): Promise<Blob> {
|
|
281
|
+
const resizedSource = await resizeImageFromURI(source, height, width);
|
|
282
|
+
return await base64ToBlob(resizedSource, `data:${mimeType}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function getImageDimensions(
|
|
286
|
+
source: string
|
|
287
|
+
): Promise<{ width: number; height: number }> {
|
|
288
|
+
return await new Promise((resolve, reject) => {
|
|
289
|
+
const image = new Image();
|
|
290
|
+
image.onload = () => {
|
|
291
|
+
resolve({
|
|
292
|
+
width: image.width,
|
|
293
|
+
height: image.height,
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
image.onerror = () => {
|
|
297
|
+
reject(new Error('Failed to load image dimensions'));
|
|
298
|
+
};
|
|
299
|
+
image.src = source;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function getResizedFileName(filename: string, mimeType: string): string {
|
|
304
|
+
if (mimeType != 'image/jpeg') {
|
|
305
|
+
return filename;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (filename.includes('.')) {
|
|
309
|
+
return filename.replace(/\.[^.]+$/, '.jpg');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return `${filename}.jpg`;
|
|
313
|
+
}
|
|
314
|
+
|
|
185
315
|
</script>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<BaseFileUploader
|
|
4
4
|
:component="pickerComponent"
|
|
5
5
|
:max-size="maxSize"
|
|
6
|
+
:max-image-size-before-resize="maxImageSizeBeforeResize"
|
|
6
7
|
:disabled="disabledInternal"
|
|
7
8
|
class="w-full"
|
|
8
9
|
tw-button="w-full"
|
|
@@ -142,6 +143,10 @@ const props = defineProps({
|
|
|
142
143
|
default: 20 * 1024 * 1024,
|
|
143
144
|
type: Number,
|
|
144
145
|
},
|
|
146
|
+
maxImageSizeBeforeResize: {
|
|
147
|
+
default: undefined,
|
|
148
|
+
type: Number,
|
|
149
|
+
},
|
|
145
150
|
accept: {
|
|
146
151
|
default: undefined,
|
|
147
152
|
type: String,
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
class="fixed inset-0 z-modal w-full overflow-y-auto overflow-x-hidden"
|
|
12
12
|
>
|
|
13
13
|
<div class="flex min-h-full w-full pt-20 sm:pt-0">
|
|
14
|
-
<div class="min-h-full grow">
|
|
14
|
+
<div class="min-h-full overflow-hidden grow">
|
|
15
15
|
<transition
|
|
16
16
|
appear
|
|
17
17
|
enter-active-class="duration-200 ease-out"
|