@hyperframes/studio 0.8.50 → 0.8.53-desktop.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{hyperframes-player-CxxDxHTW.js → hyperframes-player-PTEnAHhg.js} +3 -3
- package/dist/assets/{index-DUAIWxkI.js → index--myXp8ui.js} +1 -1
- package/dist/assets/{index-Dt3qBI-9.js → index-BjslOS9g.js} +1 -1
- package/dist/assets/index-BnXZuIJD.css +1 -0
- package/dist/assets/index-CNyviK_l.js +390 -0
- package/dist/index.css +29 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +825 -24
- package/dist/index.html +2 -2
- package/dist/index.js +8911 -9853
- package/dist/index.js.map +1 -1
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/package.json +13 -12
- package/src/App.tsx +145 -163
- package/src/captions/components/CaptionAnimationPanel.tsx +4 -4
- package/src/captions/components/CaptionOverlay.sender.test.tsx +52 -0
- package/src/captions/components/CaptionOverlay.tsx +8 -4
- package/src/captions/components/CaptionOverlayUtils.ts +2 -3
- package/src/captions/components/CaptionPropertyPanel.tsx +3 -3
- package/src/captions/components/CaptionTimeline.tsx +1 -1
- package/src/captions/components/shared.tsx +2 -2
- package/src/components/AskAgentModal.tsx +5 -5
- package/src/components/EditorShell.selectionSync.test.tsx +9 -4
- package/src/components/EditorShell.tsx +59 -62
- package/src/components/ExternalFileConflictBanner.tsx +16 -16
- package/src/components/LintModal.tsx +6 -16
- package/src/components/MediaPreview.tsx +2 -2
- package/src/components/RightPanelTabs.test.tsx +151 -0
- package/src/components/RightPanelTabs.tsx +59 -0
- package/src/components/SaveQueuePausedBanner.tsx +2 -2
- package/src/components/StudioHeader.dom.test.tsx +181 -0
- package/src/components/StudioHeader.test.ts +0 -12
- package/src/components/StudioHeader.tsx +118 -221
- package/src/components/StudioLeftPanels.tsx +202 -0
- package/src/components/StudioOverlays.tsx +1 -1
- package/src/components/{StudioRightPanel.tsx → StudioRightPanels.tsx} +57 -194
- package/src/components/{StudioRightPanel.types.ts → StudioRightPanels.types.ts} +4 -3
- package/src/components/StudioToast.tsx +2 -2
- package/src/components/TimelineHistoryButtons.dom.test.tsx +117 -0
- package/src/components/TimelineHistoryButtons.tsx +54 -0
- package/src/components/TimelineToolbar.test.tsx +8 -0
- package/src/components/TimelineToolbar.tsx +8 -12
- package/src/components/dock/Dock.test.tsx +274 -0
- package/src/components/dock/Dock.tsx +291 -0
- package/src/components/dock/dock.css +28 -0
- package/src/components/dock/dockAccessibility.test.ts +166 -0
- package/src/components/dock/dockAccessibility.ts +98 -0
- package/src/components/dock/dockLayout.test.ts +134 -0
- package/src/components/dock/dockLayout.ts +105 -0
- package/src/components/dock/dockLayoutSchema.ts +66 -0
- package/src/components/dock/dockLayoutStore.test.ts +28 -0
- package/src/components/dock/dockLayoutStore.ts +108 -0
- package/src/components/dock/dockTestHarness.ts +11 -0
- package/src/components/dock/panelRegistry.ts +67 -0
- package/src/components/editor/AnimationCard.tsx +4 -4
- package/src/components/editor/AnimationCardParts.tsx +6 -6
- package/src/components/editor/BlockParamsPanel.tsx +4 -4
- package/src/components/editor/BorderRadiusEditor.tsx +1 -1
- package/src/components/editor/CanvasContextMenu.tsx +1 -1
- package/src/components/editor/ComputedTweenNotice.tsx +1 -1
- package/src/components/editor/DomEditOverlay.tsx +1 -1
- package/src/components/editor/EaseCurveSection.tsx +1 -1
- package/src/components/editor/EaseParamFields.tsx +5 -5
- package/src/components/editor/FileTree.tsx +4 -4
- package/src/components/editor/FileTreeIcons.tsx +1 -1
- package/src/components/editor/FileTreeNodes.tsx +13 -13
- package/src/components/editor/InlineTextToolbar.tsx +1 -1
- package/src/components/editor/InspectorHeaderActions.tsx +3 -3
- package/src/components/editor/KeyframeDiamond.tsx +1 -1
- package/src/components/editor/KeyframeEaseList.tsx +1 -1
- package/src/components/editor/LayersPanel.test.ts +97 -1
- package/src/components/editor/LayersPanel.tsx +7 -5
- package/src/components/editor/MarqueeOverlay.tsx +1 -1
- package/src/components/editor/OffCanvasIndicators.tsx +1 -1
- package/src/components/editor/PreviewGuides.tsx +1 -1
- package/src/components/editor/PromotableControl.tsx +2 -2
- package/src/components/editor/PropertyPanelEmptyState.tsx +7 -7
- package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
- package/src/components/editor/SnapToolbar.test.tsx +0 -3
- package/src/components/editor/SnapToolbar.tsx +2 -2
- package/src/components/editor/TimelineFxPopover.tsx +1 -1
- package/src/components/editor/TopologyLens.test.tsx +28 -0
- package/src/components/editor/TopologyLens.tsx +7 -5
- package/src/components/editor/Transform3DCube.tsx +2 -2
- package/src/components/editor/audioFxRevealTarget.ts +1 -0
- package/src/components/editor/domEditing.test.ts +1 -1
- package/src/components/editor/domEditingAgentPrompt.test.ts +8 -6
- package/src/components/editor/domEditingAgentPrompt.ts +4 -3
- package/src/components/editor/propertyPanelColor.tsx +11 -11
- package/src/components/editor/propertyPanelColorCurveGraph.tsx +1 -1
- package/src/components/editor/propertyPanelColorGradingControls.tsx +7 -9
- package/src/components/editor/propertyPanelColorGradingSection.tsx +6 -6
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +5 -5
- package/src/components/editor/propertyPanelColorWheels.tsx +1 -1
- package/src/components/editor/propertyPanelCommitField.tsx +1 -1
- package/src/components/editor/propertyPanelFill.tsx +4 -4
- package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +7 -7
- package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +3 -3
- package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +1 -1
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +10 -10
- package/src/components/editor/propertyPanelFlatSelectRow.tsx +3 -3
- package/src/components/editor/propertyPanelFlatTextSection.test.tsx +8 -9
- package/src/components/editor/propertyPanelFlatTextSection.tsx +5 -5
- package/src/components/editor/propertyPanelFlatToggle.tsx +1 -1
- package/src/components/editor/propertyPanelFont.tsx +5 -5
- package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
- package/src/components/editor/propertyPanelFxControls.test.tsx +1 -1
- package/src/components/editor/propertyPanelFxControls.tsx +6 -6
- package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
- package/src/components/editor/propertyPanelFxPresetStyle.ts +2 -2
- package/src/components/editor/propertyPanelGradingNumberField.tsx +1 -1
- package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
- package/src/components/editor/propertyPanelPrimitives.tsx +7 -7
- package/src/components/editor/propertyPanelSections.tsx +8 -8
- package/src/components/editor/useColorGradingController.test.ts +38 -0
- package/src/components/editor/useColorGradingController.ts +9 -1
- package/src/components/editor/useColorGradingPreviews.test.ts +76 -0
- package/src/components/editor/useColorGradingPreviews.ts +11 -1
- package/src/components/editor/usePreviewCompositionRect.test.tsx +95 -0
- package/src/components/editor/usePreviewCompositionRect.ts +21 -0
- package/src/components/feedback/StudioFeedbackCard.tsx +7 -7
- package/src/components/feedback/feedbackTrigger.ts +0 -1
- package/src/components/nle/AssetPreviewOverlay.tsx +6 -2
- package/src/components/nle/CompositionBreadcrumb.tsx +3 -3
- package/src/components/nle/NLEContext.tsx +0 -43
- package/src/components/nle/NLEPreview.tsx +3 -3
- package/src/components/nle/PreviewOverlays.tsx +6 -6
- package/src/components/nle/PreviewPane.tsx +3 -3
- package/src/components/nle/TimelinePane.tsx +53 -67
- package/src/components/panels/SlideshowPanel.tsx +3 -3
- package/src/components/panels/SlideshowSubPanels.tsx +20 -17
- package/src/components/panels/VariablesBindElement.tsx +2 -2
- package/src/components/panels/VariablesDeclarationForm.tsx +2 -2
- package/src/components/panels/VariablesOtherCompositions.tsx +1 -1
- package/src/components/panels/VariablesPanel.tsx +4 -4
- package/src/components/panels/VariablesValueControls.tsx +2 -2
- package/src/components/pointerTargetSize.test.tsx +8 -0
- package/src/components/renders/FfmpegRequiredNotice.tsx +5 -5
- package/src/components/renders/RenderQueue.test.tsx +7 -0
- package/src/components/renders/RenderQueue.tsx +7 -7
- package/src/components/renders/RenderQueueItem.tsx +6 -6
- package/src/components/renders/RenderQueuePanel.tsx +1 -1
- package/src/components/sidebar/AssetCard.tsx +8 -8
- package/src/components/sidebar/AssetContextMenu.tsx +8 -8
- package/src/components/sidebar/AssetsTab.tsx +2 -2
- package/src/components/sidebar/AudioRow.tsx +6 -6
- package/src/components/sidebar/BlocksTab.tsx +6 -6
- package/src/components/sidebar/{LeftSidebar.masterComposition.test.tsx → CompositionsPanel.masterComposition.test.tsx} +6 -9
- package/src/components/sidebar/CompositionsPanel.tsx +15 -0
- package/src/components/sidebar/CompositionsTab.tsx +5 -5
- package/src/components/sidebar/GlobalAssetsView.tsx +1 -1
- package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
- package/src/components/sidebar/SidebarLintButton.test.tsx +53 -0
- package/src/components/sidebar/SidebarLintButton.tsx +50 -0
- package/src/components/timelineToolbarStyles.ts +5 -0
- package/src/components/ui/Button.test.tsx +252 -0
- package/src/components/ui/Button.tsx +89 -126
- package/src/components/ui/IconButton.tsx +38 -0
- package/src/components/ui/Input.tsx +115 -0
- package/src/components/ui/Menu.test.tsx +374 -0
- package/src/components/ui/Menu.tsx +213 -0
- package/src/components/ui/NumberField.tsx +122 -0
- package/src/components/ui/Popover.tsx +74 -0
- package/src/components/ui/SearchInput.tsx +2 -2
- package/src/components/ui/Select.tsx +100 -0
- package/src/components/ui/Slider.tsx +153 -0
- package/src/components/ui/Tabs.test.tsx +98 -0
- package/src/components/ui/Tabs.tsx +71 -0
- package/src/components/ui/Toggle.tsx +63 -0
- package/src/components/ui/Tooltip.test.tsx +110 -0
- package/src/components/ui/Tooltip.tsx +38 -112
- package/src/components/ui/cn.test.ts +30 -0
- package/src/components/ui/cn.ts +25 -0
- package/src/components/ui/index.ts +29 -2
- package/src/components/ui/mountHost.testHelpers.ts +24 -0
- package/src/components/ui/valueControls.test.tsx +524 -0
- package/src/contexts/PanelLayoutContext.tsx +3 -57
- package/src/hooks/appHotkeysDispatch.ts +3 -4
- package/src/hooks/timelineEditPermission.ts +31 -0
- package/src/hooks/useAppHotkeys.previewForwarding.test.tsx +0 -2
- package/src/hooks/useAppHotkeys.test.ts +0 -1
- package/src/hooks/useAppHotkeys.textEditing.test.tsx +0 -3
- package/src/hooks/useAppHotkeys.ts +21 -84
- package/src/hooks/useCaptionDetection.sender.test.tsx +46 -0
- package/src/hooks/useCaptionDetection.ts +3 -1
- package/src/hooks/useDomEditPreviewSync.ts +4 -6
- package/src/hooks/useDomEditSession.ts +0 -7
- package/src/hooks/useDomEditWiring.ts +4 -9
- package/src/hooks/useEditHistoryActions.test.tsx +90 -0
- package/src/hooks/useEditHistoryActions.ts +106 -0
- package/src/hooks/useFileManager.ts +21 -123
- package/src/hooks/useLintModal.test.tsx +65 -0
- package/src/hooks/useLintModal.ts +3 -0
- package/src/hooks/usePanelLayout.test.ts +108 -296
- package/src/hooks/usePanelLayout.ts +50 -255
- package/src/hooks/usePersistentEditHistory.ts +1 -1
- package/src/hooks/useProjectFileWriter.ts +150 -0
- package/src/hooks/useRightPanelIntents.test.ts +181 -0
- package/src/hooks/useRightPanelIntents.ts +63 -0
- package/src/hooks/useSdkSession.lifecycle.test.tsx +49 -4
- package/src/hooks/useSdkSession.ts +43 -10
- package/src/hooks/useServerConnection.hashProject.test.tsx +94 -0
- package/src/hooks/useServerConnection.ts +43 -10
- package/src/hooks/useSlideshowTabState.test.ts +9 -31
- package/src/hooks/useSlideshowTabState.ts +6 -19
- package/src/hooks/useStudioContextValue.test.ts +6 -18
- package/src/hooks/useStudioContextValue.ts +5 -26
- package/src/hooks/useTimelineAssetDropOps.test.tsx +41 -1
- package/src/hooks/useTimelineAssetDropOps.ts +52 -11
- package/src/hooks/useTimelineDeleteOps.test.tsx +111 -0
- package/src/hooks/useTimelineEditing.test.tsx +322 -3
- package/src/hooks/useTimelineEditing.ts +103 -16
- package/src/hooks/useTimelineEditingTypes.ts +3 -0
- package/src/hooks/useTrackPendingTimelineEdit.test.tsx +62 -0
- package/src/hooks/useTrackPendingTimelineEdit.ts +22 -0
- package/src/hostMount.test.tsx +222 -0
- package/src/index.ts +87 -0
- package/src/main.tsx +9 -3
- package/src/player/components/AudioWaveform.tsx +1 -1
- package/src/player/components/AutomationSelectionMenu.tsx +3 -3
- package/src/player/components/ClipContextMenu.tsx +3 -3
- package/src/player/components/CompositionThumbnail.test.ts +0 -3
- package/src/player/components/CompositionThumbnail.tsx +3 -4
- package/src/player/components/EditModal.tsx +1 -1
- package/src/player/components/ImageThumbnail.tsx +1 -1
- package/src/player/components/KeyframeDiamondContextMenu.tsx +4 -4
- package/src/player/components/LayerDisclosureRow.tsx +2 -2
- package/src/player/components/PlayerControls.tsx +1 -1
- package/src/player/components/ShortcutsPanel.tsx +9 -9
- package/src/player/components/SpeedMenu.tsx +3 -5
- package/src/player/components/Timeline.tsx +3 -2
- package/src/player/components/TimelineCanvas.tsx +21 -14
- package/src/player/components/TimelineClip.tsx +0 -1
- package/src/player/components/TimelineDiamondConnectors.tsx +1 -1
- package/src/player/components/TimelineEmptyState.tsx +5 -5
- package/src/player/components/TimelineFxButton.tsx +6 -6
- package/src/player/components/TimelineGroupHeader.tsx +5 -5
- package/src/player/components/TimelineGroupLaneLabels.tsx +1 -1
- package/src/player/components/TimelineGroupRow.tsx +1 -1
- package/src/player/components/TimelineLanes.test.tsx +32 -0
- package/src/player/components/TimelineLanes.tsx +2 -5
- package/src/player/components/TimelineLayerGroupHeader.tsx +2 -2
- package/src/player/components/TimelineRuler.tsx +1 -1
- package/src/player/components/TimelineShortcutHint.tsx +1 -1
- package/src/player/components/TimelineTrackHeader.test.tsx +26 -5
- package/src/player/components/TimelineTrackHeader.tsx +2 -2
- package/src/player/components/TimelineTrackPlainHeader.tsx +3 -3
- package/src/player/components/TimelineTypes.ts +2 -1
- package/src/player/components/TrackGapContextMenu.tsx +1 -1
- package/src/player/components/VideoThumbnail.tsx +4 -2
- package/src/player/components/VolumeControl.tsx +2 -2
- package/src/player/components/timelineCallbacks.ts +8 -8
- package/src/player/components/timelineClipDragPreview.ts +51 -27
- package/src/player/components/timelineClipDragTypes.ts +3 -0
- package/src/player/components/timelineDragDrop.test.tsx +61 -1
- package/src/player/components/timelineDragDrop.ts +57 -15
- package/src/player/components/timelineLaneProps.ts +3 -0
- package/src/player/components/timelineSnapping.test.ts +27 -0
- package/src/player/components/timelineSnapping.ts +14 -1
- package/src/player/components/timelineTheme.test.ts +4 -4
- package/src/player/components/timelineTheme.ts +30 -30
- package/src/player/components/trackHeaderLabelRows.tsx +4 -4
- package/src/player/components/useTimelineClipDrag.resize.test.tsx +136 -2
- package/src/player/components/useTimelineClipDrag.ts +30 -3
- package/src/player/lib/describeClips.ts +3 -54
- package/src/player/lib/playbackShortcuts.ts +2 -0
- package/src/player/lib/timelinePerformanceDiagnostics.test.ts +3 -2
- package/src/player/lib/timelinePerformanceDiagnostics.ts +1 -1
- package/src/player/store/previewIframeStore.notify.test.tsx +75 -0
- package/src/player/store/previewIframeStore.test.tsx +29 -0
- package/src/player/store/previewIframeStore.ts +25 -0
- package/src/renderQueueExports.test.tsx +58 -0
- package/src/styles/classCandidates.test.ts +105 -0
- package/src/styles/classCandidates.ts +281 -0
- package/src/styles/hex-baseline.json +84 -0
- package/src/styles/hexRatchet.test.ts +201 -0
- package/src/styles/iconTokens.ts +20 -0
- package/src/styles/palette-reset.css +6 -0
- package/src/styles/studio.css +24 -21
- package/src/styles/styleSources.test.ts +20 -0
- package/src/styles/styleSources.ts +47 -0
- package/src/styles/tailwind-preset.shared.js +3 -0
- package/src/styles/theme.css +270 -0
- package/src/styles/theme.test.ts +183 -0
- package/src/styles/tokenGate.test.ts +109 -0
- package/src/utils/sourcePatcher.ts +1 -1
- package/src/utils/studioHelpers.test.ts +18 -0
- package/src/utils/studioHelpers.ts +11 -8
- package/src/utils/studioUiPreferences.test.ts +10 -16
- package/src/utils/studioUiPreferences.ts +7 -16
- package/src/utils/timelineDropTrackInsert.test.ts +134 -0
- package/src/utils/timelineDropTrackInsert.ts +83 -0
- package/src/utils/typingTarget.ts +4 -0
- package/dist/assets/index-n2ml4tUn.js +0 -442
- package/dist/assets/index-pO6p0H27.css +0 -1
- package/src/components/PanelTabButton.tsx +0 -31
- package/src/components/StudioLeftSidebar.tsx +0 -204
- package/src/components/nle/TimelineResizeDivider.tsx +0 -101
- package/src/components/sidebar/LeftSidebar.storage.test.ts +0 -28
- package/src/components/sidebar/LeftSidebar.tsx +0 -334
- package/src/components/storyboard/AgentChatMessageButton.test.tsx +0 -47
- package/src/components/storyboard/AgentChatMessageButton.tsx +0 -45
- package/src/components/storyboard/FramePoster.test.tsx +0 -61
- package/src/components/storyboard/FramePoster.tsx +0 -86
- package/src/components/storyboard/StoryboardDirection.tsx +0 -45
- package/src/components/storyboard/StoryboardFrameFocus.tsx +0 -412
- package/src/components/storyboard/StoryboardFrameTile.tsx +0 -117
- package/src/components/storyboard/StoryboardGrid.tsx +0 -55
- package/src/components/storyboard/StoryboardLoaded.tsx +0 -307
- package/src/components/storyboard/StoryboardReviewGuide.tsx +0 -300
- package/src/components/storyboard/StoryboardScriptPanel.tsx +0 -26
- package/src/components/storyboard/StoryboardSourceEditor.tsx +0 -231
- package/src/components/storyboard/StoryboardStatusLegend.tsx +0 -21
- package/src/components/storyboard/StoryboardView.tsx +0 -169
- package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +0 -187
- package/src/components/storyboard/frameComments.test.ts +0 -95
- package/src/components/storyboard/frameComments.ts +0 -115
- package/src/components/storyboard/frameStatus.ts +0 -36
- package/src/components/storyboard/storyboardReviewStage.test.ts +0 -40
- package/src/components/storyboard/storyboardReviewStage.ts +0 -45
- package/src/components/storyboard/useFrameComments.ts +0 -98
- package/src/contexts/ViewModeContext.tsx +0 -142
- package/src/hooks/useInspectorSplitResize.ts +0 -51
- package/src/hooks/useProjectSignaturePoll.test.tsx +0 -135
- package/src/hooks/useProjectSignaturePoll.ts +0 -68
- package/src/hooks/useStoryboard.ts +0 -99
- package/src/utils/fitPanels.test.ts +0 -164
- package/src/utils/fitPanels.ts +0 -151
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react";
|
|
2
|
-
import { setFrameVoiceover } from "@hyperframes/core/storyboard";
|
|
3
|
-
import type { StoryboardFrameView } from "../../hooks/useStoryboard";
|
|
4
|
-
import { useFileManagerContext } from "../../contexts/FileManagerContext";
|
|
5
|
-
import { useViewMode } from "../../contexts/ViewModeContext";
|
|
6
|
-
import { Button } from "../ui/Button";
|
|
7
|
-
import { FramePoster, posterTime } from "./FramePoster";
|
|
8
|
-
import {
|
|
9
|
-
AgentChatMessageButton,
|
|
10
|
-
APPLY_STORYBOARD_FEEDBACK_MESSAGE,
|
|
11
|
-
} from "./AgentChatMessageButton";
|
|
12
|
-
import { FRAME_STATUS_META } from "./frameStatus";
|
|
13
|
-
import type { CommentsSubmitState } from "./useFrameComments";
|
|
14
|
-
import { isTypingTarget } from "../../utils/typingTarget";
|
|
15
|
-
|
|
16
|
-
export interface StoryboardFrameFocusProps {
|
|
17
|
-
projectId: string;
|
|
18
|
-
/** Path to STORYBOARD.md (edits are written here). */
|
|
19
|
-
storyboardPath: string;
|
|
20
|
-
frame: StoryboardFrameView;
|
|
21
|
-
frameCount: number;
|
|
22
|
-
onBack: () => void;
|
|
23
|
-
onNavigate: (delta: number) => void;
|
|
24
|
-
/** Re-parse the manifest after an edit is saved. */
|
|
25
|
-
onSaved: () => void;
|
|
26
|
-
/** Select a composition in the timeline (sets active comp + editing file + sidebar highlight). */
|
|
27
|
-
onSelectComposition: (path: string) => void;
|
|
28
|
-
/** Whether SCRIPT.md exists and owns final narration/TTS. */
|
|
29
|
-
scriptExists: boolean;
|
|
30
|
-
/** Shared board draft for this frame, preserved when entering/leaving focus. */
|
|
31
|
-
commentDraft: string;
|
|
32
|
-
onCommentDraftChange: (text: string) => void;
|
|
33
|
-
pendingComment: string | null;
|
|
34
|
-
pendingCommentCount: number;
|
|
35
|
-
commentDraftCount: number;
|
|
36
|
-
commentsSubmitState: CommentsSubmitState;
|
|
37
|
-
commentsSubmitError: string | null;
|
|
38
|
-
feedbackMessageCopied: boolean;
|
|
39
|
-
onFeedbackMessageCopied: () => void;
|
|
40
|
-
onSaveFeedback: () => void;
|
|
41
|
-
/** Project signature the board was loaded with (busts the poster cache). */
|
|
42
|
-
posterVersion?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Full-area focus on a single frame: large poster, editable voiceover guide,
|
|
47
|
-
* review feedback, full narrative, and a jump into the live preview. Edits
|
|
48
|
-
* are written back to STORYBOARD.md in place (markdown stays canonical).
|
|
49
|
-
*
|
|
50
|
-
* Mounted with a `key` per frame, so `draft` initializes from the frame and a
|
|
51
|
-
* save-triggered reload never clobbers in-progress typing.
|
|
52
|
-
*/
|
|
53
|
-
// fallow-ignore-next-line complexity
|
|
54
|
-
export function StoryboardFrameFocus({
|
|
55
|
-
projectId,
|
|
56
|
-
storyboardPath,
|
|
57
|
-
frame,
|
|
58
|
-
frameCount,
|
|
59
|
-
onBack,
|
|
60
|
-
onNavigate,
|
|
61
|
-
onSaved,
|
|
62
|
-
onSelectComposition,
|
|
63
|
-
scriptExists,
|
|
64
|
-
commentDraft,
|
|
65
|
-
onCommentDraftChange,
|
|
66
|
-
pendingComment,
|
|
67
|
-
pendingCommentCount,
|
|
68
|
-
commentDraftCount,
|
|
69
|
-
commentsSubmitState,
|
|
70
|
-
commentsSubmitError,
|
|
71
|
-
feedbackMessageCopied,
|
|
72
|
-
onFeedbackMessageCopied,
|
|
73
|
-
onSaveFeedback,
|
|
74
|
-
posterVersion,
|
|
75
|
-
}: StoryboardFrameFocusProps) {
|
|
76
|
-
const { readProjectFile, writeProjectFile } = useFileManagerContext();
|
|
77
|
-
const { setViewMode, registerViewModeGuard } = useViewMode();
|
|
78
|
-
const [draft, setDraft] = useState(frame.voiceover ?? "");
|
|
79
|
-
const [savedVoiceover, setSavedVoiceover] = useState(frame.voiceover ?? "");
|
|
80
|
-
const [busy, setBusy] = useState(false);
|
|
81
|
-
const [error, setError] = useState<string | null>(null);
|
|
82
|
-
|
|
83
|
-
const applyEdit = useCallback(
|
|
84
|
-
async (edit: (source: string) => string) => {
|
|
85
|
-
if (busy) return false; // one read-modify-write at a time; avoids a lost update
|
|
86
|
-
setBusy(true);
|
|
87
|
-
setError(null);
|
|
88
|
-
try {
|
|
89
|
-
const source = await readProjectFile(storyboardPath);
|
|
90
|
-
await writeProjectFile(storyboardPath, edit(source));
|
|
91
|
-
onSaved();
|
|
92
|
-
return true;
|
|
93
|
-
} catch (err: unknown) {
|
|
94
|
-
setError(err instanceof Error ? err.message : "failed to save");
|
|
95
|
-
return false;
|
|
96
|
-
} finally {
|
|
97
|
-
setBusy(false);
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
[readProjectFile, writeProjectFile, storyboardPath, onSaved, busy],
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
const title = frame.title ?? `Frame ${frame.index}`;
|
|
104
|
-
const dirty = draft !== savedVoiceover;
|
|
105
|
-
const canOpenPreview = frame.status !== "outline" && frame.srcExists && Boolean(frame.src);
|
|
106
|
-
|
|
107
|
-
const saveVoiceover = useCallback(async () => {
|
|
108
|
-
const saved = await applyEdit((src) => setFrameVoiceover(src, frame.index, draft));
|
|
109
|
-
if (saved) setSavedVoiceover(draft);
|
|
110
|
-
}, [applyEdit, frame.index, draft]);
|
|
111
|
-
|
|
112
|
-
// Closing the tab with a dirty voiceover would lose it silently — same
|
|
113
|
-
// guard the sibling markdown editor registers for the same class of loss.
|
|
114
|
-
useEffect(() => {
|
|
115
|
-
if (!dirty) return;
|
|
116
|
-
const onBeforeUnload = (e: BeforeUnloadEvent) => {
|
|
117
|
-
e.preventDefault();
|
|
118
|
-
};
|
|
119
|
-
window.addEventListener("beforeunload", onBeforeUnload);
|
|
120
|
-
return () => window.removeEventListener("beforeunload", onBeforeUnload);
|
|
121
|
-
}, [dirty]);
|
|
122
|
-
|
|
123
|
-
// Leaving the frame drops the in-memory voiceover draft; confirm while it's
|
|
124
|
-
// dirty. An in-flight save does NOT count as safe: if it fails after unmount
|
|
125
|
-
// the error lands on an unmounted component and the draft is silently lost,
|
|
126
|
-
// so keep confirming until the save actually lands (dirty clears on success).
|
|
127
|
-
const confirmLeave = useCallback(
|
|
128
|
-
() => !dirty || window.confirm("Discard unsaved voiceover changes?"),
|
|
129
|
-
[dirty],
|
|
130
|
-
);
|
|
131
|
-
useEffect(() => registerViewModeGuard(confirmLeave), [confirmLeave, registerViewModeGuard]);
|
|
132
|
-
|
|
133
|
-
const handleBack = () => {
|
|
134
|
-
if (confirmLeave()) onBack();
|
|
135
|
-
};
|
|
136
|
-
const handleNavigate = (delta: number) => {
|
|
137
|
-
if (confirmLeave()) onNavigate(delta);
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
// ←/→ navigate frames, Esc returns to the Board — but never while typing in a field.
|
|
141
|
-
useEffect(() => {
|
|
142
|
-
// fallow-ignore-next-line complexity
|
|
143
|
-
const onKey = (e: KeyboardEvent) => {
|
|
144
|
-
if (isTypingTarget(document.activeElement)) return;
|
|
145
|
-
if (e.key === "Escape") handleBack();
|
|
146
|
-
else if (e.key === "ArrowLeft" && frame.index > 1) handleNavigate(-1);
|
|
147
|
-
else if (e.key === "ArrowRight" && frame.index < frameCount) handleNavigate(1);
|
|
148
|
-
};
|
|
149
|
-
window.addEventListener("keydown", onKey);
|
|
150
|
-
return () => window.removeEventListener("keydown", onKey);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const openInPreview = () => {
|
|
154
|
-
if (!setViewMode("timeline")) return;
|
|
155
|
-
if (frame.src) onSelectComposition(frame.src);
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
return (
|
|
159
|
-
<div className="flex w-full max-w-[100vw] flex-1 min-h-0 min-w-0 flex-col overflow-hidden bg-neutral-950 text-neutral-200">
|
|
160
|
-
<div className="flex items-center gap-3 border-b border-neutral-800 px-4 py-2">
|
|
161
|
-
<button
|
|
162
|
-
type="button"
|
|
163
|
-
onClick={handleBack}
|
|
164
|
-
className="rounded px-2 py-1 text-xs font-medium text-neutral-300 hover:bg-neutral-800"
|
|
165
|
-
>
|
|
166
|
-
← Board
|
|
167
|
-
</button>
|
|
168
|
-
<span className="min-w-0 flex-1 truncate text-sm font-medium text-neutral-200">
|
|
169
|
-
Frame {frame.number ?? frame.index} — {title}
|
|
170
|
-
</span>
|
|
171
|
-
<div className="flex shrink-0 items-center gap-1">
|
|
172
|
-
<NavButton
|
|
173
|
-
label="‹ Prev"
|
|
174
|
-
disabled={frame.index <= 1}
|
|
175
|
-
onClick={() => handleNavigate(-1)}
|
|
176
|
-
/>
|
|
177
|
-
<NavButton
|
|
178
|
-
label="Next ›"
|
|
179
|
-
disabled={frame.index >= frameCount}
|
|
180
|
-
onClick={() => handleNavigate(1)}
|
|
181
|
-
/>
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
|
|
185
|
-
{(commentDraftCount > 0 || pendingCommentCount > 0) && (
|
|
186
|
-
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-neutral-800 bg-neutral-900/80 px-4 py-2">
|
|
187
|
-
<div>
|
|
188
|
-
<div className="text-xs font-medium text-neutral-200">
|
|
189
|
-
{commentDraftCount > 0 ? "Feedback ready to save" : "Feedback saved"}
|
|
190
|
-
</div>
|
|
191
|
-
<div className="text-[11px] text-neutral-500">
|
|
192
|
-
{commentDraftCount > 0
|
|
193
|
-
? "Save this batch and copy the message for your agent."
|
|
194
|
-
: feedbackMessageCopied
|
|
195
|
-
? "Message copied — paste it in your terminal or IDE agent chat."
|
|
196
|
-
: "The agent has not been notified yet."}
|
|
197
|
-
</div>
|
|
198
|
-
</div>
|
|
199
|
-
{commentDraftCount > 0 ? (
|
|
200
|
-
<Button
|
|
201
|
-
size="sm"
|
|
202
|
-
variant="primary"
|
|
203
|
-
onClick={onSaveFeedback}
|
|
204
|
-
loading={commentsSubmitState === "saving"}
|
|
205
|
-
>
|
|
206
|
-
Save & copy message ({commentDraftCount})
|
|
207
|
-
</Button>
|
|
208
|
-
) : (
|
|
209
|
-
<AgentChatMessageButton
|
|
210
|
-
message={APPLY_STORYBOARD_FEEDBACK_MESSAGE}
|
|
211
|
-
label={feedbackMessageCopied ? "Copy again" : "Copy prompt for agent"}
|
|
212
|
-
onCopied={onFeedbackMessageCopied}
|
|
213
|
-
/>
|
|
214
|
-
)}
|
|
215
|
-
</div>
|
|
216
|
-
)}
|
|
217
|
-
|
|
218
|
-
<div className="flex flex-1 min-h-0 flex-col overflow-auto lg:flex-row lg:overflow-hidden">
|
|
219
|
-
<div className="flex w-full shrink-0 items-center justify-center bg-neutral-900/40 p-4 sm:p-8 lg:h-full lg:w-3/5">
|
|
220
|
-
<div className="aspect-video w-full max-w-[900px] overflow-hidden rounded-lg border border-neutral-800 bg-neutral-900">
|
|
221
|
-
{canOpenPreview && frame.src ? (
|
|
222
|
-
<FramePoster
|
|
223
|
-
projectId={projectId}
|
|
224
|
-
src={frame.src}
|
|
225
|
-
seconds={posterTime(frame)}
|
|
226
|
-
title={title}
|
|
227
|
-
surface="hero"
|
|
228
|
-
posterVersion={posterVersion}
|
|
229
|
-
/>
|
|
230
|
-
) : (
|
|
231
|
-
<FramePlan frame={frame} />
|
|
232
|
-
)}
|
|
233
|
-
</div>
|
|
234
|
-
</div>
|
|
235
|
-
|
|
236
|
-
<div className="w-full shrink-0 space-y-6 border-t border-neutral-800 px-4 py-5 sm:px-6 lg:h-full lg:w-2/5 lg:overflow-auto lg:border-t-0 lg:border-l">
|
|
237
|
-
<ReadOnlyStatus status={frame.status} />
|
|
238
|
-
|
|
239
|
-
<div className="flex flex-wrap gap-x-6 gap-y-1 text-[11px] text-neutral-500">
|
|
240
|
-
{frame.duration && <span>Duration {frame.duration}</span>}
|
|
241
|
-
{frame.transitionIn && <span>Transition {frame.transitionIn}</span>}
|
|
242
|
-
</div>
|
|
243
|
-
|
|
244
|
-
<section>
|
|
245
|
-
<div className="mb-1 flex items-center justify-between">
|
|
246
|
-
<h3
|
|
247
|
-
className="text-xs font-semibold uppercase tracking-wider text-neutral-400"
|
|
248
|
-
title="Storyboard voiceover is a guide; SCRIPT.md is the final TTS source."
|
|
249
|
-
>
|
|
250
|
-
🎙 Voiceover <span className="font-normal normal-case text-neutral-600">guide</span>
|
|
251
|
-
</h3>
|
|
252
|
-
<Button
|
|
253
|
-
size="sm"
|
|
254
|
-
variant="primary"
|
|
255
|
-
onClick={saveVoiceover}
|
|
256
|
-
disabled={!dirty}
|
|
257
|
-
loading={busy}
|
|
258
|
-
>
|
|
259
|
-
{busy ? "Saving…" : "Save voiceover"}
|
|
260
|
-
</Button>
|
|
261
|
-
</div>
|
|
262
|
-
<textarea
|
|
263
|
-
value={draft}
|
|
264
|
-
onChange={(e) => setDraft(e.target.value)}
|
|
265
|
-
rows={3}
|
|
266
|
-
placeholder="What the narrator says over this frame…"
|
|
267
|
-
className="w-full resize-y rounded border border-neutral-800 bg-neutral-900 p-2 text-sm text-neutral-200 outline-none focus:border-neutral-600"
|
|
268
|
-
/>
|
|
269
|
-
<p className="mt-1 text-[11px] text-neutral-600">
|
|
270
|
-
{dirty
|
|
271
|
-
? "Unsaved changes"
|
|
272
|
-
: scriptExists
|
|
273
|
-
? "Saved. SCRIPT.md drives final TTS."
|
|
274
|
-
: "Saved to STORYBOARD.md. This voiceover guides narration for the frame."}
|
|
275
|
-
</p>
|
|
276
|
-
{error && <p className="mt-1 text-[11px] text-red-400">{error}</p>}
|
|
277
|
-
</section>
|
|
278
|
-
|
|
279
|
-
<section>
|
|
280
|
-
<div className="mb-1">
|
|
281
|
-
<h3 className="text-xs font-semibold uppercase tracking-wider text-neutral-400">
|
|
282
|
-
Frame feedback
|
|
283
|
-
</h3>
|
|
284
|
-
</div>
|
|
285
|
-
<textarea
|
|
286
|
-
value={commentDraft}
|
|
287
|
-
onChange={(e) => onCommentDraftChange(e.target.value)}
|
|
288
|
-
rows={3}
|
|
289
|
-
placeholder="Tell your agent what to change in this frame…"
|
|
290
|
-
aria-label={`Comment on ${title}`}
|
|
291
|
-
className="w-full resize-y rounded border border-neutral-800 bg-neutral-900 p-2 text-sm text-neutral-200 placeholder:text-neutral-600 outline-none focus:border-sky-700"
|
|
292
|
-
/>
|
|
293
|
-
{pendingCommentCount > 0 ? (
|
|
294
|
-
<div className="mt-2 flex flex-wrap items-center justify-between gap-2 rounded-md border border-sky-900/70 bg-sky-950/20 px-2.5 py-2">
|
|
295
|
-
<p className="text-[11px] text-sky-200">
|
|
296
|
-
Paste the agent prompt in your terminal or IDE chat.
|
|
297
|
-
</p>
|
|
298
|
-
<AgentChatMessageButton
|
|
299
|
-
message={APPLY_STORYBOARD_FEEDBACK_MESSAGE}
|
|
300
|
-
onCopied={onFeedbackMessageCopied}
|
|
301
|
-
/>
|
|
302
|
-
</div>
|
|
303
|
-
) : (
|
|
304
|
-
<p className="mt-1 text-[11px] text-neutral-600">
|
|
305
|
-
{commentDraftCount > 0
|
|
306
|
-
? "Your change is ready. Save it using the review bar above."
|
|
307
|
-
: "Add a change to prepare feedback for the agent."}
|
|
308
|
-
</p>
|
|
309
|
-
)}
|
|
310
|
-
{pendingComment && (
|
|
311
|
-
<p className="mt-1 text-[11px] text-sky-400/90">
|
|
312
|
-
<span className="font-medium">Pending:</span> “{pendingComment}”
|
|
313
|
-
</p>
|
|
314
|
-
)}
|
|
315
|
-
{commentsSubmitError && (
|
|
316
|
-
<p className="mt-1 text-[11px] text-red-400">
|
|
317
|
-
Couldn’t submit: {commentsSubmitError}
|
|
318
|
-
</p>
|
|
319
|
-
)}
|
|
320
|
-
</section>
|
|
321
|
-
|
|
322
|
-
{frame.narrative && (
|
|
323
|
-
<section>
|
|
324
|
-
<h3 className="mb-1 text-xs font-semibold uppercase tracking-wider text-neutral-400">
|
|
325
|
-
Narrative
|
|
326
|
-
</h3>
|
|
327
|
-
<p className="whitespace-pre-wrap text-sm text-neutral-300">{frame.narrative}</p>
|
|
328
|
-
</section>
|
|
329
|
-
)}
|
|
330
|
-
|
|
331
|
-
{canOpenPreview ? (
|
|
332
|
-
<Button size="sm" variant="secondary" onClick={openInPreview}>
|
|
333
|
-
Open in Preview →
|
|
334
|
-
</Button>
|
|
335
|
-
) : (
|
|
336
|
-
<div className="rounded-md border border-neutral-800 bg-neutral-900/60 px-3 py-2 text-xs text-neutral-500">
|
|
337
|
-
Preview becomes available after your agent builds this frame.
|
|
338
|
-
</div>
|
|
339
|
-
)}
|
|
340
|
-
</div>
|
|
341
|
-
</div>
|
|
342
|
-
</div>
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function NavButton({
|
|
347
|
-
label,
|
|
348
|
-
disabled,
|
|
349
|
-
onClick,
|
|
350
|
-
}: {
|
|
351
|
-
label: string;
|
|
352
|
-
disabled: boolean;
|
|
353
|
-
onClick: () => void;
|
|
354
|
-
}) {
|
|
355
|
-
return (
|
|
356
|
-
<button
|
|
357
|
-
type="button"
|
|
358
|
-
onClick={onClick}
|
|
359
|
-
disabled={disabled}
|
|
360
|
-
className="rounded px-2 py-1 text-xs font-medium text-neutral-300 hover:bg-neutral-800 enabled:active:scale-[0.98] transition-transform disabled:opacity-30"
|
|
361
|
-
>
|
|
362
|
-
{label}
|
|
363
|
-
</button>
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function ReadOnlyStatus({ status }: { status: StoryboardFrameView["status"] }) {
|
|
368
|
-
const meta = FRAME_STATUS_META[status];
|
|
369
|
-
return (
|
|
370
|
-
<div className="flex items-center gap-2">
|
|
371
|
-
<span className="text-xs font-semibold uppercase tracking-wider text-neutral-500">
|
|
372
|
-
Status
|
|
373
|
-
</span>
|
|
374
|
-
<span className={`rounded px-2 py-1 text-xs font-medium ${meta.chipClass}`}>
|
|
375
|
-
{meta.label}
|
|
376
|
-
</span>
|
|
377
|
-
<span className="text-[11px] text-neutral-600">Updated by your agent</span>
|
|
378
|
-
</div>
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// The empty state selects copy from frame status and whichever storyboard fields are available.
|
|
383
|
-
// fallow-ignore-next-line complexity
|
|
384
|
-
function FramePlan({ frame }: { frame: StoryboardFrameView }) {
|
|
385
|
-
const title = frame.title ?? `Frame ${frame.index}`;
|
|
386
|
-
const isOutline = frame.status === "outline";
|
|
387
|
-
return (
|
|
388
|
-
<div className="flex h-full w-full items-center justify-center bg-[radial-gradient(circle_at_center,_rgba(38,38,38,0.8),_rgba(10,10,10,1))] p-10">
|
|
389
|
-
<div className="max-w-xl text-center">
|
|
390
|
-
<span className="rounded-full border border-neutral-700 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-wider text-neutral-400">
|
|
391
|
-
{isOutline ? "Planned frame" : "Preview unavailable"}
|
|
392
|
-
</span>
|
|
393
|
-
<h2 className="mt-4 text-2xl font-semibold text-neutral-100">{title}</h2>
|
|
394
|
-
{frame.scene && (
|
|
395
|
-
<p className="mt-3 text-base leading-relaxed text-neutral-300">{frame.scene}</p>
|
|
396
|
-
)}
|
|
397
|
-
{!frame.scene && frame.narrative && (
|
|
398
|
-
<p className="mt-3 line-clamp-4 text-sm leading-relaxed text-neutral-400">
|
|
399
|
-
{frame.narrative}
|
|
400
|
-
</p>
|
|
401
|
-
)}
|
|
402
|
-
<p className="mt-5 text-xs text-neutral-600">
|
|
403
|
-
{isOutline
|
|
404
|
-
? "A visual preview will appear when your agent builds the sketch."
|
|
405
|
-
: frame.src
|
|
406
|
-
? `Frame file not found: ${frame.src}`
|
|
407
|
-
: "This frame does not link to a source file."}
|
|
408
|
-
</p>
|
|
409
|
-
</div>
|
|
410
|
-
</div>
|
|
411
|
-
);
|
|
412
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import type { StoryboardFrameView } from "../../hooks/useStoryboard";
|
|
2
|
-
import { FramePoster, posterTime } from "./FramePoster";
|
|
3
|
-
import { FRAME_STATUS_META } from "./frameStatus";
|
|
4
|
-
|
|
5
|
-
export interface StoryboardFrameTileProps {
|
|
6
|
-
projectId: string;
|
|
7
|
-
frame: StoryboardFrameView;
|
|
8
|
-
/** Open this frame in the full-area focus view. */
|
|
9
|
-
onOpen: (index: number) => void;
|
|
10
|
-
/** This frame's pending comment draft ("" when none). */
|
|
11
|
-
commentDraft: string;
|
|
12
|
-
onCommentDraftChange: (index: number, text: string) => void;
|
|
13
|
-
/** A submitted comment the agent has not consumed yet (null when none). */
|
|
14
|
-
pendingComment: string | null;
|
|
15
|
-
/** Project signature the board was loaded with (busts the poster cache). */
|
|
16
|
-
posterVersion?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function firstLine(text: string): string {
|
|
20
|
-
return (
|
|
21
|
-
text
|
|
22
|
-
.split("\n")
|
|
23
|
-
.find((line) => line.trim().length > 0)
|
|
24
|
-
?.trim() ?? ""
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function placeholderMessage(frame: StoryboardFrameView): string {
|
|
29
|
-
if (frame.status === "outline") return "Not built yet";
|
|
30
|
-
if (frame.src && !frame.srcExists) return "Frame file not found";
|
|
31
|
-
return "No preview";
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** A single contact-sheet tile: poster preview + its metadata. Click to focus. */
|
|
35
|
-
// fallow-ignore-next-line complexity
|
|
36
|
-
export function StoryboardFrameTile({
|
|
37
|
-
projectId,
|
|
38
|
-
frame,
|
|
39
|
-
onOpen,
|
|
40
|
-
commentDraft,
|
|
41
|
-
onCommentDraftChange,
|
|
42
|
-
pendingComment,
|
|
43
|
-
posterVersion,
|
|
44
|
-
}: StoryboardFrameTileProps) {
|
|
45
|
-
const meta = FRAME_STATUS_META[frame.status];
|
|
46
|
-
const renderable = frame.srcExists && frame.status !== "outline";
|
|
47
|
-
const title = frame.title ?? `Frame ${frame.index}`;
|
|
48
|
-
const sceneLine = frame.scene ?? firstLine(frame.narrative);
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<article className="min-w-0">
|
|
52
|
-
<button
|
|
53
|
-
type="button"
|
|
54
|
-
onClick={() => onOpen(frame.index)}
|
|
55
|
-
className="group relative block aspect-video w-full overflow-hidden rounded-lg border border-neutral-800 bg-neutral-900 text-left transition-colors hover:border-neutral-600"
|
|
56
|
-
>
|
|
57
|
-
<div className="absolute left-2 top-2 z-10 flex h-6 min-w-6 items-center justify-center rounded-full bg-black/70 px-1.5 text-xs font-semibold text-neutral-100">
|
|
58
|
-
{frame.number ?? frame.index}
|
|
59
|
-
</div>
|
|
60
|
-
{renderable && frame.src ? (
|
|
61
|
-
<FramePoster
|
|
62
|
-
projectId={projectId}
|
|
63
|
-
src={frame.src}
|
|
64
|
-
seconds={posterTime(frame)}
|
|
65
|
-
title={title}
|
|
66
|
-
posterVersion={posterVersion}
|
|
67
|
-
/>
|
|
68
|
-
) : (
|
|
69
|
-
<FrameTilePlaceholder frame={frame} />
|
|
70
|
-
)}
|
|
71
|
-
</button>
|
|
72
|
-
|
|
73
|
-
<div className="mt-2 flex items-start justify-between gap-2">
|
|
74
|
-
<h3 className="truncate text-sm font-medium text-neutral-200">{title}</h3>
|
|
75
|
-
<span
|
|
76
|
-
title={meta.tooltip}
|
|
77
|
-
aria-label={`Status: ${meta.label} — ${meta.tooltip}`}
|
|
78
|
-
className={`shrink-0 cursor-default rounded px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide ${meta.chipClass}`}
|
|
79
|
-
>
|
|
80
|
-
{meta.label}
|
|
81
|
-
</span>
|
|
82
|
-
</div>
|
|
83
|
-
{sceneLine && <p className="mt-0.5 line-clamp-2 text-xs text-neutral-400">{sceneLine}</p>}
|
|
84
|
-
{frame.voiceover && (
|
|
85
|
-
<p className="mt-1 line-clamp-2 text-xs italic text-neutral-500">
|
|
86
|
-
<span aria-hidden="true">🎙 </span>“{frame.voiceover}”
|
|
87
|
-
</p>
|
|
88
|
-
)}
|
|
89
|
-
<div className="mt-1 flex gap-3 text-[11px] text-neutral-600">
|
|
90
|
-
{frame.duration && <span>{frame.duration}</span>}
|
|
91
|
-
{frame.transitionIn && <span>↘ {frame.transitionIn}</span>}
|
|
92
|
-
</div>
|
|
93
|
-
<textarea
|
|
94
|
-
value={commentDraft}
|
|
95
|
-
onChange={(e) => onCommentDraftChange(frame.index, e.target.value)}
|
|
96
|
-
rows={2}
|
|
97
|
-
placeholder="Comment on this frame…"
|
|
98
|
-
aria-label={`Comment on ${title}`}
|
|
99
|
-
className="mt-2 w-full resize-none rounded-md border border-neutral-800 bg-neutral-900/60 px-2 py-1.5 text-xs text-neutral-200 placeholder:text-neutral-600 focus:border-sky-700 focus:outline-none"
|
|
100
|
-
/>
|
|
101
|
-
{pendingComment && (
|
|
102
|
-
<p className="mt-1 text-[11px] text-sky-400/90">
|
|
103
|
-
<span className="font-medium">Pending:</span> “{pendingComment}”
|
|
104
|
-
</p>
|
|
105
|
-
)}
|
|
106
|
-
</article>
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function FrameTilePlaceholder({ frame }: { frame: StoryboardFrameView }) {
|
|
111
|
-
return (
|
|
112
|
-
<div className="flex h-full w-full flex-col items-center justify-center gap-1 border border-dashed border-neutral-700 bg-neutral-950 text-center">
|
|
113
|
-
<span className="text-xs font-medium text-neutral-400">{frame.title ?? "Outline"}</span>
|
|
114
|
-
<span className="text-[11px] text-neutral-600">{placeholderMessage(frame)}</span>
|
|
115
|
-
</div>
|
|
116
|
-
);
|
|
117
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import type { StoryboardFrameView } from "../../hooks/useStoryboard";
|
|
2
|
-
import type { FrameCommentEntry } from "./frameComments";
|
|
3
|
-
import { StoryboardFrameTile } from "./StoryboardFrameTile";
|
|
4
|
-
|
|
5
|
-
export interface StoryboardGridProps {
|
|
6
|
-
projectId: string;
|
|
7
|
-
frames: StoryboardFrameView[];
|
|
8
|
-
/** Open a frame in the full-area focus view. */
|
|
9
|
-
onOpenFrame: (index: number) => void;
|
|
10
|
-
/** Per-frame comment drafts, keyed by frame index. */
|
|
11
|
-
commentDrafts: Record<number, string>;
|
|
12
|
-
onCommentDraftChange: (index: number, text: string) => void;
|
|
13
|
-
/** Submitted comments the agent has not consumed yet. */
|
|
14
|
-
pendingComments: FrameCommentEntry[] | null;
|
|
15
|
-
/** Project signature the board was loaded with (busts poster caches). */
|
|
16
|
-
posterVersion?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** The contact sheet: ordered frame tiles in a responsive grid. */
|
|
20
|
-
export function StoryboardGrid({
|
|
21
|
-
projectId,
|
|
22
|
-
frames,
|
|
23
|
-
onOpenFrame,
|
|
24
|
-
commentDrafts,
|
|
25
|
-
onCommentDraftChange,
|
|
26
|
-
pendingComments,
|
|
27
|
-
posterVersion,
|
|
28
|
-
}: StoryboardGridProps) {
|
|
29
|
-
if (frames.length === 0) {
|
|
30
|
-
return (
|
|
31
|
-
<div className="mt-8 rounded-lg border border-dashed border-neutral-800 px-6 py-12 text-center text-sm text-neutral-500">
|
|
32
|
-
This storyboard has no frames yet.
|
|
33
|
-
</div>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<div className="mt-8 grid gap-x-6 gap-y-8 [grid-template-columns:repeat(auto-fill,minmax(300px,1fr))]">
|
|
39
|
-
{frames.map((frame) => (
|
|
40
|
-
<StoryboardFrameTile
|
|
41
|
-
key={frame.index}
|
|
42
|
-
projectId={projectId}
|
|
43
|
-
frame={frame}
|
|
44
|
-
onOpen={onOpenFrame}
|
|
45
|
-
commentDraft={commentDrafts[frame.index] ?? ""}
|
|
46
|
-
onCommentDraftChange={onCommentDraftChange}
|
|
47
|
-
pendingComment={
|
|
48
|
-
pendingComments?.find((entry) => entry.frame === frame.index)?.text ?? null
|
|
49
|
-
}
|
|
50
|
-
posterVersion={posterVersion}
|
|
51
|
-
/>
|
|
52
|
-
))}
|
|
53
|
-
</div>
|
|
54
|
-
);
|
|
55
|
-
}
|