tauri - lightbox prompt | file history | store | icons

This commit is contained in:
lovebird 2025-09-21 07:21:31 +02:00
parent eba2cd2c69
commit 019283d4ca

View File

@ -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>
)}