mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 23:03:09 +00:00
feat(studio): storyboard view-mode toggle and shell (#1529)
Second PR in the Studio storyboarding stack. Adds the top-level toggle between the storyboard and the timeline/preview stage, behind the flag. - STUDIO_STORYBOARD_ENABLED flag (VITE_STUDIO_ENABLE_STORYBOARD, default off) now gates the UI. - ViewModeContext: timeline|storyboard state mirrored to the ?view= query param, so it survives reloads and an agent can deep-link ?view=storyboard. - Segmented Storyboard|Preview control in StudioHeader (flag-gated). - StudioApp swaps the whole center stage for a full-width StoryboardView when storyboard mode is active. - useStoryboard hook + StoryboardView shell: global-direction header, loading/error/empty states. The frame contact-sheet grid lands in PR3. - Extract StudioOverlays from App.tsx to stay within the 600-line studio decomposition budget. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
8a13a07074
commit
195d7aa7bd
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
|
||||
/**
|
||||
* Top-level Studio view mode.
|
||||
*
|
||||
* `timeline` is the existing NLE/preview stage. `storyboard` replaces that stage
|
||||
* with the storyboard contact sheet. The mode is mirrored to the `?view=` query
|
||||
* param so it survives reloads and — importantly — so an agent can deep-link the
|
||||
* user straight into the storyboard by navigating the tab to `?view=storyboard`.
|
||||
*/
|
||||
export type StudioViewMode = "timeline" | "storyboard";
|
||||
|
||||
const VIEW_QUERY_PARAM = "view";
|
||||
|
||||
function readViewModeFromUrl(): StudioViewMode {
|
||||
if (typeof window === "undefined") return "timeline";
|
||||
return new URLSearchParams(window.location.search).get(VIEW_QUERY_PARAM) === "storyboard"
|
||||
? "storyboard"
|
||||
: "timeline";
|
||||
}
|
||||
|
||||
function writeViewModeToUrl(mode: StudioViewMode): void {
|
||||
if (typeof window === "undefined") return;
|
||||
const url = new URL(window.location.href);
|
||||
if (mode === "storyboard") {
|
||||
url.searchParams.set(VIEW_QUERY_PARAM, "storyboard");
|
||||
} else {
|
||||
url.searchParams.delete(VIEW_QUERY_PARAM);
|
||||
}
|
||||
window.history.replaceState(window.history.state, "", url);
|
||||
}
|
||||
|
||||
export interface ViewModeValue {
|
||||
viewMode: StudioViewMode;
|
||||
setViewMode: (mode: StudioViewMode) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Owns the view-mode state. When `enabled` is false (storyboard flag off) the
|
||||
* mode is pinned to `timeline` and the URL is left untouched, so the feature is
|
||||
* fully inert until the flag is on.
|
||||
*/
|
||||
export function useViewModeState(enabled: boolean): ViewModeValue {
|
||||
const [viewMode, setMode] = useState<StudioViewMode>(() =>
|
||||
enabled ? readViewModeFromUrl() : "timeline",
|
||||
);
|
||||
|
||||
// Reflect back/forward navigation and agent-driven URL changes.
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
const onPopState = () => setMode(readViewModeFromUrl());
|
||||
window.addEventListener("popstate", onPopState);
|
||||
return () => window.removeEventListener("popstate", onPopState);
|
||||
}, [enabled]);
|
||||
|
||||
const setViewMode = useCallback(
|
||||
(mode: StudioViewMode) => {
|
||||
if (!enabled) return;
|
||||
setMode(mode);
|
||||
writeViewModeToUrl(mode);
|
||||
},
|
||||
[enabled],
|
||||
);
|
||||
|
||||
const effectiveMode = enabled ? viewMode : "timeline";
|
||||
return useMemo(() => ({ viewMode: effectiveMode, setViewMode }), [effectiveMode, setViewMode]);
|
||||
}
|
||||
|
||||
const ViewModeContext = createContext<ViewModeValue | null>(null);
|
||||
|
||||
export function useViewMode(): ViewModeValue {
|
||||
const ctx = useContext(ViewModeContext);
|
||||
if (!ctx) throw new Error("useViewMode must be used within ViewModeProvider");
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export function ViewModeProvider({
|
||||
value,
|
||||
children,
|
||||
}: {
|
||||
value: ViewModeValue;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return <ViewModeContext value={value}>{children}</ViewModeContext>;
|
||||
}
|
||||
Reference in New Issue
Block a user