@hyperframes/studio 0.6.0-alpha.9 → 0.6.1

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.
Files changed (111) hide show
  1. package/dist/assets/hyperframes-player-CzwFysqv.js +418 -0
  2. package/dist/assets/index-D1JDq7Gg.css +1 -0
  3. package/dist/assets/index-hYc4aP7M.js +117 -0
  4. package/dist/favicon.svg +14 -0
  5. package/dist/index.html +3 -2
  6. package/package.json +9 -9
  7. package/src/App.tsx +421 -4303
  8. package/src/captions/components/CaptionOverlay.tsx +13 -246
  9. package/src/captions/components/CaptionOverlayUtils.ts +221 -0
  10. package/src/components/AskAgentModal.tsx +120 -0
  11. package/src/components/StudioHeader.tsx +133 -0
  12. package/src/components/StudioLeftSidebar.tsx +125 -0
  13. package/src/components/StudioPreviewArea.tsx +167 -0
  14. package/src/components/StudioRightPanel.tsx +198 -0
  15. package/src/components/TimelineToolbar.tsx +89 -0
  16. package/src/components/editor/DomEditOverlay.tsx +88 -993
  17. package/src/components/editor/EaseCurveEditor.tsx +221 -0
  18. package/src/components/editor/FileTree.tsx +13 -621
  19. package/src/components/editor/FileTreeIcons.tsx +128 -0
  20. package/src/components/editor/FileTreeNodes.tsx +496 -0
  21. package/src/components/editor/MotionPanel.tsx +16 -390
  22. package/src/components/editor/MotionPanelFields.tsx +185 -0
  23. package/src/components/editor/PropertyPanel.test.ts +0 -49
  24. package/src/components/editor/PropertyPanel.tsx +132 -2763
  25. package/src/components/editor/domEditOverlayGeometry.ts +211 -0
  26. package/src/components/editor/domEditOverlayGestures.ts +138 -0
  27. package/src/components/editor/domEditOverlayStartGesture.ts +155 -0
  28. package/src/components/editor/domEditing.ts +44 -1117
  29. package/src/components/editor/domEditingAgentPrompt.ts +97 -0
  30. package/src/components/editor/domEditingDom.ts +266 -0
  31. package/src/components/editor/domEditingElement.ts +329 -0
  32. package/src/components/editor/domEditingLayers.ts +460 -0
  33. package/src/components/editor/domEditingTypes.ts +125 -0
  34. package/src/components/editor/manualEditingAvailability.test.ts +2 -2
  35. package/src/components/editor/manualEditingAvailability.ts +1 -1
  36. package/src/components/editor/manualEdits.ts +84 -1049
  37. package/src/components/editor/manualEditsDom.ts +436 -0
  38. package/src/components/editor/manualEditsParsing.ts +280 -0
  39. package/src/components/editor/manualEditsSnapshot.ts +333 -0
  40. package/src/components/editor/manualEditsTypes.ts +141 -0
  41. package/src/components/editor/propertyPanelColor.tsx +371 -0
  42. package/src/components/editor/propertyPanelFill.tsx +421 -0
  43. package/src/components/editor/propertyPanelFont.tsx +455 -0
  44. package/src/components/editor/propertyPanelHelpers.ts +401 -0
  45. package/src/components/editor/propertyPanelPrimitives.tsx +357 -0
  46. package/src/components/editor/propertyPanelSections.tsx +453 -0
  47. package/src/components/editor/propertyPanelStyleSections.tsx +411 -0
  48. package/src/components/editor/studioMotion.ts +47 -434
  49. package/src/components/editor/studioMotionOps.ts +299 -0
  50. package/src/components/editor/studioMotionTypes.ts +168 -0
  51. package/src/components/editor/useDomEditOverlayGestures.ts +393 -0
  52. package/src/components/editor/useDomEditOverlayRects.ts +207 -0
  53. package/src/components/nle/NLELayout.tsx +68 -155
  54. package/src/components/nle/NLEPreview.tsx +3 -0
  55. package/src/components/nle/useCompositionStack.ts +126 -0
  56. package/src/components/renders/RenderQueue.tsx +102 -31
  57. package/src/components/renders/useRenderQueue.ts +8 -2
  58. package/src/components/sidebar/LeftSidebar.tsx +186 -186
  59. package/src/contexts/DomEditContext.tsx +137 -0
  60. package/src/contexts/FileManagerContext.tsx +110 -0
  61. package/src/contexts/PanelLayoutContext.tsx +68 -0
  62. package/src/contexts/StudioContext.tsx +135 -0
  63. package/src/hooks/useAppHotkeys.ts +326 -0
  64. package/src/hooks/useAskAgentModal.ts +162 -0
  65. package/src/hooks/useCaptionDetection.ts +132 -0
  66. package/src/hooks/useCompositionDimensions.ts +25 -0
  67. package/src/hooks/useConsoleErrorCapture.ts +60 -0
  68. package/src/hooks/useDomEditCommits.ts +437 -0
  69. package/src/hooks/useDomEditSession.ts +342 -0
  70. package/src/hooks/useDomEditTextCommits.ts +330 -0
  71. package/src/hooks/useDomSelection.ts +398 -0
  72. package/src/hooks/useFileManager.ts +431 -0
  73. package/src/hooks/useFrameCapture.ts +77 -0
  74. package/src/hooks/useLintModal.ts +35 -0
  75. package/src/hooks/useManifestPersistence.ts +492 -0
  76. package/src/hooks/usePanelLayout.ts +68 -0
  77. package/src/hooks/usePreviewInteraction.ts +153 -0
  78. package/src/hooks/useRenderClipContent.ts +124 -0
  79. package/src/hooks/useTimelineEditing.ts +472 -0
  80. package/src/hooks/useToast.ts +20 -0
  81. package/src/player/components/Player.tsx +33 -2
  82. package/src/player/components/Timeline.test.ts +0 -8
  83. package/src/player/components/Timeline.tsx +196 -1518
  84. package/src/player/components/TimelineCanvas.tsx +434 -0
  85. package/src/player/components/TimelineClip.tsx +9 -244
  86. package/src/player/components/TimelineEmptyState.tsx +102 -0
  87. package/src/player/components/TimelineRuler.tsx +90 -0
  88. package/src/player/components/timelineIcons.tsx +49 -0
  89. package/src/player/components/timelineLayout.ts +215 -0
  90. package/src/player/components/timelineUtils.ts +211 -0
  91. package/src/player/components/useTimelineClipDrag.ts +388 -0
  92. package/src/player/components/useTimelinePlayhead.ts +200 -0
  93. package/src/player/components/useTimelineRangeSelection.ts +135 -0
  94. package/src/player/hooks/usePlaybackKeyboard.ts +171 -0
  95. package/src/player/hooks/useTimelinePlayer.ts +105 -1371
  96. package/src/player/hooks/useTimelineSyncCallbacks.ts +288 -0
  97. package/src/player/lib/playbackAdapter.ts +145 -0
  98. package/src/player/lib/playbackShortcuts.ts +68 -0
  99. package/src/player/lib/playbackTypes.ts +60 -0
  100. package/src/player/lib/timelineDOM.ts +373 -0
  101. package/src/player/lib/timelineElementHelpers.ts +303 -0
  102. package/src/player/lib/timelineIframeHelpers.ts +269 -0
  103. package/src/utils/domEditHelpers.ts +50 -0
  104. package/src/utils/studioFontHelpers.ts +83 -0
  105. package/src/utils/studioHelpers.ts +214 -0
  106. package/src/utils/studioPreviewHelpers.ts +185 -0
  107. package/src/utils/timelineDiscovery.ts +1 -1
  108. package/dist/assets/hyperframes-player-DjsVzYFP.js +0 -418
  109. package/dist/assets/index-14zH9lqh.css +0 -1
  110. package/dist/assets/index-DYCiFGWQ.js +0 -108
  111. package/src/player/components/TimelineClip.test.ts +0 -92
