UNPKG

react-advanced-gallery

Version:

A customizable React image gallery component with Bootstrap styling, fullscreen mode, animations, and support for various media types

930 lines (921 loc) 34.9 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { FullscreenViewer: () => FullscreenViewer, GalleryConfig: () => GalleryConfig, GalleryGrid: () => GalleryGrid, GalleryItem: () => GalleryItem, ImageGallery: () => ImageGallery, ImageUploader: () => ImageUploader, useGallery: () => useGallery }); module.exports = __toCommonJS(src_exports); // src/hooks/useGallery.ts var import_react = require("react"); var useGallery = ({ initialImages, initialLayout = "grid", initialAnimation = "fade", initialThumbnailsPosition = "bottom", initialEnableZoom = true }) => { const [images, setImages] = (0, import_react.useState)(initialImages); const [selectedImage, setSelectedImage] = (0, import_react.useState)(null); const [currentIndex, setCurrentIndex] = (0, import_react.useState)(0); const [isFullscreenOpen, setIsFullscreenOpen] = (0, import_react.useState)(false); const [zoomLevel, setZoomLevel] = (0, import_react.useState)(1); const [layout, setLayout] = (0, import_react.useState)(initialLayout); const [animation, setAnimation] = (0, import_react.useState)(initialAnimation); const [thumbnailsPosition, setThumbnailsPosition] = (0, import_react.useState)(initialThumbnailsPosition); const [enableZoom, setEnableZoom] = (0, import_react.useState)(initialEnableZoom); (0, import_react.useEffect)(() => { if (!isFullscreenOpen) { setZoomLevel(1); } }, [isFullscreenOpen, selectedImage]); const openFullscreen = (0, import_react.useCallback)((image) => { const index = images.findIndex((img) => img.id === image.id); setSelectedImage(image); setCurrentIndex(index !== -1 ? index : 0); setIsFullscreenOpen(true); }, [images]); const closeFullscreen = (0, import_react.useCallback)(() => { setIsFullscreenOpen(false); setSelectedImage(null); }, []); const toggleFavorite = (0, import_react.useCallback)((image) => { setImages( (prevImages) => prevImages.map( (img) => img.id === image.id ? { ...img, isFavorite: !img.isFavorite } : img ) ); if (selectedImage && selectedImage.id === image.id) { setSelectedImage({ ...selectedImage, isFavorite: !selectedImage.isFavorite }); } }, [selectedImage]); const removeImage = (0, import_react.useCallback)((image) => { const newImages = images.filter((img) => img.id !== image.id); if (isFullscreenOpen && selectedImage && selectedImage.id === image.id) { if (newImages.length > 0) { const newIndex = currentIndex >= newImages.length ? newImages.length - 1 : currentIndex; setCurrentIndex(newIndex); setSelectedImage(newImages[newIndex]); } else { closeFullscreen(); } } setImages(newImages); }, [isFullscreenOpen, selectedImage, images, currentIndex, closeFullscreen]); const navigateToImage = (0, import_react.useCallback)((index) => { if (index >= 0 && index < images.length) { setCurrentIndex(index); setSelectedImage(images[index]); } }, [images]); const navigatePrev = (0, import_react.useCallback)(() => { const newIndex = (currentIndex - 1 + images.length) % images.length; navigateToImage(newIndex); }, [currentIndex, images.length, navigateToImage]); const navigateNext = (0, import_react.useCallback)(() => { const newIndex = (currentIndex + 1) % images.length; navigateToImage(newIndex); }, [currentIndex, images.length, navigateToImage]); const zoomIn = (0, import_react.useCallback)(() => { setZoomLevel((prevZoom) => Math.min(prevZoom + 0.5, 3)); }, []); const zoomOut = (0, import_react.useCallback)(() => { setZoomLevel((prevZoom) => Math.max(prevZoom - 0.5, 0.5)); }, []); const resetZoom = (0, import_react.useCallback)(() => { setZoomLevel(1); }, []); const addImages = (0, import_react.useCallback)((newImages) => { setImages((prevImages) => [...prevImages, ...newImages]); }, []); const updateConfig = (0, import_react.useCallback)((config) => { if (config.layout) setLayout(config.layout); if (config.animation) setAnimation(config.animation); if (config.thumbnailsPosition) setThumbnailsPosition(config.thumbnailsPosition); if (config.enableZoom !== void 0) setEnableZoom(config.enableZoom); }, []); (0, import_react.useEffect)(() => { const handleKeyDown = (e) => { if (!isFullscreenOpen) return; switch (e.key) { case "Escape": closeFullscreen(); break; case "ArrowLeft": navigatePrev(); break; case "ArrowRight": navigateNext(); break; case "+": zoomIn(); break; case "-": zoomOut(); break; case "0": resetZoom(); break; default: break; } }; window.addEventListener("keydown", handleKeyDown); return () => { window.removeEventListener("keydown", handleKeyDown); }; }, [isFullscreenOpen, closeFullscreen, navigatePrev, navigateNext, zoomIn, zoomOut, resetZoom]); return { images, selectedImage, currentIndex, isFullscreenOpen, zoomLevel, layout, animation, thumbnailsPosition, enableZoom, openFullscreen, closeFullscreen, toggleFavorite, removeImage, navigateToImage, navigatePrev, navigateNext, zoomIn, zoomOut, resetZoom, addImages, updateConfig }; }; // src/components/GalleryConfig.tsx var import_jsx_runtime = require("react/jsx-runtime"); function GalleryConfig({ layout, animation, thumbnailsPosition, enableZoom, onConfigChange }) { const handleLayoutChange = (value) => { onConfigChange({ layout: value }); }; const handleAnimationChange = (value) => { onConfigChange({ animation: value }); }; const handleThumbnailsPositionChange = (value) => { onConfigChange({ thumbnailsPosition: value }); }; const handleZoomToggle = () => { onConfigChange({ enableZoom: !enableZoom }); }; return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rag-config rag-mb-4", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rag-config-header", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "rag-config-title", children: "Gallery Configuration" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rag-config-subtitle", children: "Customize your gallery experience" }) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rag-config-controls", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rag-config-group", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "layout-select", className: "rag-config-label", children: "Layout:" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( "select", { id: "layout-select", value: layout, onChange: (e) => handleLayoutChange(e.target.value), className: "rag-config-select", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "grid", children: "Grid" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "masonry", children: "Masonry" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "carousel", children: "Carousel" }) ] } ) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rag-config-group", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "animation-select", className: "rag-config-label", children: "Animation:" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( "select", { id: "animation-select", value: animation, onChange: (e) => handleAnimationChange(e.target.value), className: "rag-config-select", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "fade", children: "Fade" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "slide", children: "Slide" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "zoom", children: "Zoom" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "none", children: "None" }) ] } ) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rag-config-group", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "thumbnails-select", className: "rag-config-label", children: "Thumbnails:" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( "select", { id: "thumbnails-select", value: thumbnailsPosition, onChange: (e) => handleThumbnailsPositionChange(e.target.value), className: "rag-config-select", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "bottom", children: "Bottom" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "top", children: "Top" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "left", children: "Left" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "right", children: "Right" }) ] } ) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rag-config-group", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "zoom-toggle", className: "rag-config-label", children: "Enable Zoom:" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "rag-toggle", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "input", { type: "checkbox", id: "zoom-toggle", checked: enableZoom, onChange: handleZoomToggle, className: "rag-toggle-input" } ), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rag-toggle-slider" }) ] }) ] }) ] }) ] }) }); } // src/components/GalleryItem.tsx var import_jsx_runtime2 = require("react/jsx-runtime"); function GalleryItem({ item, onImageClick, onFavoriteToggle, onImageRemove, animationType }) { const handleFavoriteClick = (e) => { e.stopPropagation(); onFavoriteToggle(item); }; const handleRemoveClick = (e) => { e.stopPropagation(); onImageRemove(item); }; const getAnimationClass = () => { if (animationType === "none") return ""; return `rag-${animationType}-enter`; }; return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)( "div", { className: `rag-item ${getAnimationClass()}`, onClick: () => onImageClick(item), children: [ item.type === "image" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "img", { src: item.src, alt: item.alt, className: "rag-item-image", loading: "lazy" } ) : item.type === "video" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "img", { src: item.thumbnail, alt: item.alt, className: "rag-item-image", loading: "lazy" } ), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "rag-item-badge rag-item-badge-video", children: "Video" }) ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "img", { src: item.thumbnail, alt: item.alt, className: "rag-item-image", loading: "lazy" } ), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "rag-item-badge rag-item-badge-external", children: "External" }) ] }), /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rag-item-caption", children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "rag-item-title", children: item.title }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "rag-item-description", children: item.description }) ] }), /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rag-item-actions", children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "button", { className: `rag-btn rag-btn-favorite ${item.isFavorite ? "active" : ""}`, onClick: handleFavoriteClick, "aria-label": item.isFavorite ? "Remove from favorites" : "Add to favorites", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "rag-material-icons", children: item.isFavorite ? "star" : "star_border" }) } ), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "button", { className: "rag-btn rag-btn-remove", onClick: handleRemoveClick, "aria-label": "Remove image", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "rag-material-icons", children: "delete" }) } ) ] }) ] } ); } // src/components/GalleryGrid.tsx var import_jsx_runtime3 = require("react/jsx-runtime"); function GalleryGrid({ items, layout, animation, onImageClick, onFavoriteToggle, onImageRemove }) { if (layout === "grid") { return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rag-grid rag-mb-4", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( GalleryItem, { item, onImageClick, onFavoriteToggle, onImageRemove, animationType: animation }, item.id )) }); } if (layout === "masonry") { return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "rag-masonry rag-mb-4", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rag-d-flex rag-flex-column rag-gap-3", children: items.filter((_, i) => i % 3 === 0).map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( GalleryItem, { item, onImageClick, onFavoriteToggle, onImageRemove, animationType: animation }, item.id )) }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rag-d-flex rag-flex-column rag-gap-3", children: items.filter((_, i) => i % 3 === 1).map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( GalleryItem, { item, onImageClick, onFavoriteToggle, onImageRemove, animationType: animation }, item.id )) }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rag-d-flex rag-flex-column rag-gap-3", style: { display: window.innerWidth < 992 ? "none" : "flex" }, children: items.filter((_, i) => i % 3 === 2).map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( GalleryItem, { item, onImageClick, onFavoriteToggle, onImageRemove, animationType: animation }, item.id )) }) ] }); } return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rag-carousel-container rag-mb-4", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rag-carousel", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( "div", { className: "rag-carousel-item", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( GalleryItem, { item, onImageClick, onFavoriteToggle, onImageRemove, animationType: animation } ) }, item.id )) }) }); } // src/components/FullscreenViewer.tsx var import_react2 = require("react"); var import_jsx_runtime4 = require("react/jsx-runtime"); function FullscreenViewer({ isOpen, currentImage, images, currentIndex, zoomLevel, onClose, onPrev, onNext, onFavoriteToggle, onImageRemove, onZoomIn, onZoomOut, onThumbnailClick }) { const [dragPosition, setDragPosition] = (0, import_react2.useState)({ x: 0, y: 0 }); const containerRef = (0, import_react2.useRef)(null); (0, import_react2.useEffect)(() => { setDragPosition({ x: 0, y: 0 }); }, [currentIndex, zoomLevel]); const handleDownload = () => { if (!currentImage) return; const link = document.createElement("a"); link.href = currentImage.src; link.download = currentImage.title || "image"; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; const handleFullScreen = () => { if (!containerRef.current) return; if (!document.fullscreenElement) { containerRef.current.requestFullscreen().catch((err) => { console.error(`Error attempting to enable fullscreen: ${err.message}`); }); } else { document.exitFullscreen(); } }; const dragConstraints = zoomLevel > 1 ? { top: -((containerRef.current?.clientHeight || 0) * (zoomLevel - 1) / 2), left: -((containerRef.current?.clientWidth || 0) * (zoomLevel - 1) / 2), right: (containerRef.current?.clientWidth || 0) * (zoomLevel - 1) / 2, bottom: (containerRef.current?.clientHeight || 0) * (zoomLevel - 1) / 2 } : { top: 0, left: 0, right: 0, bottom: 0 }; if (!isOpen || !currentImage) return null; const formatDate = (dateString) => { if (!dateString) return ""; try { return new Date(dateString).toLocaleDateString(); } catch (e) { return ""; } }; return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)( "div", { ref: containerRef, className: "rag-fullscreen", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rag-fullscreen-header", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rag-fullscreen-title", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "button", { className: "rag-fullscreen-btn", onClick: onClose, "aria-label": "Close fullscreen view", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "close" }) } ), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: currentImage.title }) ] }), /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rag-fullscreen-actions", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "button", { className: "rag-fullscreen-btn", onClick: () => onFavoriteToggle(currentImage), "aria-label": currentImage.isFavorite ? "Remove from favorites" : "Add to favorites", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `rag-material-icons ${currentImage.isFavorite ? "active" : ""}`, children: currentImage.isFavorite ? "star" : "star_border" }) } ), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "button", { className: "rag-fullscreen-btn", onClick: () => onImageRemove(currentImage), "aria-label": "Delete image", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "delete" }) } ), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "button", { className: "rag-fullscreen-btn", onClick: handleDownload, "aria-label": "Download image", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "download" }) } ) ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rag-fullscreen-content", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "button", { className: "rag-fullscreen-nav-prev", onClick: onPrev, "aria-label": "Previous image", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "chevron_left" }) } ), currentImage.type === "video" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "video", { src: currentImage.src, poster: currentImage.thumbnail, controls: true, className: "rag-fullscreen-image" } ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "img", { src: currentImage.src, alt: currentImage.alt, className: `rag-fullscreen-image ${zoomLevel > 1 ? "zoomed" : ""}`, style: { transform: `scale(${zoomLevel})`, position: "relative", left: `${dragPosition.x}px`, top: `${dragPosition.y}px` }, draggable: false, onMouseDown: (e) => { if (zoomLevel <= 1) return; const startX = e.clientX; const startY = e.clientY; const startDragX = dragPosition.x; const startDragY = dragPosition.y; const onMouseMove = (e2) => { const dx = e2.clientX - startX; const dy = e2.clientY - startY; const newX = Math.min(Math.max(startDragX + dx, -dragConstraints.right), dragConstraints.right); const newY = Math.min(Math.max(startDragY + dy, -dragConstraints.bottom), dragConstraints.bottom); setDragPosition({ x: newX, y: newY }); }; const onMouseUp = () => { document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("mouseup", onMouseUp); }; document.addEventListener("mousemove", onMouseMove); document.addEventListener("mouseup", onMouseUp); } } ), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "button", { className: "rag-fullscreen-nav-next", onClick: onNext, "aria-label": "Next image", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "chevron_right" }) } ), /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rag-fullscreen-zoom-controls", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "button", { className: "rag-fullscreen-btn", onClick: onZoomOut, "aria-label": "Zoom out", disabled: zoomLevel <= 0.5, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "zoom_out" }) } ), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "button", { className: "rag-fullscreen-btn", onClick: onZoomIn, "aria-label": "Zoom in", disabled: zoomLevel >= 3, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "zoom_in" }) } ), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "button", { className: "rag-fullscreen-btn", onClick: handleFullScreen, "aria-label": "Toggle fullscreen", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "fullscreen" }) } ) ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rag-fullscreen-footer", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "rag-fullscreen-thumbnails", children: images.map((image, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "div", { className: `rag-fullscreen-thumbnail ${index === currentIndex ? "active" : ""}`, onClick: () => onThumbnailClick(index), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "img", { src: image.thumbnail, alt: `${image.alt} thumbnail`, loading: "lazy" } ) }, image.id )) }), currentImage.metadata && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rag-fullscreen-metadata", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: [ currentImage.metadata.camera && `Shot on ${currentImage.metadata.camera}`, currentImage.metadata.aperture, currentImage.metadata.shutterSpeed, currentImage.metadata.iso && `ISO ${currentImage.metadata.iso}`, currentImage.metadata.focalLength ].filter(Boolean).join(" \u2022 ") }), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: [ currentImage.metadata.dateTaken && `Uploaded ${formatDate(currentImage.metadata.dateTaken)}`, currentImage.metadata.fileSize, currentImage.metadata.dimensions ].filter(Boolean).join(" \u2022 ") }) ] }) ] }) ] } ); } // src/components/ImageUploader.tsx var import_react3 = require("react"); var import_jsx_runtime5 = require("react/jsx-runtime"); function ImageUploader({ onImageUpload }) { const [isDragging, setIsDragging] = (0, import_react3.useState)(false); const fileInputRef = (0, import_react3.useRef)(null); const validateFile = (file) => { const validTypes = ["image/jpeg", "image/png", "image/gif", "image/webp", "video/mp4"]; if (!validTypes.includes(file.type)) { console.error("Invalid file type. Only JPG, PNG, GIF, WebP, and MP4 files are supported."); return false; } const maxSize = 10 * 1024 * 1024; if (file.size > maxSize) { console.error("File too large. Maximum file size is 10MB."); return false; } return true; }; const handleFiles = (files) => { const validFiles = Array.from(files).filter(validateFile); if (validFiles.length > 0) { onImageUpload(validFiles); console.log(`Successfully added ${validFiles.length} file(s).`); } }; const handleDragEnter = (e) => { e.preventDefault(); e.stopPropagation(); setIsDragging(true); }; const handleDragLeave = (e) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); }; const handleDragOver = (e) => { e.preventDefault(); e.stopPropagation(); setIsDragging(true); }; const handleDrop = (e) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { handleFiles(e.dataTransfer.files); } }; const handleFileInputChange = (e) => { if (e.target.files && e.target.files.length > 0) { handleFiles(e.target.files); e.target.value = ""; } }; const handleButtonClick = () => { if (fileInputRef.current) { fileInputRef.current.click(); } }; return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "rag-uploader", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "rag-uploader-title", children: "Add New Images" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)( "div", { className: `rag-dropzone ${isDragging ? "active" : ""}`, onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "rag-material-icons rag-dropzone-icon", children: "add_photo_alternate" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "rag-dropzone-text", children: isDragging ? "Drop files here" : "Drag and drop images here, or click to browse" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "input", { type: "file", ref: fileInputRef, className: "hidden", multiple: true, accept: "image/jpeg,image/png,image/gif,image/webp,video/mp4", onChange: handleFileInputChange } ), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "button", { className: "rag-upload-btn", onClick: handleButtonClick, children: "Select Files" } ), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "rag-upload-hint", children: "Supports JPG, PNG, GIF, WebP, and MP4 up to 10MB" }) ] } ) ] }); } // src/components/ImageGallery.tsx var import_jsx_runtime6 = require("react/jsx-runtime"); var generateId = () => `${Date.now()}-${Math.floor(Math.random() * 1e3)}`; function ImageGallery({ images, layout = "grid", animation = "fade", thumbnailsPosition = "bottom", enableZoom = true, onImageClick, onFavoriteToggle, onImageRemove, onImageUpload }) { const { images: galleryImages, selectedImage, currentIndex, isFullscreenOpen, zoomLevel, layout: activeLayout, animation: activeAnimation, thumbnailsPosition: activeThumbnailsPosition, enableZoom: activeEnableZoom, openFullscreen, closeFullscreen, toggleFavorite, removeImage, navigateToImage, navigatePrev, navigateNext, zoomIn, zoomOut, addImages, updateConfig } = useGallery({ initialImages: images, initialLayout: layout, initialAnimation: animation, initialThumbnailsPosition: thumbnailsPosition, initialEnableZoom: enableZoom }); const handleImageClick = (image) => { openFullscreen(image); if (onImageClick) { onImageClick(image); } }; const handleFavoriteToggle = (image) => { toggleFavorite(image); if (onFavoriteToggle) { onFavoriteToggle(image); } }; const handleImageRemove = (image) => { removeImage(image); if (onImageRemove) { onImageRemove(image); } }; const handleImageUpload = (files) => { const newImages = files.map((file) => { const isVideo = file.type.startsWith("video/"); const url = URL.createObjectURL(file); return { id: generateId(), src: url, thumbnail: url, alt: file.name, title: file.name.split(".").slice(0, -1).join("."), description: isVideo ? "Video file" : "Image file", type: isVideo ? "video" : "image", isFavorite: false, metadata: { fileSize: `${(file.size / (1024 * 1024)).toFixed(1)} MB`, dateTaken: (/* @__PURE__ */ new Date()).toISOString() } }; }); addImages(newImages); if (onImageUpload) { onImageUpload(files); } }; const handleConfigChange = (newConfig) => { updateConfig(newConfig); }; return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "rag-container", children: [ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( GalleryConfig, { layout: activeLayout, animation: activeAnimation, thumbnailsPosition: activeThumbnailsPosition, enableZoom: activeEnableZoom, onConfigChange: handleConfigChange } ), /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( GalleryGrid, { items: galleryImages, layout: activeLayout, animation: activeAnimation, onImageClick: handleImageClick, onFavoriteToggle: handleFavoriteToggle, onImageRemove: handleImageRemove } ), /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ImageUploader, { onImageUpload: handleImageUpload }), isFullscreenOpen && selectedImage && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( FullscreenViewer, { isOpen: isFullscreenOpen, currentImage: selectedImage, images: galleryImages, currentIndex, zoomLevel, onClose: closeFullscreen, onPrev: navigatePrev, onNext: navigateNext, onFavoriteToggle: handleFavoriteToggle, onImageRemove: handleImageRemove, onZoomIn: zoomIn, onZoomOut: zoomOut, onThumbnailClick: navigateToImage } ) ] }); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { FullscreenViewer, GalleryConfig, GalleryGrid, GalleryItem, ImageGallery, ImageUploader, useGallery });