Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion web/src/components/storyboard/ShotCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,13 @@ const ShotCardInner: React.FC<ShotCardProps> = ({
pulse={meta.pulse}
/>
{!readOnly && (
<HoverActionGroup triggerSelector=".shot-card:hover" gap={0}>
<HoverActionGroup
triggerSelector=".shot-card:hover"
gap={0}
// Touch devices can't hover; keep the row actions (reorder,
// delete) visible there instead of hiding behind the reveal.
sx={{ "@media (pointer: coarse)": { opacity: 1 } }}
>
Comment on lines +282 to +288
<ToolbarIconButton
icon={<ArrowUpwardIcon sx={{ fontSize: 16 }} />}
tooltip="Move up"
Expand Down
1 change: 1 addition & 0 deletions web/src/components/storyboard/StoryboardAgentPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import useGlobalChatStore from "../../stores/GlobalChatStore";
const styles = (_theme: Theme) =>
css({
"&": {
width: "100%",
height: "100%",
minHeight: 0,
display: "flex",
Expand Down
5 changes: 5 additions & 0 deletions web/src/components/storyboard/StoryboardBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ const styles = (theme: Theme) =>
display: "flex",
flexDirection: "column",
gap: getSpacingPx(SPACING.lg),
// Reclaim horizontal room for shots on phones.
[theme.breakpoints.down("sm")]: {
padding: getSpacingPx(SPACING.md),
gap: getSpacingPx(SPACING.md)
},
Comment thread
Copilot marked this conversation as resolved.
// Children must keep their natural height so the container scrolls;
// otherwise the header panel gets flex-shrunk under the grid.
"> *": { flexShrink: 0 },
Expand Down
12 changes: 11 additions & 1 deletion web/src/components/storyboard/StoryboardSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ const styles = (theme: Theme) =>
height: "100%",
overflowY: "auto",
borderRight: `1px solid ${theme.vars.palette.divider}`,
// On phones the sidebar is a full-width pane behind a segmented switcher
// (see StoryboardSurface), not a fixed rail beside the board.
[theme.breakpoints.down("sm")]: {
width: "100%",
borderRight: "none"
},
padding: getSpacingPx(SPACING.md),
display: "flex",
flexDirection: "column",
Expand All @@ -64,7 +70,11 @@ const styles = (theme: Theme) =>
"&:hover": { backgroundColor: theme.vars.palette.action.hover },
"&.active": { backgroundColor: theme.vars.palette.action.selected },
".delete-button": { opacity: 0, transition: MOTION.opacity },
"&:hover .delete-button": { opacity: 1 }
"&:hover .delete-button": { opacity: 1 },
// Touch devices have no hover; keep the delete affordance reachable.
"@media (pointer: coarse)": {
".delete-button": { opacity: 1 }
}
}
});

Expand Down
121 changes: 107 additions & 14 deletions web/src/components/workspace/StoryboardSurface.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/** @jsxImportSource @emotion/react */
import { useCallback, useEffect } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useMediaQuery } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import ViewListRoundedIcon from "@mui/icons-material/ViewListRounded";
import TheatersIcon from "@mui/icons-material/Theaters";
import AutoAwesomeIcon from "@mui/icons-material/AutoAwesome";
import {
useWorkspaceTabsStore,
type WorkspaceTabMode
Expand All @@ -12,7 +16,7 @@ import { useDirectScreenplay } from "../../hooks/storyboard/useDirectScreenplay"
import { useStoryboardServerSync } from "../../hooks/storyboard/useStoryboardServerSync";
import { useAssembleTimeline } from "../../hooks/storyboard/useAssembleTimeline";
import { useDocumentUndoShortcuts } from "../../hooks/useDocumentUndoShortcuts";
import { FlexColumn } from "../ui_primitives";
import { FlexColumn, TabGroup } from "../ui_primitives";
import StoryboardBoard from "../storyboard/StoryboardBoard";
import StoryboardSidebar from "../storyboard/StoryboardSidebar";
import StoryboardQueueOverlay from "../storyboard/StoryboardQueueOverlay";
Expand All @@ -26,14 +30,34 @@ interface StoryboardSurfaceProps {
active: boolean;
}

type MobilePane = "boards" | "board" | "assistant";

const MOBILE_TABS = [
{ value: "boards", label: "Boards", icon: <ViewListRoundedIcon /> },
{ value: "board", label: "Board", icon: <TheatersIcon /> },
{ value: "assistant", label: "Assistant", icon: <AutoAwesomeIcon /> }
];

const MOBILE_PANES: readonly MobilePane[] = ["boards", "board", "assistant"];

const isMobilePane = (value: string): value is MobilePane =>
(MOBILE_PANES as readonly string[]).includes(value);

/**
* Workspace surface for a storyboard tab. `refId` is the board id. Ensures the
* board exists in the singleton store, mounts the agent bridge (registering this
* board under its id for the ui_storyboard_* tools) and the generation
* subscriptions, and renders the board read-only in view mode.
*
* On wide screens the sidebar, board, and assistant sit side by side. On phones
* three columns don't fit, so edit mode collapses to a single pane with a
* segmented switcher; every pane stays mounted (toggled via `display`) so
* board, chat, and scroll state survive switches.
*/
const StoryboardSurface = ({ refId, mode, active }: StoryboardSurfaceProps) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const [mobilePane, setMobilePane] = useState<MobilePane>("board");
const ensureBoard = useStoryboardStore((state) => state.ensureBoard);
const undo = useStoryboardStore((state) => state.undo);
const redo = useStoryboardStore((state) => state.redo);
Expand Down Expand Up @@ -76,6 +100,86 @@ const StoryboardSurface = ({ refId, mode, active }: StoryboardSurfaceProps) => {
});
}, [assemble, refId]);

const board = useMemo(
() => (
<StoryboardBoard
boardId={refId}
readOnly={mode === "view"}
onDirect={handleDirect}
directing={directing}
directError={error}
onAssemble={handleAssemble}
assembling={assembling}
assembleError={assembleError}
/>
),
[
refId,
mode,
handleDirect,
directing,
error,
handleAssemble,
assembling,
assembleError
]
);

if (isMobile && mode !== "view") {
return (
<FlexColumn fullHeight sx={{ minHeight: 0, position: "relative" }}>
<TabGroup
tabs={MOBILE_TABS}
value={mobilePane}
onChange={(value) => {
if (isMobilePane(value)) {
setMobilePane(value);
}
}}
size="small"
fullWidth
sx={{
flexShrink: 0,
borderBottom: `1px solid ${theme.vars.palette.divider}`
}}
/>
Comment on lines +128 to +145
<div style={{ flex: 1, minHeight: 0, display: "flex" }}>
<div
style={{
flex: 1,
minWidth: 0,
minHeight: 0,
display: mobilePane === "boards" ? "flex" : "none"
}}
>
<StoryboardSidebar activeBoardId={refId} />
</div>
<div
style={{
flex: 1,
minWidth: 0,
minHeight: 0,
display: mobilePane === "board" ? "block" : "none"
}}
>
{board}
</div>
<div
style={{
flex: 1,
minWidth: 0,
minHeight: 0,
display: mobilePane === "assistant" ? "flex" : "none"
}}
>
<StoryboardAgentPanel boardId={refId} />
</div>
Comment on lines +166 to +173
</div>
<StoryboardQueueOverlay boardId={refId} />
</FlexColumn>
);
}

return (
<div
style={{
Expand All @@ -87,18 +191,7 @@ const StoryboardSurface = ({ refId, mode, active }: StoryboardSurfaceProps) => {
>
{mode !== "view" && <StoryboardSidebar activeBoardId={refId} />}
<StoryboardQueueOverlay boardId={refId} />
<div style={{ flex: 1, minWidth: 0 }}>
<StoryboardBoard
boardId={refId}
readOnly={mode === "view"}
onDirect={handleDirect}
directing={directing}
directError={error}
onAssemble={handleAssemble}
assembling={assembling}
assembleError={assembleError}
/>
</div>
<div style={{ flex: 1, minWidth: 0 }}>{board}</div>
{mode !== "view" && (
<FlexColumn
fullHeight
Expand Down
Loading