import { useState, type ReactNode } from "react";
import { Copy, Check } from "@phosphor-icons/react";
import { useStoryboard } from "../../hooks/useStoryboard";
import { copyTextToClipboard } from "../../utils/clipboard";
import { Button } from "../ui/Button";
import { StoryboardLoaded } from "./StoryboardLoaded";
export interface StoryboardViewProps {
projectId: string;
/** Select a composition in the timeline (used by the frame focus "Open in Preview"). */
onSelectComposition: (path: string) => void;
}
/**
* Top-level storyboard stage. Replaces the timeline/preview when the view mode
* is `storyboard`. Handles the load states here; once a storyboard exists,
* {@link StoryboardLoaded} owns the Board ↔ Source experience.
*/
// fallow-ignore-next-line complexity
export function StoryboardView({ projectId, onSelectComposition }: StoryboardViewProps) {
const { data, loading, error, reload } = useStoryboard(projectId);
if (loading) return {Loading storyboard…};
if (error) {
return (
Couldn’t load the storyboard: {error}
);
}
if (!data) return {null};
if (!data.exists) {
return (
);
}
return (
);
}
function StoryboardFrame({ children }: { children: ReactNode }) {
return (
);
}
function Message({ children, tone = "muted" }: { children: ReactNode; tone?: "muted" | "error" }) {
return (
{children}
);
}
function handoffPrompt(path: string): string {
return `Create a \`${path}\` at the project root to plan this video frame by frame.
Use this format:
---
format: 1920x1080
message:
arc:
audience:
---
## Frame 1 —
- duration: 5s
- transition_in: crossfade
- status: planned
- src: compositions/frames/01-.html
Add one \`## Frame N\` section per beat. Keep the arc tight.`;
}
function EmptyState({ path }: { path: string }) {
const [copied, setCopied] = useState(false);
const prompt = handoffPrompt(path);
const onCopy = async () => {
if (await copyTextToClipboard(prompt)) {
setCopied(true);
window.setTimeout(() => setCopied(false), 2000);
}
};
return (
No storyboard yet
Add a {path}{" "}
at the project root to plan this video frame by frame. Hand this prompt to your coding
agent to scaffold it.
Prompt for your agent
: }
>
{copied ? "Copied" : "Copy prompt"}
{prompt}
);
}
/** Faded placeholder of a filled board so landing here isn't a dead end —
* it previews the contact-sheet layout {@link StoryboardGrid} renders. */
function SkeletonPreview() {
return (
Preview
{[0, 1, 2].map((i) => (
))}
);
}