@onehat/ui 0.4.79 → 0.4.81
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
|
@@ -1212,11 +1212,19 @@ function GridComponent(props) {
|
|
|
1212
1212
|
} else {
|
|
1213
1213
|
// Conform the calculated localColumnsConfig to the saved config.
|
|
1214
1214
|
// This should allow us to continue using non-serializable configurations after a refresh
|
|
1215
|
-
const reconstructedLocalColumnsConfig = savedLocalColumnsConfig
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1215
|
+
const reconstructedLocalColumnsConfig = savedLocalColumnsConfig
|
|
1216
|
+
.map((savedConfig) => {
|
|
1217
|
+
const columnConfig = localColumnsConfig.find(localConfig => localConfig.id === savedConfig.id);
|
|
1218
|
+
if (!columnConfig) {
|
|
1219
|
+
console.warn(`Column with id '${savedConfig.id}' not found in current config, skipping`);
|
|
1220
|
+
return null; // Return null for missing columns
|
|
1221
|
+
}
|
|
1222
|
+
_.assign(columnConfig, savedConfig);
|
|
1223
|
+
return columnConfig;
|
|
1224
|
+
})
|
|
1225
|
+
.filter(Boolean); // Remove null entries
|
|
1226
|
+
|
|
1227
|
+
|
|
1220
1228
|
localColumnsConfig = reconstructedLocalColumnsConfig;
|
|
1221
1229
|
}
|
|
1222
1230
|
}
|
|
@@ -61,6 +61,7 @@ import Plus from '../../Components/Icons/Plus.js';
|
|
|
61
61
|
import Trash from '../../Components/Icons/Trash.js';
|
|
62
62
|
import Edit from '../../Components/Icons/Edit.js';
|
|
63
63
|
import Rotate from '../../Components/Icons/Rotate.js';
|
|
64
|
+
import Download from '../../Components/Icons/Download.js';
|
|
64
65
|
import delay from '../../Functions/delay.js';
|
|
65
66
|
import _ from 'lodash';
|
|
66
67
|
|
|
@@ -136,8 +137,6 @@ function DraggableFileMosaic(props) {
|
|
|
136
137
|
...fileMosaicProps
|
|
137
138
|
} = props;
|
|
138
139
|
|
|
139
|
-
console.log('DraggableFileMosaic render:', { isDragSource, dragSourceType, hasItem: !!dragSourceItem.item });
|
|
140
|
-
|
|
141
140
|
// If not a drag source, just return the regular FileMosaic
|
|
142
141
|
if (!isDragSource) {
|
|
143
142
|
return <FileMosaic {...fileMosaicProps} />;
|
|
@@ -145,7 +144,6 @@ function DraggableFileMosaic(props) {
|
|
|
145
144
|
|
|
146
145
|
// Create a completely separate draggable container
|
|
147
146
|
const DragSourceContainer = withDragSource(({ dragSourceRef, ...dragProps }) => {
|
|
148
|
-
console.log('DragSourceContainer render with props:', dragProps);
|
|
149
147
|
return (
|
|
150
148
|
<div
|
|
151
149
|
ref={dragSourceRef}
|
|
@@ -240,6 +238,9 @@ function AttachmentsElement(props) {
|
|
|
240
238
|
modelid = useRef(modelidCalc),
|
|
241
239
|
id = props.id || (model && modelid.current ? `attachments-${model}-${modelid.current}` : 'attachments'),
|
|
242
240
|
forceUpdate = useForceUpdate(),
|
|
241
|
+
iconBlobUrlsRef = useRef(new Set()), // to track created blob URLs for cleanup
|
|
242
|
+
modalBlobUrlsRef = useRef(new Set()), // For modal images
|
|
243
|
+
[areBlobUrlsReady, setAreBlobUrlsReady] = useState(false),
|
|
243
244
|
[isReady, setIsReady] = useState(false),
|
|
244
245
|
[isUploading, setIsUploading] = useState(false),
|
|
245
246
|
[isLoading, setIsLoading] = useState(false),
|
|
@@ -272,8 +273,32 @@ function AttachmentsElement(props) {
|
|
|
272
273
|
getFiles = () => {
|
|
273
274
|
return setFilesRaw.current;
|
|
274
275
|
},
|
|
275
|
-
buildFiles = () => {
|
|
276
|
-
|
|
276
|
+
buildFiles = async () => {
|
|
277
|
+
setAreBlobUrlsReady(false);
|
|
278
|
+
cleanupIconBlobUrls();
|
|
279
|
+
|
|
280
|
+
// FilesUI doesn't allow headers to be passed with URLs,
|
|
281
|
+
// but these URLs require authentication.
|
|
282
|
+
// So we need to fetch the files ourselves, create blob URLs,
|
|
283
|
+
// and pass those to FilesUI.
|
|
284
|
+
const files = await Promise.all(_.map(Attachments.entities, async (entity) => {
|
|
285
|
+
let imageUrl = entity.attachments__uri;
|
|
286
|
+
|
|
287
|
+
// create authenticated blob URLs
|
|
288
|
+
try {
|
|
289
|
+
const response = await fetch(entity.attachments__uri, {
|
|
290
|
+
headers: Attachments.headers // Use your repository's headers
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
if (response.ok) {
|
|
294
|
+
const blob = await response.blob();
|
|
295
|
+
imageUrl = URL.createObjectURL(blob);
|
|
296
|
+
iconBlobUrlsRef.current.add(imageUrl);
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.warn('Failed to fetch authenticated image:', error);
|
|
300
|
+
}
|
|
301
|
+
|
|
277
302
|
return {
|
|
278
303
|
id: entity.id, // string | number The identifier of the file
|
|
279
304
|
// file: null, // File The file object obtained from client drop or selection
|
|
@@ -284,20 +309,39 @@ function AttachmentsElement(props) {
|
|
|
284
309
|
// errors: null, // string[] The list of errors according to the validation criteria or the result of the given custom validation function.
|
|
285
310
|
// uploadStatus: null, // UPLOADSTATUS The current upload status. (e.g. "uploading").
|
|
286
311
|
// uploadMessage: null, // string A message that shows the result of the upload process.
|
|
287
|
-
imageUrl:
|
|
312
|
+
imageUrl: imageUrl, // string A string representation or web url of the image that will be set to the "src" prop of an <img/> tag. If given, the component will use this image source instead of reading the image file.
|
|
288
313
|
downloadUrl: entity.attachments__uri, // string The url to be used to perform a GET request in order to download the file. If defined, the download icon will be shown.
|
|
289
314
|
// progress: null, // number The current percentage of upload progress. This value will have a higher priority over the upload progress value calculated inside the component.
|
|
290
315
|
// extraUploadData: null, // Record<string, any> The additional data that will be sent to the server when files are uploaded individually
|
|
291
316
|
// extraData: null, // Object Any kind of extra data that could be needed.
|
|
292
317
|
// serverResponse: null, // ServerResponse The upload response from server.
|
|
293
318
|
// xhr: null, // XMLHttpRequest A reference to the XHR object that allows the upload, progress and abort events.
|
|
319
|
+
|
|
294
320
|
};
|
|
295
|
-
});
|
|
321
|
+
}));
|
|
296
322
|
setFiles(files);
|
|
323
|
+
setAreBlobUrlsReady(true);
|
|
297
324
|
},
|
|
298
325
|
clearFiles = () => {
|
|
326
|
+
cleanupIconBlobUrls();
|
|
299
327
|
setFiles([]);
|
|
300
328
|
},
|
|
329
|
+
cleanupIconBlobUrls = () => {
|
|
330
|
+
iconBlobUrlsRef.current.forEach((url) => {
|
|
331
|
+
if (url.startsWith('blob:')) {
|
|
332
|
+
URL.revokeObjectURL(url);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
iconBlobUrlsRef.current.clear();
|
|
336
|
+
},
|
|
337
|
+
cleanupModalBlobUrls = () => {
|
|
338
|
+
modalBlobUrlsRef.current.forEach((url) => {
|
|
339
|
+
if (url.startsWith('blob:')) {
|
|
340
|
+
URL.revokeObjectURL(url);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
modalBlobUrlsRef.current.clear();
|
|
344
|
+
},
|
|
301
345
|
onFileDelete = (id) => {
|
|
302
346
|
const
|
|
303
347
|
files = getFiles(),
|
|
@@ -410,22 +454,41 @@ function AttachmentsElement(props) {
|
|
|
410
454
|
},
|
|
411
455
|
|
|
412
456
|
// Lightbox
|
|
413
|
-
buildModalBody = (
|
|
457
|
+
buildModalBody = async (item) => {
|
|
414
458
|
const
|
|
415
|
-
currentFile =
|
|
416
|
-
currentIx = Attachments.getIxById(id),
|
|
459
|
+
currentFile = item,
|
|
460
|
+
currentIx = Attachments.getIxById(item.id),
|
|
417
461
|
prevFile = Attachments.getByIx(currentIx - 1),
|
|
418
462
|
nextFile = Attachments.getByIx(currentIx + 1),
|
|
419
463
|
isPrevDisabled = !prevFile,
|
|
420
464
|
isNextDisabled = !nextFile,
|
|
421
|
-
onPrev = () => {
|
|
422
|
-
|
|
465
|
+
onPrev = async () => {
|
|
466
|
+
cleanupModalBlobUrls();
|
|
467
|
+
const modalBody = await buildModalBody(prevFile);
|
|
468
|
+
updateModalBody(modalBody);
|
|
423
469
|
},
|
|
424
|
-
onNext = () => {
|
|
425
|
-
|
|
470
|
+
onNext = async () => {
|
|
471
|
+
cleanupModalBlobUrls();
|
|
472
|
+
const modalBody = await buildModalBody(nextFile);
|
|
473
|
+
updateModalBody(modalBody);
|
|
426
474
|
},
|
|
427
|
-
url = currentFile.attachments__uri,
|
|
428
475
|
isPdf = currentFile.attachments__mimetype === 'application/pdf';
|
|
476
|
+
|
|
477
|
+
let url = currentFile.attachments__uri;
|
|
478
|
+
try {
|
|
479
|
+
const response = await fetch(currentFile.attachments__uri, {
|
|
480
|
+
headers: Attachments.headers // Use your repository's headers
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
if (response.ok) {
|
|
484
|
+
const blob = await response.blob();
|
|
485
|
+
url = URL.createObjectURL(blob);
|
|
486
|
+
modalBlobUrlsRef.current.add(url);
|
|
487
|
+
}
|
|
488
|
+
} catch (error) {
|
|
489
|
+
console.warn('Failed to fetch authenticated file for modal:', error);
|
|
490
|
+
}
|
|
491
|
+
|
|
429
492
|
let body = null;
|
|
430
493
|
if (isPdf) {
|
|
431
494
|
body = <iframe
|
|
@@ -455,18 +518,17 @@ function AttachmentsElement(props) {
|
|
|
455
518
|
/>
|
|
456
519
|
</HStack>;
|
|
457
520
|
},
|
|
458
|
-
onViewLightbox = (
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
521
|
+
onViewLightbox = async (item) => {
|
|
522
|
+
cleanupModalBlobUrls();
|
|
523
|
+
const modalBody = await buildModalBody(item);
|
|
463
524
|
showModal({
|
|
464
525
|
title: 'Lightbox',
|
|
465
|
-
body:
|
|
526
|
+
body: modalBody,
|
|
466
527
|
canClose: true,
|
|
467
528
|
includeCancel: true,
|
|
468
529
|
w: 1920,
|
|
469
530
|
h: 1080,
|
|
531
|
+
onClose: cleanupModalBlobUrls,
|
|
470
532
|
});
|
|
471
533
|
},
|
|
472
534
|
|
|
@@ -692,7 +754,7 @@ function AttachmentsElement(props) {
|
|
|
692
754
|
}
|
|
693
755
|
if (usesDirectories) {
|
|
694
756
|
const
|
|
695
|
-
wasAlreadyLoaded = AttachmentDirectories.
|
|
757
|
+
wasAlreadyLoaded = AttachmentDirectories.isLoaded,
|
|
696
758
|
currentConditions = AttachmentDirectories.getParamConditions() || {},
|
|
697
759
|
newConditions = {
|
|
698
760
|
'conditions[AttachmentDirectories.model]': selectorSelected.repository.name,
|
|
@@ -715,7 +777,7 @@ function AttachmentsElement(props) {
|
|
|
715
777
|
}
|
|
716
778
|
}
|
|
717
779
|
|
|
718
|
-
buildFiles();
|
|
780
|
+
await buildFiles();
|
|
719
781
|
} else {
|
|
720
782
|
Attachments.clear();
|
|
721
783
|
if (usesDirectories) {
|
|
@@ -747,6 +809,8 @@ function AttachmentsElement(props) {
|
|
|
747
809
|
AttachmentDirectories.off('beforeLoad', setDirectoriesTrue);
|
|
748
810
|
AttachmentDirectories.off('loadRootNodes', setDirectoriesFalse);
|
|
749
811
|
}
|
|
812
|
+
cleanupIconBlobUrls();
|
|
813
|
+
cleanupModalBlobUrls();
|
|
750
814
|
};
|
|
751
815
|
}, [model, modelid.current, showAll, getTreeSelection()]);
|
|
752
816
|
|
|
@@ -767,48 +831,52 @@ function AttachmentsElement(props) {
|
|
|
767
831
|
let content = null;
|
|
768
832
|
// icon or list view
|
|
769
833
|
if (viewMode === ATTACHMENTS_VIEW_MODES__ICON || isUploading) {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
'p-1',
|
|
777
|
-
isLoading ? [
|
|
778
|
-
'border-t-4',
|
|
779
|
-
'border-t-[#f00]',
|
|
780
|
-
] : null,
|
|
781
|
-
)}
|
|
782
|
-
>
|
|
783
|
-
<HStack
|
|
834
|
+
if (isLoading || !areBlobUrlsReady) {
|
|
835
|
+
content = <VStack className="AttachmentsElement-icon-VStack1 h-full flex-1 border p-1 justify-center items-center">
|
|
836
|
+
<Spinner />
|
|
837
|
+
</VStack>;
|
|
838
|
+
} else {
|
|
839
|
+
content = <VStack
|
|
784
840
|
className={clsx(
|
|
785
|
-
'AttachmentsElement-
|
|
786
|
-
'
|
|
787
|
-
'flex-
|
|
788
|
-
'
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
'
|
|
792
|
-
'
|
|
793
|
-
'h-full',
|
|
841
|
+
'AttachmentsElement-icon-VStack1',
|
|
842
|
+
'h-full',
|
|
843
|
+
'flex-1',
|
|
844
|
+
'border',
|
|
845
|
+
'p-1',
|
|
846
|
+
isLoading ? [
|
|
847
|
+
'border-t-4',
|
|
848
|
+
'border-t-[#f00]',
|
|
794
849
|
] : null,
|
|
795
850
|
)}
|
|
796
851
|
>
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
852
|
+
<HStack
|
|
853
|
+
className={clsx(
|
|
854
|
+
'AttachmentsElement-HStack',
|
|
855
|
+
'gap-2',
|
|
856
|
+
'flex-wrap',
|
|
857
|
+
'items-start',
|
|
858
|
+
files.length === 0 ? [
|
|
859
|
+
// So the 'No files' text is centered
|
|
860
|
+
'justify-center',
|
|
861
|
+
'items-center',
|
|
862
|
+
'h-full',
|
|
863
|
+
] : null,
|
|
864
|
+
)}
|
|
865
|
+
>
|
|
866
|
+
{files.length === 0 && <Text className="text-grey-600 italic">No files {usesDirectories ? 'in this directory' : ''}</Text>}
|
|
867
|
+
{files.map((file) => {
|
|
868
|
+
const fileEntity = Attachments.getById(file.id);
|
|
869
|
+
let eyeProps = {};
|
|
870
|
+
if (file.type && (file.type.match(/^image\//) || file.type === 'application/pdf')) {
|
|
871
|
+
eyeProps = {
|
|
872
|
+
onSee: () => {
|
|
873
|
+
onViewLightbox(fileEntity);
|
|
874
|
+
},
|
|
875
|
+
};
|
|
876
|
+
}
|
|
807
877
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
fileEntity = Attachments.getById(file.id),
|
|
811
|
-
dragSourceItem = {
|
|
878
|
+
// Create drag source item for this file
|
|
879
|
+
const dragSourceItem = {
|
|
812
880
|
item: fileEntity, // Get the actual entity
|
|
813
881
|
sourceComponentRef: null, // Could be set to a ref if needed
|
|
814
882
|
getDragProxy: () => {
|
|
@@ -820,63 +888,64 @@ function AttachmentsElement(props) {
|
|
|
820
888
|
}
|
|
821
889
|
};
|
|
822
890
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
891
|
+
return <Box
|
|
892
|
+
key={file.id}
|
|
893
|
+
className="mr-2"
|
|
894
|
+
>
|
|
895
|
+
{useFileMosaic &&
|
|
896
|
+
<DraggableFileMosaic
|
|
897
|
+
{...file}
|
|
898
|
+
backgroundBlurImage={false}
|
|
899
|
+
onDownload={onDownload}
|
|
900
|
+
{..._fileMosaic}
|
|
901
|
+
{...eyeProps}
|
|
902
|
+
isDragSource={canCrud && usesDirectories}
|
|
903
|
+
dragSourceType="Attachments"
|
|
904
|
+
dragSourceItem={dragSourceItem}
|
|
905
|
+
onDragStart={() => {
|
|
906
|
+
setTimeout(() => setIsDragging(true), 50); // Delay to avoid interfering with drag initialization
|
|
907
|
+
}}
|
|
908
|
+
onDragEnd={() => {
|
|
909
|
+
setIsDragging(false);
|
|
910
|
+
}}
|
|
911
|
+
/>}
|
|
912
|
+
{!useFileMosaic &&
|
|
913
|
+
<FileCardCustom
|
|
914
|
+
{...file}
|
|
915
|
+
backgroundBlurImage={false}
|
|
916
|
+
{..._fileMosaic}
|
|
917
|
+
{...eyeProps}
|
|
918
|
+
isDragSource={canCrud && usesDirectories}
|
|
919
|
+
dragSourceType="Attachments"
|
|
920
|
+
dragSourceItem={dragSourceItem}
|
|
921
|
+
item={Attachments.getById(file.id)}
|
|
922
|
+
onDragStart={() => {
|
|
923
|
+
setTimeout(() => setIsDragging(true), 50); // Delay to avoid interfering with drag initialization
|
|
924
|
+
}}
|
|
925
|
+
onDragEnd={() => {
|
|
926
|
+
setIsDragging(false);
|
|
927
|
+
}}
|
|
928
|
+
/>}
|
|
929
|
+
</Box>;
|
|
930
|
+
})}
|
|
931
|
+
</HStack>
|
|
932
|
+
{Attachments.total <= collapsedMax ? null :
|
|
933
|
+
<Button
|
|
934
|
+
onPress={toggleShowAll}
|
|
935
|
+
className="AttachmentsElement-toggleShowAll mt-2"
|
|
936
|
+
text={'Show ' + (showAll ? ' Less' : ' All ' + Attachments.total)}
|
|
937
|
+
_text={{
|
|
938
|
+
className: `
|
|
939
|
+
text-grey-600
|
|
940
|
+
italic
|
|
941
|
+
text-left
|
|
942
|
+
w-full
|
|
943
|
+
`,
|
|
944
|
+
}}
|
|
945
|
+
variant="outline"
|
|
946
|
+
/>}
|
|
947
|
+
</VStack>;
|
|
948
|
+
}
|
|
880
949
|
} else if (viewMode === ATTACHMENTS_VIEW_MODES__LIST) {
|
|
881
950
|
content = <AttachmentsGridEditor
|
|
882
951
|
Repository={Attachments}
|
|
@@ -912,32 +981,32 @@ function AttachmentsElement(props) {
|
|
|
912
981
|
_icon={{
|
|
913
982
|
size: 'xl',
|
|
914
983
|
}}
|
|
915
|
-
onPress={() => onViewLightbox(item
|
|
984
|
+
onPress={() => onViewLightbox(item)}
|
|
916
985
|
tooltip="View"
|
|
917
986
|
/>;
|
|
918
987
|
},
|
|
919
988
|
},
|
|
920
|
-
{
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
},
|
|
989
|
+
// {
|
|
990
|
+
// id: 'download',
|
|
991
|
+
// header: 'Get',
|
|
992
|
+
// w: 60,
|
|
993
|
+
// isSortable: false,
|
|
994
|
+
// isEditable: false,
|
|
995
|
+
// isReorderable: false,
|
|
996
|
+
// isResizable: false,
|
|
997
|
+
// isHidable: false,
|
|
998
|
+
// renderer: (item) => {
|
|
999
|
+
// return <IconButton
|
|
1000
|
+
// className="w-[60px]"
|
|
1001
|
+
// icon={Download}
|
|
1002
|
+
// _icon={{
|
|
1003
|
+
// size: 'xl',
|
|
1004
|
+
// }}
|
|
1005
|
+
// onPress={() => onDownload(item.id)}
|
|
1006
|
+
// tooltip="Download"
|
|
1007
|
+
// />;
|
|
1008
|
+
// },
|
|
1009
|
+
// },
|
|
941
1010
|
{
|
|
942
1011
|
"id": "attachments__filename",
|
|
943
1012
|
"header": "Filename",
|
|
@@ -1024,7 +1093,7 @@ function AttachmentsElement(props) {
|
|
|
1024
1093
|
</VStack>;
|
|
1025
1094
|
|
|
1026
1095
|
// Always wrap content in dropzone when canCrud is true, but conditionally disable functionality
|
|
1027
|
-
if (canCrud) {
|
|
1096
|
+
if (canCrud && !isDragging) {
|
|
1028
1097
|
content = <Dropzone
|
|
1029
1098
|
value={files}
|
|
1030
1099
|
onChange={isDragging ? () => {} : onDropzoneChange} // Disable onChange when dragging
|