@visns-studio/visns-components 5.4.9 → 5.4.10
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/package.json
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"array-move": "^4.0.0",
|
|
16
16
|
"awesome-debounce-promise": "^2.1.0",
|
|
17
17
|
"axios": "^1.8.4",
|
|
18
|
+
"browser-image-compression": "^2.0.2",
|
|
18
19
|
"dayjs": "^1.11.13",
|
|
19
20
|
"fabric": "^6.6.2",
|
|
20
21
|
"file-saver": "^2.0.5",
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"reactjs-popup": "^2.0.6",
|
|
54
55
|
"style-loader": "^4.0.0",
|
|
55
56
|
"swapy": "^1.0.5",
|
|
57
|
+
"sweetalert2": "^11.17.2",
|
|
56
58
|
"truncate": "^3.0.0",
|
|
57
59
|
"uuid": "^10.0.0",
|
|
58
60
|
"validator": "^13.15.0",
|
|
@@ -79,7 +81,7 @@
|
|
|
79
81
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
80
82
|
},
|
|
81
83
|
"name": "@visns-studio/visns-components",
|
|
82
|
-
"version": "5.4.
|
|
84
|
+
"version": "5.4.10",
|
|
83
85
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
84
86
|
"main": "src/index.js",
|
|
85
87
|
"files": [
|
|
@@ -100,5 +102,5 @@
|
|
|
100
102
|
"url": "https://github.com/visnsstudio/visns-components/issues"
|
|
101
103
|
},
|
|
102
104
|
"homepage": "https://github.com/visnsstudio/visns-components#readme",
|
|
103
|
-
"packageManager": "yarn@4.
|
|
105
|
+
"packageManager": "yarn@4.7.0"
|
|
104
106
|
}
|
|
@@ -6,6 +6,7 @@ import { CircleX, File, TrashCan } from 'akar-icons';
|
|
|
6
6
|
import { toast } from 'react-toastify';
|
|
7
7
|
import { arrayMoveImmutable } from 'array-move';
|
|
8
8
|
import Popup from 'reactjs-popup';
|
|
9
|
+
import imageCompression from 'browser-image-compression';
|
|
9
10
|
|
|
10
11
|
import 'react-confirm-alert/src/react-confirm-alert.css';
|
|
11
12
|
|
|
@@ -11,6 +11,7 @@ import _ from 'lodash';
|
|
|
11
11
|
import ReactDataGrid from '@inovua/reactdatagrid-community';
|
|
12
12
|
import { CircleX } from 'akar-icons';
|
|
13
13
|
import { confirmAlert } from 'react-confirm-alert';
|
|
14
|
+
import imageCompression from 'browser-image-compression';
|
|
14
15
|
|
|
15
16
|
import '@inovua/reactdatagrid-community/index.css';
|
|
16
17
|
import 'react-confirm-alert/src/react-confirm-alert.css';
|
|
@@ -22,6 +23,49 @@ import Table from './DataGrid';
|
|
|
22
23
|
|
|
23
24
|
import styles from './styles/Form.module.scss';
|
|
24
25
|
|
|
26
|
+
// Helper function to check if a file is an image and optimize it if needed
|
|
27
|
+
const optimizeImageIfNeeded = async (file) => {
|
|
28
|
+
// Check if the file is an image
|
|
29
|
+
const isImage = file.type.startsWith('image/');
|
|
30
|
+
|
|
31
|
+
if (isImage) {
|
|
32
|
+
// Image compression options
|
|
33
|
+
const options = {
|
|
34
|
+
maxSizeMB: 1, // Max file size in MB
|
|
35
|
+
maxWidthOrHeight: 1920, // Max width or height in pixels
|
|
36
|
+
useWebWorker: true,
|
|
37
|
+
fileType: file.type,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
// Show optimization toast
|
|
42
|
+
const optimizingToastId = toast.info(`Optimizing ${file.name}...`, {
|
|
43
|
+
autoClose: false,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Compress the image
|
|
47
|
+
const compressedFile = await imageCompression(file, options);
|
|
48
|
+
|
|
49
|
+
// Update toast
|
|
50
|
+
toast.update(optimizingToastId, {
|
|
51
|
+
render: `${file.name} optimized successfully!`,
|
|
52
|
+
type: 'success',
|
|
53
|
+
autoClose: 1000,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Return the compressed file
|
|
57
|
+
return compressedFile;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('Error optimizing image:', error);
|
|
60
|
+
// If optimization fails, return the original file
|
|
61
|
+
return file;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// If not an image, return the original file
|
|
66
|
+
return file;
|
|
67
|
+
};
|
|
68
|
+
|
|
25
69
|
function Form({
|
|
26
70
|
ajaxSetting,
|
|
27
71
|
api,
|
|
@@ -701,6 +745,10 @@ function Form({
|
|
|
701
745
|
if (Array.isArray(formData[fileKey])) {
|
|
702
746
|
const uploadPromises = formData[fileKey].map(
|
|
703
747
|
async (file) => {
|
|
748
|
+
// Optimize image if it's an image file
|
|
749
|
+
const optimizedFile =
|
|
750
|
+
await optimizeImageIfNeeded(file);
|
|
751
|
+
|
|
704
752
|
const toastId = toast.info(
|
|
705
753
|
`Uploading ${file.name}`,
|
|
706
754
|
{
|
|
@@ -709,14 +757,19 @@ function Form({
|
|
|
709
757
|
}
|
|
710
758
|
);
|
|
711
759
|
|
|
712
|
-
const fileRes = await Vapor.store(
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
760
|
+
const fileRes = await Vapor.store(
|
|
761
|
+
optimizedFile,
|
|
762
|
+
{
|
|
763
|
+
progress: (progress) => {
|
|
764
|
+
setUploadProgress(
|
|
765
|
+
progress * 100
|
|
766
|
+
);
|
|
767
|
+
toast.update(toastId, {
|
|
768
|
+
progress,
|
|
769
|
+
});
|
|
770
|
+
},
|
|
771
|
+
}
|
|
772
|
+
);
|
|
720
773
|
|
|
721
774
|
toast.update(toastId, {
|
|
722
775
|
render: `${file.name} uploaded successfully!`,
|
|
@@ -729,7 +782,7 @@ function Form({
|
|
|
729
782
|
key: fileRes.key,
|
|
730
783
|
bucket: fileRes.bucket,
|
|
731
784
|
filename: file.name,
|
|
732
|
-
filesize:
|
|
785
|
+
filesize: optimizedFile.size,
|
|
733
786
|
extension: fileRes.extension,
|
|
734
787
|
file_relationship: fileKey,
|
|
735
788
|
fileable_field:
|
|
@@ -742,6 +795,11 @@ function Form({
|
|
|
742
795
|
uploadPromises
|
|
743
796
|
);
|
|
744
797
|
} else {
|
|
798
|
+
// Optimize image if it's an image file
|
|
799
|
+
const optimizedFile = await optimizeImageIfNeeded(
|
|
800
|
+
formData[fileKey]
|
|
801
|
+
);
|
|
802
|
+
|
|
745
803
|
const toastId = toast.info(
|
|
746
804
|
`Uploading ${formData[fileKey].name}`,
|
|
747
805
|
{
|
|
@@ -750,17 +808,14 @@ function Form({
|
|
|
750
808
|
}
|
|
751
809
|
);
|
|
752
810
|
|
|
753
|
-
const fileRes = await Vapor.store(
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
},
|
|
762
|
-
}
|
|
763
|
-
);
|
|
811
|
+
const fileRes = await Vapor.store(optimizedFile, {
|
|
812
|
+
progress: (progress) => {
|
|
813
|
+
setUploadProgress(progress * 100);
|
|
814
|
+
toast.update(toastId, {
|
|
815
|
+
progress,
|
|
816
|
+
});
|
|
817
|
+
},
|
|
818
|
+
});
|
|
764
819
|
|
|
765
820
|
toast.update(toastId, {
|
|
766
821
|
render: `${formData[fileKey].name} uploaded successfully!`,
|
|
@@ -772,7 +827,7 @@ function Form({
|
|
|
772
827
|
formData['key'] = fileRes.key;
|
|
773
828
|
formData['bucket'] = fileRes.bucket;
|
|
774
829
|
formData['filename'] = formData[fileKey].name;
|
|
775
|
-
formData['filesize'] =
|
|
830
|
+
formData['filesize'] = optimizedFile.size;
|
|
776
831
|
formData['extension'] = fileRes.extension;
|
|
777
832
|
formData['file_relationship'] = fileKey;
|
|
778
833
|
formData['fileable_field'] =
|
|
@@ -930,8 +985,9 @@ function Form({
|
|
|
930
985
|
method,
|
|
931
986
|
payload = {};
|
|
932
987
|
|
|
933
|
-
|
|
934
|
-
|
|
988
|
+
// Helper function to get dataId from ajaxSetting
|
|
989
|
+
const getDataId = () => {
|
|
990
|
+
if (ajaxSetting?.where) {
|
|
935
991
|
for (const item of ajaxSetting.where) {
|
|
936
992
|
if (
|
|
937
993
|
item.hasOwnProperty('urlParam') &&
|
|
@@ -940,9 +996,9 @@ function Form({
|
|
|
940
996
|
return item.value;
|
|
941
997
|
}
|
|
942
998
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
}
|
|
999
|
+
}
|
|
1000
|
+
return 0;
|
|
1001
|
+
};
|
|
946
1002
|
|
|
947
1003
|
if (
|
|
948
1004
|
formSettings.hasOwnProperty('json') &&
|
|
@@ -14,6 +14,7 @@ import truncate from 'truncate';
|
|
|
14
14
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
|
15
15
|
import { saveAs } from 'file-saver';
|
|
16
16
|
import { toast } from 'react-toastify';
|
|
17
|
+
import imageCompression from 'browser-image-compression';
|
|
17
18
|
import {
|
|
18
19
|
CircleCheck,
|
|
19
20
|
Copy,
|
|
@@ -43,6 +44,49 @@ import styles from './styles/GenericDetail.module.scss';
|
|
|
43
44
|
const localizer = momentLocalizer(moment);
|
|
44
45
|
const DnDCalendar = withDragAndDrop(Calendar);
|
|
45
46
|
|
|
47
|
+
// Helper function to check if a file is an image and optimize it if needed
|
|
48
|
+
const optimizeImageIfNeeded = async (file) => {
|
|
49
|
+
// Check if the file is an image
|
|
50
|
+
const isImage = file.type.startsWith('image/');
|
|
51
|
+
|
|
52
|
+
if (isImage) {
|
|
53
|
+
// Image compression options
|
|
54
|
+
const options = {
|
|
55
|
+
maxSizeMB: 1, // Max file size in MB
|
|
56
|
+
maxWidthOrHeight: 1920, // Max width or height in pixels
|
|
57
|
+
useWebWorker: true,
|
|
58
|
+
fileType: file.type,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Show optimization toast
|
|
63
|
+
const optimizingToastId = toast.info(`Optimizing ${file.name}...`, {
|
|
64
|
+
autoClose: false,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Compress the image
|
|
68
|
+
const compressedFile = await imageCompression(file, options);
|
|
69
|
+
|
|
70
|
+
// Update toast
|
|
71
|
+
toast.update(optimizingToastId, {
|
|
72
|
+
render: `${file.name} optimized successfully!`,
|
|
73
|
+
type: 'success',
|
|
74
|
+
autoClose: 1000,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Return the compressed file
|
|
78
|
+
return compressedFile;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Error optimizing image:', error);
|
|
81
|
+
// If optimization fails, return the original file
|
|
82
|
+
return file;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// If not an image, return the original file
|
|
87
|
+
return file;
|
|
88
|
+
};
|
|
89
|
+
|
|
46
90
|
function GenericDetail({
|
|
47
91
|
extraUrlParam,
|
|
48
92
|
layout = 'full',
|
|
@@ -1275,63 +1319,157 @@ function GenericDetail({
|
|
|
1275
1319
|
</div>
|
|
1276
1320
|
<Dropzone
|
|
1277
1321
|
onDrop={(acceptedFiles) => {
|
|
1278
|
-
const uploadFile = (file) => {
|
|
1279
|
-
|
|
1322
|
+
const uploadFile = async (file) => {
|
|
1323
|
+
// Optimize image if it's an image file
|
|
1324
|
+
const optimizedFile =
|
|
1325
|
+
await optimizeImageIfNeeded(
|
|
1326
|
+
file
|
|
1327
|
+
);
|
|
1328
|
+
|
|
1329
|
+
// Show upload toast
|
|
1330
|
+
const uploadToastId =
|
|
1331
|
+
toast.info(
|
|
1332
|
+
`Uploading ${file.name}...`,
|
|
1333
|
+
{
|
|
1334
|
+
autoClose: false,
|
|
1335
|
+
}
|
|
1336
|
+
);
|
|
1337
|
+
|
|
1338
|
+
Vapor.store(optimizedFile, {
|
|
1280
1339
|
progress: (progress) => {
|
|
1281
1340
|
setLoadingProgress(
|
|
1282
1341
|
progress * 100
|
|
1283
1342
|
);
|
|
1284
1343
|
},
|
|
1285
|
-
})
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
response.extension,
|
|
1299
|
-
file_relationship:
|
|
1300
|
-
activeTabConfig.file_relationship,
|
|
1301
|
-
};
|
|
1344
|
+
})
|
|
1345
|
+
.then((response) => {
|
|
1346
|
+
setLoadingProgress(0);
|
|
1347
|
+
|
|
1348
|
+
// Update upload toast
|
|
1349
|
+
toast.update(
|
|
1350
|
+
uploadToastId,
|
|
1351
|
+
{
|
|
1352
|
+
render: `Processing ${file.name}...`,
|
|
1353
|
+
type: 'info',
|
|
1354
|
+
autoClose: false,
|
|
1355
|
+
}
|
|
1356
|
+
);
|
|
1302
1357
|
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1358
|
+
const requestBody = {
|
|
1359
|
+
uuid: response.uuid,
|
|
1360
|
+
key: response.key,
|
|
1361
|
+
bucket: response.bucket,
|
|
1362
|
+
filename: file.name,
|
|
1363
|
+
file_name:
|
|
1364
|
+
file.name,
|
|
1365
|
+
filesize:
|
|
1366
|
+
optimizedFile.size,
|
|
1367
|
+
file_size:
|
|
1368
|
+
optimizedFile.size,
|
|
1369
|
+
extension:
|
|
1370
|
+
response.extension,
|
|
1371
|
+
file_extension:
|
|
1372
|
+
response.extension,
|
|
1373
|
+
file_relationship:
|
|
1374
|
+
activeTabConfig.file_relationship,
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
CustomFetch(
|
|
1378
|
+
`${activeTabConfig.url}/${routeParams[urlParam]}`,
|
|
1379
|
+
'PUT',
|
|
1380
|
+
requestBody,
|
|
1381
|
+
(res) => {
|
|
1382
|
+
// Check if there's an error in the response
|
|
1383
|
+
if (
|
|
1384
|
+
res.error &&
|
|
1385
|
+
res.error !==
|
|
1386
|
+
''
|
|
1387
|
+
) {
|
|
1388
|
+
toast.update(
|
|
1389
|
+
uploadToastId,
|
|
1390
|
+
{
|
|
1391
|
+
render: `Error: ${res.error}`,
|
|
1392
|
+
type: 'error',
|
|
1393
|
+
autoClose: 5000,
|
|
1394
|
+
}
|
|
1395
|
+
);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if (
|
|
1399
|
+
activeTabConfig.file_relationship &&
|
|
1316
1400
|
res.data[
|
|
1317
1401
|
activeTabConfig
|
|
1318
1402
|
.file_relationship
|
|
1319
1403
|
]
|
|
1320
|
-
);
|
|
1321
|
-
} else {
|
|
1322
|
-
if (
|
|
1323
|
-
res?.data
|
|
1324
|
-
?.files
|
|
1325
1404
|
) {
|
|
1326
1405
|
setFiles(
|
|
1406
|
+
res
|
|
1407
|
+
.data[
|
|
1408
|
+
activeTabConfig
|
|
1409
|
+
.file_relationship
|
|
1410
|
+
]
|
|
1411
|
+
);
|
|
1412
|
+
// Update the main data state with the new data
|
|
1413
|
+
setData(
|
|
1327
1414
|
res.data
|
|
1328
|
-
.files
|
|
1329
1415
|
);
|
|
1416
|
+
|
|
1417
|
+
// Update the upload toast
|
|
1418
|
+
toast.update(
|
|
1419
|
+
uploadToastId,
|
|
1420
|
+
{
|
|
1421
|
+
render: `${file.name} uploaded successfully!`,
|
|
1422
|
+
type: 'success',
|
|
1423
|
+
autoClose: 3000,
|
|
1424
|
+
}
|
|
1425
|
+
);
|
|
1426
|
+
} else {
|
|
1427
|
+
if (
|
|
1428
|
+
res
|
|
1429
|
+
?.data
|
|
1430
|
+
?.files
|
|
1431
|
+
) {
|
|
1432
|
+
setFiles(
|
|
1433
|
+
res
|
|
1434
|
+
.data
|
|
1435
|
+
.files
|
|
1436
|
+
);
|
|
1437
|
+
// Update the main data state with the new data
|
|
1438
|
+
setData(
|
|
1439
|
+
res.data
|
|
1440
|
+
);
|
|
1441
|
+
|
|
1442
|
+
// Update the upload toast
|
|
1443
|
+
toast.update(
|
|
1444
|
+
uploadToastId,
|
|
1445
|
+
{
|
|
1446
|
+
render: `${file.name} uploaded successfully!`,
|
|
1447
|
+
type: 'success',
|
|
1448
|
+
autoClose: 3000,
|
|
1449
|
+
}
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1330
1452
|
}
|
|
1331
1453
|
}
|
|
1332
|
-
|
|
1333
|
-
)
|
|
1334
|
-
|
|
1454
|
+
);
|
|
1455
|
+
})
|
|
1456
|
+
.catch((error) => {
|
|
1457
|
+
console.error(
|
|
1458
|
+
'Error uploading file:',
|
|
1459
|
+
error
|
|
1460
|
+
);
|
|
1461
|
+
toast.update(
|
|
1462
|
+
uploadToastId,
|
|
1463
|
+
{
|
|
1464
|
+
render: `Error uploading file: ${
|
|
1465
|
+
error.message ||
|
|
1466
|
+
'Unknown error'
|
|
1467
|
+
}`,
|
|
1468
|
+
type: 'error',
|
|
1469
|
+
autoClose: 5000,
|
|
1470
|
+
}
|
|
1471
|
+
);
|
|
1472
|
+
});
|
|
1335
1473
|
};
|
|
1336
1474
|
|
|
1337
1475
|
acceptedFiles.forEach((a) => {
|
|
@@ -206,6 +206,17 @@ function GenericIndex({
|
|
|
206
206
|
rowsSelectedRef.current = rowsSelected;
|
|
207
207
|
}, [rowsSelected]);
|
|
208
208
|
|
|
209
|
+
const handleCustomAction = async () => {
|
|
210
|
+
try {
|
|
211
|
+
const { data, method, message, url } = setting?.functions
|
|
212
|
+
?.customAction
|
|
213
|
+
? setting.functions.customAction
|
|
214
|
+
: config.form.functions.customAction;
|
|
215
|
+
} catch (err) {
|
|
216
|
+
console.error(err);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
209
220
|
const handleCheckboxUpdate = useCallback(async () => {
|
|
210
221
|
try {
|
|
211
222
|
const { data, method, message, url } = setting?.functions
|
|
@@ -548,6 +559,17 @@ function GenericIndex({
|
|
|
548
559
|
</button>
|
|
549
560
|
)}
|
|
550
561
|
|
|
562
|
+
{setting.functions?.customAction && (
|
|
563
|
+
<button className={styles.btn} onClick={handleCustomAction}>
|
|
564
|
+
{setting.functions.customAction.label || 'Delete'}
|
|
565
|
+
</button>
|
|
566
|
+
)}
|
|
567
|
+
{config?.form?.functions?.customAction && (
|
|
568
|
+
<button className={styles.btn} onClick={handleCustomAction}>
|
|
569
|
+
{config.form.functions.customAction.label || 'Delete'}
|
|
570
|
+
</button>
|
|
571
|
+
)}
|
|
572
|
+
|
|
551
573
|
{setting.export?.label && (
|
|
552
574
|
<button className={styles.btn} onClick={handleExport}>
|
|
553
575
|
{setting.export.label}
|