File size: 4,870 Bytes
921d377 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | import React, { useState } from 'react';
import { X, Edit, Download, Share2, ZoomIn, ZoomOut, Maximize2 } from 'lucide-react';
import { resolveFileUrl } from './resolveFileUrl';
export function ImageViewer({ imageUrl, onClose, onEdit }) {
const [zoom, setZoom] = useState(100);
const resolvedUrl = resolveFileUrl(imageUrl);
const handleDownload = async () => {
try {
const response = await fetch(resolvedUrl);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `homepilot-${Date.now()}.png`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}
catch (error) {
console.error('Download failed:', error);
}
};
const handleShare = async () => {
try {
if (navigator.share) {
const response = await fetch(resolvedUrl);
const blob = await response.blob();
const file = new File([blob], 'image.png', { type: 'image/png' });
await navigator.share({
files: [file],
title: 'Generated Image',
text: 'Check out this AI-generated image!',
});
}
else {
// Fallback: copy URL to clipboard
await navigator.clipboard.writeText(resolvedUrl);
alert('Image URL copied to clipboard!');
}
}
catch (error) {
console.error('Share failed:', error);
}
};
const handleZoomIn = () => {
setZoom((prev) => Math.min(prev + 25, 200));
};
const handleZoomOut = () => {
setZoom((prev) => Math.max(prev - 25, 50));
};
const handleZoomReset = () => {
setZoom(100);
};
return (<div className="fixed inset-0 z-50 bg-black/95 backdrop-blur-sm flex flex-col" onClick={onClose}>
{/* Top Controls Bar */}
<div className="absolute top-0 left-0 right-0 flex items-center justify-between p-4 bg-gradient-to-b from-black/80 to-transparent z-10">
<div className="flex items-center gap-2">
{onEdit && (<button onClick={(e) => {
e.stopPropagation();
onEdit(imageUrl);
}} className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors" title="Edit image">
<Edit size={18}/>
<span className="text-sm font-medium">Edit image</span>
</button>)}
<button onClick={(e) => {
e.stopPropagation();
handleDownload();
}} className="p-2 bg-white/10 hover:bg-white/20 rounded-lg transition-colors" title="Download">
<Download size={18}/>
</button>
<button onClick={(e) => {
e.stopPropagation();
handleShare();
}} className="p-2 bg-white/10 hover:bg-white/20 rounded-lg transition-colors" title="Share">
<Share2 size={18}/>
</button>
<div className="w-px h-6 bg-white/20"/>
<button onClick={(e) => {
e.stopPropagation();
handleZoomOut();
}} className="p-2 bg-white/10 hover:bg-white/20 rounded-lg transition-colors" title="Zoom out" disabled={zoom <= 50}>
<ZoomOut size={18}/>
</button>
<span className="text-sm font-medium px-2 min-w-[4rem] text-center">{zoom}%</span>
<button onClick={(e) => {
e.stopPropagation();
handleZoomIn();
}} className="p-2 bg-white/10 hover:bg-white/20 rounded-lg transition-colors" title="Zoom in" disabled={zoom >= 200}>
<ZoomIn size={18}/>
</button>
<button onClick={(e) => {
e.stopPropagation();
handleZoomReset();
}} className="p-2 bg-white/10 hover:bg-white/20 rounded-lg transition-colors" title="Reset zoom">
<Maximize2 size={18}/>
</button>
</div>
<button onClick={onClose} className="p-2 bg-white/10 hover:bg-white/20 rounded-lg transition-colors" aria-label="Close">
<X size={24}/>
</button>
</div>
{/* Image Display Area */}
<div className="flex-1 flex items-center justify-center p-20 overflow-auto">
<img src={resolveFileUrl(imageUrl)} onClick={(e) => e.stopPropagation()} className="rounded-lg shadow-2xl border border-white/10 transition-transform" style={{
transform: `scale(${zoom / 100})`,
maxWidth: '90vw',
maxHeight: '70vh',
objectFit: 'contain',
}} alt="preview"/>
</div>
</div>);
}
|