@mgonza02/goal-image-gallery 1.0.7 → 1.0.9
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 +23 -0
- package/lib/GoalImageGallery.js +2 -2
- package/lib/examples.js +184 -2
- package/lib/hooks/use-image-gallery.js +35 -6
- package/lib/index.d.ts +10 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ All notable changes to the GoalImageGallery project will be documented in this f
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.9] - 2025-07-14
|
|
9
|
+
|
|
10
|
+
### ✨ New Features
|
|
11
|
+
- **Enhanced Bulk Upload Support**: The `handleAfterUpload` method now fully supports arrays of images
|
|
12
|
+
- **Comprehensive Bulk Upload Example**: Added detailed bulk upload example with drag-and-drop functionality
|
|
13
|
+
- **Improved Documentation**: Updated examples to demonstrate array-based upload patterns
|
|
14
|
+
- **Better TypeScript Support**: Enhanced type definitions for bulk upload scenarios
|
|
15
|
+
|
|
16
|
+
### 🔧 Improvements
|
|
17
|
+
- Enhanced examples.js with a complete bulk upload gallery demonstration
|
|
18
|
+
- Added custom bulk upload selector component with drag-and-drop support
|
|
19
|
+
- Improved documentation for array-based `handleAfterUpload` usage
|
|
20
|
+
- Better error handling and progress indication for bulk uploads
|
|
21
|
+
|
|
22
|
+
## [1.0.8] - 2025-07-14
|
|
23
|
+
|
|
24
|
+
### 🧹 Bug Fixes
|
|
25
|
+
- Fixed an issue with the `use-image-gallery` hook where the `imageCodes` prop was not being correctly split into an array.
|
|
26
|
+
- Updated the `GoalImageGallery` component to ensure the `currentCompany` prop can accept both string and number types for better flexibility.
|
|
27
|
+
- Removed unused `clipboardSupport` prop from the `GoalImageGallery` component to avoid confusion.
|
|
28
|
+
- Ensured consistent handling of `currentCompany` across all components.
|
|
29
|
+
|
|
30
|
+
|
|
8
31
|
## [1.0.7] - 2025-07-14
|
|
9
32
|
### 🧹 Bug fix
|
|
10
33
|
- Fixed an issue with clipboard support detection in the `use-image-gallery` hook.
|
package/lib/GoalImageGallery.js
CHANGED
|
@@ -231,7 +231,7 @@ const GoalImageGallery = _ref => {
|
|
|
231
231
|
noImageUrl = "/images/no-image.png",
|
|
232
232
|
showError = message => console.error(message),
|
|
233
233
|
hasPermission = () => true,
|
|
234
|
-
currentCompany =
|
|
234
|
+
currentCompany = 0,
|
|
235
235
|
slot = {},
|
|
236
236
|
slotProps = {}
|
|
237
237
|
} = _ref;
|
|
@@ -415,7 +415,7 @@ GoalImageGallery.propTypes = {
|
|
|
415
415
|
noImageUrl: PropTypes.string,
|
|
416
416
|
showError: PropTypes.func,
|
|
417
417
|
hasPermission: PropTypes.func,
|
|
418
|
-
currentCompany: PropTypes.string,
|
|
418
|
+
currentCompany: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
419
419
|
slot: PropTypes.object,
|
|
420
420
|
slotProps: PropTypes.object
|
|
421
421
|
};
|
package/lib/examples.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// GoalImageGallery Usage Examples
|
|
2
2
|
// This file demonstrates various ways to use the GoalImageGallery component
|
|
3
3
|
|
|
4
|
-
import React, { useState, useCallback } from 'react';
|
|
4
|
+
import React, { useState, useCallback, useRef } from 'react';
|
|
5
5
|
import { Box, Typography, Button, Divider } from '@mui/material';
|
|
6
6
|
import GoalImageGallery from './GoalImageGallery';
|
|
7
7
|
|
|
@@ -646,6 +646,184 @@ export const CustomSelectorExample = () => {
|
|
|
646
646
|
});
|
|
647
647
|
};
|
|
648
648
|
|
|
649
|
+
// Example 9: Bulk Upload Gallery
|
|
650
|
+
export const BulkUploadExample = () => {
|
|
651
|
+
const [galleryImages, setGalleryImages] = useState(['bulk1', 'bulk2']);
|
|
652
|
+
const [uploading, setUploading] = useState(false);
|
|
653
|
+
const currentCompany = 'bulk-company';
|
|
654
|
+
const handleBulkUpload = useCallback(async imageData => {
|
|
655
|
+
setUploading(true);
|
|
656
|
+
try {
|
|
657
|
+
// Simulate bulk upload API call
|
|
658
|
+
const response = await new Promise(resolve => {
|
|
659
|
+
setTimeout(() => {
|
|
660
|
+
resolve({
|
|
661
|
+
success: true,
|
|
662
|
+
data: {
|
|
663
|
+
code: `bulk-${Date.now()}`
|
|
664
|
+
},
|
|
665
|
+
message: 'Bulk upload completed successfully'
|
|
666
|
+
});
|
|
667
|
+
}, 1000);
|
|
668
|
+
});
|
|
669
|
+
return response;
|
|
670
|
+
} catch (error) {
|
|
671
|
+
return {
|
|
672
|
+
success: false,
|
|
673
|
+
message: error.message
|
|
674
|
+
};
|
|
675
|
+
} finally {
|
|
676
|
+
setUploading(false);
|
|
677
|
+
}
|
|
678
|
+
}, []);
|
|
679
|
+
const handleBulkRefresh = useCallback(result => {
|
|
680
|
+
setGalleryImages(prev => [...prev, result.data.code]);
|
|
681
|
+
}, []);
|
|
682
|
+
|
|
683
|
+
// Custom bulk upload component
|
|
684
|
+
const BulkUploadSelector = _ref2 => {
|
|
685
|
+
let {
|
|
686
|
+
afterUpload,
|
|
687
|
+
activate,
|
|
688
|
+
onCancel,
|
|
689
|
+
multiple
|
|
690
|
+
} = _ref2;
|
|
691
|
+
const [dragActive, setDragActive] = useState(false);
|
|
692
|
+
const [selectedFiles, setSelectedFiles] = useState([]);
|
|
693
|
+
const fileInputRef = useRef(null);
|
|
694
|
+
const handleDrag = e => {
|
|
695
|
+
e.preventDefault();
|
|
696
|
+
e.stopPropagation();
|
|
697
|
+
if (e.type === 'dragenter' || e.type === 'dragover') {
|
|
698
|
+
setDragActive(true);
|
|
699
|
+
} else if (e.type === 'dragleave') {
|
|
700
|
+
setDragActive(false);
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
const handleDrop = e => {
|
|
704
|
+
e.preventDefault();
|
|
705
|
+
e.stopPropagation();
|
|
706
|
+
setDragActive(false);
|
|
707
|
+
const files = Array.from(e.dataTransfer.files);
|
|
708
|
+
handleFiles(files);
|
|
709
|
+
};
|
|
710
|
+
const handleFiles = async files => {
|
|
711
|
+
const imageFiles = files.filter(file => file.type.startsWith('image/'));
|
|
712
|
+
setSelectedFiles(imageFiles);
|
|
713
|
+
if (imageFiles.length > 0) {
|
|
714
|
+
// Simulate uploading multiple files and getting their codes
|
|
715
|
+
const uploadPromises = imageFiles.map(async (file, index) => {
|
|
716
|
+
return new Promise(resolve => {
|
|
717
|
+
setTimeout(() => {
|
|
718
|
+
resolve({
|
|
719
|
+
code: `bulk-img-${Date.now()}-${index}`,
|
|
720
|
+
url: URL.createObjectURL(file),
|
|
721
|
+
filename: file.name,
|
|
722
|
+
size: file.size,
|
|
723
|
+
mimeType: file.type
|
|
724
|
+
});
|
|
725
|
+
}, 500 + index * 100);
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
const uploadedImages = await Promise.all(uploadPromises);
|
|
729
|
+
|
|
730
|
+
// Call afterUpload with array of images
|
|
731
|
+
await afterUpload(uploadedImages);
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
const handleFileSelect = () => {
|
|
735
|
+
fileInputRef.current?.click();
|
|
736
|
+
};
|
|
737
|
+
return /*#__PURE__*/_jsxs(Box, {
|
|
738
|
+
onDragEnter: handleDrag,
|
|
739
|
+
onDragLeave: handleDrag,
|
|
740
|
+
onDragOver: handleDrag,
|
|
741
|
+
onDrop: handleDrop,
|
|
742
|
+
sx: {
|
|
743
|
+
border: `2px dashed ${dragActive ? '#007bff' : '#ccc'}`,
|
|
744
|
+
borderRadius: '12px',
|
|
745
|
+
padding: '40px',
|
|
746
|
+
textAlign: 'center',
|
|
747
|
+
backgroundColor: dragActive ? '#f8f9fa' : '#fff',
|
|
748
|
+
cursor: 'pointer',
|
|
749
|
+
transition: 'all 0.3s ease',
|
|
750
|
+
'&:hover': {
|
|
751
|
+
borderColor: '#007bff',
|
|
752
|
+
backgroundColor: '#f8f9fa'
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
onClick: handleFileSelect,
|
|
756
|
+
children: [/*#__PURE__*/_jsx("input", {
|
|
757
|
+
ref: fileInputRef,
|
|
758
|
+
type: "file",
|
|
759
|
+
multiple: true,
|
|
760
|
+
accept: "image/*",
|
|
761
|
+
style: {
|
|
762
|
+
display: 'none'
|
|
763
|
+
},
|
|
764
|
+
onChange: e => handleFiles(Array.from(e.target.files))
|
|
765
|
+
}), /*#__PURE__*/_jsx(Typography, {
|
|
766
|
+
variant: "h6",
|
|
767
|
+
gutterBottom: true,
|
|
768
|
+
children: "Bulk Upload Images"
|
|
769
|
+
}), /*#__PURE__*/_jsx(Typography, {
|
|
770
|
+
variant: "body2",
|
|
771
|
+
color: "text.secondary",
|
|
772
|
+
paragraph: true,
|
|
773
|
+
children: "Drop multiple images here or click to select"
|
|
774
|
+
}), selectedFiles.length > 0 && /*#__PURE__*/_jsxs(Typography, {
|
|
775
|
+
variant: "body2",
|
|
776
|
+
color: "success.main",
|
|
777
|
+
children: [selectedFiles.length, " file", selectedFiles.length !== 1 ? 's' : '', " selected"]
|
|
778
|
+
}), /*#__PURE__*/_jsx(Button, {
|
|
779
|
+
variant: "contained",
|
|
780
|
+
sx: {
|
|
781
|
+
mt: 2,
|
|
782
|
+
mr: 1
|
|
783
|
+
},
|
|
784
|
+
disabled: uploading,
|
|
785
|
+
children: uploading ? 'Uploading...' : 'Select Files'
|
|
786
|
+
}), /*#__PURE__*/_jsx(Button, {
|
|
787
|
+
variant: "outlined",
|
|
788
|
+
onClick: onCancel,
|
|
789
|
+
children: "Cancel"
|
|
790
|
+
})]
|
|
791
|
+
});
|
|
792
|
+
};
|
|
793
|
+
return /*#__PURE__*/_jsxs(Box, {
|
|
794
|
+
sx: {
|
|
795
|
+
p: 2
|
|
796
|
+
},
|
|
797
|
+
children: [/*#__PURE__*/_jsx(Typography, {
|
|
798
|
+
variant: "h6",
|
|
799
|
+
gutterBottom: true,
|
|
800
|
+
children: "Bulk Upload Gallery"
|
|
801
|
+
}), /*#__PURE__*/_jsx(Typography, {
|
|
802
|
+
variant: "body2",
|
|
803
|
+
color: "text.secondary",
|
|
804
|
+
paragraph: true,
|
|
805
|
+
children: "This example demonstrates bulk upload functionality where multiple images can be uploaded at once. The handleAfterUpload method now accepts arrays of image objects."
|
|
806
|
+
}), /*#__PURE__*/_jsx(GoalImageGallery, {
|
|
807
|
+
imageCodes: galleryImages,
|
|
808
|
+
canEdit: true,
|
|
809
|
+
ownerEntity: {
|
|
810
|
+
id: 'bulk-gallery',
|
|
811
|
+
type: 'gallery'
|
|
812
|
+
},
|
|
813
|
+
imageHandlerApi: handleBulkUpload,
|
|
814
|
+
afterUpload: handleBulkRefresh,
|
|
815
|
+
multiple: true,
|
|
816
|
+
emptyMessage: "Bulk upload gallery - drop multiple images to upload!",
|
|
817
|
+
showImageInfo: true,
|
|
818
|
+
allowDownload: true,
|
|
819
|
+
currentCompany: currentCompany,
|
|
820
|
+
slot: {
|
|
821
|
+
selector: BulkUploadSelector
|
|
822
|
+
}
|
|
823
|
+
})]
|
|
824
|
+
});
|
|
825
|
+
};
|
|
826
|
+
|
|
649
827
|
// Complete Demo Component
|
|
650
828
|
export const GoalImageGalleryExamples = () => {
|
|
651
829
|
return /*#__PURE__*/_jsxs(Box, {
|
|
@@ -688,7 +866,11 @@ export const GoalImageGalleryExamples = () => {
|
|
|
688
866
|
sx: {
|
|
689
867
|
my: 3
|
|
690
868
|
}
|
|
691
|
-
}), /*#__PURE__*/_jsx(CustomSelectorExample, {})
|
|
869
|
+
}), /*#__PURE__*/_jsx(CustomSelectorExample, {}), /*#__PURE__*/_jsx(Divider, {
|
|
870
|
+
sx: {
|
|
871
|
+
my: 3
|
|
872
|
+
}
|
|
873
|
+
}), /*#__PURE__*/_jsx(BulkUploadExample, {})]
|
|
692
874
|
});
|
|
693
875
|
};
|
|
694
876
|
export default GoalImageGalleryExamples;
|
|
@@ -26,7 +26,7 @@ export const useImageGallery = _ref => {
|
|
|
26
26
|
|
|
27
27
|
// Process image codes into image list
|
|
28
28
|
useEffect(() => {
|
|
29
|
-
const splitedImages = Array.isArray(imageCodes) ? imageCodes : imageCodes?.split(/[\s,]+/);
|
|
29
|
+
const splitedImages = Array.isArray(imageCodes) ? imageCodes : (imageCodes || '')?.split(/[\s,]+/);
|
|
30
30
|
let newList = [];
|
|
31
31
|
if (splitedImages) {
|
|
32
32
|
const list = splitedImages.map((image_code, index) => ({
|
|
@@ -96,22 +96,51 @@ export const useImageGallery = _ref => {
|
|
|
96
96
|
setLoading(false);
|
|
97
97
|
}
|
|
98
98
|
}, [ownerEntity, currentCompany, imageList, onRefresh, showError, imageHandlerApi]);
|
|
99
|
-
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Handles the upload completion for single or multiple images
|
|
102
|
+
* @param {Object|Object[]} uploadedImages - Single image object or array of image objects
|
|
103
|
+
* @param {string} uploadedImages.code - The image code returned from the upload
|
|
104
|
+
* @param {string} [uploadedImages.url] - Optional image URL
|
|
105
|
+
* @param {string} [uploadedImages.filename] - Optional filename
|
|
106
|
+
*/
|
|
107
|
+
const handleAfterUpload = useCallback(async uploadedImages => {
|
|
100
108
|
const currentImageCode = selectedImage?.image_code || '';
|
|
109
|
+
|
|
110
|
+
// Handle both single image and array of images
|
|
111
|
+
const imagesArray = Array.isArray(uploadedImages) ? uploadedImages : [uploadedImages];
|
|
101
112
|
const imagesToSave = imageList.reduce((acc, img) => {
|
|
102
113
|
if (img.image_code !== '+') {
|
|
103
|
-
|
|
114
|
+
// If this is the image being replaced, replace with the first uploaded image
|
|
115
|
+
if (img.image_code === currentImageCode && imagesArray.length > 0) {
|
|
116
|
+
acc.push(imagesArray[0].code);
|
|
117
|
+
} else {
|
|
118
|
+
acc.push(img.image_code);
|
|
119
|
+
}
|
|
104
120
|
}
|
|
105
121
|
return acc;
|
|
106
122
|
}, []);
|
|
123
|
+
|
|
124
|
+
// If no current image code (adding new images), add all new image codes
|
|
107
125
|
if (!currentImageCode) {
|
|
108
|
-
|
|
126
|
+
imagesArray.forEach(image => {
|
|
127
|
+
if (!imagesToSave.includes(image.code)) {
|
|
128
|
+
imagesToSave.push(image.code);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
} else if (imagesArray.length > 1) {
|
|
132
|
+
// If replacing and multiple images uploaded, add additional images
|
|
133
|
+
imagesArray.slice(1).forEach(image => {
|
|
134
|
+
if (!imagesToSave.includes(image.code)) {
|
|
135
|
+
imagesToSave.push(image.code);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
109
138
|
}
|
|
110
139
|
const result = await imageHandlerApi({
|
|
111
140
|
...ownerEntity,
|
|
112
141
|
companyId: currentCompany,
|
|
113
|
-
|
|
114
|
-
|
|
142
|
+
company_id: currentCompany,
|
|
143
|
+
image_code: imagesToSave
|
|
115
144
|
});
|
|
116
145
|
if (result.success) {
|
|
117
146
|
setSelectingImage(false);
|
package/lib/index.d.ts
CHANGED
|
@@ -34,6 +34,15 @@ export interface ImageItem {
|
|
|
34
34
|
image_code: string;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// Uploaded image interface
|
|
38
|
+
export interface UploadedImage {
|
|
39
|
+
code: string;
|
|
40
|
+
url?: string;
|
|
41
|
+
filename?: string;
|
|
42
|
+
size?: number;
|
|
43
|
+
mimeType?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
37
46
|
// Hook interfaces
|
|
38
47
|
export interface UseImageGalleryOptions {
|
|
39
48
|
imageCodes?: string | string[];
|
|
@@ -62,7 +71,7 @@ export interface UseImageGalleryReturn {
|
|
|
62
71
|
handleImageSelect: (image: ImageItem | null) => void;
|
|
63
72
|
handleCancel: () => void;
|
|
64
73
|
handleRemoveImage: (image: ImageItem) => Promise<void>;
|
|
65
|
-
handleAfterUpload: (
|
|
74
|
+
handleAfterUpload: (images: UploadedImage | UploadedImage[]) => Promise<void>;
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
export interface UseImageZoomReturn {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mgonza02/goal-image-gallery",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Advanced, accessible, and highly customizable React image gallery component with multi-method upload, responsive design, and extensive TypeScript support",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/index.js",
|