Move info and redraw actions to ComicCanvas, update sidebar UI
Shifted the info and redraw buttons from the editor toolbar to the ComicCanvas component, allowing for contextual actions per page. Enhanced ComicCanvas to support page navigation via image clicks and added optional callbacks for info and navigation. Updated the page sidebar to display page thumbnails instead of numbers, improved the add page button, and adjusted button sizes for better usability.
This commit is contained in:
@@ -128,14 +128,10 @@ export default function StoryEditorPage() {
|
|||||||
if (wasGenerating) {
|
if (wasGenerating) {
|
||||||
setShowGenerateModal(true);
|
setShowGenerateModal(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "API key saved",
|
|
||||||
description: "Your Together API key has been saved successfully",
|
|
||||||
duration: 3000,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleGeneratePage = async (data: {
|
const handleGeneratePage = async (data: {
|
||||||
prompt: string;
|
prompt: string;
|
||||||
style: string;
|
style: string;
|
||||||
@@ -216,7 +212,6 @@ export default function StoryEditorPage() {
|
|||||||
<div className="h-screen flex flex-col bg-background">
|
<div className="h-screen flex flex-col bg-background">
|
||||||
<EditorToolbar
|
<EditorToolbar
|
||||||
title={story.title}
|
title={story.title}
|
||||||
onInfoClick={() => setShowInfoSheet(true)}
|
|
||||||
onContinueStory={handleAddPage}
|
onContinueStory={handleAddPage}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -229,7 +224,12 @@ export default function StoryEditorPage() {
|
|||||||
loadingPageId={loadingPageId}
|
loadingPageId={loadingPageId}
|
||||||
onApiKeyClick={handleApiKeyClick}
|
onApiKeyClick={handleApiKeyClick}
|
||||||
/>
|
/>
|
||||||
<ComicCanvas page={pages[currentPage]} />
|
<ComicCanvas
|
||||||
|
page={pages[currentPage]}
|
||||||
|
onInfoClick={() => setShowInfoSheet(true)}
|
||||||
|
onNextPage={() => setCurrentPage((prev) => (prev < pages.length - 1 ? prev + 1 : prev))}
|
||||||
|
onPrevPage={() => setCurrentPage((prev) => (prev > 0 ? prev - 1 : prev))}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ApiKeyModal
|
<ApiKeyModal
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { RefreshCw, Download } from "lucide-react";
|
import { RefreshCw, Download, Info } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
interface PageData {
|
interface PageData {
|
||||||
@@ -14,9 +14,12 @@ interface PageData {
|
|||||||
|
|
||||||
interface ComicCanvasProps {
|
interface ComicCanvasProps {
|
||||||
page: PageData;
|
page: PageData;
|
||||||
|
onInfoClick?: () => void;
|
||||||
|
onNextPage?: () => void;
|
||||||
|
onPrevPage?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ComicCanvas({ page }: ComicCanvasProps) {
|
export function ComicCanvas({ page, onInfoClick, 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 */}
|
||||||
@@ -32,7 +35,20 @@ export function ComicCanvas({ page }: ComicCanvasProps) {
|
|||||||
<img
|
<img
|
||||||
src={page.image || "/placeholder.svg"}
|
src={page.image || "/placeholder.svg"}
|
||||||
alt={`Page ${page.id}`}
|
alt={`Page ${page.id}`}
|
||||||
className="w-full h-full object-cover opacity-90 grayscale-10 contrast-110"
|
className="w-full h-full object-cover opacity-90 grayscale-10 contrast-110 cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
|
const clickX = e.clientX - rect.left;
|
||||||
|
const imageWidth = rect.width;
|
||||||
|
|
||||||
|
if (clickX > imageWidth / 2) {
|
||||||
|
// Right half - next page
|
||||||
|
onNextPage?.();
|
||||||
|
} else {
|
||||||
|
// Left half - previous page
|
||||||
|
onPrevPage?.();
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="scan-line opacity-30" />
|
<div className="scan-line opacity-30" />
|
||||||
@@ -44,6 +60,28 @@ export function ComicCanvas({ page }: ComicCanvasProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Action buttons below the page image */}
|
||||||
|
<div className="flex items-center justify-center gap-2 mt-4">
|
||||||
|
{onInfoClick && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="hover:bg-secondary text-muted-foreground hover:text-white h-9 w-9"
|
||||||
|
onClick={onInfoClick}
|
||||||
|
>
|
||||||
|
<Info className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="hover:bg-secondary text-muted-foreground hover:text-white gap-2 text-xs h-9 px-3"
|
||||||
|
>
|
||||||
|
<RefreshCw className="w-4 h-4" />
|
||||||
|
<span>Redraw</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col items-center gap-3 mt-4">
|
<div className="flex flex-col items-center gap-3 mt-4">
|
||||||
{/* <div className="text-xs text-muted-foreground">Page {page.id}</div> */}
|
{/* <div className="text-xs text-muted-foreground">Page {page.id}</div> */}
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,11 @@ import { useRouter } from "next/navigation";
|
|||||||
|
|
||||||
interface EditorToolbarProps {
|
interface EditorToolbarProps {
|
||||||
title: string;
|
title: string;
|
||||||
onInfoClick: () => void;
|
|
||||||
onContinueStory?: () => void;
|
onContinueStory?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EditorToolbar({
|
export function EditorToolbar({
|
||||||
title,
|
title,
|
||||||
onInfoClick,
|
|
||||||
onContinueStory,
|
onContinueStory,
|
||||||
}: EditorToolbarProps) {
|
}: EditorToolbarProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -34,41 +32,24 @@ export function EditorToolbar({
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-1 sm:gap-2 flex-shrink-0">
|
<div className="flex items-center gap-1 sm:gap-2 flex-shrink-0">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
className="hover:bg-secondary text-muted-foreground hover:text-white gap-1.5 sm:gap-2 text-xs h-8 sm:h-9 px-2 sm:px-3 hidden md:flex"
|
||||||
className="hover:bg-secondary text-muted-foreground hover:text-white h-8 w-8 sm:h-9 sm:w-9"
|
>
|
||||||
onClick={onInfoClick}
|
<Download className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
||||||
>
|
<span>Download PDF</span>
|
||||||
<Info className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
</Button>
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
onClick={onContinueStory}
|
||||||
className="hover:bg-secondary text-muted-foreground hover:text-white gap-1.5 sm:gap-2 text-xs h-8 sm:h-9 px-2 sm:px-3 hidden md:flex"
|
className="gap-1.5 sm:gap-2 text-xs bg-white hover:bg-neutral-200 text-black h-8 sm:h-9 px-3 sm:px-4"
|
||||||
>
|
>
|
||||||
<RefreshCw className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
<Plus className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
||||||
<span>Redraw</span>
|
<span className="hidden sm:inline">Continue story</span>
|
||||||
</Button>
|
<span className="sm:hidden">Add</span>
|
||||||
|
</Button>
|
||||||
<Button
|
</div>
|
||||||
variant="ghost"
|
|
||||||
className="hover:bg-secondary text-muted-foreground hover:text-white gap-1.5 sm:gap-2 text-xs h-8 sm:h-9 px-2 sm:px-3 hidden md:flex"
|
|
||||||
>
|
|
||||||
<Download className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
|
||||||
<span>Download PDF</span>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={onContinueStory}
|
|
||||||
className="gap-1.5 sm:gap-2 text-xs bg-white hover:bg-neutral-200 text-black h-8 sm:h-9 px-3 sm:px-4"
|
|
||||||
>
|
|
||||||
<Plus className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
|
||||||
<span className="hidden sm:inline">Continue story</span>
|
|
||||||
<span className="sm:hidden">Add</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,37 +31,56 @@ export function PageSidebar({
|
|||||||
onApiKeyClick,
|
onApiKeyClick,
|
||||||
}: PageSidebarProps) {
|
}: PageSidebarProps) {
|
||||||
return (
|
return (
|
||||||
<aside className="w-16 md:w-20 border-r border-border/50 bg-background/50 flex flex-col items-center py-4 gap-2 justify-between">
|
<aside className="w-20 md:w-24 border-r border-border/50 bg-background/50 flex flex-col items-center py-4 gap-2 justify-between">
|
||||||
{/* Top section: page numbers */}
|
{/* Top section: page thumbnails */}
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="flex flex-col items-center gap-3">
|
||||||
{pages.map((page, index) => (
|
{pages.map((page, index) => (
|
||||||
<button
|
<button
|
||||||
key={page.id}
|
key={page.id}
|
||||||
onClick={() => onPageSelect(index)}
|
onClick={() => onPageSelect(index)}
|
||||||
disabled={loadingPageId === page.id}
|
disabled={loadingPageId === page.id}
|
||||||
className={`
|
className={`
|
||||||
w-9 h-9 rounded-md transition-all
|
w-16 h-16 rounded-lg transition-all relative overflow-hidden
|
||||||
flex items-center justify-center font-medium text-sm tracking-tight
|
|
||||||
${
|
${
|
||||||
currentPage === index
|
currentPage === index
|
||||||
? "bg-black border-2 border-indigo text-white shadow-lg shadow-indigo/20"
|
? "ring-2 ring-indigo shadow-lg shadow-indigo/20"
|
||||||
: "glass-panel glass-panel-hover text-muted-foreground hover:text-white"
|
: "glass-panel glass-panel-hover hover:ring-1 hover:ring-white/20"
|
||||||
}
|
}
|
||||||
${loadingPageId === page.id ? "opacity-50" : ""}
|
${loadingPageId === page.id ? "opacity-50" : ""}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{loadingPageId === page.id ? <Loader2 className="w-4 h-4 animate-spin" /> : index + 1}
|
{loadingPageId === page.id ? (
|
||||||
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
|
<Loader2 className="w-6 h-6 animate-spin text-white" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<img
|
||||||
|
src={page.image || "/placeholder.svg"}
|
||||||
|
alt={`Page ${index + 1}`}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
<div className={`
|
||||||
|
absolute bottom-1 left-1 px-1.5 py-0.5 rounded text-[10px] font-medium tracking-tight
|
||||||
|
${
|
||||||
|
currentPage === index
|
||||||
|
? "bg-indigo text-white"
|
||||||
|
: "bg-black/70 text-white"
|
||||||
|
}
|
||||||
|
`}>
|
||||||
|
{index + 1}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<Button
|
<button
|
||||||
onClick={onAddPage}
|
onClick={onAddPage}
|
||||||
variant="ghost"
|
className="w-16 h-16 rounded-lg border-2 border-dashed border-border/50 hover:border-indigo/50 bg-background/50 hover:bg-background/80 transition-all group flex items-center justify-center"
|
||||||
size="icon"
|
|
||||||
className="w-9 h-9 bg-white hover:bg-neutral-200 text-black border-0 transition-all group"
|
|
||||||
>
|
>
|
||||||
<Plus className="w-4 h-4 text-black transition-transform group-hover:scale-110" />
|
<Plus className="w-6 h-6 text-muted-foreground group-hover:text-indigo transition-transform group-hover:scale-110" />
|
||||||
</Button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col items-center gap-3">
|
<div className="flex flex-col items-center gap-3">
|
||||||
@@ -70,13 +89,13 @@ export function PageSidebar({
|
|||||||
onClick={onApiKeyClick}
|
onClick={onApiKeyClick}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="w-9 h-9 glass-panel glass-panel-hover text-muted-foreground hover:text-white"
|
className="w-10 h-10 glass-panel glass-panel-hover text-muted-foreground hover:text-white"
|
||||||
title="Manage API Key"
|
title="Manage API Key"
|
||||||
>
|
>
|
||||||
<Key className="w-4 h-4" />
|
<Key className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="w-9 h-9 glass-panel glass-panel-hover rounded-md flex items-center justify-center text-muted-foreground hover:text-white transition-colors">
|
<div className="w-10 h-10 glass-panel glass-panel-hover rounded-md flex items-center justify-center text-muted-foreground hover:text-white transition-colors">
|
||||||
<UserButton
|
<UserButton
|
||||||
appearance={{
|
appearance={{
|
||||||
elements: {
|
elements: {
|
||||||
|
|||||||
Reference in New Issue
Block a user