Add loading state and disable interactions during page generation
This commit is contained in:
@@ -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={() =>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user