import { useEffect, useState } from 'react'; import { useAppTranslation } from '@features/localization/renderer'; import { FileIcon } from '@renderer/components/team/editor/FileIcon'; import { useStore } from '@renderer/store'; import { isImageMime } from '@renderer/utils/attachmentUtils'; import { Loader2 } from 'lucide-react'; import { AttachmentThumbnail } from './AttachmentThumbnail'; import { ImageLightbox } from './ImageLightbox'; import type { AttachmentFileData, AttachmentMeta } from '@shared/types'; interface AttachmentDisplayProps { teamName: string; messageId: string; attachments: AttachmentMeta[]; } export const AttachmentDisplay = ({ teamName, messageId, attachments, }: AttachmentDisplayProps): React.JSX.Element | null => { const { t } = useAppTranslation('team'); const revealFileInEditor = useStore((s) => s.revealFileInEditor); const [state, setState] = useState<{ loaded: AttachmentFileData[]; loading: boolean; key: string; }>({ loaded: [], loading: true, key: `${teamName}:${messageId}` }); const [lightboxIndex, setLightboxIndex] = useState(null); const currentKey = `${teamName}:${messageId}`; // Reset loading state when deps change (React 18+ pattern: derive from props) if (state.key !== currentKey) { setState({ loaded: [], loading: true, key: currentKey }); } useEffect(() => { let cancelled = false; void window.electronAPI.teams .getAttachments(teamName, messageId) .then((data) => { if (!cancelled) setState({ loaded: data, loading: false, key: `${teamName}:${messageId}` }); }) .catch(() => { if (!cancelled) setState((prev) => ({ ...prev, loading: false })); }); return () => { cancelled = true; }; }, [teamName, messageId]); const { loaded, loading } = state; if (attachments.length === 0) return null; if (loading) { return (
{t('taskAttachments.loading')}
); } // Build lookup for loaded data const dataById = new Map(loaded.map((d) => [d.id, d])); const items = attachments .map((meta) => { const data = dataById.get(meta.id); if (!data) return null; const isImage = isImageMime(data.mimeType); return { meta, dataUrl: isImage ? `data:${data.mimeType};base64,${data.data}` : undefined, filePath: data.filePath ?? meta.filePath, isImage, }; }) .filter(Boolean) as { meta: AttachmentMeta; dataUrl: string | undefined; filePath: string | undefined; isImage: boolean; }[]; if (items.length === 0) return null; // Build lightbox slides for images only, with visual→lightbox index mapping const imageSlides: { src: string; alt: string }[] = []; const visualToLightbox = new Map(); items.forEach((item, i) => { if (item.isImage && item.dataUrl) { visualToLightbox.set(i, imageSlides.length); imageSlides.push({ src: item.dataUrl, alt: item.meta.filename }); } }); return ( <>
{items.map((item, i) => item.isImage && item.dataUrl ? ( setLightboxIndex(visualToLightbox.get(i)!) : undefined } /> ) : item.filePath ? ( ) : (
{item.meta.filename}
) )}
{lightboxIndex !== null && imageSlides[lightboxIndex] ? ( setLightboxIndex(null)} slides={imageSlides} index={lightboxIndex} /> ) : null} ); };