@mgonza02/goal-image-gallery 1.0.0
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/IMPLEMENTATION_SUMMARY.md +259 -0
- package/LICENSE +21 -0
- package/MIGRATION.md +428 -0
- package/QUICK_REFERENCE.md +235 -0
- package/README.md +479 -0
- package/lib/GoalImageGallery.js +244 -0
- package/lib/components/ImageGalleryEmptyState.js +87 -0
- package/lib/components/ImageGalleryGrid.js +108 -0
- package/lib/components/ImageGalleryItem.js +175 -0
- package/lib/components/ImageGalleryModal.js +149 -0
- package/lib/components/index.js +5 -0
- package/lib/examples.js +503 -0
- package/lib/hooks/use-image-gallery.js +252 -0
- package/lib/index.d.ts +187 -0
- package/lib/index.js +148 -0
- package/lib/styles/styled-components.js +173 -0
- package/lib/utils/image-utils.js +129 -0
- package/package.json +93 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { useTheme } from '@mui/material/styles';
|
|
4
|
+
import { useMediaQuery } from '@mui/material';
|
|
5
|
+
import ImageGalleryGrid from './components/ImageGalleryGrid';
|
|
6
|
+
import ImageGalleryModal from './components/ImageGalleryModal';
|
|
7
|
+
import { useImageGallery, useImageZoom, useClipboard, useKeyboardNavigation } from './hooks/use-image-gallery';
|
|
8
|
+
import { downloadImage } from './utils/image-utils';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Enhanced GoalImageGallery Component
|
|
12
|
+
*
|
|
13
|
+
* This component provides a modern, accessible image gallery with advanced upload capabilities.
|
|
14
|
+
*
|
|
15
|
+
* Key Features:
|
|
16
|
+
* - Multi-method image upload (file selection, clipboard paste, URL loading)
|
|
17
|
+
* - Responsive grid layout with hover effects
|
|
18
|
+
* - Full-screen image viewer with zoom controls
|
|
19
|
+
* - Keyboard navigation support
|
|
20
|
+
* - Drag and drop functionality
|
|
21
|
+
* - Mobile-friendly interface
|
|
22
|
+
* - Accessibility features (ARIA labels, keyboard navigation)
|
|
23
|
+
* - Loading states and error handling
|
|
24
|
+
*
|
|
25
|
+
* Upload Methods:
|
|
26
|
+
* 1. File Upload: Click to select or drag and drop image files
|
|
27
|
+
* 2. Clipboard Paste: Use Ctrl+V to paste images from clipboard
|
|
28
|
+
* 3. URL Loading: Enter image URLs to load remote images
|
|
29
|
+
*
|
|
30
|
+
* Keyboard Shortcuts (when image is open):
|
|
31
|
+
* - Arrow Left/Right: Navigate between images
|
|
32
|
+
* - +/=: Zoom in
|
|
33
|
+
* - -: Zoom out
|
|
34
|
+
* - Escape: Close image viewer
|
|
35
|
+
*
|
|
36
|
+
* Props:
|
|
37
|
+
* - canEdit: Enable editing capabilities
|
|
38
|
+
* - ownerEntity: Entity that owns the images
|
|
39
|
+
* - imageHandlerApi: API function for image operations
|
|
40
|
+
* - afterUpload: Callback after successful upload
|
|
41
|
+
* - imageCodes: Array or string of image codes
|
|
42
|
+
* - multiple: Allow multiple image selection
|
|
43
|
+
* - permission: Required permission for editing
|
|
44
|
+
* - emptyMessage: Message shown when no images
|
|
45
|
+
* - showImageInfo: Show image information overlay
|
|
46
|
+
* - allowDownload: Enable download functionality
|
|
47
|
+
* - getImageUrl: Function to get image URL from code
|
|
48
|
+
* - noImageUrl: Fallback image URL
|
|
49
|
+
* - showError: Function to show error messages
|
|
50
|
+
* - hasPermission: Function to check user permissions
|
|
51
|
+
*/
|
|
52
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
53
|
+
const GoalImageGallery = _ref => {
|
|
54
|
+
let {
|
|
55
|
+
canEdit = false,
|
|
56
|
+
ownerEntity,
|
|
57
|
+
imageHandlerApi,
|
|
58
|
+
afterUpload: onRefresh,
|
|
59
|
+
imageCodes,
|
|
60
|
+
multiple = false,
|
|
61
|
+
permission,
|
|
62
|
+
emptyMessage = 'No hay imágenes para mostrar',
|
|
63
|
+
showImageInfo = false,
|
|
64
|
+
allowDownload = false,
|
|
65
|
+
getImageUrl = _ref2 => {
|
|
66
|
+
let {
|
|
67
|
+
imageCode
|
|
68
|
+
} = _ref2;
|
|
69
|
+
return `/images/${imageCode}`;
|
|
70
|
+
},
|
|
71
|
+
noImageUrl = '/images/no-image.png',
|
|
72
|
+
showError = message => console.error(message),
|
|
73
|
+
hasPermission = () => true,
|
|
74
|
+
currentCompany = 'default'
|
|
75
|
+
} = _ref;
|
|
76
|
+
const theme = useTheme();
|
|
77
|
+
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
|
78
|
+
const [updatable, setUpdatable] = useState(false);
|
|
79
|
+
|
|
80
|
+
// Initialize permissions
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const isAllowed = !permission || hasPermission(permission);
|
|
83
|
+
setUpdatable(!!(isAllowed && canEdit && imageHandlerApi && ownerEntity));
|
|
84
|
+
}, [canEdit, ownerEntity, permission, hasPermission, imageHandlerApi]);
|
|
85
|
+
|
|
86
|
+
// Main gallery state and handlers
|
|
87
|
+
const {
|
|
88
|
+
openImage,
|
|
89
|
+
selectedImageIndex,
|
|
90
|
+
selectingImage,
|
|
91
|
+
imageList,
|
|
92
|
+
selectedImage,
|
|
93
|
+
zoomLevel,
|
|
94
|
+
loading,
|
|
95
|
+
removable,
|
|
96
|
+
setZoomLevel,
|
|
97
|
+
handleOpenImage,
|
|
98
|
+
handleCloseImage,
|
|
99
|
+
handleImageSelect,
|
|
100
|
+
handleCancel,
|
|
101
|
+
handleRemoveImage,
|
|
102
|
+
handleAfterUpload
|
|
103
|
+
} = useImageGallery({
|
|
104
|
+
imageCodes,
|
|
105
|
+
multiple,
|
|
106
|
+
canEdit,
|
|
107
|
+
updatable,
|
|
108
|
+
imageHandlerApi,
|
|
109
|
+
ownerEntity,
|
|
110
|
+
currentCompany,
|
|
111
|
+
onRefresh,
|
|
112
|
+
showError
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Zoom and navigation handlers
|
|
116
|
+
const {
|
|
117
|
+
handleZoomIn,
|
|
118
|
+
handleZoomOut,
|
|
119
|
+
handlePreviousImage,
|
|
120
|
+
handleNextImage
|
|
121
|
+
} = useImageZoom(getImageUrl, imageList);
|
|
122
|
+
|
|
123
|
+
// Enhanced zoom handlers that update the main state
|
|
124
|
+
const handleZoomInEnhanced = () => {
|
|
125
|
+
setZoomLevel(prev => Math.min(prev + 0.5, 3));
|
|
126
|
+
};
|
|
127
|
+
const handleZoomOutEnhanced = () => {
|
|
128
|
+
setZoomLevel(prev => Math.max(prev - 0.5, 0.5));
|
|
129
|
+
};
|
|
130
|
+
const handlePreviousImageEnhanced = () => {
|
|
131
|
+
const newImageUrl = handlePreviousImage();
|
|
132
|
+
handleOpenImage(newImageUrl, selectedImageIndex > 0 ? selectedImageIndex - 1 : imageList.length - 1);
|
|
133
|
+
};
|
|
134
|
+
const handleNextImageEnhanced = () => {
|
|
135
|
+
const newImageUrl = handleNextImage();
|
|
136
|
+
handleOpenImage(newImageUrl, selectedImageIndex < imageList.length - 1 ? selectedImageIndex + 1 : 0);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Download handler
|
|
140
|
+
const handleDownloadImage = imageCode => {
|
|
141
|
+
const imageUrl = getImageUrl({
|
|
142
|
+
imageCode
|
|
143
|
+
});
|
|
144
|
+
downloadImage(imageUrl, imageCode);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Clipboard paste handler
|
|
148
|
+
const handlePasteImage = file => {
|
|
149
|
+
handleImageSelect(null);
|
|
150
|
+
setTimeout(() => {
|
|
151
|
+
const event = new CustomEvent('pasteImage', {
|
|
152
|
+
detail: {
|
|
153
|
+
file
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
window.dispatchEvent(event);
|
|
157
|
+
}, 100);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Clipboard support
|
|
161
|
+
const {
|
|
162
|
+
clipboardSupported
|
|
163
|
+
} = useClipboard(updatable, selectingImage, handlePasteImage);
|
|
164
|
+
|
|
165
|
+
// Keyboard navigation
|
|
166
|
+
useKeyboardNavigation(openImage, handlePreviousImageEnhanced, handleNextImageEnhanced, handleCloseImage, handleZoomInEnhanced, handleZoomOutEnhanced);
|
|
167
|
+
|
|
168
|
+
// Show image selector if needed
|
|
169
|
+
if (updatable && (selectingImage || !imageList || imageList.length === 0)) {
|
|
170
|
+
return /*#__PURE__*/_jsxs("div", {
|
|
171
|
+
style: {
|
|
172
|
+
padding: '20px',
|
|
173
|
+
border: '2px dashed #ccc',
|
|
174
|
+
textAlign: 'center',
|
|
175
|
+
borderRadius: '8px',
|
|
176
|
+
backgroundColor: '#f5f5f5'
|
|
177
|
+
},
|
|
178
|
+
children: [/*#__PURE__*/_jsx("p", {
|
|
179
|
+
children: multiple ? 'Seleccionar Imágenes' : 'Seleccionar Imagen'
|
|
180
|
+
}), /*#__PURE__*/_jsx("p", {
|
|
181
|
+
style: {
|
|
182
|
+
color: '#666',
|
|
183
|
+
fontSize: '14px'
|
|
184
|
+
},
|
|
185
|
+
children: multiple ? 'Arrastra imágenes aquí o haz clic para seleccionar' : 'Arrastra una imagen aquí o haz clic para seleccionar'
|
|
186
|
+
}), /*#__PURE__*/_jsx("button", {
|
|
187
|
+
onClick: handleCancel,
|
|
188
|
+
style: {
|
|
189
|
+
marginTop: '10px'
|
|
190
|
+
},
|
|
191
|
+
children: "Cancelar"
|
|
192
|
+
})]
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
196
|
+
children: [/*#__PURE__*/_jsx(ImageGalleryGrid, {
|
|
197
|
+
imageList: imageList,
|
|
198
|
+
emptyMessage: emptyMessage,
|
|
199
|
+
multiple: multiple,
|
|
200
|
+
updatable: updatable,
|
|
201
|
+
removable: removable,
|
|
202
|
+
allowDownload: allowDownload,
|
|
203
|
+
showImageInfo: showImageInfo,
|
|
204
|
+
loading: loading,
|
|
205
|
+
clipboardSupported: clipboardSupported,
|
|
206
|
+
getImageUrl: getImageUrl,
|
|
207
|
+
noImageUrl: noImageUrl,
|
|
208
|
+
onImageClick: handleOpenImage,
|
|
209
|
+
onImageSelect: handleImageSelect,
|
|
210
|
+
onRemoveImage: handleRemoveImage,
|
|
211
|
+
onDownloadImage: handleDownloadImage
|
|
212
|
+
}), /*#__PURE__*/_jsx(ImageGalleryModal, {
|
|
213
|
+
open: openImage !== null,
|
|
214
|
+
imageUrl: openImage,
|
|
215
|
+
imageList: imageList,
|
|
216
|
+
selectedImageIndex: selectedImageIndex,
|
|
217
|
+
zoomLevel: zoomLevel,
|
|
218
|
+
noImageUrl: noImageUrl,
|
|
219
|
+
onClose: handleCloseImage,
|
|
220
|
+
onZoomIn: handleZoomInEnhanced,
|
|
221
|
+
onZoomOut: handleZoomOutEnhanced,
|
|
222
|
+
onPreviousImage: handlePreviousImageEnhanced,
|
|
223
|
+
onNextImage: handleNextImageEnhanced
|
|
224
|
+
})]
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
GoalImageGallery.propTypes = {
|
|
228
|
+
canEdit: PropTypes.bool,
|
|
229
|
+
ownerEntity: PropTypes.object,
|
|
230
|
+
imageHandlerApi: PropTypes.func,
|
|
231
|
+
afterUpload: PropTypes.func,
|
|
232
|
+
imageCodes: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
|
233
|
+
multiple: PropTypes.bool,
|
|
234
|
+
permission: PropTypes.string,
|
|
235
|
+
emptyMessage: PropTypes.string,
|
|
236
|
+
showImageInfo: PropTypes.bool,
|
|
237
|
+
allowDownload: PropTypes.bool,
|
|
238
|
+
getImageUrl: PropTypes.func,
|
|
239
|
+
noImageUrl: PropTypes.string,
|
|
240
|
+
showError: PropTypes.func,
|
|
241
|
+
hasPermission: PropTypes.func,
|
|
242
|
+
currentCompany: PropTypes.string
|
|
243
|
+
};
|
|
244
|
+
export default GoalImageGallery;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Typography, Stack, Chip, Button } from '@mui/material';
|
|
4
|
+
import { Image as ImageIcon, CloudUpload as UploadIcon, ContentPaste as PasteIcon, Link as LinkIcon, Add as AddIcon } from '@mui/icons-material';
|
|
5
|
+
import { StyledEmptyStateContainer } from '../styles/styled-components';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Empty state component for image gallery
|
|
9
|
+
*/
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
11
|
+
const ImageGalleryEmptyState = _ref => {
|
|
12
|
+
let {
|
|
13
|
+
emptyMessage,
|
|
14
|
+
updatable,
|
|
15
|
+
clipboardSupported,
|
|
16
|
+
onAddImage
|
|
17
|
+
} = _ref;
|
|
18
|
+
return /*#__PURE__*/_jsx(StyledEmptyStateContainer, {
|
|
19
|
+
children: /*#__PURE__*/_jsxs(Stack, {
|
|
20
|
+
spacing: 3,
|
|
21
|
+
alignItems: "center",
|
|
22
|
+
children: [/*#__PURE__*/_jsx(ImageIcon, {
|
|
23
|
+
sx: {
|
|
24
|
+
fontSize: 64,
|
|
25
|
+
color: 'text.secondary'
|
|
26
|
+
}
|
|
27
|
+
}), /*#__PURE__*/_jsx(Typography, {
|
|
28
|
+
variant: "h6",
|
|
29
|
+
color: "text.secondary",
|
|
30
|
+
children: emptyMessage
|
|
31
|
+
}), updatable && /*#__PURE__*/_jsxs(_Fragment, {
|
|
32
|
+
children: [/*#__PURE__*/_jsx(Button, {
|
|
33
|
+
variant: "contained",
|
|
34
|
+
startIcon: /*#__PURE__*/_jsx(AddIcon, {}),
|
|
35
|
+
onClick: onAddImage,
|
|
36
|
+
sx: {
|
|
37
|
+
mt: 2
|
|
38
|
+
},
|
|
39
|
+
children: "Agregar imagen"
|
|
40
|
+
}), /*#__PURE__*/_jsxs(Stack, {
|
|
41
|
+
direction: "row",
|
|
42
|
+
spacing: 1,
|
|
43
|
+
alignItems: "center",
|
|
44
|
+
sx: {
|
|
45
|
+
mt: 2
|
|
46
|
+
},
|
|
47
|
+
children: [/*#__PURE__*/_jsx(Chip, {
|
|
48
|
+
icon: /*#__PURE__*/_jsx(UploadIcon, {}),
|
|
49
|
+
label: "Subir archivo",
|
|
50
|
+
variant: "outlined",
|
|
51
|
+
size: "small",
|
|
52
|
+
onClick: onAddImage
|
|
53
|
+
}), clipboardSupported && /*#__PURE__*/_jsx(Chip, {
|
|
54
|
+
icon: /*#__PURE__*/_jsx(PasteIcon, {}),
|
|
55
|
+
label: "Pegar (Ctrl+V)",
|
|
56
|
+
variant: "outlined",
|
|
57
|
+
size: "small",
|
|
58
|
+
color: "secondary"
|
|
59
|
+
}), /*#__PURE__*/_jsx(Chip, {
|
|
60
|
+
icon: /*#__PURE__*/_jsx(LinkIcon, {}),
|
|
61
|
+
label: "Desde URL",
|
|
62
|
+
variant: "outlined",
|
|
63
|
+
size: "small",
|
|
64
|
+
color: "info"
|
|
65
|
+
})]
|
|
66
|
+
}), /*#__PURE__*/_jsx(Typography, {
|
|
67
|
+
variant: "caption",
|
|
68
|
+
color: "text.secondary",
|
|
69
|
+
textAlign: "center",
|
|
70
|
+
children: "Soporta m\xFAltiples m\xE9todos de carga: archivos, portapapeles y URLs"
|
|
71
|
+
})]
|
|
72
|
+
})]
|
|
73
|
+
})
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
ImageGalleryEmptyState.propTypes = {
|
|
77
|
+
emptyMessage: PropTypes.string.isRequired,
|
|
78
|
+
updatable: PropTypes.bool,
|
|
79
|
+
clipboardSupported: PropTypes.bool,
|
|
80
|
+
onAddImage: PropTypes.func
|
|
81
|
+
};
|
|
82
|
+
ImageGalleryEmptyState.defaultProps = {
|
|
83
|
+
updatable: false,
|
|
84
|
+
clipboardSupported: false,
|
|
85
|
+
onAddImage: () => {}
|
|
86
|
+
};
|
|
87
|
+
export default ImageGalleryEmptyState;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Grid, Card, CardContent, useMediaQuery } from '@mui/material';
|
|
4
|
+
import { useTheme } from '@mui/material/styles';
|
|
5
|
+
import ImageGalleryItem from './ImageGalleryItem';
|
|
6
|
+
import ImageGalleryEmptyState from './ImageGalleryEmptyState';
|
|
7
|
+
import { StyledImageGridContainer } from '../styles/styled-components';
|
|
8
|
+
import { getGridColumns } from '../utils/image-utils';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Image grid component for displaying gallery images
|
|
12
|
+
*/
|
|
13
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
|
+
const ImageGalleryGrid = _ref => {
|
|
15
|
+
let {
|
|
16
|
+
imageList,
|
|
17
|
+
emptyMessage,
|
|
18
|
+
multiple,
|
|
19
|
+
updatable,
|
|
20
|
+
removable,
|
|
21
|
+
allowDownload,
|
|
22
|
+
showImageInfo,
|
|
23
|
+
loading,
|
|
24
|
+
clipboardSupported,
|
|
25
|
+
getImageUrl,
|
|
26
|
+
noImageUrl,
|
|
27
|
+
onImageClick,
|
|
28
|
+
onImageSelect,
|
|
29
|
+
onRemoveImage,
|
|
30
|
+
onDownloadImage
|
|
31
|
+
} = _ref;
|
|
32
|
+
const theme = useTheme();
|
|
33
|
+
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
|
34
|
+
const isTablet = useMediaQuery(theme.breakpoints.down('lg'));
|
|
35
|
+
if (imageList.length === 0) {
|
|
36
|
+
return /*#__PURE__*/_jsx(Card, {
|
|
37
|
+
elevation: 2,
|
|
38
|
+
children: /*#__PURE__*/_jsx(CardContent, {
|
|
39
|
+
children: /*#__PURE__*/_jsx(ImageGalleryEmptyState, {
|
|
40
|
+
emptyMessage: emptyMessage,
|
|
41
|
+
updatable: updatable,
|
|
42
|
+
clipboardSupported: clipboardSupported,
|
|
43
|
+
onAddImage: () => onImageSelect(null)
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return /*#__PURE__*/_jsx(Card, {
|
|
49
|
+
elevation: 2,
|
|
50
|
+
children: /*#__PURE__*/_jsx(CardContent, {
|
|
51
|
+
children: /*#__PURE__*/_jsx(StyledImageGridContainer, {
|
|
52
|
+
children: /*#__PURE__*/_jsx(Grid, {
|
|
53
|
+
container: true,
|
|
54
|
+
spacing: 2,
|
|
55
|
+
children: imageList.map((image, index) => /*#__PURE__*/_jsx(Grid, {
|
|
56
|
+
item: true,
|
|
57
|
+
xs: getGridColumns(isMobile, isTablet, multiple),
|
|
58
|
+
sx: {
|
|
59
|
+
position: 'relative'
|
|
60
|
+
},
|
|
61
|
+
children: /*#__PURE__*/_jsx(ImageGalleryItem, {
|
|
62
|
+
image: image,
|
|
63
|
+
index: index,
|
|
64
|
+
getImageUrl: getImageUrl,
|
|
65
|
+
noImageUrl: noImageUrl,
|
|
66
|
+
loading: loading,
|
|
67
|
+
updatable: updatable,
|
|
68
|
+
removable: removable,
|
|
69
|
+
allowDownload: allowDownload,
|
|
70
|
+
showImageInfo: showImageInfo,
|
|
71
|
+
onImageClick: onImageClick,
|
|
72
|
+
onImageSelect: onImageSelect,
|
|
73
|
+
onRemoveImage: onRemoveImage,
|
|
74
|
+
onDownloadImage: onDownloadImage
|
|
75
|
+
})
|
|
76
|
+
}, image.id))
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
ImageGalleryGrid.propTypes = {
|
|
83
|
+
imageList: PropTypes.array.isRequired,
|
|
84
|
+
emptyMessage: PropTypes.string.isRequired,
|
|
85
|
+
multiple: PropTypes.bool,
|
|
86
|
+
updatable: PropTypes.bool,
|
|
87
|
+
removable: PropTypes.bool,
|
|
88
|
+
allowDownload: PropTypes.bool,
|
|
89
|
+
showImageInfo: PropTypes.bool,
|
|
90
|
+
loading: PropTypes.bool,
|
|
91
|
+
clipboardSupported: PropTypes.bool,
|
|
92
|
+
getImageUrl: PropTypes.func.isRequired,
|
|
93
|
+
noImageUrl: PropTypes.string.isRequired,
|
|
94
|
+
onImageClick: PropTypes.func.isRequired,
|
|
95
|
+
onImageSelect: PropTypes.func.isRequired,
|
|
96
|
+
onRemoveImage: PropTypes.func.isRequired,
|
|
97
|
+
onDownloadImage: PropTypes.func.isRequired
|
|
98
|
+
};
|
|
99
|
+
ImageGalleryGrid.defaultProps = {
|
|
100
|
+
multiple: false,
|
|
101
|
+
updatable: false,
|
|
102
|
+
removable: false,
|
|
103
|
+
allowDownload: false,
|
|
104
|
+
showImageInfo: false,
|
|
105
|
+
loading: false,
|
|
106
|
+
clipboardSupported: false
|
|
107
|
+
};
|
|
108
|
+
export default ImageGalleryGrid;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Box, IconButton, Skeleton, Tooltip, Typography, Button } from '@mui/material';
|
|
4
|
+
import { alpha, useTheme } from '@mui/material/styles';
|
|
5
|
+
import { ChangeCircle as ChangeIcon, Download as DownloadIcon, Visibility as ViewIcon, Add as AddIcon, Delete as DeleteIcon } from '@mui/icons-material';
|
|
6
|
+
import { StyledImageContainer, ImageOverlay } from '../styles/styled-components';
|
|
7
|
+
import { handleImageError } from '../utils/image-utils';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Individual image item component
|
|
11
|
+
*/
|
|
12
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
const ImageGalleryItem = _ref => {
|
|
14
|
+
let {
|
|
15
|
+
image,
|
|
16
|
+
index,
|
|
17
|
+
getImageUrl,
|
|
18
|
+
noImageUrl,
|
|
19
|
+
loading,
|
|
20
|
+
updatable,
|
|
21
|
+
removable,
|
|
22
|
+
allowDownload,
|
|
23
|
+
showImageInfo,
|
|
24
|
+
onImageClick,
|
|
25
|
+
onImageSelect,
|
|
26
|
+
onRemoveImage,
|
|
27
|
+
onDownloadImage
|
|
28
|
+
} = _ref;
|
|
29
|
+
const theme = useTheme();
|
|
30
|
+
|
|
31
|
+
// Handle add button (+) image
|
|
32
|
+
if (image.image_code === '+') {
|
|
33
|
+
return /*#__PURE__*/_jsx(StyledImageContainer, {
|
|
34
|
+
sx: {
|
|
35
|
+
height: 200,
|
|
36
|
+
display: 'flex',
|
|
37
|
+
alignItems: 'center',
|
|
38
|
+
justifyContent: 'center',
|
|
39
|
+
border: '2px dashed',
|
|
40
|
+
borderColor: 'primary.main',
|
|
41
|
+
backgroundColor: alpha(theme.palette.primary.main, 0.04),
|
|
42
|
+
cursor: 'pointer',
|
|
43
|
+
'&:hover': {
|
|
44
|
+
backgroundColor: alpha(theme.palette.primary.main, 0.08)
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
onClick: () => onImageSelect(null),
|
|
48
|
+
children: /*#__PURE__*/_jsx(Button, {
|
|
49
|
+
variant: "outlined",
|
|
50
|
+
startIcon: /*#__PURE__*/_jsx(AddIcon, {}),
|
|
51
|
+
sx: {
|
|
52
|
+
width: '100%',
|
|
53
|
+
height: '100%',
|
|
54
|
+
border: 'none',
|
|
55
|
+
backgroundColor: 'transparent',
|
|
56
|
+
'&:hover': {
|
|
57
|
+
border: 'none',
|
|
58
|
+
backgroundColor: 'transparent'
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
children: "Agregar imagen"
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return /*#__PURE__*/_jsxs(StyledImageContainer, {
|
|
66
|
+
children: [loading && /*#__PURE__*/_jsx(Skeleton, {
|
|
67
|
+
variant: "rectangular",
|
|
68
|
+
width: "100%",
|
|
69
|
+
height: 200,
|
|
70
|
+
animation: "wave"
|
|
71
|
+
}), /*#__PURE__*/_jsx("img", {
|
|
72
|
+
src: getImageUrl({
|
|
73
|
+
imageCode: image.image_code,
|
|
74
|
+
thumbMail: true
|
|
75
|
+
}),
|
|
76
|
+
alt: image.title || `Imagen ${index + 1}`,
|
|
77
|
+
onClick: () => onImageClick(getImageUrl({
|
|
78
|
+
imageCode: image.image_code
|
|
79
|
+
}), index),
|
|
80
|
+
onError: e => handleImageError(e, noImageUrl),
|
|
81
|
+
style: {
|
|
82
|
+
width: '100%',
|
|
83
|
+
height: 200,
|
|
84
|
+
objectFit: 'cover',
|
|
85
|
+
display: loading ? 'none' : 'block'
|
|
86
|
+
}
|
|
87
|
+
}, image.image_code), /*#__PURE__*/_jsxs(ImageOverlay, {
|
|
88
|
+
className: "image-overlay",
|
|
89
|
+
children: [/*#__PURE__*/_jsx(Tooltip, {
|
|
90
|
+
title: "Ver imagen",
|
|
91
|
+
children: /*#__PURE__*/_jsx(IconButton, {
|
|
92
|
+
size: "small",
|
|
93
|
+
onClick: e => {
|
|
94
|
+
e.stopPropagation();
|
|
95
|
+
onImageClick(getImageUrl({
|
|
96
|
+
imageCode: image.image_code
|
|
97
|
+
}), index);
|
|
98
|
+
},
|
|
99
|
+
children: /*#__PURE__*/_jsx(ViewIcon, {})
|
|
100
|
+
})
|
|
101
|
+
}), allowDownload && /*#__PURE__*/_jsx(Tooltip, {
|
|
102
|
+
title: "Descargar imagen",
|
|
103
|
+
children: /*#__PURE__*/_jsx(IconButton, {
|
|
104
|
+
size: "small",
|
|
105
|
+
onClick: e => {
|
|
106
|
+
e.stopPropagation();
|
|
107
|
+
onDownloadImage(image.image_code);
|
|
108
|
+
},
|
|
109
|
+
children: /*#__PURE__*/_jsx(DownloadIcon, {})
|
|
110
|
+
})
|
|
111
|
+
}), updatable && /*#__PURE__*/_jsx(Tooltip, {
|
|
112
|
+
title: "Cambiar imagen",
|
|
113
|
+
children: /*#__PURE__*/_jsx(IconButton, {
|
|
114
|
+
size: "small",
|
|
115
|
+
onClick: e => {
|
|
116
|
+
e.stopPropagation();
|
|
117
|
+
onImageSelect(image);
|
|
118
|
+
},
|
|
119
|
+
children: /*#__PURE__*/_jsx(ChangeIcon, {})
|
|
120
|
+
})
|
|
121
|
+
}), removable && /*#__PURE__*/_jsx(Tooltip, {
|
|
122
|
+
title: "Eliminar imagen",
|
|
123
|
+
children: /*#__PURE__*/_jsx(IconButton, {
|
|
124
|
+
size: "small",
|
|
125
|
+
onClick: e => {
|
|
126
|
+
e.stopPropagation();
|
|
127
|
+
if (window.confirm('¿Está seguro de que desea eliminar esta imagen?')) {
|
|
128
|
+
onRemoveImage(image);
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
color: "error",
|
|
132
|
+
children: /*#__PURE__*/_jsx(DeleteIcon, {})
|
|
133
|
+
})
|
|
134
|
+
})]
|
|
135
|
+
}), showImageInfo && /*#__PURE__*/_jsx(Box, {
|
|
136
|
+
sx: {
|
|
137
|
+
position: 'absolute',
|
|
138
|
+
bottom: 0,
|
|
139
|
+
left: 0,
|
|
140
|
+
right: 0,
|
|
141
|
+
backgroundColor: alpha(theme.palette.common.black, 0.7),
|
|
142
|
+
color: 'white',
|
|
143
|
+
p: 1
|
|
144
|
+
},
|
|
145
|
+
children: /*#__PURE__*/_jsx(Typography, {
|
|
146
|
+
variant: "caption",
|
|
147
|
+
noWrap: true,
|
|
148
|
+
children: image.title || `Imagen ${index + 1}`
|
|
149
|
+
})
|
|
150
|
+
})]
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
ImageGalleryItem.propTypes = {
|
|
154
|
+
image: PropTypes.object.isRequired,
|
|
155
|
+
index: PropTypes.number.isRequired,
|
|
156
|
+
getImageUrl: PropTypes.func.isRequired,
|
|
157
|
+
noImageUrl: PropTypes.string.isRequired,
|
|
158
|
+
loading: PropTypes.bool,
|
|
159
|
+
updatable: PropTypes.bool,
|
|
160
|
+
removable: PropTypes.bool,
|
|
161
|
+
allowDownload: PropTypes.bool,
|
|
162
|
+
showImageInfo: PropTypes.bool,
|
|
163
|
+
onImageClick: PropTypes.func.isRequired,
|
|
164
|
+
onImageSelect: PropTypes.func.isRequired,
|
|
165
|
+
onRemoveImage: PropTypes.func.isRequired,
|
|
166
|
+
onDownloadImage: PropTypes.func.isRequired
|
|
167
|
+
};
|
|
168
|
+
ImageGalleryItem.defaultProps = {
|
|
169
|
+
loading: false,
|
|
170
|
+
updatable: false,
|
|
171
|
+
removable: false,
|
|
172
|
+
allowDownload: false,
|
|
173
|
+
showImageInfo: false
|
|
174
|
+
};
|
|
175
|
+
export default ImageGalleryItem;
|