@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,135 @@
1
+ import { useRef, useState, useCallback } from "react";
2
+ import { buildClipRangeSelection, type TimelineRangeSelection } from "./timelineEditing";
3
+ import type { TimelineElement } from "../store/playerStore";
4
+ import { liveTime } from "../store/playerStore";
5
+ import { GUTTER } from "./timelineLayout";
6
+
7
+ interface UseTimelineRangeSelectionInput {
8
+ scrollRef: React.RefObject<HTMLDivElement | null>;
9
+ ppsRef: React.RefObject<number>;
10
+ effectiveDuration: number;
11
+ pps: number;
12
+ onSeek?: (time: number) => void;
13
+ seekFromX: (clientX: number) => void;
14
+ autoScrollDuringDrag: (clientX: number) => void;
15
+ dragScrollRaf: React.RefObject<number>;
16
+ isDragging: React.RefObject<boolean>;
17
+ setShowPopover: (v: boolean) => void;
18
+ }
19
+
20
+ export function useTimelineRangeSelection({
21
+ scrollRef,
22
+ ppsRef: _ppsRef,
23
+ effectiveDuration: _effectiveDuration,
24
+ pps,
25
+ onSeek: _onSeek,
26
+ seekFromX,
27
+ autoScrollDuringDrag,
28
+ dragScrollRaf,
29
+ isDragging,
30
+ setShowPopover,
31
+ }: UseTimelineRangeSelectionInput) {
32
+ const isRangeSelecting = useRef(false);
33
+ const rangeAnchorTime = useRef(0);
34
+ const [rangeSelection, setRangeSelection] = useState<TimelineRangeSelection | null>(null);
35
+ const shiftClickClipRef = useRef<{
36
+ element: TimelineElement;
37
+ anchorX: number;
38
+ anchorY: number;
39
+ } | null>(null);
40
+
41
+ const handlePointerDown = useCallback(
42
+ (e: React.PointerEvent) => {
43
+ if (e.button !== 0) return;
44
+ if (e.shiftKey) {
45
+ (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
46
+ isRangeSelecting.current = true;
47
+ setShowPopover(false);
48
+ const rect = scrollRef.current?.getBoundingClientRect();
49
+ if (rect) {
50
+ const x = e.clientX - rect.left + (scrollRef.current?.scrollLeft ?? 0) - GUTTER;
51
+ const time = Math.max(0, x / pps);
52
+ rangeAnchorTime.current = time;
53
+ setRangeSelection({ start: time, end: time, anchorX: e.clientX, anchorY: e.clientY });
54
+ }
55
+ return;
56
+ }
57
+ shiftClickClipRef.current = null;
58
+ if ((e.target as HTMLElement).closest("[data-clip]")) return;
59
+ (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
60
+ isDragging.current = true;
61
+ setRangeSelection(null);
62
+ setShowPopover(false);
63
+ seekFromX(e.clientX);
64
+ },
65
+ [seekFromX, pps, scrollRef, isDragging, setShowPopover],
66
+ );
67
+
68
+ const handlePointerMove = useCallback(
69
+ (e: React.PointerEvent) => {
70
+ if (isRangeSelecting.current) {
71
+ const rect = scrollRef.current?.getBoundingClientRect();
72
+ if (rect) {
73
+ const x = e.clientX - rect.left + (scrollRef.current?.scrollLeft ?? 0) - GUTTER;
74
+ setRangeSelection((prev) =>
75
+ prev
76
+ ? { ...prev, end: Math.max(0, x / pps), anchorX: e.clientX, anchorY: e.clientY }
77
+ : null,
78
+ );
79
+ }
80
+ return;
81
+ }
82
+ if (!isDragging.current) return;
83
+ seekFromX(e.clientX);
84
+ autoScrollDuringDrag(e.clientX);
85
+ },
86
+ [seekFromX, autoScrollDuringDrag, pps, scrollRef, isDragging],
87
+ );
88
+
89
+ const handlePointerUp = useCallback(() => {
90
+ if (isRangeSelecting.current) {
91
+ isRangeSelecting.current = false;
92
+ const pendingShiftClick = shiftClickClipRef.current;
93
+ shiftClickClipRef.current = null;
94
+ setRangeSelection((prev) => {
95
+ if (prev && pendingShiftClick && Math.abs(prev.end - prev.start) <= 0.2) {
96
+ setShowPopover(true);
97
+ return buildClipRangeSelection(pendingShiftClick.element, pendingShiftClick);
98
+ }
99
+ if (prev && Math.abs(prev.end - prev.start) > 0.2) {
100
+ setShowPopover(true);
101
+ return prev;
102
+ }
103
+ return null;
104
+ });
105
+ return;
106
+ }
107
+ isDragging.current = false;
108
+ cancelAnimationFrame(dragScrollRaf.current);
109
+ }, [isDragging, dragScrollRaf, setShowPopover]);
110
+
111
+ return {
112
+ rangeSelection,
113
+ setRangeSelection,
114
+ shiftClickClipRef,
115
+ handlePointerDown,
116
+ handlePointerMove,
117
+ handlePointerUp,
118
+ };
119
+ }
120
+
121
+ /* ── Seek + scroll utilities (used in Timeline only) ──────────────── */
122
+ export function seekTimeFromScrollX(
123
+ scrollEl: HTMLDivElement,
124
+ clientX: number,
125
+ effectiveDuration: number,
126
+ pps: number,
127
+ onSeek?: (time: number) => void,
128
+ ): void {
129
+ const rect = scrollEl.getBoundingClientRect();
130
+ const x = clientX - rect.left + scrollEl.scrollLeft - GUTTER;
131
+ if (x < 0) return;
132
+ const time = Math.max(0, Math.min(effectiveDuration, x / pps));
133
+ liveTime.notify(time);
134
+ onSeek?.(time);
135
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Keyboard shortcut handler for playback (Space/JKL/Arrow keys) and
3
+ * iframe shortcut listener setup.
4
+ *
5
+ * Accepts stable playback callbacks and returns the keyboard event handlers
6
+ * and iframe listener setup function. Has no side effects of its own.
7
+ */
8
+
9
+ import { useRef, useCallback } from "react";
10
+ import { useCaptionStore } from "../../captions/store";
11
+ import { shouldIgnorePlaybackShortcutEvent, SHUTTLE_SPEEDS } from "../lib/playbackShortcuts";
12
+ import { usePlayerStore } from "../store/playerStore";
13
+ import { stepFrameTime, STUDIO_PREVIEW_FPS } from "../lib/time";
14
+ import type { PlaybackAdapter } from "../lib/playbackTypes";
15
+
16
+ interface UsePlaybackKeyboardParams {
17
+ iframeRef: React.RefObject<HTMLIFrameElement | null>;
18
+ shuttleDirectionRef: React.MutableRefObject<"forward" | "backward" | null>;
19
+ shuttleSpeedIndexRef: React.MutableRefObject<number>;
20
+ iframeShortcutCleanupRef: React.MutableRefObject<(() => void) | null>;
21
+ getAdapter: () => PlaybackAdapter | null;
22
+ play: () => void;
23
+ playBackward: (rate: number) => void;
24
+ pause: () => void;
25
+ seek: (time: number) => void;
26
+ }
27
+
28
+ export function usePlaybackKeyboard({
29
+ iframeRef,
30
+ shuttleDirectionRef,
31
+ shuttleSpeedIndexRef,
32
+ iframeShortcutCleanupRef,
33
+ getAdapter,
34
+ play,
35
+ playBackward,
36
+ pause,
37
+ seek,
38
+ }: UsePlaybackKeyboardParams) {
39
+ const pressedCodesRef = useRef(new Set<string>());
40
+ const playbackKeyDownRef = useRef<(e: KeyboardEvent) => void>(() => {});
41
+ const playbackKeyUpRef = useRef<(e: KeyboardEvent) => void>(() => {});
42
+
43
+ const stepFrames = useCallback(
44
+ (deltaFrames: number) => {
45
+ const adapter = getAdapter();
46
+ const currentTime = adapter?.getTime() ?? usePlayerStore.getState().currentTime;
47
+ seek(stepFrameTime(currentTime, deltaFrames, STUDIO_PREVIEW_FPS));
48
+ },
49
+ [getAdapter, seek],
50
+ );
51
+
52
+ const shuttle = useCallback(
53
+ (direction: "forward" | "backward") => {
54
+ if (shuttleDirectionRef.current === direction) {
55
+ shuttleSpeedIndexRef.current = Math.min(
56
+ shuttleSpeedIndexRef.current + 1,
57
+ SHUTTLE_SPEEDS.length - 1,
58
+ );
59
+ } else {
60
+ shuttleSpeedIndexRef.current = 0;
61
+ }
62
+ const speed = SHUTTLE_SPEEDS[shuttleSpeedIndexRef.current];
63
+ usePlayerStore.getState().setPlaybackRate(speed);
64
+ if (direction === "forward") {
65
+ play();
66
+ } else {
67
+ playBackward(speed);
68
+ }
69
+ },
70
+ [play, playBackward, shuttleDirectionRef, shuttleSpeedIndexRef],
71
+ );
72
+
73
+ const togglePlay = useCallback(() => {
74
+ if (usePlayerStore.getState().isPlaying) {
75
+ pause();
76
+ } else {
77
+ play();
78
+ }
79
+ }, [play, pause]);
80
+
81
+ const handlePlaybackKeyDown = useCallback(
82
+ (e: KeyboardEvent) => {
83
+ if (e.defaultPrevented) return;
84
+ const captionState = useCaptionStore.getState();
85
+ if (
86
+ shouldIgnorePlaybackShortcutEvent(e, {
87
+ isCaptionEditMode: captionState.isEditMode,
88
+ selectedCaptionSegmentCount: captionState.selectedSegmentIds.size,
89
+ })
90
+ ) {
91
+ return;
92
+ }
93
+ pressedCodesRef.current.add(e.code);
94
+ if (e.code === "Space") {
95
+ e.preventDefault();
96
+ togglePlay();
97
+ return;
98
+ }
99
+ if (e.code === "ArrowLeft") {
100
+ e.preventDefault();
101
+ stepFrames(e.shiftKey ? -10 : -1);
102
+ return;
103
+ }
104
+ if (e.code === "ArrowRight") {
105
+ e.preventDefault();
106
+ stepFrames(e.shiftKey ? 10 : 1);
107
+ return;
108
+ }
109
+ if (e.repeat) return;
110
+ if (e.code === "KeyK") {
111
+ e.preventDefault();
112
+ pause();
113
+ return;
114
+ }
115
+ if (e.code === "KeyJ") {
116
+ e.preventDefault();
117
+ if (pressedCodesRef.current.has("KeyK")) {
118
+ stepFrames(-1);
119
+ return;
120
+ }
121
+ shuttle("backward");
122
+ return;
123
+ }
124
+ if (e.code === "KeyL") {
125
+ e.preventDefault();
126
+ if (pressedCodesRef.current.has("KeyK")) {
127
+ stepFrames(1);
128
+ return;
129
+ }
130
+ shuttle("forward");
131
+ }
132
+ },
133
+ [pause, shuttle, stepFrames, togglePlay],
134
+ );
135
+
136
+ const handlePlaybackKeyUp = useCallback((e: KeyboardEvent) => {
137
+ pressedCodesRef.current.delete(e.code);
138
+ }, []);
139
+
140
+ playbackKeyDownRef.current = handlePlaybackKeyDown;
141
+ playbackKeyUpRef.current = handlePlaybackKeyUp;
142
+
143
+ const attachIframeShortcutListeners = useCallback(() => {
144
+ iframeShortcutCleanupRef.current?.();
145
+ iframeShortcutCleanupRef.current = null;
146
+
147
+ const iframeWin = iframeRef.current?.contentWindow;
148
+ const iframeDoc = iframeRef.current?.contentDocument;
149
+ if (!iframeWin && !iframeDoc) return;
150
+
151
+ const handleIframeKeyDown = (e: KeyboardEvent) => playbackKeyDownRef.current(e);
152
+ const handleIframeKeyUp = (e: KeyboardEvent) => playbackKeyUpRef.current(e);
153
+ iframeWin?.addEventListener("keydown", handleIframeKeyDown, true);
154
+ iframeWin?.addEventListener("keyup", handleIframeKeyUp, true);
155
+ iframeDoc?.addEventListener("keydown", handleIframeKeyDown, true);
156
+ iframeDoc?.addEventListener("keyup", handleIframeKeyUp, true);
157
+ iframeShortcutCleanupRef.current = () => {
158
+ iframeWin?.removeEventListener("keydown", handleIframeKeyDown, true);
159
+ iframeWin?.removeEventListener("keyup", handleIframeKeyUp, true);
160
+ iframeDoc?.removeEventListener("keydown", handleIframeKeyDown, true);
161
+ iframeDoc?.removeEventListener("keyup", handleIframeKeyUp, true);
162
+ };
163
+ }, [iframeRef, iframeShortcutCleanupRef]);
164
+
165
+ return {
166
+ playbackKeyDownRef,
167
+ playbackKeyUpRef,
168
+ attachIframeShortcutListeners,
169
+ togglePlay,
170
+ };
171
+ }