tauri fixes

This commit is contained in:
babayaga 2025-09-18 14:49:46 +02:00
parent 599c2c3b53
commit ea12aae7c8
9 changed files with 77 additions and 46 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -145,7 +145,7 @@ function App() {
const saveAndClose = async () => { const saveAndClose = async () => {
// Find the last generated image // Find the last generated image
const generatedFiles = files.filter(file => file.path.startsWith('generated_')); const generatedFiles = files.filter(file => file.isGenerated);
if (generatedFiles.length === 0) { if (generatedFiles.length === 0) {
addDebugMessage('warn', 'No generated images to save'); addDebugMessage('warn', 'No generated images to save');
return; return;
@ -158,7 +158,7 @@ function App() {
// Send the final result back to images.ts for saving // Send the final result back to images.ts for saving
const result = { const result = {
prompt, prompt,
files: files.filter(f => !f.path.startsWith('generated_')).map(f => f.path), files: files.filter(f => !f.isGenerated).map(f => f.path),
dst, dst,
generatedImage: { generatedImage: {
src: lastGenerated.src, src: lastGenerated.src,

View File

@ -20,10 +20,10 @@ export default function ImageGallery({
const [lightboxOpen, setLightboxOpen] = useState(false); const [lightboxOpen, setLightboxOpen] = useState(false);
const [lightboxLoaded, setLightboxLoaded] = useState(false); const [lightboxLoaded, setLightboxLoaded] = useState(false);
// Reset current index if images change // Reset current index if images change, preventing out-of-bounds errors
useEffect(() => { useEffect(() => {
if (currentIndex >= images.length && images.length > 0) { if (images.length > 0 && currentIndex >= images.length) {
setCurrentIndex(0); setCurrentIndex(Math.max(0, images.length - 1));
} }
}, [images.length, currentIndex]); }, [images.length, currentIndex]);
@ -71,17 +71,14 @@ export default function ImageGallery({
preloadImage(index); preloadImage(index);
}; };
const handleThumbnailClick = (imagePath: string, index: number) => { const handleThumbnailClick = (event: React.MouseEvent<HTMLButtonElement>, imagePath: string, index: number) => {
// Always change the main image first if (event.ctrlKey || event.metaKey) {
setCurrentIndex(index); if (showSelection && onImageSelect) {
onImageSelect(imagePath);
// If it's a generated image and selection is enabled, also toggle selection }
/* } else {
const isGenerated = imagePath.startsWith('generated_'); setCurrentIndex(index);
if (showSelection && onImageSelect) {
onImageSelect(imagePath);
} }
*/
}; };
if (images.length === 0) { if (images.length === 0) {
@ -100,9 +97,24 @@ export default function ImageGallery({
); );
} }
const currentImage = images[currentIndex]; // Safeguard against rendering with an invalid index after a delete/remove operation
const isGenerated = currentImage?.path.startsWith('generated_'); const safeIndex = Math.max(0, Math.min(currentIndex, images.length - 1));
const isSelected = currentImage?.selected || false; const currentImage = images.length > 0 ? images[safeIndex] : null;
if (!currentImage) {
// This should theoretically not be reached if the length check above is sound,
// but it's an extra layer of protection.
return (
<div className="flex flex-col items-center justify-center p-8 text-center">
<div className="w-64 h-64 bg-slate-100 dark:bg-slate-700 rounded-xl flex items-center justify-center">
<p>Loading...</p>
</div>
</div>
);
}
const isGenerated = !!currentImage.isGenerated;
const isSelected = currentImage.selected || false;
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
@ -119,7 +131,7 @@ export default function ImageGallery({
? 'border-green-300' ? 'border-green-300'
: 'border-white/30' : 'border-white/30'
}`} }`}
onDoubleClick={() => openLightbox(currentIndex)} onDoubleClick={() => openLightbox(safeIndex)}
title="Double-click for fullscreen" title="Double-click for fullscreen"
/> />
<div className="absolute top-2 left-2 flex flex-col gap-2"> <div className="absolute top-2 left-2 flex flex-col gap-2">
@ -145,7 +157,7 @@ export default function ImageGallery({
{/* Compact Image Info */} {/* Compact Image Info */}
<div className="text-center mb-3"> <div className="text-center mb-3">
<p className="text-xs text-slate-600 dark:text-slate-400"> <p className="text-xs text-slate-600 dark:text-slate-400">
{currentImage.path.split(/[/\\]/).pop()} {currentIndex + 1}/{images.length} {currentImage.path.split(/[/\\]/).pop()} {safeIndex + 1}/{images.length}
</p> </p>
</div> </div>
@ -153,15 +165,16 @@ export default function ImageGallery({
<div className="grid grid-cols-4 md:grid-cols-6 lg:grid-cols-8 gap-2"> <div className="grid grid-cols-4 md:grid-cols-6 lg:grid-cols-8 gap-2">
{images.map((image, index) => { {images.map((image, index) => {
const thumbIsGenerating = image.path.startsWith('generating_'); const thumbIsGenerating = image.path.startsWith('generating_');
const thumbIsGenerated = image.path.startsWith('generated_'); const thumbIsGenerated = !!image.isGenerated;
const thumbIsSelected = image.selected || false; const thumbIsSelected = image.selected || false;
return ( return (
<button <button
type="button"
key={image.path} key={image.path}
onClick={() => handleThumbnailClick(image.path, index)} onClick={(e) => handleThumbnailClick(e, image.path, index)}
onDoubleClick={() => openLightbox(index)} onDoubleClick={() => openLightbox(index)}
className={`relative aspect-square rounded-lg overflow-hidden transition-all duration-300 border-2 ${ className={`group relative aspect-square rounded-lg overflow-hidden transition-all duration-300 border-2 ${
currentIndex === index currentIndex === index
? 'ring-2 ring-orange-500 border-orange-500' ? 'ring-2 ring-orange-500 border-orange-500'
: thumbIsSelected : thumbIsSelected
@ -213,15 +226,17 @@ export default function ImageGallery({
)} )}
{/* Delete button (permanent) */} {/* Delete button (permanent) */}
{!thumbIsGenerating && onImageDelete && ( {!thumbIsGenerating && onImageDelete && onImageRemove && (
<button <button
type="button"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
if (window.confirm('Are you sure you want to permanently delete this file from your disk?')) { if (window.confirm('Are you sure you want to permanently delete this file from your disk?')) {
onImageDelete(image.path); onImageDelete(image.path);
onImageRemove(image.path);
} }
}} }}
className="absolute bottom-1 right-1 bg-red-500/80 hover:bg-red-600 text-white rounded-full w-5 h-5 flex items-center justify-center text-xs opacity-0 group-hover:opacity-100 transition-all duration-200" className="absolute bottom-1 right-1 bg-red-600/70 hover:bg-red-600 text-white rounded-full w-5 h-5 flex items-center justify-center text-xs transition-all duration-200"
title="Delete File Permanently" title="Delete File Permanently"
> >
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" viewBox="0 0 20 20" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" viewBox="0 0 20 20" fill="currentColor">
@ -243,8 +258,8 @@ export default function ImageGallery({
<div className="relative w-full h-full flex items-center justify-center"> <div className="relative w-full h-full flex items-center justify-center">
{lightboxLoaded ? ( {lightboxLoaded ? (
<img <img
src={images[currentIndex].src} src={images[safeIndex].src}
alt={images[currentIndex].path} alt={images[safeIndex].path}
className="max-w-full max-h-full object-contain" className="max-w-full max-h-full object-contain"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
/> />
@ -267,14 +282,14 @@ export default function ImageGallery({
</button> </button>
{/* Navigation Buttons */} {/* Navigation Buttons */}
{currentIndex > 0 && ( {safeIndex > 0 && (
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setCurrentIndex(currentIndex - 1); setCurrentIndex(safeIndex - 1);
setLightboxLoaded(false); setLightboxLoaded(false);
const img = new Image(); const img = new Image();
img.src = images[currentIndex - 1].src; img.src = images[safeIndex - 1].src;
img.onload = () => setLightboxLoaded(true); img.onload = () => setLightboxLoaded(true);
}} }}
className="absolute left-4 top-1/2 transform -translate-y-1/2 p-4 text-white text-3xl bg-black/75 rounded-lg hover:bg-black/90 transition-all duration-200" className="absolute left-4 top-1/2 transform -translate-y-1/2 p-4 text-white text-3xl bg-black/75 rounded-lg hover:bg-black/90 transition-all duration-200"
@ -284,14 +299,14 @@ export default function ImageGallery({
</button> </button>
)} )}
{currentIndex < images.length - 1 && ( {safeIndex < images.length - 1 && (
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setCurrentIndex(currentIndex + 1); setCurrentIndex(safeIndex + 1);
setLightboxLoaded(false); setLightboxLoaded(false);
const img = new Image(); const img = new Image();
img.src = images[currentIndex + 1].src; img.src = images[safeIndex + 1].src;
img.onload = () => setLightboxLoaded(true); img.onload = () => setLightboxLoaded(true);
}} }}
className="absolute right-4 top-1/2 transform -translate-y-1/2 p-4 text-white text-3xl bg-black/75 rounded-lg hover:bg-black/90 transition-all duration-200" className="absolute right-4 top-1/2 transform -translate-y-1/2 p-4 text-white text-3xl bg-black/75 rounded-lg hover:bg-black/90 transition-all duration-200"
@ -304,7 +319,7 @@ export default function ImageGallery({
{/* Info */} {/* Info */}
{lightboxLoaded && ( {lightboxLoaded && (
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 bg-black/75 text-white px-4 py-2 rounded-lg text-sm"> <div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 bg-black/75 text-white px-4 py-2 rounded-lg text-sm">
{images[currentIndex].path.split(/[/\\]/).pop()} {currentIndex + 1} of {images.length} ESC to close {images[safeIndex].path.split(/[/\\]/).pop()} {safeIndex + 1} of {images.length} ESC to close
</div> </div>
)} )}
</div> </div>

View File

@ -70,13 +70,12 @@ export function useTauriListeners({
if (imageData.base64 && imageData.mimeType && imageData.filename) { if (imageData.base64 && imageData.mimeType && imageData.filename) {
const src = `data:${imageData.mimeType};base64,${imageData.base64}`; const src = `data:${imageData.mimeType};base64,${imageData.base64}`;
const hasGeneratingPlaceholder = document.querySelector('[src^="data:image/svg+xml"]'); // A bit hacky, but avoids depending on files state const isGeneratedImage = isGenerating || document.querySelector('[src^="data:image/svg+xml"]');
const isGeneratedImage = isGenerating || hasGeneratingPlaceholder || imageData.filename.includes('_out') || imageData.filename.includes('generated_');
if (isGeneratedImage) { if (isGeneratedImage) {
const generatedImageFile: ImageFile = { path: `generated_${imageData.filename}`, src }; const generatedImageFile: ImageFile = { path: imageData.filename, src, isGenerated: true };
setFiles(prev => { setFiles(prev => {
const withoutPlaceholder = prev.filter(file => !file.path.startsWith('generating_') && !file.path.endsWith(imageData.filename) && file.path !== `generated_${imageData.filename}`); const withoutPlaceholder = prev.filter(file => !file.path.startsWith('generating_') && file.path !== imageData.filename);
return [...withoutPlaceholder, generatedImageFile]; return [...withoutPlaceholder, generatedImageFile];
}); });
@ -87,7 +86,7 @@ export function useTauriListeners({
setIsGenerating(false); setIsGenerating(false);
addDebugMessage('info', '✅ Generated image added to files', { filename: imageData.filename, prompt }); addDebugMessage('info', '✅ Generated image added to files', { filename: imageData.filename, prompt });
} else { } else {
const newImageFile = { path: imageData.filename, src }; const newImageFile: ImageFile = { path: imageData.filename, src, isGenerated: false };
setFiles(prevFiles => { setFiles(prevFiles => {
const exists = prevFiles.some(f => f.path === imageData.filename); const exists = prevFiles.some(f => f.path === imageData.filename);
if (!exists) { if (!exists) {

View File

@ -2,6 +2,7 @@ export interface ImageFile {
path: string; path: string;
src: string; src: string;
selected?: boolean; selected?: boolean;
isGenerated?: boolean;
} }
export interface GeneratedImage { export interface GeneratedImage {

View File

@ -239,9 +239,15 @@ async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
? path.basename(genFiles[0], path.extname(genFiles[0])) ? path.basename(genFiles[0], path.extname(genFiles[0]))
: 'generated'; : 'generated';
const newFileName = `${baseFileName}_gen_0.png`; let i = 0;
let newFileName;
let finalDstPath;
do {
newFileName = `${baseFileName}_gen_${i}.png`;
finalDstPath = path.resolve(dstDir, newFileName);
i++;
} while (exists(finalDstPath));
const finalDstPath = path.resolve(dstDir, newFileName);
logger.info(`📝 Determined destination path for generated image: ${finalDstPath}`); logger.info(`📝 Determined destination path for generated image: ${finalDstPath}`);
// --- End new logic --- // --- End new logic ---
@ -262,8 +268,10 @@ async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
} else { } else {
// Image creation // Image creation
logger.info(`Creating image with prompt: "${genPrompt}"`); logger.info(`Creating image with prompt: "${genPrompt}"`);
const creationArgv = { ...argv };
delete creationArgv.include;
const parsedOptions = ImageOptionsSchema().parse({ const parsedOptions = ImageOptionsSchema().parse({
...argv, ...creationArgv,
prompt: genPrompt, prompt: genPrompt,
dst: finalDstPath // Use the new path dst: finalDstPath // Use the new path
}); });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB