@hyperframes/studio 0.8.49 → 0.8.51
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-CqON2Rzo.js → hyperframes-player-Cn_PpHqP.js} +3 -3
- package/dist/assets/index-C05SNrvY.js +360 -0
- package/dist/assets/{index-mIgSsKqs.js → index-CPS3Cvj8.js} +1 -1
- package/dist/assets/{index-BGovDksE.js → index-Clrb11lj.js} +1 -1
- package/dist/assets/index-DUR9qzQM.css +1 -0
- package/dist/index.d.ts +29 -16
- package/dist/index.html +2 -2
- package/dist/index.js +7283 -8606
- package/dist/index.js.map +1 -1
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/package.json +11 -12
- package/src/App.tsx +132 -143
- package/src/captions/components/CaptionAnimationPanel.tsx +4 -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 +4 -0
- package/src/components/EditorShell.tsx +28 -7
- package/src/components/ExternalFileConflictBanner.tsx +16 -16
- package/src/components/LintModal.tsx +6 -16
- package/src/components/MediaPreview.tsx +2 -2
- package/src/components/SaveQueuePausedBanner.tsx +2 -2
- package/src/components/StudioHeader.tsx +2 -62
- package/src/components/StudioLeftSidebar.tsx +7 -4
- package/src/components/StudioOverlays.tsx +2 -6
- package/src/components/StudioRightPanel.tsx +4 -4
- package/src/components/StudioToast.tsx +2 -2
- package/src/components/TimelineToolbar.tsx +7 -7
- package/src/components/dock/Dock.test.tsx +192 -0
- package/src/components/dock/Dock.tsx +264 -0
- package/src/components/dock/dock.css +23 -0
- package/src/components/dock/dockLayout.ts +58 -0
- package/src/components/dock/dockLayoutSchema.ts +66 -0
- package/src/components/dock/dockLayoutStore.ts +91 -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.tsx +3 -3
- package/src/components/editor/MarqueeOverlay.tsx +1 -1
- package/src/components/editor/OffCanvasIndicators.tsx +1 -1
- package/src/components/editor/PreviewGuides.test.tsx +88 -0
- package/src/components/editor/PreviewGuides.tsx +125 -0
- 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 +21 -0
- package/src/components/editor/SnapToolbar.tsx +27 -3
- package/src/components/editor/TimelineFxPopover.tsx +1 -1
- package/src/components/editor/TopologyLens.tsx +4 -4
- 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 +9 -3
- package/src/components/editor/previewGuidesStore.ts +26 -0
- 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/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/NLEPreview.test.ts +13 -0
- package/src/components/nle/NLEPreview.tsx +12 -7
- package/src/components/nle/PreviewOverlays.tsx +6 -6
- package/src/components/nle/PreviewPane.test.tsx +52 -0
- package/src/components/nle/PreviewPane.tsx +4 -2
- package/src/components/nle/TimelinePane.tsx +17 -3
- package/src/components/nle/TimelineResizeDivider.tsx +2 -2
- package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +1 -1
- package/src/components/nle/useCanvasZOrderTimelineMirror.ts +3 -3
- package/src/components/panels/SlideshowPanel.tsx +2 -2
- 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 +3 -3
- package/src/components/panels/VariablesValueControls.tsx +2 -2
- package/src/components/renders/FfmpegRequiredNotice.tsx +5 -5
- package/src/components/renders/RenderQueue.test.tsx +7 -0
- package/src/components/renders/RenderQueue.tsx +6 -6
- package/src/components/renders/RenderQueueItem.tsx +6 -6
- 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/CompositionsTab.tsx +5 -5
- package/src/components/sidebar/GlobalAssetsView.tsx +1 -1
- package/src/components/sidebar/LeftSidebar.lintBadge.test.tsx +58 -0
- package/src/components/sidebar/LeftSidebar.tsx +19 -5
- package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
- package/src/components/ui/Button.test.tsx +60 -0
- package/src/components/ui/Button.tsx +4 -4
- package/src/components/ui/SearchInput.tsx +2 -2
- package/src/components/ui/Tooltip.tsx +1 -1
- package/src/hooks/appHotkeysDispatch.ts +11 -0
- package/src/hooks/timelineAudioGroupCreate.ts +2 -2
- package/src/hooks/timelineTrackVisibility.ts +8 -11
- package/src/hooks/useAppHotkeys.textEditing.test.tsx +1 -0
- package/src/hooks/useAppHotkeys.ts +4 -1
- package/src/hooks/useAskAgentModal.ts +2 -0
- package/src/hooks/useClipboard.test.ts +234 -0
- package/src/hooks/useClipboard.ts +264 -70
- package/src/hooks/useGroupCommits.test.ts +0 -1
- package/src/hooks/useLintModal.test.tsx +65 -0
- package/src/hooks/useLintModal.ts +3 -0
- package/src/hooks/useRazorSplit.history.test.tsx +0 -1
- package/src/hooks/useRazorSplit.test.ts +0 -2
- package/src/hooks/useRazorSplit.test.tsx +0 -1
- package/src/hooks/useSdkSession.lifecycle.test.tsx +49 -4
- package/src/hooks/useSdkSession.ts +43 -10
- package/src/hooks/useStudioContextValue.ts +5 -19
- package/src/hooks/useTimelineAssetDropOps.test.tsx +41 -1
- package/src/hooks/useTimelineAssetDropOps.ts +52 -11
- package/src/hooks/useTimelineEditing.test.tsx +0 -1
- package/src/main.tsx +9 -3
- package/src/player/components/AudioWaveform.tsx +1 -1
- package/src/player/components/AutomationSelectionMenu.tsx +2 -2
- package/src/player/components/ClipContextMenu.tsx +90 -10
- 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 +16 -15
- package/src/player/components/TimelineCanvas.tsx +45 -8
- 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/TimelineGestureOverlay.test.tsx +35 -1
- package/src/player/components/TimelineGestureOverlay.tsx +1 -1
- 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 +54 -1
- package/src/player/components/TimelineLanes.tsx +2 -5
- package/src/player/components/TimelineLayerGroupHeader.tsx +2 -2
- package/src/player/components/TimelineOverlays.tsx +12 -0
- package/src/player/components/TimelineRuler.tsx +1 -1
- package/src/player/components/TimelineShortcutHint.tsx +1 -1
- package/src/player/components/TimelineTrackHeader.tsx +1 -1
- package/src/player/components/TimelineTrackPlainHeader.tsx +3 -3
- package/src/player/components/TimelineTypes.ts +6 -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/timelineAuthoredMoveTarget.ts +1 -2
- package/src/player/components/timelineCallbacks.ts +8 -8
- package/src/player/components/timelineClipChildren.test.ts +2 -2
- package/src/player/components/timelineClipChildren.tsx +2 -1
- package/src/player/components/timelineClipDragCommit.ts +2 -2
- package/src/player/components/timelineClipDragPreview.ts +51 -27
- package/src/player/components/timelineClipDragTypes.ts +3 -0
- package/src/player/components/timelineDragDrop.test.tsx +92 -1
- package/src/player/components/timelineDragDrop.ts +82 -33
- package/src/player/components/timelineEditCapabilities.ts +1 -2
- package/src/player/components/timelineEditing.test.ts +0 -16
- package/src/player/components/timelineGroupEditing.test.ts +1 -3
- package/src/player/components/timelineGroupEditing.ts +0 -1
- 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/timelineTrackDisplay.ts +1 -1
- package/src/player/components/timelineTrackPersistPipeline.test.ts +0 -53
- package/src/player/components/trackHeaderLabelRows.tsx +4 -4
- package/src/player/components/useTimelineClipContextMenu.ts +32 -0
- package/src/player/components/useTimelineClipDrag.resize.test.tsx +136 -2
- package/src/player/components/useTimelineClipDrag.ts +30 -3
- package/src/player/components/useTimelineTrackDerivations.ts +1 -1
- package/src/player/hooks/timelineDomBinding.test.ts +0 -3
- package/src/player/hooks/timelineSyncHydration.test.ts +18 -0
- package/src/player/hooks/timelineSyncHydration.ts +37 -18
- package/src/player/hooks/useTimelinePlayer.test.ts +3 -20
- package/src/player/hooks/useTimelineRowElements.test.ts +28 -0
- package/src/player/hooks/useTimelineRowElements.ts +17 -0
- package/src/player/hooks/useTimelineSyncCallbacks.emptyManifest.test.tsx +41 -0
- package/src/player/hooks/useTimelineSyncCallbacks.ts +9 -10
- package/src/player/lib/describeClips.test.ts +95 -0
- package/src/player/lib/describeClips.ts +52 -0
- package/src/player/lib/timelineDOM.test.ts +0 -80
- package/src/player/lib/timelineDOM.ts +1 -61
- package/src/player/lib/timelineElementHelpers.ts +2 -53
- package/src/player/store/playerStore.ts +5 -0
- package/src/player/store/timelineElement.ts +1 -3
- package/src/shims/canvasBrowserStub.js +50 -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/tailwind-preset.shared.js +3 -0
- package/src/styles/theme.css +213 -0
- package/src/styles/theme.test.ts +196 -0
- package/src/utils/clipboardPayload.test.ts +42 -2
- package/src/utils/clipboardPayload.ts +57 -32
- package/src/utils/previewSafeMargins.test.ts +34 -0
- package/src/utils/previewSafeMargins.ts +10 -0
- package/src/utils/razorSplitTransaction.test.ts +0 -1
- package/src/utils/sdkCanvasComposition.test.ts +77 -0
- package/src/utils/studioUiPreferences.test.ts +43 -0
- package/src/utils/studioUiPreferences.ts +34 -6
- package/src/utils/timelineDropTrackInsert.test.ts +134 -0
- package/src/utils/timelineDropTrackInsert.ts +83 -0
- package/src/utils/timelineElementSplit.test.ts +1 -2
- package/src/utils/timelineElementSplit.ts +0 -1
- package/src/webmcp/tools/lookTools.test.ts +40 -0
- package/src/webmcp/tools/lookTools.ts +32 -1
- package/dist/assets/index-B9LfWNyM.css +0 -1
- package/dist/assets/index-Bgmx2h7W.js +0 -441
- package/src/components/StudioGlobalDragOverlay.tsx +0 -24
- 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/useProjectSignaturePoll.test.tsx +0 -135
- package/src/hooks/useProjectSignaturePoll.ts +0 -68
- package/src/hooks/useStoryboard.ts +0 -99
- package/src/player/hooks/useExpandedTimelineElements.test.ts +0 -774
- package/src/player/hooks/useExpandedTimelineElements.ts +0 -460
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export function StudioGlobalDragOverlay() {
|
|
2
|
-
return (
|
|
3
|
-
<div className="hf-backdrop-in absolute inset-0 z-[90] flex items-center justify-center bg-black/50 backdrop-blur-sm pointer-events-none">
|
|
4
|
-
<div className="flex flex-col items-center gap-3 px-8 py-6 rounded-xl border-2 border-dashed border-studio-accent/60 bg-studio-accent/[0.06]">
|
|
5
|
-
<svg
|
|
6
|
-
width="32"
|
|
7
|
-
height="32"
|
|
8
|
-
viewBox="0 0 24 24"
|
|
9
|
-
fill="none"
|
|
10
|
-
stroke="currentColor"
|
|
11
|
-
strokeWidth="1.5"
|
|
12
|
-
strokeLinecap="round"
|
|
13
|
-
strokeLinejoin="round"
|
|
14
|
-
className="text-studio-accent"
|
|
15
|
-
>
|
|
16
|
-
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
17
|
-
<polyline points="7 10 12 15 17 10" />
|
|
18
|
-
<line x1="12" y1="15" x2="12" y2="3" />
|
|
19
|
-
</svg>
|
|
20
|
-
<span className="text-sm font-medium text-studio-accent">Drop to add at the playhead</span>
|
|
21
|
-
</div>
|
|
22
|
-
</div>
|
|
23
|
-
);
|
|
24
|
-
}
|
|
@@ -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
|
-
}
|