tauri - lightbox prompt | file history | store | icons
This commit is contained in:
parent
eba2cd2c69
commit
019283d4ca
@ -40,6 +40,9 @@ export default function ImageGallery({
|
||||
const [lightboxLoaded, setLightboxLoaded] = useState(false);
|
||||
const [isPanning, setIsPanning] = useState(false);
|
||||
const [lightboxPrompt, setLightboxPrompt] = useState('');
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
const [skipDeleteConfirm, setSkipDeleteConfirm] = useState(false);
|
||||
const [rememberChoice, setRememberChoice] = useState(false);
|
||||
const panStartRef = useRef<{ x: number; y: number } | null>(null);
|
||||
|
||||
// Sync lightbox prompt with history navigation
|
||||
@ -49,6 +52,65 @@ export default function ImageGallery({
|
||||
}
|
||||
}, [historyIndex, promptHistory, lightboxOpen]);
|
||||
|
||||
// Handle keyboard events for lightbox
|
||||
useEffect(() => {
|
||||
if (!lightboxOpen) return;
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Delete' && !isGenerating && onImageDelete) {
|
||||
e.preventDefault();
|
||||
const safeIndex = Math.max(0, Math.min(currentIndex, images.length - 1));
|
||||
if (images[safeIndex]) {
|
||||
handleDeleteImage(images[safeIndex].path);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [lightboxOpen, currentIndex, images, isGenerating, onImageDelete]);
|
||||
|
||||
const handleDeleteImage = (imagePath: string) => {
|
||||
if (skipDeleteConfirm) {
|
||||
// Skip confirmation and delete immediately
|
||||
onImageDelete?.(imagePath);
|
||||
// Close lightbox if this was the last image or adjust index
|
||||
if (images.length <= 1) {
|
||||
setLightboxOpen(false);
|
||||
} else {
|
||||
// Adjust current index if needed
|
||||
const deletingIndex = images.findIndex(img => img.path === imagePath);
|
||||
if (deletingIndex === currentIndex && currentIndex >= images.length - 1) {
|
||||
setCurrentIndex(Math.max(0, currentIndex - 1));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Show confirmation dialog
|
||||
setShowDeleteConfirm(true);
|
||||
}
|
||||
};
|
||||
|
||||
const confirmDelete = (remember: boolean) => {
|
||||
const safeIndex = Math.max(0, Math.min(currentIndex, images.length - 1));
|
||||
if (images[safeIndex]) {
|
||||
if (remember) {
|
||||
setSkipDeleteConfirm(true);
|
||||
}
|
||||
onImageDelete?.(images[safeIndex].path);
|
||||
|
||||
// Close lightbox if this was the last image or adjust index
|
||||
if (images.length <= 1) {
|
||||
setLightboxOpen(false);
|
||||
} else {
|
||||
// Adjust current index if needed
|
||||
if (currentIndex >= images.length - 1) {
|
||||
setCurrentIndex(Math.max(0, currentIndex - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
setShowDeleteConfirm(false);
|
||||
};
|
||||
|
||||
// Reset current index if images change, preventing out-of-bounds errors
|
||||
useEffect(() => {
|
||||
if (images.length > 0 && currentIndex >= images.length) {
|
||||
@ -414,10 +476,17 @@ export default function ImageGallery({
|
||||
rows={2}
|
||||
className="w-full bg-white/10 border border-white/30 rounded-lg px-3 py-2 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent backdrop-blur-sm disabled:opacity-50 resize-none"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && lightboxPrompt.trim() && !isGenerating) {
|
||||
onLightboxPromptSubmit(lightboxPrompt, images[safeIndex].path);
|
||||
setLightboxPrompt('');
|
||||
// Keep lightbox open to show generation progress
|
||||
if (e.key === 'Enter' && !e.shiftKey && lightboxPrompt.trim() && !isGenerating) {
|
||||
if (e.ctrlKey) {
|
||||
// Ctrl+Enter: Submit and keep prompt for iteration
|
||||
e.preventDefault();
|
||||
onLightboxPromptSubmit(lightboxPrompt, images[safeIndex].path);
|
||||
} else {
|
||||
// Enter: Submit and clear prompt
|
||||
e.preventDefault();
|
||||
onLightboxPromptSubmit(lightboxPrompt, images[safeIndex].path);
|
||||
setLightboxPrompt('');
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
setLightboxPrompt('');
|
||||
} else if (e.ctrlKey && e.key === 'ArrowUp' && navigateHistory) {
|
||||
@ -427,6 +496,7 @@ export default function ImageGallery({
|
||||
e.preventDefault();
|
||||
navigateHistory('down');
|
||||
}
|
||||
// Shift+Enter: Allow new line (default textarea behavior)
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
@ -471,7 +541,7 @@ export default function ImageGallery({
|
||||
}}
|
||||
disabled={!lightboxPrompt.trim() || isGenerating}
|
||||
className="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 text-white p-2 rounded-lg transition-colors duration-200 disabled:opacity-50 flex items-center justify-center group relative self-start"
|
||||
title="Generate (Enter)"
|
||||
title="Generate (Enter to clear, Ctrl+Enter to keep)"
|
||||
>
|
||||
{isGenerating ? (
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent"></div>
|
||||
@ -497,10 +567,55 @@ export default function ImageGallery({
|
||||
Generating edit... • ESC to close
|
||||
</div>
|
||||
) : (
|
||||
`${images[safeIndex].path.split(/[/\\]/).pop()} • ${safeIndex + 1} of ${images.length} • Scroll to zoom • Double-click to reset • ESC to close`
|
||||
`${images[safeIndex].path.split(/[/\\]/).pop()} • ${safeIndex + 1} of ${images.length} • Enter: generate & clear • Ctrl+Enter: generate & keep • Del: delete • ESC to close`
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
{showDeleteConfirm && (
|
||||
<div className="absolute inset-0 bg-black/90 flex items-center justify-center z-50">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl p-6 max-w-md mx-4 shadow-2xl">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
||||
Delete Image?
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||
Are you sure you want to delete "{images[Math.max(0, Math.min(currentIndex, images.length - 1))]?.path.split(/[/\\]/).pop()}"?
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
<label className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="rounded"
|
||||
checked={rememberChoice}
|
||||
onChange={(e) => setRememberChoice(e.target.checked)}
|
||||
/>
|
||||
Remember for this session (skip confirmation)
|
||||
</label>
|
||||
<div className="flex gap-3 justify-end">
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowDeleteConfirm(false);
|
||||
setRememberChoice(false);
|
||||
}}
|
||||
className="px-4 py-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
confirmDelete(rememberChoice);
|
||||
setRememberChoice(false);
|
||||
}}
|
||||
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user