@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.9",
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.4.0"
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(file, {
713
- progress: (progress) => {
714
- setUploadProgress(progress * 100);
715
- toast.update(toastId, {
716
- progress,
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: file.size,
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
- formData[fileKey],
755
- {
756
- progress: (progress) => {
757
- setUploadProgress(progress * 100);
758
- toast.update(toastId, {
759
- progress,
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'] = formData[fileKey].size;
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
- if (ajaxSetting?.where) {
934
- const getDataId = () => {
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
- return 0;
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
- Vapor.store(file, {
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
- }).then((response) => {
1286
- setLoadingProgress(0);
1287
- const requestBody = {
1288
- uuid: response.uuid,
1289
- key: response.key,
1290
- bucket: response.bucket,
1291
- filename: file.name,
1292
- file_name: file.name,
1293
- filesize: file.size,
1294
- file_size: file.size,
1295
- extension:
1296
- response.extension,
1297
- file_extension:
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
- CustomFetch(
1304
- `${activeTabConfig.url}/${routeParams[urlParam]}`,
1305
- 'PUT',
1306
- requestBody,
1307
- (res) => {
1308
- if (
1309
- activeTabConfig.file_relationship &&
1310
- res.data[
1311
- activeTabConfig
1312
- .file_relationship
1313
- ]
1314
- ) {
1315
- setFiles(
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}