@@ -0,0 +1,153 @@
1
+ import { useCallback } from "react";
2
+ import { liveTime, usePlayerStore } from "../player";
3
+ import {
4
+ getPreviewLocalPointer,
5
+ buildRasterClickSelectionContext,
6
+ pauseStudioPreviewPlayback,
7
+ } from "../utils/studioPreviewHelpers";
8
+ import { STUDIO_PREVIEW_SELECTION_ENABLED } from "../components/editor/manualEditingAvailability";
9
+ import {
10
+ isLargeRasterDomEditSelection,
11
+ type DomEditSelection,
12
+ } from "../components/editor/domEditing";
13
+ import type { AgentModalAnchorPoint } from "../utils/studioHelpers";
14
+
15
+ // ── Types ──
16
+
17
+ export interface UsePreviewInteractionParams {
18
+ captionEditMode: boolean;
19
+ compositionLoading: boolean;
20
+ previewIframeRef: React.MutableRefObject<HTMLIFrameElement | null>;
21
+ activeCompPath: string | null;
22
+ showToast: (message: string, tone?: "error" | "info") => void;
23
+
24
+ // From useDomSelection
25
+ applyDomSelection: (
26
+ selection: DomEditSelection | null,
27
+ options?: { revealPanel?: boolean; additive?: boolean; preserveGroup?: boolean },
28
+ ) => void;
29
+ resolveDomSelectionFromPreviewPoint: (
30
+ clientX: number,
31
+ clientY: number,
32
+ options?: { preferClipAncestor?: boolean },
33
+ ) => DomEditSelection | null;
34
+ updateDomEditHoverSelection: (selection: DomEditSelection | null) => void;
35
+
36
+ // From useAskAgentModal
37
+ preloadAgentPromptSnippet: (selection: DomEditSelection) => Promise<void>;
38
+ setAgentPromptSelectionContext: (context: string | undefined) => void;
39
+ setAgentModalAnchorPoint: (point: AgentModalAnchorPoint | null) => void;
40
+ setAgentModalOpen: (open: boolean) => void;
41
+ }
42
+
43
+ // ── Hook ──
44
+
45
+ export function usePreviewInteraction({
46
+ captionEditMode,
47
+ compositionLoading,
48
+ previewIframeRef,
49
+ showToast,
50
+ applyDomSelection,
51
+ resolveDomSelectionFromPreviewPoint,
52
+ updateDomEditHoverSelection,
53
+ preloadAgentPromptSnippet,
54
+ setAgentPromptSelectionContext,
55
+ setAgentModalAnchorPoint,
56
+ setAgentModalOpen,
57
+ }: UsePreviewInteractionParams) {
58
+ const handlePreviewCanvasMouseDown = useCallback(
59
+ (e: React.MouseEvent<HTMLDivElement>, options?: { preferClipAncestor?: boolean }) => {
60
+ if (!STUDIO_PREVIEW_SELECTION_ENABLED || captionEditMode || compositionLoading) return;
61
+ const nextSelection = resolveDomSelectionFromPreviewPoint(e.clientX, e.clientY, {
62
+ preferClipAncestor: options?.preferClipAncestor ?? false,
63
+ });
64
+ if (!nextSelection) {
65
+ if (!e.shiftKey) applyDomSelection(null, { revealPanel: false });
66
+ return;
67
+ }
68
+ e.preventDefault();
69
+ e.stopPropagation();
70
+ const localPointer = previewIframeRef.current
71
+ ? getPreviewLocalPointer(previewIframeRef.current, e.clientX, e.clientY)
72
+ : null;
73
+ applyDomSelection(nextSelection, { additive: e.shiftKey });
74
+ if (
75
+ !e.shiftKey &&
76
+ localPointer &&
77
+ isLargeRasterDomEditSelection(nextSelection, localPointer.viewport)
78
+ ) {
79
+ setAgentPromptSelectionContext(
80
+ buildRasterClickSelectionContext(nextSelection, localPointer),
81
+ );
82
+ setAgentModalAnchorPoint({ x: e.clientX, y: e.clientY });
83
+ void preloadAgentPromptSnippet(nextSelection);
84
+ setAgentModalOpen(true);
85
+ }
86
+ },
87
+ [
88
+ applyDomSelection,
89
+ captionEditMode,
90
+ compositionLoading,
91
+ preloadAgentPromptSnippet,
92
+ resolveDomSelectionFromPreviewPoint,
93
+ previewIframeRef,
94
+ setAgentModalAnchorPoint,
95
+ setAgentModalOpen,
96
+ setAgentPromptSelectionContext,
97
+ ],
98
+ );
99
+
100
+ const handlePreviewCanvasPointerMove = useCallback(
101
+ (e: React.PointerEvent<HTMLDivElement>, options?: { preferClipAncestor?: boolean }) => {
102
+ if (!STUDIO_PREVIEW_SELECTION_ENABLED || captionEditMode || compositionLoading) {
103
+ updateDomEditHoverSelection(null);
104
+ return null;
105
+ }
106
+
107
+ const nextSelection = resolveDomSelectionFromPreviewPoint(e.clientX, e.clientY, {
108
+ preferClipAncestor: options?.preferClipAncestor ?? false,
109
+ });
110
+ updateDomEditHoverSelection(nextSelection);
111
+ return nextSelection;
112
+ },
113
+ [
114
+ captionEditMode,
115
+ compositionLoading,
116
+ resolveDomSelectionFromPreviewPoint,
117
+ updateDomEditHoverSelection,
118
+ ],
119
+ );
120
+
121
+ const handlePreviewCanvasPointerLeave = useCallback(() => {
122
+ updateDomEditHoverSelection(null);
123
+ }, [updateDomEditHoverSelection]);
124
+
125
+ const handleBlockedDomMove = useCallback(
126
+ (selection: DomEditSelection) => {
127
+ showToast(
128
+ selection.capabilities.reasonIfDisabled ??
129
+ "This element can't be adjusted directly from the preview.",
130
+ "info",
131
+ );
132
+ },
133
+ [showToast],
134
+ );
135
+
136
+ const handleDomManualDragStart = useCallback(() => {
137
+ const pausedTime = pauseStudioPreviewPlayback(previewIframeRef.current);
138
+ const playerStore = usePlayerStore.getState();
139
+ playerStore.setIsPlaying(false);
140
+ if (pausedTime != null) {
141
+ playerStore.setCurrentTime(pausedTime);
142
+ liveTime.notify(pausedTime);
143
+ }
144
+ }, [previewIframeRef]);
145
+
146
+ return {
147
+ handlePreviewCanvasMouseDown,
148
+ handlePreviewCanvasPointerMove,
149
+ handlePreviewCanvasPointerLeave,
150
+ handleBlockedDomMove,
151
+ handleDomManualDragStart,
152
+ };
153
+ }
@@ -0,0 +1,124 @@
1
+ import { useCallback, type ReactNode } from "react";
2
+ import { createElement } from "react";
3
+ import { CompositionThumbnail, VideoThumbnail } from "../player";
4
+ import type { TimelineElement } from "../player";
5
+ import { AudioWaveform } from "../player/components/AudioWaveform";
6
+ import { getTimelineElementLabel } from "../utils/studioHelpers";
7
+
8
+ interface UseRenderClipContentOptions {
9
+ projectIdRef: { current: string | null };
10
+ compIdToSrc: Map<string, string>;
11
+ activePreviewUrl: string | null;
12
+ effectiveTimelineDuration: number;
13
+ }
14
+
15
+ export function useRenderClipContent({
16
+ projectIdRef,
17
+ compIdToSrc,
18
+ activePreviewUrl,
19
+ effectiveTimelineDuration,
20
+ }: UseRenderClipContentOptions) {
21
+ return useCallback(
22
+ (el: TimelineElement, style: { clip: string; label: string }): ReactNode => {
23
+ const pid = projectIdRef.current;
24
+ if (!pid) return null;
25
+
26
+ // Resolve composition source path using the compIdToSrc map
27
+ let compSrc = el.compositionSrc;
28
+ if (compSrc && compIdToSrc.size > 0) {
29
+ const resolved =
30
+ compIdToSrc.get(el.id) ||
31
+ compIdToSrc.get(compSrc.replace(/^compositions\//, "").replace(/\.html$/, ""));
32
+ if (resolved) compSrc = resolved;
33
+ }
34
+
35
+ // Composition clips — always use the comp's own preview URL for thumbnails.
36
+ // This renders the composition in isolation so we get clean frames
37
+ // instead of capturing the master at a time when the comp is fading in.
38
+ if (compSrc) {
39
+ return createElement(CompositionThumbnail, {
40
+ previewUrl: `/api/projects/${pid}/preview/comp/${compSrc}`,
41
+ label: getTimelineElementLabel(el),
42
+ labelColor: style.label,
43
+ accentColor: style.clip,
44
+ seekTime: 0,
45
+ duration: el.duration,
46
+ });
47
+ }
48
+
49
+ // When drilled into a composition, render all inner elements via
50
+ // CompositionThumbnail at their start time — most accurate visual.
51
+ if (activePreviewUrl && el.duration > 0) {
52
+ return createElement(CompositionThumbnail, {
53
+ previewUrl: activePreviewUrl,
54
+ label: getTimelineElementLabel(el),
55
+ labelColor: style.label,
56
+ accentColor: style.clip,
57
+ selector: el.selector,
58
+ selectorIndex: el.selectorIndex,
59
+ seekTime: el.start,
60
+ duration: el.duration,
61
+ });
62
+ }
63
+
64
+ const htmlPreviewEligible =
65
+ el.duration > 0 &&
66
+ effectiveTimelineDuration > 0 &&
67
+ el.duration < effectiveTimelineDuration * 0.92 &&
68
+ !/(backdrop|background|overlay|scrim|mask)/i.test(el.id);
69
+
70
+ // Audio clips — waveform visualization
71
+ if (el.tag === "audio") {
72
+ const previewBase = `/api/projects/${pid}/preview/`;
73
+ const previewIdx = el.src?.startsWith("http") ? el.src.indexOf(previewBase) : -1;
74
+ const srcRelative = el.src
75
+ ? previewIdx !== -1
76
+ ? decodeURIComponent(el.src.slice(previewIdx + previewBase.length))
77
+ : el.src.startsWith("http")
78
+ ? null
79
+ : el.src
80
+ : null;
81
+ const audioUrl = srcRelative
82
+ ? `/api/projects/${pid}/preview/${srcRelative}`
83
+ : (el.src ?? "");
84
+ const waveformUrl = srcRelative
85
+ ? `/api/projects/${pid}/waveform/${srcRelative}`
86
+ : undefined;
87
+ return createElement(AudioWaveform, {
88
+ audioUrl,
89
+ waveformUrl,
90
+ label: getTimelineElementLabel(el),
91
+ labelColor: style.label,
92
+ });
93
+ }
94
+
95
+ if ((el.tag === "video" || el.tag === "img") && el.src) {
96
+ const mediaSrc = el.src.startsWith("http")
97
+ ? el.src
98
+ : `/api/projects/${pid}/preview/${el.src}`;
99
+ return createElement(VideoThumbnail, {
100
+ videoSrc: mediaSrc,
101
+ label: getTimelineElementLabel(el),
102
+ labelColor: style.label,
103
+ duration: el.duration,
104
+ });
105
+ }
106
+
107
+ if (htmlPreviewEligible) {
108
+ return createElement(CompositionThumbnail, {
109
+ previewUrl: `/api/projects/${pid}/preview`,
110
+ label: getTimelineElementLabel(el),
111
+ labelColor: style.label,
112
+ accentColor: style.clip,
113
+ selector: el.selector,
114
+ selectorIndex: el.selectorIndex,
115
+ seekTime: el.start,
116
+ duration: el.duration,
117
+ });
118
+ }
119
+
120
+ return null;
121
+ },
122
+ [projectIdRef, compIdToSrc, activePreviewUrl, effectiveTimelineDuration],
123
+ );
124
+ }