139 lines
5.4 KiB
TypeScript
139 lines
5.4 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { gatewayService } from '../lib/gatewayService';
|
|
|
|
interface ThumbnailProps {
|
|
documentId: string;
|
|
pageIndex: number;
|
|
onClick: () => void;
|
|
onDelete?: () => void;
|
|
onMoveUp?: () => void;
|
|
onMoveDown?: () => void;
|
|
totalPages?: number;
|
|
}
|
|
|
|
export const Thumbnail: React.FC<ThumbnailProps> = ({
|
|
documentId,
|
|
pageIndex,
|
|
onClick,
|
|
onDelete,
|
|
onMoveUp,
|
|
onMoveDown,
|
|
totalPages,
|
|
}) => {
|
|
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
let active = true;
|
|
|
|
const fetchThumbnail = async () => {
|
|
try {
|
|
setLoading(true);
|
|
// Request a lower resolution/zoom image for the thumbnail
|
|
const url = await gatewayService.renderPage({
|
|
documentId,
|
|
pageIndex,
|
|
zoom: 0.2, // Small zoom for thumbnail size
|
|
rotation: 0,
|
|
});
|
|
|
|
if (active) {
|
|
setImageUrl(url);
|
|
}
|
|
} catch (err) {
|
|
console.error(`Failed to load thumbnail for page ${pageIndex}`, err);
|
|
} finally {
|
|
if (active) setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchThumbnail();
|
|
|
|
return () => {
|
|
active = false;
|
|
};
|
|
}, [documentId, pageIndex]);
|
|
|
|
return (
|
|
<div className="thumbnail-card" onClick={onClick}>
|
|
<div className="thumbnail-preview relative group overflow-hidden bg-white hover:border-indigo-500 transition-colors">
|
|
{loading ? (
|
|
<div className="flex flex-col items-center justify-center h-full w-full bg-slate-900">
|
|
<div className="w-5 h-5 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin mb-2" />
|
|
<span className="thumbnail-label">Loading...</span>
|
|
</div>
|
|
) : imageUrl ? (
|
|
<>
|
|
<img
|
|
src={imageUrl}
|
|
alt={`Page ${pageIndex + 1}`}
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
{/* Group Hover Overlay Controls */}
|
|
<div className="absolute inset-0 bg-slate-950/60 opacity-0 group-hover:opacity-100 transition-opacity flex flex-col justify-between p-2 pointer-events-auto">
|
|
<div className="flex justify-end">
|
|
{onDelete && totalPages && totalPages > 1 && (
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onDelete();
|
|
}}
|
|
className="p-1 bg-rose-500 hover:bg-rose-600 text-white rounded shadow transition-colors cursor-pointer"
|
|
title="Delete Page"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
</svg>
|
|
</button>
|
|
)}
|
|
</div>
|
|
<div className="flex justify-between w-full">
|
|
{onMoveUp ? (
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onMoveUp();
|
|
}}
|
|
className="p-1 bg-indigo-600 hover:bg-indigo-500 text-white rounded shadow transition-colors cursor-pointer"
|
|
title="Move Page Up"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M5 10l7-7m0 0l7 7m-7-7v18" />
|
|
</svg>
|
|
</button>
|
|
) : (
|
|
<div />
|
|
)}
|
|
{onMoveDown ? (
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onMoveDown();
|
|
}}
|
|
className="p-1 bg-indigo-600 hover:bg-indigo-500 text-white rounded shadow transition-colors cursor-pointer"
|
|
title="Move Page Down"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M19 14l-7 7m0 0l-7-7m7 7V3" />
|
|
</svg>
|
|
</button>
|
|
) : (
|
|
<div />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<div className="flex flex-col items-center justify-center h-full w-full">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" className="thumbnail-svg-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
<span className="thumbnail-label text-rose-400">Error</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<span className="thumbnail-label text-center mt-1">Page {pageIndex + 1}</span>
|
|
</div>
|
|
);
|
|
};
|