@visns-studio/visns-components 5.11.7 → 5.11.8

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
@@ -87,7 +87,7 @@
87
87
  "react-dom": "^17.0.0 || ^18.0.0"
88
88
  },
89
89
  "name": "@visns-studio/visns-components",
90
- "version": "5.11.7",
90
+ "version": "5.11.8",
91
91
  "description": "Various packages to assist in the development of our Custom Applications.",
92
92
  "main": "src/index.js",
93
93
  "files": [
@@ -841,6 +841,88 @@ function Field({
841
841
  </div>
842
842
  );
843
843
  case 'file':
844
+ const isImageFile = (file) => {
845
+ if (!file) return false;
846
+
847
+ // Handle different file object structures
848
+ const fileName = file.filename || file.name || file.file_name || '';
849
+ const fileExtension = file.file_extension || '';
850
+ const mimeType = file.type || file.mime_type || file.mimeType || '';
851
+
852
+ // Check by file extension property (from API)
853
+ const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
854
+ if (fileExtension && imageExtensions.includes(fileExtension.toLowerCase())) return true;
855
+
856
+ // Check by filename extension
857
+ const imageExtensionRegex = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i;
858
+ if (imageExtensionRegex.test(fileName)) return true;
859
+
860
+ // Check by MIME type
861
+ if (mimeType && mimeType.startsWith('image/')) return true;
862
+
863
+ // If file is just a string (filename only), check extension
864
+ if (typeof file === 'string' && imageExtensionRegex.test(file)) return true;
865
+
866
+ return false;
867
+ };
868
+
869
+ const getFileIcon = (file) => {
870
+ if (isImageFile(file)) {
871
+ return '🖼️';
872
+ }
873
+
874
+ // Handle different file structures and string filenames
875
+ const fileName = typeof file === 'string' ? file : (file.filename || file.name || file.file_name || '');
876
+ const ext = fileName.split('.').pop()?.toLowerCase();
877
+
878
+ switch (ext) {
879
+ case 'pdf': return '📄';
880
+ case 'doc':
881
+ case 'docx': return '📝';
882
+ case 'xls':
883
+ case 'xlsx': return '📊';
884
+ case 'ppt':
885
+ case 'pptx': return '📽️';
886
+ case 'zip':
887
+ case 'rar': return '🗜️';
888
+ case 'mp4':
889
+ case 'avi':
890
+ case 'mov': return '🎥';
891
+ case 'mp3':
892
+ case 'wav': return '🎵';
893
+ default: return '📎';
894
+ }
895
+ };
896
+
897
+ const getImageSrc = (file) => {
898
+ if (!file || !isImageFile(file)) return null;
899
+
900
+ // Handle different image source formats - API file objects
901
+ if (file.file_url) return file.file_url; // S3 or API URLs
902
+ if (file.imageSrc) return file.imageSrc;
903
+ if (file.url) return file.url;
904
+ if (file.src) return file.src;
905
+ if (file.path) return file.path;
906
+ if (file.preview) return file.preview;
907
+
908
+ // Handle full path construction
909
+ if (file.file_full_path) return file.file_full_path;
910
+ if (file.file_path) return file.file_path;
911
+
912
+ // Handle base64 data
913
+ if (file.data && typeof file.data === 'string' && file.data.startsWith('data:image/')) {
914
+ return file.data;
915
+ }
916
+
917
+ // Handle file download URLs (legacy support)
918
+ if (file.key && file.uuid) {
919
+ // This could be constructed based on your API structure
920
+ return `/api/files/download/${file.uuid}/${file.key}`;
921
+ }
922
+
923
+ return null;
924
+ };
925
+
844
926
  if (
845
927
  settings.hasOwnProperty('multiple') &&
846
928
  settings.multiple === true
@@ -857,73 +939,109 @@ function Field({
857
939
 
858
940
  {inputValue && inputValue.length > 0 && (
859
941
  <ul className={styles['file-list']}>
860
- {inputValue.map((file, index) => (
861
- <li
862
- key={`file-${settings.id}-${index}`}
863
- >
864
- <span
865
- className={styles['file-name']}
942
+ {inputValue.map((file, index) => {
943
+ const fileIsImage = isImageFile(file);
944
+ const fileImageSrc = getImageSrc(file);
945
+
946
+ return (
947
+ <li
948
+ key={`file-${settings.id}-${index}`}
949
+ className={fileIsImage ? styles['file-list-item-image'] : styles['file-list-item']}
866
950
  >
867
- {file.name ||
868
- file.file_name ||
869
- ''}
870
- </span>
871
- <button
872
- className={
873
- styles['trash-button']
874
- }
875
- onClick={(e) => {
876
- e.preventDefault();
877
- e.stopPropagation();
878
- setFormData(
879
- (prevFormData) => ({
880
- ...prevFormData,
881
- [settings.id]:
882
- inputValue.filter(
883
- (
884
- _,
885
- fileIndex
886
- ) =>
887
- fileIndex !==
888
- index
889
- ),
890
- })
891
- );
892
- }}
893
- >
894
- <Trash2
895
- strokeWidth={2}
896
- size={20}
897
- />
898
- </button>
899
- </li>
900
- ))}
951
+ {fileIsImage && fileImageSrc ? (
952
+ <div className={styles['file-image-preview']}>
953
+ <img
954
+ src={fileImageSrc}
955
+ alt={file.name || file.file_name || 'Preview'}
956
+ className={styles['file-thumbnail']}
957
+ onError={(e) => {
958
+ e.target.style.display = 'none';
959
+ e.target.nextElementSibling.style.display = 'flex';
960
+ }}
961
+ />
962
+ <span
963
+ className={styles['file-icon']}
964
+ style={{ display: 'none' }}
965
+ >
966
+ {getFileIcon(file)}
967
+ </span>
968
+ </div>
969
+ ) : (
970
+ <span className={styles['file-icon']}>
971
+ {getFileIcon(file)}
972
+ </span>
973
+ )}
974
+
975
+ <span className={styles['file-name']}>
976
+ {file.name ||
977
+ file.file_name ||
978
+ ''}
979
+ </span>
980
+
981
+ <button
982
+ className={styles['trash-button']}
983
+ onClick={(e) => {
984
+ e.preventDefault();
985
+ e.stopPropagation();
986
+ setFormData(
987
+ (prevFormData) => ({
988
+ ...prevFormData,
989
+ [settings.id]:
990
+ inputValue.filter(
991
+ (
992
+ _,
993
+ fileIndex
994
+ ) =>
995
+ fileIndex !==
996
+ index
997
+ ),
998
+ })
999
+ );
1000
+ }}
1001
+ >
1002
+ <Trash2
1003
+ strokeWidth={2}
1004
+ size={20}
1005
+ />
1006
+ </button>
1007
+ </li>
1008
+ );
1009
+ })}
901
1010
  </ul>
902
1011
  )}
903
1012
  </div>
904
1013
  );
905
1014
  } else {
1015
+ const isImage = isImageFile(inputValue);
1016
+ const imageSrc = getImageSrc(inputValue);
1017
+
1018
+ // Debug logging to understand inputValue structure
1019
+ if (inputValue && process.env.NODE_ENV === 'development') {
1020
+ console.log('File upload inputValue:', inputValue, 'isImage:', isImage, 'imageSrc:', imageSrc);
1021
+ }
1022
+
906
1023
  return (
907
1024
  <div
908
- style={{
909
- display: 'inline-flex',
910
- alignItems: 'center',
911
- }}
1025
+ className={`${styles['single-file-container']} ${isImage ? styles['single-file-image'] : ''}`}
912
1026
  >
913
1027
  <label
914
1028
  htmlFor={settings.id}
915
- style={{
916
- padding: '6px 12px',
917
- border: '1px solid #ccc',
918
- display: 'inline-block',
919
- backgroundColor: '#f8f8f8',
920
- cursor: 'pointer',
921
- }}
1029
+ className={`${styles['file-upload-button']} ${isImage ? styles['file-upload-button-image'] : ''}`}
922
1030
  >
923
- {inputValue?.filename
924
- ? 'Replace File...'
925
- : 'Choose file...'}
1031
+ <span className={styles['file-button-icon']}>
1032
+ {inputValue ? getFileIcon(inputValue) : '📁'}
1033
+ </span>
1034
+ <span className={styles['file-button-text']}>
1035
+ {inputValue?.filename || inputValue?.name
1036
+ ? isImage
1037
+ ? 'Replace Image...'
1038
+ : 'Replace File...'
1039
+ : isImage
1040
+ ? 'Choose Image...'
1041
+ : 'Choose File...'}
1042
+ </span>
926
1043
  </label>
1044
+
927
1045
  <input
928
1046
  id={settings.id}
929
1047
  data-name={settings.id}
@@ -933,35 +1051,44 @@ function Field({
933
1051
  onChange={onChange}
934
1052
  style={{ display: 'none' }}
935
1053
  />
936
- <span style={{ marginLeft: '10px' }}>
937
- {inputValue
938
- ? inputValue.filename
939
- ? inputValue.filename
940
- : inputValue.name
941
- ? inputValue.name
942
- : ''
943
- : ''}
944
- </span>
945
- {inputValue &&
946
- inputValue.key &&
947
- inputValue.uuid && (
948
- <div
949
- style={{
950
- marginLeft: '10px',
951
- cursor: 'pointer',
952
- }}
953
- data-tooltip-id="system-tooltip"
954
- data-tooltip-content={
955
- inputValue.filename
956
- }
957
- onClick={(e) => {
958
- e.preventDefault();
959
- onFileDownload(inputValue);
960
- }}
961
- >
962
- <Download size={24} />
1054
+
1055
+ {inputValue && (
1056
+ <div className={styles['file-info']}>
1057
+ {isImage && imageSrc && (
1058
+ <div className={styles['image-preview-container']}>
1059
+ <img
1060
+ src={imageSrc}
1061
+ alt={inputValue.filename || inputValue.name || 'Preview'}
1062
+ className={styles['image-preview']}
1063
+ onError={(e) => {
1064
+ e.target.style.display = 'none';
1065
+ console.warn('Failed to load image preview:', imageSrc);
1066
+ }}
1067
+ />
1068
+ </div>
1069
+ )}
1070
+
1071
+ <div className={styles['file-details']}>
1072
+ <span className={styles['file-name-display']}>
1073
+ {inputValue.filename || inputValue.name || ''}
1074
+ </span>
1075
+
1076
+ {inputValue.key && inputValue.uuid && (
1077
+ <button
1078
+ className={styles['download-button']}
1079
+ data-tooltip-id="system-tooltip"
1080
+ data-tooltip-content={`Download ${inputValue.filename || inputValue.name}`}
1081
+ onClick={(e) => {
1082
+ e.preventDefault();
1083
+ onFileDownload(inputValue);
1084
+ }}
1085
+ >
1086
+ <Download size={20} />
1087
+ </button>
1088
+ )}
963
1089
  </div>
964
- )}
1090
+ </div>
1091
+ )}
965
1092
  </div>
966
1093
  );
967
1094
  }