@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,334 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
memo,
|
|
3
|
-
useState,
|
|
4
|
-
useCallback,
|
|
5
|
-
useImperativeHandle,
|
|
6
|
-
useMemo,
|
|
7
|
-
useRef,
|
|
8
|
-
forwardRef,
|
|
9
|
-
type ReactNode,
|
|
10
|
-
} from "react";
|
|
11
|
-
import { CompositionsTab } from "./CompositionsTab";
|
|
12
|
-
import { AssetsTab } from "./AssetsTab";
|
|
13
|
-
import { trackStudioEvent } from "../../utils/studioTelemetry";
|
|
14
|
-
import { safeLocalStorage } from "../../utils/safeStorage";
|
|
15
|
-
import { resolveMasterCompositionPath } from "../../utils/studioUrlState";
|
|
16
|
-
import { BlocksTab, type BlockPreviewInfo } from "./BlocksTab";
|
|
17
|
-
import { FileTree } from "../editor/FileTree";
|
|
18
|
-
import { Tooltip } from "../ui";
|
|
19
|
-
|
|
20
|
-
export type SidebarTab = "compositions" | "assets" | "code" | "blocks";
|
|
21
|
-
|
|
22
|
-
export interface LeftSidebarHandle {
|
|
23
|
-
selectTab: (tab: SidebarTab) => void;
|
|
24
|
-
getTab: () => SidebarTab;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const STORAGE_KEY = "hf-studio-sidebar-tab";
|
|
28
|
-
|
|
29
|
-
const SIDEBAR_TABS: Array<{ id: SidebarTab; label: string; tooltip: string }> = [
|
|
30
|
-
{ id: "code", label: "Code", tooltip: "Source code editor" },
|
|
31
|
-
{ id: "compositions", label: "Comps", tooltip: "Compositions and sub-compositions" },
|
|
32
|
-
{ id: "assets", label: "Assets", tooltip: "Videos, images, audio, fonts" },
|
|
33
|
-
{ id: "blocks", label: "Catalog", tooltip: "Browse blocks and components" },
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
// Both the `localStorage` reference and `getItem` itself can throw when the
|
|
37
|
-
// browsing context is partitioned or site data is blocked — the same case
|
|
38
|
-
// telemetry/config.ts documents. This runs as a `useState` initializer, so an
|
|
39
|
-
// unguarded throw here takes the whole editor to the crash boundary rather
|
|
40
|
-
// than losing one remembered tab.
|
|
41
|
-
export function getPersistedTab(): SidebarTab {
|
|
42
|
-
let stored: string | null = null;
|
|
43
|
-
try {
|
|
44
|
-
stored = safeLocalStorage()?.getItem(STORAGE_KEY) ?? null;
|
|
45
|
-
} catch {
|
|
46
|
-
/* storage unavailable — fall back to the default tab */
|
|
47
|
-
}
|
|
48
|
-
if (stored === "assets") return "assets";
|
|
49
|
-
if (stored === "code") return "code";
|
|
50
|
-
if (stored === "blocks") return "blocks";
|
|
51
|
-
return "compositions";
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface LeftSidebarProps {
|
|
55
|
-
width?: number;
|
|
56
|
-
projectId: string;
|
|
57
|
-
compositions: string[];
|
|
58
|
-
assets: string[];
|
|
59
|
-
activeComposition: string | null;
|
|
60
|
-
onSelectComposition: (comp: string) => void;
|
|
61
|
-
onImportFiles?: (files: FileList, dir?: string) => void | Promise<void>;
|
|
62
|
-
fileTree?: string[];
|
|
63
|
-
editingFile?: { path: string; content: string | null } | null;
|
|
64
|
-
onSelectFile?: (path: string) => void;
|
|
65
|
-
onCreateFile?: (path: string) => void;
|
|
66
|
-
onCreateFolder?: (path: string) => void;
|
|
67
|
-
onDeleteFile?: (path: string) => void;
|
|
68
|
-
onRenameFile?: (oldPath: string, newPath: string) => void;
|
|
69
|
-
onDuplicateFile?: (path: string) => void;
|
|
70
|
-
onMoveFile?: (oldPath: string, newPath: string) => void;
|
|
71
|
-
codeChildren?: ReactNode;
|
|
72
|
-
onRenderComposition?: (comp: string) => void;
|
|
73
|
-
isRendering?: boolean;
|
|
74
|
-
onLint?: () => void;
|
|
75
|
-
linting?: boolean;
|
|
76
|
-
lintFindingCount?: number;
|
|
77
|
-
lintFindingsByFile?: Map<string, { count: number; messages: string[] }>;
|
|
78
|
-
onToggleCollapse?: () => void;
|
|
79
|
-
onAddBlock?: (blockName: string) => void | Promise<void>;
|
|
80
|
-
onPreviewBlock?: (preview: BlockPreviewInfo | null) => void;
|
|
81
|
-
takeoverContent?: ReactNode;
|
|
82
|
-
onAddAssetToTimeline?: (path: string) => void;
|
|
83
|
-
onAddCompositionToTimeline?: (path: string) => void;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export const LeftSidebar = memo(
|
|
87
|
-
forwardRef<LeftSidebarHandle, LeftSidebarProps>(function LeftSidebar(
|
|
88
|
-
{
|
|
89
|
-
width = 240,
|
|
90
|
-
projectId,
|
|
91
|
-
compositions,
|
|
92
|
-
assets,
|
|
93
|
-
activeComposition,
|
|
94
|
-
onSelectComposition,
|
|
95
|
-
onImportFiles,
|
|
96
|
-
fileTree: fileProp,
|
|
97
|
-
editingFile,
|
|
98
|
-
onSelectFile,
|
|
99
|
-
onCreateFile,
|
|
100
|
-
onCreateFolder,
|
|
101
|
-
onDeleteFile,
|
|
102
|
-
onRenameFile,
|
|
103
|
-
onDuplicateFile,
|
|
104
|
-
onMoveFile,
|
|
105
|
-
codeChildren,
|
|
106
|
-
onRenderComposition,
|
|
107
|
-
isRendering,
|
|
108
|
-
onLint,
|
|
109
|
-
linting,
|
|
110
|
-
lintFindingCount,
|
|
111
|
-
lintFindingsByFile,
|
|
112
|
-
onToggleCollapse,
|
|
113
|
-
onAddBlock,
|
|
114
|
-
onPreviewBlock,
|
|
115
|
-
takeoverContent,
|
|
116
|
-
onAddAssetToTimeline,
|
|
117
|
-
onAddCompositionToTimeline,
|
|
118
|
-
},
|
|
119
|
-
ref,
|
|
120
|
-
) {
|
|
121
|
-
const [tab, setTab] = useState<SidebarTab>(getPersistedTab);
|
|
122
|
-
const tabRef = useRef(tab);
|
|
123
|
-
tabRef.current = tab;
|
|
124
|
-
const tablistRef = useRef<HTMLDivElement>(null);
|
|
125
|
-
const masterCompositionPath = useMemo(
|
|
126
|
-
() => resolveMasterCompositionPath(compositions),
|
|
127
|
-
[compositions],
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
const selectTab = useCallback((t: SidebarTab) => {
|
|
131
|
-
setTab(t);
|
|
132
|
-
try {
|
|
133
|
-
safeLocalStorage()?.setItem(STORAGE_KEY, t);
|
|
134
|
-
} catch {
|
|
135
|
-
/* storage unavailable — the tab just won't be remembered */
|
|
136
|
-
}
|
|
137
|
-
trackStudioEvent("tab_switch", { panel: "left_sidebar", tab: t });
|
|
138
|
-
}, []);
|
|
139
|
-
|
|
140
|
-
const getTab = useCallback(() => tabRef.current, []);
|
|
141
|
-
|
|
142
|
-
useImperativeHandle(ref, () => ({ selectTab, getTab }), [selectTab, getTab]);
|
|
143
|
-
|
|
144
|
-
// APG tabs pattern: Left/Right move focus AND selection between tabs.
|
|
145
|
-
const handleTablistKeyDown = useCallback(
|
|
146
|
-
(e: React.KeyboardEvent) => {
|
|
147
|
-
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
|
|
148
|
-
e.preventDefault();
|
|
149
|
-
const ids = SIDEBAR_TABS.map((t) => t.id);
|
|
150
|
-
const idx = ids.indexOf(tabRef.current);
|
|
151
|
-
const next = ids[(idx + (e.key === "ArrowRight" ? 1 : -1) + ids.length) % ids.length];
|
|
152
|
-
selectTab(next);
|
|
153
|
-
tablistRef.current?.querySelector<HTMLButtonElement>(`[data-tab-id="${next}"]`)?.focus();
|
|
154
|
-
},
|
|
155
|
-
[selectTab],
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
return (
|
|
159
|
-
<div
|
|
160
|
-
className="flex flex-col h-full overflow-hidden rounded-lg border border-neutral-800/50 bg-neutral-950"
|
|
161
|
-
style={{ width }}
|
|
162
|
-
>
|
|
163
|
-
{takeoverContent ? (
|
|
164
|
-
<div className="flex min-h-0 flex-1">{takeoverContent}</div>
|
|
165
|
-
) : (
|
|
166
|
-
<>
|
|
167
|
-
{/* Tabs — Code first */}
|
|
168
|
-
<div className="border-b border-neutral-800/50 px-3 py-3 flex-shrink-0">
|
|
169
|
-
<div className="flex items-center gap-2">
|
|
170
|
-
<div
|
|
171
|
-
ref={tablistRef}
|
|
172
|
-
role="tablist"
|
|
173
|
-
aria-label="Sidebar panels"
|
|
174
|
-
onKeyDown={handleTablistKeyDown}
|
|
175
|
-
className="grid min-w-0 flex-1 gap-0.5 rounded-[18px] bg-neutral-900 p-1 shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]"
|
|
176
|
-
style={{ gridTemplateColumns: "1fr 1fr 1fr 1fr" }}
|
|
177
|
-
>
|
|
178
|
-
{SIDEBAR_TABS.map((t) => (
|
|
179
|
-
<Tooltip key={t.id} label={t.tooltip} side="bottom">
|
|
180
|
-
<button
|
|
181
|
-
type="button"
|
|
182
|
-
role="tab"
|
|
183
|
-
data-tab-id={t.id}
|
|
184
|
-
aria-selected={tab === t.id}
|
|
185
|
-
aria-controls={`sidebar-panel-${t.id}`}
|
|
186
|
-
tabIndex={tab === t.id ? 0 : -1}
|
|
187
|
-
onClick={() => selectTab(t.id)}
|
|
188
|
-
className={`rounded-[14px] px-1.5 py-2 text-[10px] font-semibold truncate transition-all active:scale-[0.97] ${
|
|
189
|
-
tab === t.id
|
|
190
|
-
? "bg-neutral-800 text-white"
|
|
191
|
-
: "text-neutral-500 hover:text-neutral-200"
|
|
192
|
-
}`}
|
|
193
|
-
>
|
|
194
|
-
{t.label}
|
|
195
|
-
</button>
|
|
196
|
-
</Tooltip>
|
|
197
|
-
))}
|
|
198
|
-
</div>
|
|
199
|
-
{onToggleCollapse && (
|
|
200
|
-
<button
|
|
201
|
-
type="button"
|
|
202
|
-
onClick={onToggleCollapse}
|
|
203
|
-
className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md border border-transparent text-neutral-500 transition-colors hover:border-neutral-800 hover:bg-neutral-900 hover:text-neutral-300"
|
|
204
|
-
title="Hide sidebar"
|
|
205
|
-
aria-label="Hide sidebar"
|
|
206
|
-
>
|
|
207
|
-
<svg
|
|
208
|
-
width="14"
|
|
209
|
-
height="14"
|
|
210
|
-
viewBox="0 0 24 24"
|
|
211
|
-
fill="none"
|
|
212
|
-
stroke="currentColor"
|
|
213
|
-
strokeWidth="1.5"
|
|
214
|
-
strokeLinecap="round"
|
|
215
|
-
strokeLinejoin="round"
|
|
216
|
-
aria-hidden="true"
|
|
217
|
-
>
|
|
218
|
-
<path d="m14 7-5 5 5 5" />
|
|
219
|
-
<path d="M19 4v16" />
|
|
220
|
-
</svg>
|
|
221
|
-
</button>
|
|
222
|
-
)}
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
|
|
226
|
-
{/* Tab content */}
|
|
227
|
-
{tab === "compositions" && (
|
|
228
|
-
<div
|
|
229
|
-
id="sidebar-panel-compositions"
|
|
230
|
-
role="tabpanel"
|
|
231
|
-
className="flex flex-col flex-1 min-h-0"
|
|
232
|
-
>
|
|
233
|
-
<CompositionsTab
|
|
234
|
-
projectId={projectId}
|
|
235
|
-
compositions={compositions}
|
|
236
|
-
activeComposition={activeComposition}
|
|
237
|
-
masterCompositionPath={masterCompositionPath}
|
|
238
|
-
onSelect={onSelectComposition}
|
|
239
|
-
onAddToTimeline={onAddCompositionToTimeline}
|
|
240
|
-
onRenderComposition={onRenderComposition}
|
|
241
|
-
isRendering={isRendering}
|
|
242
|
-
lintFindingsByFile={lintFindingsByFile}
|
|
243
|
-
/>
|
|
244
|
-
</div>
|
|
245
|
-
)}
|
|
246
|
-
{tab === "assets" && (
|
|
247
|
-
<div
|
|
248
|
-
id="sidebar-panel-assets"
|
|
249
|
-
role="tabpanel"
|
|
250
|
-
className="flex flex-col flex-1 min-h-0"
|
|
251
|
-
>
|
|
252
|
-
<AssetsTab
|
|
253
|
-
projectId={projectId}
|
|
254
|
-
assets={assets}
|
|
255
|
-
onImport={onImportFiles}
|
|
256
|
-
onDelete={onDeleteFile}
|
|
257
|
-
onRename={onRenameFile}
|
|
258
|
-
onAddAssetToTimeline={onAddAssetToTimeline}
|
|
259
|
-
/>
|
|
260
|
-
</div>
|
|
261
|
-
)}
|
|
262
|
-
{tab === "code" && (
|
|
263
|
-
<div id="sidebar-panel-code" role="tabpanel" className="flex flex-1 min-h-0">
|
|
264
|
-
{(fileProp?.length ?? 0) > 0 && (
|
|
265
|
-
<div className="w-[160px] flex-shrink-0 border-r border-neutral-800 overflow-y-auto">
|
|
266
|
-
<FileTree
|
|
267
|
-
files={fileProp ?? []}
|
|
268
|
-
activeFile={editingFile?.path ?? null}
|
|
269
|
-
onSelectFile={onSelectFile ?? (() => {})}
|
|
270
|
-
onCreateFile={onCreateFile}
|
|
271
|
-
onCreateFolder={onCreateFolder}
|
|
272
|
-
onDeleteFile={onDeleteFile}
|
|
273
|
-
onRenameFile={onRenameFile}
|
|
274
|
-
onDuplicateFile={onDuplicateFile}
|
|
275
|
-
onMoveFile={onMoveFile}
|
|
276
|
-
onImportFiles={onImportFiles}
|
|
277
|
-
lintFindingsByFile={lintFindingsByFile}
|
|
278
|
-
/>
|
|
279
|
-
</div>
|
|
280
|
-
)}
|
|
281
|
-
<div className="flex-1 overflow-hidden min-w-0">
|
|
282
|
-
{codeChildren ?? (
|
|
283
|
-
<div className="flex items-center justify-center h-full text-neutral-600 text-sm">
|
|
284
|
-
Select a file to edit
|
|
285
|
-
</div>
|
|
286
|
-
)}
|
|
287
|
-
</div>
|
|
288
|
-
</div>
|
|
289
|
-
)}
|
|
290
|
-
|
|
291
|
-
{tab === "blocks" && (
|
|
292
|
-
<div
|
|
293
|
-
id="sidebar-panel-blocks"
|
|
294
|
-
role="tabpanel"
|
|
295
|
-
className="flex flex-col flex-1 min-h-0"
|
|
296
|
-
>
|
|
297
|
-
<BlocksTab onAddBlock={onAddBlock} onPreviewBlock={onPreviewBlock} />
|
|
298
|
-
</div>
|
|
299
|
-
)}
|
|
300
|
-
|
|
301
|
-
{/* Lint button pinned at the bottom */}
|
|
302
|
-
{onLint && (
|
|
303
|
-
<div className="border-t border-neutral-800 p-2 flex-shrink-0">
|
|
304
|
-
<button
|
|
305
|
-
onClick={onLint}
|
|
306
|
-
disabled={linting}
|
|
307
|
-
className="w-full flex items-center justify-center gap-1.5 px-2 py-1.5 rounded-md text-[11px] font-medium text-neutral-500 enabled:hover:text-amber-300 enabled:hover:bg-neutral-800 enabled:active:scale-[0.98] transition-colors disabled:opacity-40"
|
|
308
|
-
>
|
|
309
|
-
<svg
|
|
310
|
-
width="12"
|
|
311
|
-
height="12"
|
|
312
|
-
viewBox="0 0 24 24"
|
|
313
|
-
fill="none"
|
|
314
|
-
stroke="currentColor"
|
|
315
|
-
strokeWidth="2"
|
|
316
|
-
>
|
|
317
|
-
<path d="M9 11l3 3L22 4" />
|
|
318
|
-
<path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11" />
|
|
319
|
-
</svg>
|
|
320
|
-
{linting ? "Linting…" : "Lint"}
|
|
321
|
-
{!linting && lintFindingCount != null && lintFindingCount > 0 && (
|
|
322
|
-
<span className="ml-1 min-w-[16px] rounded-full bg-amber-500/20 px-1 text-[9px] font-bold text-amber-400">
|
|
323
|
-
{lintFindingCount}
|
|
324
|
-
</span>
|
|
325
|
-
)}
|
|
326
|
-
</button>
|
|
327
|
-
</div>
|
|
328
|
-
)}
|
|
329
|
-
</>
|
|
330
|
-
)}
|
|
331
|
-
</div>
|
|
332
|
-
);
|
|
333
|
-
}),
|
|
334
|
-
);
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// @vitest-environment happy-dom
|
|
2
|
-
|
|
3
|
-
import React, { act } from "react";
|
|
4
|
-
import { createRoot } from "react-dom/client";
|
|
5
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
-
import { AgentChatMessageButton } from "./AgentChatMessageButton";
|
|
7
|
-
|
|
8
|
-
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
9
|
-
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
document.body.innerHTML = "";
|
|
12
|
-
vi.useRealTimers();
|
|
13
|
-
vi.restoreAllMocks();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe("AgentChatMessageButton", () => {
|
|
17
|
-
it("reports a successful copy and resets to an explicit re-copy action", async () => {
|
|
18
|
-
vi.useFakeTimers();
|
|
19
|
-
const writeText = vi.fn().mockResolvedValue(undefined);
|
|
20
|
-
Object.defineProperty(navigator, "clipboard", {
|
|
21
|
-
configurable: true,
|
|
22
|
-
value: { writeText },
|
|
23
|
-
});
|
|
24
|
-
const onCopied = vi.fn();
|
|
25
|
-
const host = document.createElement("div");
|
|
26
|
-
document.body.append(host);
|
|
27
|
-
const root = createRoot(host);
|
|
28
|
-
act(() => {
|
|
29
|
-
root.render(<AgentChatMessageButton message="handoff" onCopied={onCopied} />);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const button = host.querySelector("button");
|
|
33
|
-
if (!button) throw new Error("copy button not rendered");
|
|
34
|
-
await act(async () => {
|
|
35
|
-
button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
36
|
-
await Promise.resolve();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
expect(writeText).toHaveBeenCalledWith("handoff");
|
|
40
|
-
expect(onCopied).toHaveBeenCalledOnce();
|
|
41
|
-
expect(button.textContent).toBe("Copied — paste in your agent chat");
|
|
42
|
-
|
|
43
|
-
act(() => vi.advanceTimersByTime(3000));
|
|
44
|
-
expect(button.textContent).toBe("Copy again");
|
|
45
|
-
act(() => root.unmount());
|
|
46
|
-
});
|
|
47
|
-
});
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { Button } from "../ui/Button";
|
|
3
|
-
|
|
4
|
-
export const APPLY_STORYBOARD_FEEDBACK_MESSAGE =
|
|
5
|
-
"Read the storyboard feedback I saved in .hyperframes/frame-comments.json and revise the frames.";
|
|
6
|
-
|
|
7
|
-
export function AgentChatMessageButton({
|
|
8
|
-
message,
|
|
9
|
-
label = "Copy prompt for agent",
|
|
10
|
-
onCopied,
|
|
11
|
-
}: {
|
|
12
|
-
message: string;
|
|
13
|
-
label?: string;
|
|
14
|
-
onCopied?: () => void;
|
|
15
|
-
}) {
|
|
16
|
-
const [copyState, setCopyState] = useState<"idle" | "copied" | "again" | "failed">("idle");
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (copyState !== "copied") return;
|
|
20
|
-
const timeout = window.setTimeout(() => setCopyState("again"), 3000);
|
|
21
|
-
return () => window.clearTimeout(timeout);
|
|
22
|
-
}, [copyState]);
|
|
23
|
-
|
|
24
|
-
const copyMessage = async () => {
|
|
25
|
-
try {
|
|
26
|
-
await navigator.clipboard.writeText(message);
|
|
27
|
-
setCopyState("copied");
|
|
28
|
-
onCopied?.();
|
|
29
|
-
} catch {
|
|
30
|
-
setCopyState("failed");
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<Button size="sm" variant="secondary" onClick={() => void copyMessage()}>
|
|
36
|
-
{copyState === "copied"
|
|
37
|
-
? "Copied — paste in your agent chat"
|
|
38
|
-
: copyState === "again"
|
|
39
|
-
? "Copy again"
|
|
40
|
-
: copyState === "failed"
|
|
41
|
-
? "Copy failed"
|
|
42
|
-
: label}
|
|
43
|
-
</Button>
|
|
44
|
-
);
|
|
45
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
// @vitest-environment happy-dom
|
|
2
|
-
|
|
3
|
-
import React, { act } from "react";
|
|
4
|
-
import { createRoot, type Root } from "react-dom/client";
|
|
5
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
6
|
-
import { FramePoster } from "./FramePoster";
|
|
7
|
-
|
|
8
|
-
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
9
|
-
|
|
10
|
-
const roots: Root[] = [];
|
|
11
|
-
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
act(() => roots.splice(0).forEach((root) => root.unmount()));
|
|
14
|
-
document.body.replaceChildren();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
// A fresh host per render: several cases compare two surfaces side by side.
|
|
18
|
-
function renderPoster(surface?: "tile" | "hero"): HTMLImageElement {
|
|
19
|
-
const host = document.createElement("div");
|
|
20
|
-
document.body.appendChild(host);
|
|
21
|
-
const root = createRoot(host);
|
|
22
|
-
roots.push(root);
|
|
23
|
-
act(() => {
|
|
24
|
-
root.render(
|
|
25
|
-
<FramePoster
|
|
26
|
-
projectId="demo"
|
|
27
|
-
src="frames/01-hero.html"
|
|
28
|
-
seconds={3}
|
|
29
|
-
title="hero"
|
|
30
|
-
surface={surface}
|
|
31
|
-
/>,
|
|
32
|
-
);
|
|
33
|
-
});
|
|
34
|
-
const img = host.querySelector("img");
|
|
35
|
-
if (!img) throw new Error("poster did not render an <img>");
|
|
36
|
-
return img;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
describe("FramePoster", () => {
|
|
40
|
-
// Regression: the contact sheet stretched a 240x135 capture across a wide,
|
|
41
|
-
// high-density card, making body copy, thin lines, and sprite details blurry.
|
|
42
|
-
it.each([
|
|
43
|
-
[undefined, "storyboard"],
|
|
44
|
-
["tile", "storyboard"],
|
|
45
|
-
["hero", "source"],
|
|
46
|
-
] as const)("captures the %s surface at %s density", (surface, output) => {
|
|
47
|
-
const url = new URL(renderPoster(surface).src);
|
|
48
|
-
|
|
49
|
-
expect(url.searchParams.get("output")).toBe(output);
|
|
50
|
-
expect(url.pathname).toBe("/api/projects/demo/thumbnail/frames/01-hero.html");
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("defaults to the tile surface", () => {
|
|
54
|
-
expect(renderPoster().className).toBe(renderPoster("tile").className);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("letterboxes only the hero, so a tile still fills its cell", () => {
|
|
58
|
-
expect(renderPoster("hero").className).toContain("object-contain");
|
|
59
|
-
expect(renderPoster("tile").className).toContain("object-cover");
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { buildProjectApiPath } from "../../utils/projectRouting";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
|
-
import { buildCompositionThumbnailUrl } from "../../player/components/CompositionThumbnail";
|
|
4
|
-
|
|
5
|
-
export interface FramePosterProps {
|
|
6
|
-
projectId: string;
|
|
7
|
-
/** Project-relative path to the frame's HTML sub-composition. */
|
|
8
|
-
src: string;
|
|
9
|
-
/** Time (seconds) to seek to for the poster. */
|
|
10
|
-
seconds: number;
|
|
11
|
-
title: string;
|
|
12
|
-
/**
|
|
13
|
-
* Where this poster is rendered. A contact-sheet tile is ~300px wide and there
|
|
14
|
-
* are many of them; the focus hero is up to 900px wide and there is exactly
|
|
15
|
-
* one. Tiles use a bounded high-density capture; the hero uses source density.
|
|
16
|
-
* The surface also decides whether the result fills or letterboxes its cell.
|
|
17
|
-
*/
|
|
18
|
-
surface?: "tile" | "hero";
|
|
19
|
-
/**
|
|
20
|
-
* Project content signature to key the poster URL on. The thumbnail route
|
|
21
|
-
* regenerates when the frame's source changes, but the browser only refetches
|
|
22
|
-
* when the URL does — so bake the signature in to pick up fresh posters after
|
|
23
|
-
* a background board refresh.
|
|
24
|
-
*/
|
|
25
|
-
posterVersion?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Server-rendered poster for a frame. The thumbnail route seeks the composition
|
|
30
|
-
* by time (at its real fps) and caches the result, so there's no live iframe,
|
|
31
|
-
* no postMessage seek, and no client-side fps assumption. Shared by the
|
|
32
|
-
* contact-sheet tile and the frame-focus view.
|
|
33
|
-
*/
|
|
34
|
-
export function FramePoster({
|
|
35
|
-
projectId,
|
|
36
|
-
src,
|
|
37
|
-
seconds,
|
|
38
|
-
title,
|
|
39
|
-
surface = "tile",
|
|
40
|
-
posterVersion,
|
|
41
|
-
}: FramePosterProps) {
|
|
42
|
-
const [failed, setFailed] = useState(false);
|
|
43
|
-
// The <img> is reused (no key) when a tile/hero swaps to a different frame, so a
|
|
44
|
-
// prior load error would stick. Reset when the poster target changes — including
|
|
45
|
-
// a new posterVersion, so a frame that failed mid-write retries once it settles.
|
|
46
|
-
useEffect(() => setFailed(false), [src, seconds, posterVersion, surface]);
|
|
47
|
-
if (failed) {
|
|
48
|
-
return (
|
|
49
|
-
<div className="flex h-full w-full items-center justify-center text-[11px] text-neutral-600">
|
|
50
|
-
Preview unavailable
|
|
51
|
-
</div>
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
let url = buildCompositionThumbnailUrl({
|
|
55
|
-
previewUrl: buildProjectApiPath(projectId, `/preview/comp/${src}`),
|
|
56
|
-
seekTime: seconds,
|
|
57
|
-
duration: 0,
|
|
58
|
-
origin: window.location.origin,
|
|
59
|
-
// The normal 240x135 preview is unreadable in the contact sheet, while source
|
|
60
|
-
// density is unbounded across all tiles. Give tiles a capped review density
|
|
61
|
-
// and reserve true source output for the single focus hero.
|
|
62
|
-
output: surface === "hero" ? "source" : "storyboard",
|
|
63
|
-
});
|
|
64
|
-
if (posterVersion) {
|
|
65
|
-
const withVersion = new URL(url, window.location.origin);
|
|
66
|
-
withVersion.searchParams.set("sig", posterVersion);
|
|
67
|
-
url = withVersion.toString();
|
|
68
|
-
}
|
|
69
|
-
return (
|
|
70
|
-
<img
|
|
71
|
-
src={url}
|
|
72
|
-
alt={title}
|
|
73
|
-
draggable={false}
|
|
74
|
-
loading="lazy"
|
|
75
|
-
onError={() => setFailed(true)}
|
|
76
|
-
className={`h-full w-full ${surface === "hero" ? "object-contain" : "object-cover"}`}
|
|
77
|
-
/>
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Time (seconds) to show a frame at — past the intro so the key moment is visible. */
|
|
82
|
-
export function posterTime(frame: { poster?: number; durationSeconds?: number }): number {
|
|
83
|
-
if (frame.poster != null) return frame.poster;
|
|
84
|
-
if (frame.durationSeconds != null) return frame.durationSeconds * 0.66;
|
|
85
|
-
return 1.5;
|
|
86
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { StoryboardGlobals } from "@hyperframes/core/storyboard";
|
|
2
|
-
|
|
3
|
-
export interface StoryboardDirectionProps {
|
|
4
|
-
globals: StoryboardGlobals;
|
|
5
|
-
frameCount: number;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Global direction header: the message/thesis plus arc, audience, and format.
|
|
10
|
-
* This is the storyboard's "north star" pulled from the manifest frontmatter.
|
|
11
|
-
*/
|
|
12
|
-
export function StoryboardDirection({ globals, frameCount }: StoryboardDirectionProps) {
|
|
13
|
-
const meta = [
|
|
14
|
-
{ label: "Arc", value: globals.arc },
|
|
15
|
-
{ label: "Audience", value: globals.audience },
|
|
16
|
-
{ label: "Voice", value: globals.extra.voice },
|
|
17
|
-
{ label: "Format", value: globals.format },
|
|
18
|
-
{ label: "Frames", value: String(frameCount) },
|
|
19
|
-
].filter((item): item is { label: string; value: string } => Boolean(item.value));
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<header className="border-b border-neutral-800 pb-5">
|
|
23
|
-
<div className="text-[11px] font-medium uppercase tracking-wider text-neutral-500">
|
|
24
|
-
Storyboard
|
|
25
|
-
</div>
|
|
26
|
-
{globals.message ? (
|
|
27
|
-
<h1 className="mt-1 text-2xl font-semibold leading-tight text-neutral-100">
|
|
28
|
-
{globals.message}
|
|
29
|
-
</h1>
|
|
30
|
-
) : (
|
|
31
|
-
<h1 className="mt-1 text-2xl font-semibold leading-tight text-neutral-400">
|
|
32
|
-
Untitled storyboard
|
|
33
|
-
</h1>
|
|
34
|
-
)}
|
|
35
|
-
<dl className="mt-4 flex flex-wrap gap-x-8 gap-y-2">
|
|
36
|
-
{meta.map((item) => (
|
|
37
|
-
<div key={item.label} className="flex items-baseline gap-2">
|
|
38
|
-
<dt className="text-[11px] uppercase tracking-wider text-neutral-500">{item.label}</dt>
|
|
39
|
-
<dd className="text-sm text-neutral-300">{item.value}</dd>
|
|
40
|
-
</div>
|
|
41
|
-
))}
|
|
42
|
-
</dl>
|
|
43
|
-
</header>
|
|
44
|
-
);
|
|
45
|
-
}
|