Add loading state and disable interactions during page generation

This commit is contained in:
Riccardo Giorato
2025-12-26 19:50:30 +01:00
parent 89048bae15
commit f3c1398bd2
3 changed files with 37 additions and 9 deletions
+2
View File
@@ -287,6 +287,8 @@ export default function StoryEditorPage() {
/> />
<ComicCanvas <ComicCanvas
page={pages[currentPage]} page={pages[currentPage]}
pageIndex={currentPage}
isLoading={loadingPageId === currentPage}
onInfoClick={() => setShowInfoSheet(true)} onInfoClick={() => setShowInfoSheet(true)}
onRedrawClick={handleRedrawPage} onRedrawClick={handleRedrawPage}
onNextPage={() => onNextPage={() =>
+20 -7
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import { RefreshCw, Download, Info } from "lucide-react"; import { RefreshCw, Download, Info, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
interface PageData { interface PageData {
@@ -8,19 +8,22 @@ interface PageData {
title: string; title: string;
image: string; image: string;
prompt: string; prompt: string;
characterUpload?: string; characterUploads?: string[];
style: string; style: string;
dbId?: string;
} }
interface ComicCanvasProps { interface ComicCanvasProps {
page: PageData; page: PageData;
pageIndex: number;
isLoading?: boolean;
onInfoClick?: () => void; onInfoClick?: () => void;
onRedrawClick?: () => void; onRedrawClick?: () => void;
onNextPage?: () => void; onNextPage?: () => void;
onPrevPage?: () => void; onPrevPage?: () => void;
} }
export function ComicCanvas({ page, onInfoClick, onRedrawClick, onNextPage, onPrevPage }: ComicCanvasProps) { export function ComicCanvas({ page, pageIndex, isLoading = false, onInfoClick, onRedrawClick, onNextPage, onPrevPage }: ComicCanvasProps) {
return ( return (
<main className="flex-1 overflow-auto p-4 md:p-8 flex items-start justify-center relative"> <main className="flex-1 overflow-auto p-4 md:p-8 flex items-start justify-center relative">
{/* Dot grid background */} {/* Dot grid background */}
@@ -78,9 +81,14 @@ export function ComicCanvas({ page, onInfoClick, onRedrawClick, onNextPage, onPr
variant="ghost" variant="ghost"
className="hover:bg-secondary text-muted-foreground hover:text-white gap-2 text-xs h-9 px-3" className="hover:bg-secondary text-muted-foreground hover:text-white gap-2 text-xs h-9 px-3"
onClick={onRedrawClick} onClick={onRedrawClick}
disabled={isLoading}
> >
<RefreshCw className="w-4 h-4" /> {isLoading ? (
<span>Redraw</span> <Loader2 className="w-4 h-4 animate-spin" />
) : (
<RefreshCw className="w-4 h-4" />
)}
<span>{isLoading ? "Redrawing..." : "Redraw"}</span>
</Button> </Button>
</div> </div>
@@ -93,9 +101,14 @@ export function ComicCanvas({ page, onInfoClick, onRedrawClick, onNextPage, onPr
variant="ghost" variant="ghost"
className="hover:bg-secondary text-muted-foreground hover:text-white gap-2 text-xs h-9 px-3 flex-1" className="hover:bg-secondary text-muted-foreground hover:text-white gap-2 text-xs h-9 px-3 flex-1"
onClick={onRedrawClick} onClick={onRedrawClick}
disabled={isLoading}
> >
<RefreshCw className="w-4 h-4" /> {isLoading ? (
<span>Redraw</span> <Loader2 className="w-4 h-4 animate-spin" />
) : (
<RefreshCw className="w-4 h-4" />
)}
<span>{isLoading ? "Redrawing..." : "Redraw"}</span>
</Button> </Button>
<Button <Button
+15 -2
View File
@@ -8,6 +8,7 @@ import {
DialogContent, DialogContent,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogClose,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { useKeyboardShortcut } from "@/hooks/use-keyboard-shortcut"; import { useKeyboardShortcut } from "@/hooks/use-keyboard-shortcut";
@@ -53,7 +54,7 @@ export function GeneratePageModal({
} }
}, [isOpen, isRedrawMode, existingPrompt]); }, [isOpen, isRedrawMode, existingPrompt]);
// Keyboard shortcut for form submission // Keyboard shortcut for form submission (disabled during generation)
useKeyboardShortcut(() => { useKeyboardShortcut(() => {
if (isOpen && !isGenerating && prompt.trim()) { if (isOpen && !isGenerating && prompt.trim()) {
handleGenerate(); handleGenerate();
@@ -135,14 +136,26 @@ export function GeneratePageModal({
const handleOpenChange = (open: boolean) => {
// Prevent closing the modal if generation is running
if (!open && isGenerating) {
return;
}
onClose();
};
return ( return (
<> <>
<Dialog open={isOpen} onOpenChange={onClose}> <Dialog open={isOpen} onOpenChange={handleOpenChange}>
<DialogContent className="border border-border/50 rounded-lg bg-background max-w-lg"> <DialogContent className="border border-border/50 rounded-lg bg-background max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle className="text-xl text-white font-heading"> <DialogTitle className="text-xl text-white font-heading">
{isRedrawMode ? `Redraw Page ${pageNumber}` : `Generate Page ${pageNumber}`} {isRedrawMode ? `Redraw Page ${pageNumber}` : `Generate Page ${pageNumber}`}
</DialogTitle> </DialogTitle>
<DialogClose disabled={isGenerating} className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogClose>
</DialogHeader> </DialogHeader>
<div className="space-y-4 mt-4"> <div className="space-y-4 mt-4">