@availity/mui-file-selector 1.7.1 → 1.8.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/CHANGELOG.md +14 -0
- package/dist/index.d.mts +32 -7
- package/dist/index.d.ts +32 -7
- package/dist/index.js +164 -60
- package/dist/index.mjs +172 -68
- package/package.json +1 -1
- package/src/lib/Dropzone.tsx +63 -1
- package/src/lib/Dropzone2.tsx +60 -2
- package/src/lib/ErrorAlert.tsx +1 -0
- package/src/lib/FileSelector.tsx +19 -1
- package/src/lib/FileSelector2.tsx +8 -1
- package/src/lib/FileTypesMessage.tsx +14 -0
- package/src/lib/HeaderMessage.tsx +8 -3
package/dist/index.mjs
CHANGED
|
@@ -114,6 +114,66 @@ var FilePickerBtn = (_a) => {
|
|
|
114
114
|
] });
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
+
// src/lib/util.ts
|
|
118
|
+
import {
|
|
119
|
+
FileArchiveIcon,
|
|
120
|
+
FileCodeIcon,
|
|
121
|
+
FileCsvIcon,
|
|
122
|
+
FileExcelIcon,
|
|
123
|
+
FileIcon,
|
|
124
|
+
FileImageIcon,
|
|
125
|
+
FileLinesIcon,
|
|
126
|
+
FilePdfIcon,
|
|
127
|
+
FilePowerpointIcon,
|
|
128
|
+
FileWordIcon
|
|
129
|
+
} from "@availity/mui-icon";
|
|
130
|
+
function formatBytes(bytes, decimals = 2) {
|
|
131
|
+
if (!+bytes) return "0 Bytes";
|
|
132
|
+
const k = 1024;
|
|
133
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
134
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
135
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
136
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
|
137
|
+
}
|
|
138
|
+
var FILE_EXT_ICONS = {
|
|
139
|
+
png: FileImageIcon,
|
|
140
|
+
jpg: FileImageIcon,
|
|
141
|
+
jpeg: FileImageIcon,
|
|
142
|
+
gif: FileImageIcon,
|
|
143
|
+
csv: FileCsvIcon,
|
|
144
|
+
ppt: FilePowerpointIcon,
|
|
145
|
+
pptx: FilePowerpointIcon,
|
|
146
|
+
xls: FileExcelIcon,
|
|
147
|
+
xlsx: FileExcelIcon,
|
|
148
|
+
doc: FileWordIcon,
|
|
149
|
+
docx: FileWordIcon,
|
|
150
|
+
txt: FileLinesIcon,
|
|
151
|
+
text: FileLinesIcon,
|
|
152
|
+
zip: FileArchiveIcon,
|
|
153
|
+
"7zip": FileArchiveIcon,
|
|
154
|
+
xml: FileCodeIcon,
|
|
155
|
+
html: FileCodeIcon,
|
|
156
|
+
pdf: FilePdfIcon
|
|
157
|
+
};
|
|
158
|
+
var isValidKey = (key) => key ? key in FILE_EXT_ICONS : false;
|
|
159
|
+
var getFileExtension = (fileName) => {
|
|
160
|
+
var _a;
|
|
161
|
+
return ((_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase()) || "";
|
|
162
|
+
};
|
|
163
|
+
var getFileExtIcon = (fileName) => {
|
|
164
|
+
const ext = getFileExtension(fileName);
|
|
165
|
+
return isValidKey(ext) ? FILE_EXT_ICONS[ext] : FileIcon;
|
|
166
|
+
};
|
|
167
|
+
var dedupeErrors = (errors) => {
|
|
168
|
+
const dedupedErrors = errors.reduce((acc, error) => {
|
|
169
|
+
if (!acc.find((err) => err.code === error.code)) {
|
|
170
|
+
acc.push(error);
|
|
171
|
+
}
|
|
172
|
+
return acc;
|
|
173
|
+
}, []);
|
|
174
|
+
return dedupedErrors;
|
|
175
|
+
};
|
|
176
|
+
|
|
117
177
|
// src/lib/Dropzone.tsx
|
|
118
178
|
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
119
179
|
var outerBoxStyles = {
|
|
@@ -153,6 +213,7 @@ var Dropzone = ({
|
|
|
153
213
|
enableDropArea = true,
|
|
154
214
|
maxFiles,
|
|
155
215
|
maxSize,
|
|
216
|
+
maxTotalSize,
|
|
156
217
|
multiple,
|
|
157
218
|
name,
|
|
158
219
|
onChange,
|
|
@@ -205,7 +266,48 @@ var Dropzone = ({
|
|
|
205
266
|
}
|
|
206
267
|
setTotalSize((prev) => prev + newSize);
|
|
207
268
|
const previous = (_a2 = watch(name)) != null ? _a2 : [];
|
|
208
|
-
|
|
269
|
+
if (maxTotalSize) {
|
|
270
|
+
const currentTotalSize = previous.reduce((sum, file) => sum + file.size, 0);
|
|
271
|
+
let newSize2 = 0;
|
|
272
|
+
const availableSize = Math.max(0, maxTotalSize - currentTotalSize);
|
|
273
|
+
let sizeCounter = 0;
|
|
274
|
+
const cutoffIndex = acceptedFiles.findIndex((file) => {
|
|
275
|
+
sizeCounter += file.size;
|
|
276
|
+
return sizeCounter > availableSize;
|
|
277
|
+
});
|
|
278
|
+
if (cutoffIndex !== -1) {
|
|
279
|
+
const filesToAdd2 = acceptedFiles.slice(0, cutoffIndex === 0 ? 0 : cutoffIndex);
|
|
280
|
+
fileRejections.push({
|
|
281
|
+
file: acceptedFiles[cutoffIndex],
|
|
282
|
+
errors: [
|
|
283
|
+
{
|
|
284
|
+
code: "upload-too-large",
|
|
285
|
+
message: `Total upload size exceeds the limit of ${formatBytes(maxTotalSize)}.`
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
id: counter.increment()
|
|
289
|
+
});
|
|
290
|
+
acceptedFiles = filesToAdd2;
|
|
291
|
+
}
|
|
292
|
+
newSize2 = acceptedFiles.reduce((sum, file) => sum + file.size, 0);
|
|
293
|
+
setTotalSize((prev) => prev + newSize2);
|
|
294
|
+
}
|
|
295
|
+
const remainingSlots = maxFiles ? Math.max(0, maxFiles - previous.length) : acceptedFiles.length;
|
|
296
|
+
const filesToAdd = acceptedFiles.slice(0, remainingSlots);
|
|
297
|
+
setValue(name, previous.concat(filesToAdd));
|
|
298
|
+
if (maxFiles && acceptedFiles.length > remainingSlots) {
|
|
299
|
+
fileRejections.push({
|
|
300
|
+
file: acceptedFiles[remainingSlots],
|
|
301
|
+
// Use the first excess file
|
|
302
|
+
errors: [
|
|
303
|
+
{
|
|
304
|
+
code: "too-many-files",
|
|
305
|
+
message: `Too many files. You may only upload ${maxFiles} file(s).`
|
|
306
|
+
}
|
|
307
|
+
],
|
|
308
|
+
id: counter.increment()
|
|
309
|
+
});
|
|
310
|
+
}
|
|
209
311
|
if (fileRejections.length > 0) {
|
|
210
312
|
const TOO_MANY_FILES_CODE = "too-many-files";
|
|
211
313
|
let hasTooManyFiles = false;
|
|
@@ -306,68 +408,6 @@ import { CloudUploadIcon as CloudUploadIcon2, PlusIcon as PlusIcon2 } from "@ava
|
|
|
306
408
|
import { Box as Box2, Stack as Stack2 } from "@availity/mui-layout";
|
|
307
409
|
import { Typography as Typography2 } from "@availity/mui-typography";
|
|
308
410
|
import Upload from "@availity/upload-core";
|
|
309
|
-
|
|
310
|
-
// src/lib/util.ts
|
|
311
|
-
import {
|
|
312
|
-
FileArchiveIcon,
|
|
313
|
-
FileCodeIcon,
|
|
314
|
-
FileCsvIcon,
|
|
315
|
-
FileExcelIcon,
|
|
316
|
-
FileIcon,
|
|
317
|
-
FileImageIcon,
|
|
318
|
-
FileLinesIcon,
|
|
319
|
-
FilePdfIcon,
|
|
320
|
-
FilePowerpointIcon,
|
|
321
|
-
FileWordIcon
|
|
322
|
-
} from "@availity/mui-icon";
|
|
323
|
-
function formatBytes(bytes, decimals = 2) {
|
|
324
|
-
if (!+bytes) return "0 Bytes";
|
|
325
|
-
const k = 1024;
|
|
326
|
-
const dm = decimals < 0 ? 0 : decimals;
|
|
327
|
-
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
328
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
329
|
-
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
|
330
|
-
}
|
|
331
|
-
var FILE_EXT_ICONS = {
|
|
332
|
-
png: FileImageIcon,
|
|
333
|
-
jpg: FileImageIcon,
|
|
334
|
-
jpeg: FileImageIcon,
|
|
335
|
-
gif: FileImageIcon,
|
|
336
|
-
csv: FileCsvIcon,
|
|
337
|
-
ppt: FilePowerpointIcon,
|
|
338
|
-
pptx: FilePowerpointIcon,
|
|
339
|
-
xls: FileExcelIcon,
|
|
340
|
-
xlsx: FileExcelIcon,
|
|
341
|
-
doc: FileWordIcon,
|
|
342
|
-
docx: FileWordIcon,
|
|
343
|
-
txt: FileLinesIcon,
|
|
344
|
-
text: FileLinesIcon,
|
|
345
|
-
zip: FileArchiveIcon,
|
|
346
|
-
"7zip": FileArchiveIcon,
|
|
347
|
-
xml: FileCodeIcon,
|
|
348
|
-
html: FileCodeIcon,
|
|
349
|
-
pdf: FilePdfIcon
|
|
350
|
-
};
|
|
351
|
-
var isValidKey = (key) => key ? key in FILE_EXT_ICONS : false;
|
|
352
|
-
var getFileExtension = (fileName) => {
|
|
353
|
-
var _a;
|
|
354
|
-
return ((_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase()) || "";
|
|
355
|
-
};
|
|
356
|
-
var getFileExtIcon = (fileName) => {
|
|
357
|
-
const ext = getFileExtension(fileName);
|
|
358
|
-
return isValidKey(ext) ? FILE_EXT_ICONS[ext] : FileIcon;
|
|
359
|
-
};
|
|
360
|
-
var dedupeErrors = (errors) => {
|
|
361
|
-
const dedupedErrors = errors.reduce((acc, error) => {
|
|
362
|
-
if (!acc.find((err) => err.code === error.code)) {
|
|
363
|
-
acc.push(error);
|
|
364
|
-
}
|
|
365
|
-
return acc;
|
|
366
|
-
}, []);
|
|
367
|
-
return dedupedErrors;
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
// src/lib/Dropzone2.tsx
|
|
371
411
|
import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
372
412
|
var counter2 = createCounter();
|
|
373
413
|
function startUpload(file, options) {
|
|
@@ -389,6 +429,7 @@ var Dropzone2 = ({
|
|
|
389
429
|
enableDropArea = true,
|
|
390
430
|
maxFiles,
|
|
391
431
|
maxSize,
|
|
432
|
+
maxTotalSize,
|
|
392
433
|
multiple,
|
|
393
434
|
name,
|
|
394
435
|
onChange,
|
|
@@ -443,8 +484,50 @@ var Dropzone2 = ({
|
|
|
443
484
|
}
|
|
444
485
|
setTotalSize((prev) => prev + newSize);
|
|
445
486
|
const previous = (_a2 = watch(name)) != null ? _a2 : [];
|
|
446
|
-
|
|
487
|
+
if (maxTotalSize) {
|
|
488
|
+
const currentTotalSize = previous.reduce((sum, upload) => sum + upload.file.size, 0);
|
|
489
|
+
console.log({ previous });
|
|
490
|
+
let newSize2 = 0;
|
|
491
|
+
const availableSize = Math.max(0, maxTotalSize - currentTotalSize);
|
|
492
|
+
let sizeCounter = 0;
|
|
493
|
+
const cutoffIndex = acceptedFiles.findIndex((file) => {
|
|
494
|
+
sizeCounter += file.size;
|
|
495
|
+
return sizeCounter > availableSize;
|
|
496
|
+
});
|
|
497
|
+
if (cutoffIndex !== -1) {
|
|
498
|
+
const filesToAdd2 = acceptedFiles.slice(0, cutoffIndex === 0 ? 0 : cutoffIndex);
|
|
499
|
+
fileRejections.push({
|
|
500
|
+
file: acceptedFiles[cutoffIndex],
|
|
501
|
+
errors: [
|
|
502
|
+
{
|
|
503
|
+
code: "upload-too-large",
|
|
504
|
+
message: `Total upload size exceeds the limit of ${formatBytes(maxTotalSize)}.`
|
|
505
|
+
}
|
|
506
|
+
],
|
|
507
|
+
id: counter2.increment()
|
|
508
|
+
});
|
|
509
|
+
acceptedFiles = filesToAdd2;
|
|
510
|
+
}
|
|
511
|
+
newSize2 = acceptedFiles.reduce((sum, file) => sum + file.size, 0);
|
|
512
|
+
setTotalSize((prev) => prev + newSize2);
|
|
513
|
+
}
|
|
514
|
+
const remainingSlots = maxFiles ? Math.max(0, maxFiles - previous.length) : acceptedFiles.length;
|
|
515
|
+
const filesToAdd = acceptedFiles.slice(0, remainingSlots);
|
|
516
|
+
const uploads = filesToAdd.map((file) => startUpload(file, uploadOptions));
|
|
447
517
|
setValue(name, previous.concat(yield Promise.all(uploads)));
|
|
518
|
+
if (maxFiles && acceptedFiles.length > remainingSlots) {
|
|
519
|
+
fileRejections.push({
|
|
520
|
+
file: acceptedFiles[remainingSlots],
|
|
521
|
+
// Use the first excess file
|
|
522
|
+
errors: [
|
|
523
|
+
{
|
|
524
|
+
code: "too-many-files",
|
|
525
|
+
message: `Too many files. You may only upload ${maxFiles} file(s).`
|
|
526
|
+
}
|
|
527
|
+
],
|
|
528
|
+
id: counter2.increment()
|
|
529
|
+
});
|
|
530
|
+
}
|
|
448
531
|
if (fileRejections.length > 0) {
|
|
449
532
|
const TOO_MANY_FILES_CODE = "too-many-files";
|
|
450
533
|
let hasTooManyFiles = false;
|
|
@@ -541,6 +624,7 @@ import { List, ListItem } from "@availity/mui-list";
|
|
|
541
624
|
import { Fragment as Fragment4, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
542
625
|
var codes = {
|
|
543
626
|
"file-too-large": "File exceeds maximum size",
|
|
627
|
+
"upload-too-large": "File causes maximum total upload size to be exceeded",
|
|
544
628
|
"file-invalid-type": "File has an invalid type",
|
|
545
629
|
"file-too-small": "File is smaller than minimum size",
|
|
546
630
|
"too-many-file": "Too many files",
|
|
@@ -1052,14 +1136,18 @@ import { jsxs as jsxs11 } from "react/jsx-runtime";
|
|
|
1052
1136
|
var FileTypesMessage = ({
|
|
1053
1137
|
allowedFileTypes = [],
|
|
1054
1138
|
customSizeMessage,
|
|
1139
|
+
customTotalSizeMessage,
|
|
1055
1140
|
customTypesMessage,
|
|
1056
1141
|
maxFileSize,
|
|
1142
|
+
maxTotalSize,
|
|
1057
1143
|
variant = "caption"
|
|
1058
1144
|
}) => {
|
|
1059
1145
|
const fileSizeMsg = customSizeMessage || (typeof maxFileSize === "number" ? `Maximum file size is ${formatBytes(maxFileSize)}. ` : null);
|
|
1146
|
+
const totalFileSizeMsg = customTotalSizeMessage || (typeof maxTotalSize === "number" ? `Maximum total upload size is ${formatBytes(maxTotalSize)}. ` : null);
|
|
1060
1147
|
const fileTypesMsg = customTypesMessage || (allowedFileTypes.length > 0 ? `Supported file types include: ${allowedFileTypes.join(", ")}` : "All file types allowed.");
|
|
1061
1148
|
return /* @__PURE__ */ jsxs11(Typography4, { variant, children: [
|
|
1062
1149
|
fileSizeMsg,
|
|
1150
|
+
totalFileSizeMsg,
|
|
1063
1151
|
fileTypesMsg
|
|
1064
1152
|
] });
|
|
1065
1153
|
};
|
|
@@ -1067,12 +1155,14 @@ var FileTypesMessage = ({
|
|
|
1067
1155
|
// src/lib/HeaderMessage.tsx
|
|
1068
1156
|
import { Typography as Typography5 } from "@availity/mui-typography";
|
|
1069
1157
|
import { jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1070
|
-
var HeaderMessage = ({ maxFiles, maxSize }) => {
|
|
1158
|
+
var HeaderMessage = ({ maxFiles, maxSize, maxTotalSize }) => {
|
|
1071
1159
|
return /* @__PURE__ */ jsxs12(Typography5, { variant: "h6", children: [
|
|
1072
1160
|
"Attach up to ",
|
|
1073
1161
|
maxFiles,
|
|
1074
1162
|
" file(s), with a maximum individual size of ",
|
|
1075
|
-
formatBytes(maxSize)
|
|
1163
|
+
formatBytes(maxSize),
|
|
1164
|
+
" ",
|
|
1165
|
+
maxTotalSize && `and a maximum total size of ${formatBytes(maxTotalSize)}`
|
|
1076
1166
|
] });
|
|
1077
1167
|
};
|
|
1078
1168
|
|
|
@@ -1087,6 +1177,7 @@ var FileSelector = ({
|
|
|
1087
1177
|
clientId,
|
|
1088
1178
|
children,
|
|
1089
1179
|
customSizeMessage,
|
|
1180
|
+
customTotalSizeMessage,
|
|
1090
1181
|
customTypesMessage,
|
|
1091
1182
|
customerId,
|
|
1092
1183
|
customFileRow,
|
|
@@ -1097,6 +1188,7 @@ var FileSelector = ({
|
|
|
1097
1188
|
label = "Upload file",
|
|
1098
1189
|
maxFiles,
|
|
1099
1190
|
maxSize,
|
|
1191
|
+
maxTotalSize,
|
|
1100
1192
|
multiple = true,
|
|
1101
1193
|
onChange,
|
|
1102
1194
|
onDrop,
|
|
@@ -1160,6 +1252,7 @@ var FileSelector = ({
|
|
|
1160
1252
|
enableDropArea,
|
|
1161
1253
|
maxFiles,
|
|
1162
1254
|
maxSize,
|
|
1255
|
+
maxTotalSize,
|
|
1163
1256
|
multiple,
|
|
1164
1257
|
onChange,
|
|
1165
1258
|
onDrop,
|
|
@@ -1173,7 +1266,9 @@ var FileSelector = ({
|
|
|
1173
1266
|
{
|
|
1174
1267
|
allowedFileTypes,
|
|
1175
1268
|
maxFileSize: maxSize,
|
|
1269
|
+
maxTotalSize,
|
|
1176
1270
|
customSizeMessage,
|
|
1271
|
+
customTotalSizeMessage,
|
|
1177
1272
|
customTypesMessage,
|
|
1178
1273
|
variant: "caption"
|
|
1179
1274
|
}
|
|
@@ -1181,11 +1276,12 @@ var FileSelector = ({
|
|
|
1181
1276
|
children
|
|
1182
1277
|
] }) : /* @__PURE__ */ jsxs13(Grid4, { container: true, rowSpacing: 3, flexDirection: "column", children: [
|
|
1183
1278
|
/* @__PURE__ */ jsxs13(Grid4, { children: [
|
|
1184
|
-
/* @__PURE__ */ jsx14(HeaderMessage, { maxFiles, maxSize }),
|
|
1279
|
+
/* @__PURE__ */ jsx14(HeaderMessage, { maxFiles, maxSize, maxTotalSize }),
|
|
1185
1280
|
/* @__PURE__ */ jsx14(
|
|
1186
1281
|
FileTypesMessage,
|
|
1187
1282
|
{
|
|
1188
1283
|
allowedFileTypes,
|
|
1284
|
+
customTotalSizeMessage,
|
|
1189
1285
|
customSizeMessage,
|
|
1190
1286
|
customTypesMessage,
|
|
1191
1287
|
variant: "body2"
|
|
@@ -1202,6 +1298,7 @@ var FileSelector = ({
|
|
|
1202
1298
|
enableDropArea,
|
|
1203
1299
|
maxFiles,
|
|
1204
1300
|
maxSize,
|
|
1301
|
+
maxTotalSize,
|
|
1205
1302
|
multiple,
|
|
1206
1303
|
onChange,
|
|
1207
1304
|
onDrop,
|
|
@@ -1263,6 +1360,7 @@ var FileSelector2 = ({
|
|
|
1263
1360
|
clientId,
|
|
1264
1361
|
children,
|
|
1265
1362
|
customSizeMessage,
|
|
1363
|
+
customTotalSizeMessage,
|
|
1266
1364
|
customTypesMessage,
|
|
1267
1365
|
customerId,
|
|
1268
1366
|
customFileRow,
|
|
@@ -1273,6 +1371,7 @@ var FileSelector2 = ({
|
|
|
1273
1371
|
label = "Upload file",
|
|
1274
1372
|
maxFiles,
|
|
1275
1373
|
maxSize,
|
|
1374
|
+
maxTotalSize,
|
|
1276
1375
|
multiple = true,
|
|
1277
1376
|
onChange,
|
|
1278
1377
|
onDrop,
|
|
@@ -1333,6 +1432,7 @@ var FileSelector2 = ({
|
|
|
1333
1432
|
enableDropArea,
|
|
1334
1433
|
maxFiles,
|
|
1335
1434
|
maxSize,
|
|
1435
|
+
maxTotalSize,
|
|
1336
1436
|
multiple,
|
|
1337
1437
|
onChange,
|
|
1338
1438
|
onDrop,
|
|
@@ -1347,7 +1447,9 @@ var FileSelector2 = ({
|
|
|
1347
1447
|
{
|
|
1348
1448
|
allowedFileTypes,
|
|
1349
1449
|
maxFileSize: maxSize,
|
|
1450
|
+
maxTotalSize,
|
|
1350
1451
|
customSizeMessage,
|
|
1452
|
+
customTotalSizeMessage,
|
|
1351
1453
|
customTypesMessage,
|
|
1352
1454
|
variant: "caption"
|
|
1353
1455
|
}
|
|
@@ -1355,12 +1457,13 @@ var FileSelector2 = ({
|
|
|
1355
1457
|
children
|
|
1356
1458
|
] }) : /* @__PURE__ */ jsxs14(Grid5, { container: true, rowSpacing: 3, flexDirection: "column", children: [
|
|
1357
1459
|
/* @__PURE__ */ jsxs14(Grid5, { children: [
|
|
1358
|
-
/* @__PURE__ */ jsx15(HeaderMessage, { maxFiles, maxSize }),
|
|
1460
|
+
/* @__PURE__ */ jsx15(HeaderMessage, { maxFiles, maxSize, maxTotalSize }),
|
|
1359
1461
|
/* @__PURE__ */ jsx15(
|
|
1360
1462
|
FileTypesMessage,
|
|
1361
1463
|
{
|
|
1362
1464
|
allowedFileTypes,
|
|
1363
1465
|
customSizeMessage,
|
|
1466
|
+
customTotalSizeMessage,
|
|
1364
1467
|
customTypesMessage,
|
|
1365
1468
|
variant: "body2"
|
|
1366
1469
|
}
|
|
@@ -1376,6 +1479,7 @@ var FileSelector2 = ({
|
|
|
1376
1479
|
enableDropArea,
|
|
1377
1480
|
maxFiles,
|
|
1378
1481
|
maxSize,
|
|
1482
|
+
maxTotalSize,
|
|
1379
1483
|
multiple,
|
|
1380
1484
|
onChange,
|
|
1381
1485
|
onDrop,
|
package/package.json
CHANGED
package/src/lib/Dropzone.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import { Box, Stack } from '@availity/mui-layout';
|
|
|
11
11
|
import { Typography } from '@availity/mui-typography';
|
|
12
12
|
|
|
13
13
|
import { FilePickerBtn } from './FilePickerBtn';
|
|
14
|
+
import { formatBytes } from './util';
|
|
14
15
|
|
|
15
16
|
export const outerBoxStyles = {
|
|
16
17
|
backgroundColor: 'background.secondary',
|
|
@@ -66,6 +67,10 @@ export type DropzoneProps = {
|
|
|
66
67
|
* Maximum size of each file in bytes
|
|
67
68
|
*/
|
|
68
69
|
maxSize?: number;
|
|
70
|
+
/**
|
|
71
|
+
* Maximum size of total upload in bytes
|
|
72
|
+
*/
|
|
73
|
+
maxTotalSize?: number;
|
|
69
74
|
/**
|
|
70
75
|
* Whether multiple file selection is allowed
|
|
71
76
|
*/
|
|
@@ -110,6 +115,7 @@ export const Dropzone = ({
|
|
|
110
115
|
enableDropArea = true,
|
|
111
116
|
maxFiles,
|
|
112
117
|
maxSize,
|
|
118
|
+
maxTotalSize,
|
|
113
119
|
multiple,
|
|
114
120
|
name,
|
|
115
121
|
onChange,
|
|
@@ -169,8 +175,64 @@ export const Dropzone = ({
|
|
|
169
175
|
|
|
170
176
|
const previous = watch(name) ?? [];
|
|
171
177
|
|
|
178
|
+
if (maxTotalSize) {
|
|
179
|
+
// Calculate current total size
|
|
180
|
+
const currentTotalSize = previous.reduce((sum: number, file: File) => sum + file.size, 0);
|
|
181
|
+
let newSize = 0;
|
|
182
|
+
|
|
183
|
+
const availableSize = Math.max(0, maxTotalSize - currentTotalSize);
|
|
184
|
+
let sizeCounter = 0;
|
|
185
|
+
|
|
186
|
+
// Find the index where we exceed the total size limit
|
|
187
|
+
const cutoffIndex = acceptedFiles.findIndex((file) => {
|
|
188
|
+
sizeCounter += file.size;
|
|
189
|
+
return sizeCounter > availableSize;
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// If we found files that exceed the limit
|
|
193
|
+
if (cutoffIndex !== -1) {
|
|
194
|
+
// Files that fit within the size limit
|
|
195
|
+
const filesToAdd = acceptedFiles.slice(0, cutoffIndex === 0 ? 0 : cutoffIndex);
|
|
196
|
+
|
|
197
|
+
// Create rejection for excess files
|
|
198
|
+
fileRejections.push({
|
|
199
|
+
file: acceptedFiles[cutoffIndex],
|
|
200
|
+
errors: [
|
|
201
|
+
{
|
|
202
|
+
code: 'upload-too-large',
|
|
203
|
+
message: `Total upload size exceeds the limit of ${formatBytes(maxTotalSize)}.`,
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
id: counter.increment(),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Update acceptedFiles to only include files that fit
|
|
210
|
+
acceptedFiles = filesToAdd;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Calculate size of accepted files for the state update
|
|
214
|
+
newSize = acceptedFiles.reduce((sum, file) => sum + file.size, 0);
|
|
215
|
+
setTotalSize((prev) => prev + newSize);
|
|
216
|
+
}
|
|
217
|
+
|
|
172
218
|
// Set accepted files to form context
|
|
173
|
-
|
|
219
|
+
const remainingSlots = maxFiles ? Math.max(0, maxFiles - previous.length) : acceptedFiles.length;
|
|
220
|
+
const filesToAdd = acceptedFiles.slice(0, remainingSlots);
|
|
221
|
+
setValue(name, previous.concat(filesToAdd));
|
|
222
|
+
|
|
223
|
+
// Add rejections for excess files if needed
|
|
224
|
+
if (maxFiles && acceptedFiles.length > remainingSlots) {
|
|
225
|
+
fileRejections.push({
|
|
226
|
+
file: acceptedFiles[remainingSlots], // Use the first excess file
|
|
227
|
+
errors: [
|
|
228
|
+
{
|
|
229
|
+
code: 'too-many-files',
|
|
230
|
+
message: `Too many files. You may only upload ${maxFiles} file(s).`,
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
id: counter.increment(),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
174
236
|
|
|
175
237
|
if (fileRejections.length > 0) {
|
|
176
238
|
const TOO_MANY_FILES_CODE = 'too-many-files';
|
package/src/lib/Dropzone2.tsx
CHANGED
|
@@ -12,7 +12,7 @@ import type { UploadOptions } from '@availity/upload-core';
|
|
|
12
12
|
import type { OnSuccessPayload } from 'tus-js-client';
|
|
13
13
|
|
|
14
14
|
import { FilePickerBtn } from './FilePickerBtn';
|
|
15
|
-
import { dedupeErrors } from './util';
|
|
15
|
+
import { dedupeErrors, formatBytes } from './util';
|
|
16
16
|
import { createCounter, DropzoneContainer, innerBoxStyles, outerBoxStyles } from './Dropzone';
|
|
17
17
|
import type { DropzoneProps } from './Dropzone';
|
|
18
18
|
|
|
@@ -59,6 +59,7 @@ export const Dropzone2 = ({
|
|
|
59
59
|
enableDropArea = true,
|
|
60
60
|
maxFiles,
|
|
61
61
|
maxSize,
|
|
62
|
+
maxTotalSize,
|
|
62
63
|
multiple,
|
|
63
64
|
name,
|
|
64
65
|
onChange,
|
|
@@ -121,10 +122,67 @@ export const Dropzone2 = ({
|
|
|
121
122
|
|
|
122
123
|
const previous = watch(name) ?? [];
|
|
123
124
|
|
|
125
|
+
if (maxTotalSize) {
|
|
126
|
+
// Calculate current total size
|
|
127
|
+
const currentTotalSize = previous.reduce((sum: number, upload: Upload) => sum + upload.file.size, 0);
|
|
128
|
+
console.log({ previous });
|
|
129
|
+
let newSize = 0;
|
|
130
|
+
|
|
131
|
+
const availableSize = Math.max(0, maxTotalSize - currentTotalSize);
|
|
132
|
+
let sizeCounter = 0;
|
|
133
|
+
|
|
134
|
+
// Find the index where we exceed the total size limit
|
|
135
|
+
const cutoffIndex = acceptedFiles.findIndex((file) => {
|
|
136
|
+
sizeCounter += file.size;
|
|
137
|
+
return sizeCounter > availableSize;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// If we found files that exceed the limit
|
|
141
|
+
if (cutoffIndex !== -1) {
|
|
142
|
+
// Files that fit within the size limit
|
|
143
|
+
const filesToAdd = acceptedFiles.slice(0, cutoffIndex === 0 ? 0 : cutoffIndex);
|
|
144
|
+
|
|
145
|
+
// Create rejection for excess files
|
|
146
|
+
fileRejections.push({
|
|
147
|
+
file: acceptedFiles[cutoffIndex],
|
|
148
|
+
errors: [
|
|
149
|
+
{
|
|
150
|
+
code: 'upload-too-large',
|
|
151
|
+
message: `Total upload size exceeds the limit of ${formatBytes(maxTotalSize)}.`,
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
id: counter.increment(),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Update acceptedFiles to only include files that fit
|
|
158
|
+
acceptedFiles = filesToAdd;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Calculate size of accepted files for the state update
|
|
162
|
+
newSize = acceptedFiles.reduce((sum, file) => sum + file.size, 0);
|
|
163
|
+
setTotalSize((prev) => prev + newSize);
|
|
164
|
+
}
|
|
165
|
+
|
|
124
166
|
// Set accepted files to form context
|
|
125
|
-
const
|
|
167
|
+
const remainingSlots = maxFiles ? Math.max(0, maxFiles - previous.length) : acceptedFiles.length;
|
|
168
|
+
const filesToAdd = acceptedFiles.slice(0, remainingSlots);
|
|
169
|
+
const uploads = filesToAdd.map((file) => startUpload(file, uploadOptions));
|
|
126
170
|
setValue(name, previous.concat(await Promise.all(uploads)));
|
|
127
171
|
|
|
172
|
+
// Add rejections for excess files if needed
|
|
173
|
+
if (maxFiles && acceptedFiles.length > remainingSlots) {
|
|
174
|
+
fileRejections.push({
|
|
175
|
+
file: acceptedFiles[remainingSlots], // Use the first excess file
|
|
176
|
+
errors: [
|
|
177
|
+
{
|
|
178
|
+
code: 'too-many-files',
|
|
179
|
+
message: `Too many files. You may only upload ${maxFiles} file(s).`,
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
id: counter.increment(),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
128
186
|
if (fileRejections.length > 0) {
|
|
129
187
|
const TOO_MANY_FILES_CODE = 'too-many-files';
|
|
130
188
|
let hasTooManyFiles = false;
|
package/src/lib/ErrorAlert.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { formatBytes } from './util';
|
|
|
5
5
|
|
|
6
6
|
const codes: Record<string, string> = {
|
|
7
7
|
'file-too-large': 'File exceeds maximum size',
|
|
8
|
+
'upload-too-large': 'File causes maximum total upload size to be exceeded',
|
|
8
9
|
'file-invalid-type': 'File has an invalid type',
|
|
9
10
|
'file-too-small': 'File is smaller than minimum size',
|
|
10
11
|
'too-many-file': 'Too many files',
|
package/src/lib/FileSelector.tsx
CHANGED
|
@@ -59,6 +59,11 @@ export type FileSelectorProps = {
|
|
|
59
59
|
* Overrides the standard file size message
|
|
60
60
|
*/
|
|
61
61
|
customSizeMessage?: React.ReactNode;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Overrides the standard total upload size message
|
|
65
|
+
*/
|
|
66
|
+
customTotalSizeMessage?: React.ReactNode;
|
|
62
67
|
/**
|
|
63
68
|
* Overrides the standard file types message
|
|
64
69
|
*/
|
|
@@ -98,10 +103,16 @@ export type FileSelectorProps = {
|
|
|
98
103
|
* Use Kibi or Mibibytes. eg: 1kb = 1024 bytes; 1mb = 1024kb
|
|
99
104
|
*/
|
|
100
105
|
maxSize: number;
|
|
106
|
+
/**
|
|
107
|
+
* Maximum size allowed for total upload in bytes
|
|
108
|
+
* Use Kibi or Mibibytes. eg: 1kb = 1024 bytes; 1mb = 1024kb
|
|
109
|
+
*/
|
|
110
|
+
maxTotalSize?: number;
|
|
101
111
|
/**
|
|
102
112
|
* Whether multiple file selection is allowed
|
|
103
113
|
* @default true
|
|
104
114
|
*/
|
|
115
|
+
|
|
105
116
|
multiple?: boolean;
|
|
106
117
|
/**
|
|
107
118
|
* Callback fired when files are selected
|
|
@@ -145,6 +156,7 @@ export const FileSelector = ({
|
|
|
145
156
|
clientId,
|
|
146
157
|
children,
|
|
147
158
|
customSizeMessage,
|
|
159
|
+
customTotalSizeMessage,
|
|
148
160
|
customTypesMessage,
|
|
149
161
|
customerId,
|
|
150
162
|
customFileRow,
|
|
@@ -155,6 +167,7 @@ export const FileSelector = ({
|
|
|
155
167
|
label = 'Upload file',
|
|
156
168
|
maxFiles,
|
|
157
169
|
maxSize,
|
|
170
|
+
maxTotalSize,
|
|
158
171
|
multiple = true,
|
|
159
172
|
onChange,
|
|
160
173
|
onDrop,
|
|
@@ -240,6 +253,7 @@ export const FileSelector = ({
|
|
|
240
253
|
enableDropArea={enableDropArea}
|
|
241
254
|
maxFiles={maxFiles}
|
|
242
255
|
maxSize={maxSize}
|
|
256
|
+
maxTotalSize={maxTotalSize}
|
|
243
257
|
multiple={multiple}
|
|
244
258
|
onChange={onChange}
|
|
245
259
|
onDrop={onDrop}
|
|
@@ -250,7 +264,9 @@ export const FileSelector = ({
|
|
|
250
264
|
<FileTypesMessage
|
|
251
265
|
allowedFileTypes={allowedFileTypes}
|
|
252
266
|
maxFileSize={maxSize}
|
|
267
|
+
maxTotalSize={maxTotalSize}
|
|
253
268
|
customSizeMessage={customSizeMessage}
|
|
269
|
+
customTotalSizeMessage={customTotalSizeMessage}
|
|
254
270
|
customTypesMessage={customTypesMessage}
|
|
255
271
|
variant="caption"
|
|
256
272
|
/>
|
|
@@ -259,9 +275,10 @@ export const FileSelector = ({
|
|
|
259
275
|
) : (
|
|
260
276
|
<Grid container rowSpacing={3} flexDirection="column">
|
|
261
277
|
<Grid>
|
|
262
|
-
<HeaderMessage maxFiles={maxFiles} maxSize={maxSize} />
|
|
278
|
+
<HeaderMessage maxFiles={maxFiles} maxSize={maxSize} maxTotalSize={maxTotalSize} />
|
|
263
279
|
<FileTypesMessage
|
|
264
280
|
allowedFileTypes={allowedFileTypes}
|
|
281
|
+
customTotalSizeMessage={customTotalSizeMessage}
|
|
265
282
|
customSizeMessage={customSizeMessage}
|
|
266
283
|
customTypesMessage={customTypesMessage}
|
|
267
284
|
variant="body2"
|
|
@@ -276,6 +293,7 @@ export const FileSelector = ({
|
|
|
276
293
|
enableDropArea={enableDropArea}
|
|
277
294
|
maxFiles={maxFiles}
|
|
278
295
|
maxSize={maxSize}
|
|
296
|
+
maxTotalSize={maxTotalSize}
|
|
279
297
|
multiple={multiple}
|
|
280
298
|
onChange={onChange}
|
|
281
299
|
onDrop={onDrop}
|