240 lines
8.8 KiB
TypeScript
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;
|