mono/packages/ui/src/components/widgets/GalleryWidget.tsx
2026-02-06 21:23:24 +01:00

240 lines
8.8 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Gallery } from '@/pages/Post/renderers/components/Gallery';
import { ImagePickerDialog } from '@/components/widgets/ImagePickerDialog';
import SmartLightbox from '@/pages/Post/components/SmartLightbox';
import { T } from '@/i18n';
import { ImageIcon, Plus, Settings } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useAuth } from '@/hooks/useAuth';
import { PostMediaItem } from '@/pages/Post/types';
import { isVideoType, normalizeMediaType, detectMediaType } from '@/lib/mediaRegistry';
const { fetchMediaItemsByIds } = await import('@/lib/db');
interface GalleryWidgetProps {
pictureIds?: string[];
thumbnailLayout?: 'strip' | 'grid';
imageFit?: 'contain' | 'cover';
onPropsChange?: (props: Record<string, any>) => void;
isEditMode?: boolean;
id?: string;
}
const GalleryWidget: React.FC<GalleryWidgetProps> = ({
pictureIds: propPictureIds = [],
thumbnailLayout = 'strip',
imageFit = 'cover',
onPropsChange,
isEditMode = false,
id
}) => {
const { user } = useAuth();
// Normalize pictureIds to always be an array
const normalizePictureIds = (ids: any): string[] => {
if (!ids) return [];
if (Array.isArray(ids)) return ids;
if (typeof ids === 'string') return [ids];
return [];
};
const [pictureIds, setPictureIds] = useState<string[]>(normalizePictureIds(propPictureIds));
const [mediaItems, setMediaItems] = useState<PostMediaItem[]>([]);
const [selectedItem, setSelectedItem] = useState<PostMediaItem | null>(null);
const [loading, setLoading] = useState(false);
const [showPicker, setShowPicker] = useState(false);
const [lightboxOpen, setLightboxOpen] = useState(false);
// Sync local state with props
useEffect(() => {
const normalizedProps = normalizePictureIds(propPictureIds);
if (JSON.stringify(normalizedProps) !== JSON.stringify(pictureIds)) {
setPictureIds(normalizedProps);
}
}, [propPictureIds]);
useEffect(() => {
if (pictureIds && pictureIds.length > 0) {
fetchMediaItems();
} else {
setMediaItems([]);
setSelectedItem(null);
}
}, [pictureIds]);
const fetchMediaItems = async () => {
if (!pictureIds || pictureIds.length === 0) return;
setLoading(true);
try {
const items = await fetchMediaItemsByIds(pictureIds, { maintainOrder: true });
console.log('Fetched media items:', items);
// Transform to PostMediaItem format
const postMediaItems = items.map((item, index) => ({
...item,
post_id: '', // Not part of a post
position: index,
visible: true,
is_selected: false,
comments: [{ count: 0 }]
})) as PostMediaItem[];
console.log('Transformed to PostMediaItems:', postMediaItems);
setMediaItems(postMediaItems);
// Always set first item as selected when items change
if (postMediaItems.length > 0) {
console.log('Setting selected item:', postMediaItems[0]);
setSelectedItem(postMediaItems[0]);
}
} catch (error) {
console.error('Error fetching gallery media:', error);
} finally {
setLoading(false);
}
};
const handlePicturesSelected = (selectedPictureIds: string[]) => {
setPictureIds(selectedPictureIds);
if (onPropsChange) {
onPropsChange({ pictureIds: selectedPictureIds });
}
setShowPicker(false);
};
const handleMediaSelect = (item: PostMediaItem) => {
setSelectedItem(item);
};
const handleExpand = (item: PostMediaItem) => {
setSelectedItem(item);
setLightboxOpen(true);
};
const handlePublish = async (option: 'overwrite' | 'new' | 'version', imageUrl: string, newTitle: string, description?: string, parentId?: string, collectionIds?: string[]) => {
// TODO: Implement publish logic
console.log('Publish:', { option, imageUrl, newTitle, description, parentId, collectionIds });
};
const handleNavigateLightbox = (direction: 'prev' | 'next') => {
if (!selectedItem) return;
const currentIndex = mediaItems.findIndex(item => item.id === selectedItem.id);
const newIndex = direction === 'next'
? (currentIndex + 1) % mediaItems.length
: (currentIndex - 1 + mediaItems.length) % mediaItems.length;
setSelectedItem(mediaItems[newIndex]);
};
const handleOpenInWizard = () => {
// TODO: Implement wizard navigation
console.log('Open in wizard:', selectedItem);
};
// Empty state
if (!pictureIds || pictureIds.length === 0) {
return (
<div className="flex flex-col items-center justify-center h-full min-h-[400px] bg-muted/30 border-1 border-dashed">
<ImageIcon className="w-16 h-16 text-muted-foreground mb-4" />
<p className="text-muted-foreground mb-4">
<T>No pictures selected</T>
</p>
{isEditMode && (
<Button onClick={() => setShowPicker(true)} variant="outline">
<Plus className="w-4 h-4 mr-2" />
<T>Select Pictures</T>
</Button>
)}
{showPicker && (
<ImagePickerDialog
isOpen={showPicker}
onClose={() => setShowPicker(false)}
onMultiSelect={handlePicturesSelected}
multiple={true}
currentValues={pictureIds}
/>
)}
</div>
);
}
// Loading state
if (loading) {
return (
<div className="flex items-center justify-center h-full min-h-[400px]">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
);
}
// Gallery view
if (!selectedItem || mediaItems.length === 0) {
return null;
}
return (
<div className="relative w-full aspect-video flex flex-col">
{/* Edit Mode Controls */}
{isEditMode && (
<div className="absolute top-2 right-2 z-50">
<Button
onClick={() => setShowPicker(true)}
variant="secondary"
size="sm"
className="shadow-lg"
>
<Settings className="w-4 h-4 mr-2" />
<T>Configure</T>
</Button>
</div>
)}
{/* Gallery Component - needs to fill container */}
<div className="flex-1 w-full min-h-[500px]">
<Gallery
mediaItems={mediaItems}
selectedItem={selectedItem}
onMediaSelect={handleMediaSelect}
onExpand={handleExpand}
isOwner={!!user}
showDesktopLayout={true}
thumbnailLayout={thumbnailLayout}
imageFit={imageFit}
className="h-full w-full [&_.hidden]:!block"
/>
</div>
{/* Image Picker Dialog */}
{showPicker && (
<ImagePickerDialog
isOpen={showPicker}
onClose={() => setShowPicker(false)}
onMultiSelect={handlePicturesSelected}
multiple={true}
currentValues={pictureIds}
/>
)}
{/* Smart Lightbox for fullscreen viewing */}
{selectedItem && (
<SmartLightbox
isOpen={lightboxOpen}
onClose={() => setLightboxOpen(false)}
mediaItem={selectedItem}
user={user}
isVideo={isVideoType(normalizeMediaType(selectedItem.mediaType || detectMediaType(selectedItem.image_url)))}
onPublish={handlePublish}
onNavigate={handleNavigateLightbox}
onOpenInWizard={handleOpenInWizard}
currentIndex={mediaItems.findIndex(item => item.id === selectedItem.id)}
totalCount={mediaItems.length}
/>
)}
</div>
);
};
export default GalleryWidget;