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 () => {
// 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) {
addDebugMessage('warn', 'No generated images to save');
return;
@ -158,7 +158,7 @@ function App() {
// Send the final result back to images.ts for saving
const result = {
prompt,
files: files.filter(f => !f.path.startsWith('generated_')).map(f => f.path),
files: files.filter(f => !f.isGenerated).map(f => f.path),
dst,
generatedImage: {
src: lastGenerated.src,

View File

@ -20,10 +20,10 @@ export default function ImageGallery({
const [lightboxOpen, setLightboxOpen] = 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(() => {
if (currentIndex >= images.length && images.length > 0) {
setCurrentIndex(0);
if (images.length > 0 && currentIndex >= images.length) {
setCurrentIndex(Math.max(0, images.length - 1));
}
}, [images.length, currentIndex]);
@ -71,17 +71,14 @@ export default function ImageGallery({
preloadImage(index);
};
const handleThumbnailClick = (imagePath: string, index: number) => {
// Always change the main image first
setCurrentIndex(index);
// If it's a generated image and selection is enabled, also toggle selection
/*
const isGenerated = imagePath.startsWith('generated_');
if (showSelection && onImageSelect) {
onImageSelect(imagePath);
const handleThumbnailClick = (event: React.MouseEvent<HTMLButtonElement>, imagePath: string, index: number) => {
if (event.ctrlKey || event.metaKey) {
if (showSelection && onImageSelect) {
onImageSelect(imagePath);
}
} else {
setCurrentIndex(index);
}
*/
};
if (images.length === 0) {
@ -100,9 +97,24 @@ export default function ImageGallery({
);
}
const currentImage = images[currentIndex];
const isGenerated = currentImage?.path.startsWith('generated_');
const isSelected = currentImage?.selected || false;
// Safeguard against rendering with an invalid index after a delete/remove operation
const safeIndex = Math.max(0, Math.min(currentIndex, images.length - 1));
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 (
<div className="flex flex-col">
@ -119,7 +131,7 @@ export default function ImageGallery({
? 'border-green-300'
: 'border-white/30'
}`}
onDoubleClick={() => openLightbox(currentIndex)}
onDoubleClick={() => openLightbox(safeIndex)}
title="Double-click for fullscreen"
/>
<div className="absolute top-2 left-2 flex flex-col gap-2">
@ -145,7 +157,7 @@ export default function ImageGallery({
{/* Compact Image Info */}
<div className="text-center mb-3">
<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>
</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">
{images.map((image, index) => {
const thumbIsGenerating = image.path.startsWith('generating_');
const thumbIsGenerated = image.path.startsWith('generated_');
const thumbIsGenerated = !!image.isGenerated;
const thumbIsSelected = image.selected || false;
return (
<button
type="button"
key={image.path}
onClick={() => handleThumbnailClick(image.path, index)}
onClick={(e) => handleThumbnailClick(e, image.path, 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
? 'ring-2 ring-orange-500 border-orange-500'
: thumbIsSelected
@ -213,15 +226,17 @@ export default function ImageGallery({
)}
{/* Delete button (permanent) */}
{!thumbIsGenerating && onImageDelete && (
{!thumbIsGenerating && onImageDelete && onImageRemove && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
if (window.confirm('Are you sure you want to permanently delete this file from your disk?')) {
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"
>
<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">
{lightboxLoaded ? (
<img
src={images[currentIndex].src}
alt={images[currentIndex].path}
src={images[safeIndex].src}
alt={images[safeIndex].path}
className="max-w-full max-h-full object-contain"
onClick={(e) => e.stopPropagation()}
/>
@ -267,14 +282,14 @@ export default function ImageGallery({
</button>
{/* Navigation Buttons */}
{currentIndex > 0 && (
{safeIndex > 0 && (
<button
onClick={(e) => {
e.stopPropagation();
setCurrentIndex(currentIndex - 1);
setCurrentIndex(safeIndex - 1);
setLightboxLoaded(false);
const img = new Image();
img.src = images[currentIndex - 1].src;
img.src = images[safeIndex - 1].src;
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"
@ -284,14 +299,14 @@ export default function ImageGallery({
</button>
)}
{currentIndex < images.length - 1 && (
{safeIndex < images.length - 1 && (
<button
onClick={(e) => {
e.stopPropagation();
setCurrentIndex(currentIndex + 1);
setCurrentIndex(safeIndex + 1);
setLightboxLoaded(false);
const img = new Image();
img.src = images[currentIndex + 1].src;
img.src = images[safeIndex + 1].src;
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"
@ -304,7 +319,7 @@ export default function ImageGallery({
{/* Info */}
{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">
{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>

View File

@ -70,13 +70,12 @@ export function useTauriListeners({
if (imageData.base64 && imageData.mimeType && imageData.filename) {
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 || hasGeneratingPlaceholder || imageData.filename.includes('_out') || imageData.filename.includes('generated_');
const isGeneratedImage = isGenerating || document.querySelector('[src^="data:image/svg+xml"]');
if (isGeneratedImage) {
const generatedImageFile: ImageFile = { path: `generated_${imageData.filename}`, src };
const generatedImageFile: ImageFile = { path: imageData.filename, src, isGenerated: true };
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];
});
@ -87,7 +86,7 @@ export function useTauriListeners({
setIsGenerating(false);
addDebugMessage('info', '✅ Generated image added to files', { filename: imageData.filename, prompt });
} else {
const newImageFile = { path: imageData.filename, src };
const newImageFile: ImageFile = { path: imageData.filename, src, isGenerated: false };
setFiles(prevFiles => {
const exists = prevFiles.some(f => f.path === imageData.filename);
if (!exists) {

View File

@ -2,6 +2,7 @@ export interface ImageFile {
path: string;
src: string;
selected?: boolean;
isGenerated?: boolean;
}
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]))
: '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}`);
// --- End new logic ---
@ -262,8 +268,10 @@ async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
} else {
// Image creation
logger.info(`Creating image with prompt: "${genPrompt}"`);
const creationArgv = { ...argv };
delete creationArgv.include;
const parsedOptions = ImageOptionsSchema().parse({
...argv,
...creationArgv,
prompt: genPrompt,
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