@remotion/studio 4.0.443 → 4.0.445

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 (39) hide show
  1. package/dist/components/Canvas.js +197 -42
  2. package/dist/components/CanvasOrLoading.js +5 -0
  3. package/dist/components/Editor.js +10 -2
  4. package/dist/components/RenderErrorContext.d.ts +4 -0
  5. package/dist/components/RenderErrorContext.js +7 -0
  6. package/dist/components/RenderModal/RenderModalJSONPropsEditor.js +7 -5
  7. package/dist/components/RenderModal/SchemaEditor/SchemaEditor.js +5 -1
  8. package/dist/components/RenderModal/SchemaEditor/SchemaErrorMessages.js +5 -3
  9. package/dist/components/Timeline/Timeline.js +8 -5
  10. package/dist/components/Timeline/TimelineListItem.js +71 -8
  11. package/dist/components/Timeline/TimelinePinchZoom.d.ts +2 -0
  12. package/dist/components/Timeline/TimelinePinchZoom.js +240 -0
  13. package/dist/components/Timeline/TimelineSlider.js +22 -8
  14. package/dist/components/Timeline/TimelineVideoInfo.js +1 -18
  15. package/dist/components/Timeline/TimelineZoomControls.js +6 -3
  16. package/dist/components/Timeline/timeline-scroll-logic.d.ts +13 -1
  17. package/dist/components/Timeline/timeline-scroll-logic.js +20 -6
  18. package/dist/components/Timeline/use-sequence-props-subscription.d.ts +1 -1
  19. package/dist/components/Timeline/use-sequence-props-subscription.js +25 -6
  20. package/dist/error-overlay/react-overlay/listen-to-runtime-errors.js +1 -5
  21. package/dist/error-overlay/remotion-overlay/CopyStackTrace.d.ts +5 -0
  22. package/dist/error-overlay/remotion-overlay/CopyStackTrace.js +50 -0
  23. package/dist/error-overlay/remotion-overlay/ErrorDisplay.js +16 -13
  24. package/dist/error-overlay/remotion-overlay/MediaPlaybackErrorExplainer.d.ts +4 -0
  25. package/dist/error-overlay/remotion-overlay/MediaPlaybackErrorExplainer.js +87 -0
  26. package/dist/esm/{chunk-cpv44d93.js → chunk-bqd9dhnk.js} +3532 -2899
  27. package/dist/esm/internals.mjs +3532 -2899
  28. package/dist/esm/previewEntry.mjs +3727 -3098
  29. package/dist/esm/renderEntry.mjs +1 -1
  30. package/dist/helpers/frame-database.d.ts +2 -2
  31. package/dist/helpers/frame-database.js +36 -25
  32. package/dist/helpers/get-effective-translation.d.ts +13 -1
  33. package/dist/helpers/get-effective-translation.js +45 -1
  34. package/dist/helpers/use-max-media-duration.js +1 -1
  35. package/dist/state/timeline-zoom.d.ts +5 -1
  36. package/dist/state/timeline-zoom.js +21 -16
  37. package/package.json +9 -9
  38. package/dist/error-overlay/remotion-overlay/CompositionIdsDropdown.d.ts +0 -5
  39. package/dist/error-overlay/remotion-overlay/CompositionIdsDropdown.js +0 -108
@@ -0,0 +1,240 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TimelinePinchZoom = void 0;
4
+ const react_1 = require("react");
5
+ const remotion_1 = require("remotion");
6
+ const is_current_selected_still_1 = require("../../helpers/is-current-selected-still");
7
+ const editor_zoom_gestures_1 = require("../../state/editor-zoom-gestures");
8
+ const timeline_zoom_1 = require("../../state/timeline-zoom");
9
+ const timeline_refs_1 = require("./timeline-refs");
10
+ const timeline_scroll_logic_1 = require("./timeline-scroll-logic");
11
+ /**
12
+ * Maps wheel deltaY to zoom delta. Must be large enough that typical ctrl+wheel
13
+ * pinch steps change `TimelineZoomCtx` zoom by at least one 0.1 step after
14
+ * `Math.round(z * 10) / 10` in `timeline-zoom.tsx` (0.005 was too small).
15
+ */
16
+ const ZOOM_WHEEL_DELTA = 0.06;
17
+ const TimelinePinchZoom = () => {
18
+ const isVideoComposition = (0, is_current_selected_still_1.useIsVideoComposition)();
19
+ const videoConfig = remotion_1.Internals.useUnsafeVideoConfig();
20
+ const { canvasContent } = (0, react_1.useContext)(remotion_1.Internals.CompositionManager);
21
+ const { zoom, setZoom } = (0, react_1.useContext)(timeline_zoom_1.TimelineZoomCtx);
22
+ const { editorZoomGestures } = (0, react_1.useContext)(editor_zoom_gestures_1.EditorZoomGesturesContext);
23
+ const zoomRef = (0, react_1.useRef)(zoom);
24
+ zoomRef.current = zoom;
25
+ const pinchBaseZoomRef = (0, react_1.useRef)(null);
26
+ const suppressWheelFromWebKitPinchRef = (0, react_1.useRef)(false);
27
+ const touchPinchRef = (0, react_1.useRef)(null);
28
+ const onWheel = (0, react_1.useCallback)((e) => {
29
+ if (!editorZoomGestures || !isVideoComposition) {
30
+ return;
31
+ }
32
+ const { ctrlKey, metaKey, clientX, deltaY, deltaMode } = e;
33
+ const wantsToZoom = ctrlKey || metaKey;
34
+ if (!wantsToZoom) {
35
+ return;
36
+ }
37
+ if (suppressWheelFromWebKitPinchRef.current && wantsToZoom) {
38
+ e.preventDefault();
39
+ return;
40
+ }
41
+ if (!videoConfig || videoConfig.durationInFrames < 2) {
42
+ return;
43
+ }
44
+ if (!canvasContent || canvasContent.type !== 'composition') {
45
+ return;
46
+ }
47
+ const scrollEl = timeline_refs_1.scrollableRef.current;
48
+ if (!scrollEl) {
49
+ return;
50
+ }
51
+ e.preventDefault();
52
+ const anchorContentX = (0, timeline_scroll_logic_1.viewportClientXToScrollContentX)({
53
+ clientX,
54
+ scrollEl,
55
+ });
56
+ let scaledDeltaY = deltaY;
57
+ if (deltaMode === WheelEvent.DOM_DELTA_LINE) {
58
+ scaledDeltaY *= 16;
59
+ }
60
+ else if (deltaMode === WheelEvent.DOM_DELTA_PAGE) {
61
+ scaledDeltaY *= scrollEl.clientHeight;
62
+ }
63
+ setZoom(canvasContent.compositionId, (z) => z - scaledDeltaY * ZOOM_WHEEL_DELTA, { anchorFrame: null, anchorContentX });
64
+ }, [
65
+ editorZoomGestures,
66
+ isVideoComposition,
67
+ videoConfig,
68
+ canvasContent,
69
+ setZoom,
70
+ ]);
71
+ const supportsWebKitPinch = typeof window !== 'undefined' && 'GestureEvent' in window;
72
+ (0, react_1.useEffect)(() => {
73
+ const el = timeline_refs_1.timelineVerticalScroll.current;
74
+ if (!el) {
75
+ return;
76
+ }
77
+ el.addEventListener('wheel', onWheel, { passive: false });
78
+ return () => {
79
+ el.removeEventListener('wheel', onWheel);
80
+ };
81
+ }, [onWheel]);
82
+ (0, react_1.useEffect)(() => {
83
+ const el = timeline_refs_1.timelineVerticalScroll.current;
84
+ if (!el || !editorZoomGestures || !supportsWebKitPinch) {
85
+ return;
86
+ }
87
+ const endWebKitPinch = () => {
88
+ pinchBaseZoomRef.current = null;
89
+ suppressWheelFromWebKitPinchRef.current = false;
90
+ };
91
+ const onGestureStart = (event) => {
92
+ var _a;
93
+ const e = event;
94
+ if (!isVideoComposition) {
95
+ return;
96
+ }
97
+ if (!videoConfig || videoConfig.durationInFrames < 2) {
98
+ return;
99
+ }
100
+ if (!canvasContent || canvasContent.type !== 'composition') {
101
+ return;
102
+ }
103
+ const scrollEl = timeline_refs_1.scrollableRef.current;
104
+ if (!scrollEl) {
105
+ return;
106
+ }
107
+ e.preventDefault();
108
+ suppressWheelFromWebKitPinchRef.current = true;
109
+ pinchBaseZoomRef.current = (_a = zoomRef.current[canvasContent.compositionId]) !== null && _a !== void 0 ? _a : timeline_zoom_1.TIMELINE_MIN_ZOOM;
110
+ };
111
+ const onGestureChange = (event) => {
112
+ const e = event;
113
+ const base = pinchBaseZoomRef.current;
114
+ if (base === null ||
115
+ !isVideoComposition ||
116
+ !videoConfig ||
117
+ videoConfig.durationInFrames < 2) {
118
+ return;
119
+ }
120
+ if (!canvasContent || canvasContent.type !== 'composition') {
121
+ return;
122
+ }
123
+ const scrollEl = timeline_refs_1.scrollableRef.current;
124
+ if (!scrollEl) {
125
+ return;
126
+ }
127
+ e.preventDefault();
128
+ const anchorContentX = (0, timeline_scroll_logic_1.viewportClientXToScrollContentX)({
129
+ clientX: e.clientX,
130
+ scrollEl,
131
+ });
132
+ setZoom(canvasContent.compositionId, () => base * e.scale, {
133
+ anchorFrame: null,
134
+ anchorContentX,
135
+ });
136
+ };
137
+ const onGestureEnd = () => {
138
+ endWebKitPinch();
139
+ };
140
+ el.addEventListener('gesturestart', onGestureStart, { passive: false });
141
+ el.addEventListener('gesturechange', onGestureChange, { passive: false });
142
+ el.addEventListener('gestureend', onGestureEnd);
143
+ el.addEventListener('gesturecancel', onGestureEnd);
144
+ return () => {
145
+ el.removeEventListener('gesturestart', onGestureStart);
146
+ el.removeEventListener('gesturechange', onGestureChange);
147
+ el.removeEventListener('gestureend', onGestureEnd);
148
+ el.removeEventListener('gesturecancel', onGestureEnd);
149
+ };
150
+ }, [
151
+ editorZoomGestures,
152
+ supportsWebKitPinch,
153
+ isVideoComposition,
154
+ videoConfig,
155
+ canvasContent,
156
+ setZoom,
157
+ ]);
158
+ (0, react_1.useEffect)(() => {
159
+ const el = timeline_refs_1.timelineVerticalScroll.current;
160
+ if (!el || !editorZoomGestures) {
161
+ return;
162
+ }
163
+ const onTouchStart = (event) => {
164
+ var _a;
165
+ if (event.touches.length !== 2) {
166
+ touchPinchRef.current = null;
167
+ return;
168
+ }
169
+ if (!isVideoComposition ||
170
+ !videoConfig ||
171
+ videoConfig.durationInFrames < 2) {
172
+ return;
173
+ }
174
+ if (!canvasContent || canvasContent.type !== 'composition') {
175
+ return;
176
+ }
177
+ const [t0, t1] = [event.touches[0], event.touches[1]];
178
+ const initialDistance = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY);
179
+ if (initialDistance < 1e-6) {
180
+ return;
181
+ }
182
+ touchPinchRef.current = {
183
+ initialDistance,
184
+ initialZoom: (_a = zoomRef.current[canvasContent.compositionId]) !== null && _a !== void 0 ? _a : timeline_zoom_1.TIMELINE_MIN_ZOOM,
185
+ };
186
+ };
187
+ const onTouchMove = (event) => {
188
+ const pinch = touchPinchRef.current;
189
+ if (pinch === null ||
190
+ event.touches.length !== 2 ||
191
+ !videoConfig ||
192
+ videoConfig.durationInFrames < 2) {
193
+ return;
194
+ }
195
+ if (!canvasContent || canvasContent.type !== 'composition') {
196
+ return;
197
+ }
198
+ const scrollEl = timeline_refs_1.scrollableRef.current;
199
+ if (!scrollEl) {
200
+ return;
201
+ }
202
+ event.preventDefault();
203
+ const [t0, t1] = [event.touches[0], event.touches[1]];
204
+ const dist = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY);
205
+ const ratio = dist / pinch.initialDistance;
206
+ const clientX = (t0.clientX + t1.clientX) / 2;
207
+ const anchorContentX = (0, timeline_scroll_logic_1.viewportClientXToScrollContentX)({
208
+ clientX,
209
+ scrollEl,
210
+ });
211
+ setZoom(canvasContent.compositionId, () => pinch.initialZoom * ratio, {
212
+ anchorFrame: null,
213
+ anchorContentX,
214
+ });
215
+ };
216
+ const onTouchEnd = (event) => {
217
+ if (event.touches.length < 2) {
218
+ touchPinchRef.current = null;
219
+ }
220
+ };
221
+ el.addEventListener('touchstart', onTouchStart, { passive: true });
222
+ el.addEventListener('touchmove', onTouchMove, { passive: false });
223
+ el.addEventListener('touchend', onTouchEnd);
224
+ el.addEventListener('touchcancel', onTouchEnd);
225
+ return () => {
226
+ el.removeEventListener('touchstart', onTouchStart);
227
+ el.removeEventListener('touchmove', onTouchMove);
228
+ el.removeEventListener('touchend', onTouchEnd);
229
+ el.removeEventListener('touchcancel', onTouchEnd);
230
+ };
231
+ }, [
232
+ editorZoomGestures,
233
+ isVideoComposition,
234
+ videoConfig,
235
+ canvasContent,
236
+ setZoom,
237
+ ]);
238
+ return null;
239
+ };
240
+ exports.TimelinePinchZoom = TimelinePinchZoom;
@@ -5,6 +5,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const remotion_1 = require("remotion");
7
7
  const get_left_of_timeline_slider_1 = require("../../helpers/get-left-of-timeline-slider");
8
+ const timeline_zoom_1 = require("../../state/timeline-zoom");
8
9
  const imperative_state_1 = require("./imperative-state");
9
10
  const timeline_refs_1 = require("./timeline-refs");
10
11
  const TimelineSliderHandle_1 = require("./TimelineSliderHandle");
@@ -32,20 +33,33 @@ const TimelineSlider = () => {
32
33
  };
33
34
  exports.TimelineSlider = TimelineSlider;
34
35
  const Inner = () => {
36
+ var _a;
35
37
  const videoConfig = (0, remotion_1.useVideoConfig)();
36
38
  const timelinePosition = remotion_1.Internals.Timeline.useTimelinePosition();
37
39
  const ref = (0, react_1.useRef)(null);
38
40
  const timelineWidth = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
41
+ const { zoom: zoomMap } = (0, react_1.useContext)(timeline_zoom_1.TimelineZoomCtx);
42
+ const { canvasContent } = (0, react_1.useContext)(remotion_1.Internals.CompositionManager);
39
43
  if (timelineWidth === null) {
40
44
  throw new Error('Unexpectedly did not have timeline width');
41
45
  }
42
- const style = (0, react_1.useMemo)(() => {
43
- const left = (0, get_left_of_timeline_slider_1.getXPositionOfItemInTimelineImperatively)(timelinePosition, videoConfig.durationInFrames, timelineWidth);
44
- return {
45
- ...container,
46
- transform: `translateX(${left}px)`,
47
- };
48
- }, [timelinePosition, videoConfig.durationInFrames, timelineWidth]);
46
+ const zoomLevel = (canvasContent === null || canvasContent === void 0 ? void 0 : canvasContent.type) === 'composition'
47
+ ? ((_a = zoomMap[canvasContent.compositionId]) !== null && _a !== void 0 ? _a : timeline_zoom_1.TIMELINE_MIN_ZOOM)
48
+ : timeline_zoom_1.TIMELINE_MIN_ZOOM;
49
+ (0, react_1.useLayoutEffect)(() => {
50
+ var _a;
51
+ const el = ref.current;
52
+ const measuredWidth = (_a = timeline_refs_1.sliderAreaRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth;
53
+ if (!el || measuredWidth === undefined || measuredWidth === 0) {
54
+ return;
55
+ }
56
+ el.style.transform = `translateX(${(0, get_left_of_timeline_slider_1.getXPositionOfItemInTimelineImperatively)(timelinePosition, videoConfig.durationInFrames, measuredWidth)}px)`;
57
+ }, [
58
+ timelinePosition,
59
+ videoConfig.durationInFrames,
60
+ timelineWidth,
61
+ zoomLevel,
62
+ ]);
49
63
  (0, react_1.useImperativeHandle)(exports.redrawTimelineSliderFast, () => {
50
64
  return {
51
65
  draw: (frame, width) => {
@@ -76,7 +90,7 @@ const Inner = () => {
76
90
  current.removeEventListener('scroll', onScroll);
77
91
  };
78
92
  }, []);
79
- return (jsx_runtime_1.jsxs("div", { ref: ref, style: style, children: [
93
+ return (jsx_runtime_1.jsxs("div", { ref: ref, style: container, children: [
80
94
  jsx_runtime_1.jsx("div", { style: line }), jsx_runtime_1.jsx(TimelineSliderHandle_1.TimelineSliderHandle, {})
81
95
  ] }));
82
96
  };
@@ -151,11 +151,6 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore,
151
151
  const ref = (0, react_1.useRef)(null);
152
152
  const [error, setError] = (0, react_1.useState)(null);
153
153
  const aspectRatio = (0, react_1.useRef)((0, frame_database_1.getAspectRatioFromCache)(src));
154
- (0, react_1.useEffect)(() => {
155
- return () => {
156
- (0, frame_database_1.clearFramesForSrc)(src);
157
- };
158
- }, [src]);
159
154
  // for rendering frames
160
155
  (0, react_1.useEffect)(() => {
161
156
  if (error) {
@@ -201,11 +196,9 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore,
201
196
  if (unfilled.length === 0) {
202
197
  return () => {
203
198
  current.removeChild(canvas);
204
- (0, frame_database_1.clearOldFrames)();
205
199
  };
206
200
  }
207
201
  }
208
- (0, frame_database_1.clearOldFrames)();
209
202
  (0, extract_frames_1.extractFrames)({
210
203
  timestampsInSeconds: ({ track, }) => {
211
204
  aspectRatio.current = track.width / track.height;
@@ -234,14 +227,7 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore,
234
227
  }
235
228
  frame = undefined;
236
229
  const databaseKey = (0, frame_database_1.makeFrameDatabaseKey)(src, transformed.timestamp);
237
- const existingFrame = frame_database_1.frameDatabase.get(databaseKey);
238
- if (existingFrame) {
239
- existingFrame.frame.close();
240
- }
241
- frame_database_1.frameDatabase.set(databaseKey, {
242
- frame: transformed,
243
- lastUsed: Date.now(),
244
- });
230
+ (0, frame_database_1.addFrameToCache)(databaseKey, transformed);
245
231
  if (aspectRatio.current === null) {
246
232
  throw new Error('Aspect ratio is not set');
247
233
  }
@@ -288,9 +274,6 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore,
288
274
  })
289
275
  .catch((e) => {
290
276
  setError(e);
291
- })
292
- .finally(() => {
293
- (0, frame_database_1.clearOldFrames)();
294
277
  });
295
278
  return () => {
296
279
  controller.abort();
@@ -33,19 +33,22 @@ const TimelineZoomControls = () => {
33
33
  if (canvasContent === null || canvasContent.type !== 'composition') {
34
34
  return;
35
35
  }
36
- setZoom(canvasContent.compositionId, (z) => Math.max(timeline_zoom_1.TIMELINE_MIN_ZOOM, z - 0.2));
36
+ setZoom(canvasContent.compositionId, (z) => Math.max(timeline_zoom_1.TIMELINE_MIN_ZOOM, z - 0.2), { anchorFrame: null, anchorContentX: null });
37
37
  }, [canvasContent, setZoom]);
38
38
  const onPlusClicked = (0, react_1.useCallback)(() => {
39
39
  if (canvasContent === null || canvasContent.type !== 'composition') {
40
40
  return;
41
41
  }
42
- setZoom(canvasContent.compositionId, (z) => Math.min(timeline_zoom_1.TIMELINE_MAX_ZOOM, z + 0.2));
42
+ setZoom(canvasContent.compositionId, (z) => Math.min(timeline_zoom_1.TIMELINE_MAX_ZOOM, z + 0.2), { anchorFrame: null, anchorContentX: null });
43
43
  }, [canvasContent, setZoom]);
44
44
  const onChange = (0, react_1.useCallback)((e) => {
45
45
  if (canvasContent === null || canvasContent.type !== 'composition') {
46
46
  return;
47
47
  }
48
- setZoom(canvasContent.compositionId, () => Number(e.target.value));
48
+ setZoom(canvasContent.compositionId, () => Number(e.target.value), {
49
+ anchorFrame: null,
50
+ anchorContentX: null,
51
+ });
49
52
  }, [canvasContent, setZoom]);
50
53
  const isStill = (0, is_current_selected_still_1.useIsStill)();
51
54
  if (isStill ||
@@ -35,9 +35,21 @@ export declare const getFrameFromX: ({ clientX, durationInFrames, width, extrapo
35
35
  width: number;
36
36
  extrapolate: "clamp" | "extend";
37
37
  }) => number;
38
- export declare const zoomAndPreserveCursor: ({ oldZoom, newZoom, currentFrame, currentDurationInFrames, }: {
38
+ /**
39
+ * Horizontal position inside the scrollable timeline content (0 … scrollWidth)
40
+ * for a viewport `clientX`, so pinch-anchoring matches the pointer (not a
41
+ * rounded frame index).
42
+ */
43
+ export declare const viewportClientXToScrollContentX: ({ clientX, scrollEl, }: {
44
+ clientX: number;
45
+ scrollEl: HTMLDivElement;
46
+ }) => number;
47
+ export declare const zoomAndPreserveCursor: ({ oldZoom, newZoom, currentFrame, currentDurationInFrames, anchorFrame, anchorContentX, }: {
39
48
  oldZoom: number;
40
49
  newZoom: number;
41
50
  currentFrame: number;
42
51
  currentDurationInFrames: number;
52
+ anchorFrame: number | null;
53
+ /** Prefer this over `anchorFrame` when not null (subpixel-accurate anchor). */
54
+ anchorContentX: number | null;
43
55
  }) => void;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.zoomAndPreserveCursor = exports.getFrameFromX = exports.getFrameWhileScrollingRight = exports.getFrameIncrementFromWidth = exports.getScrollPositionForCursorOnRightEdge = exports.getScrollPositionForCursorOnLeftEdge = exports.scrollToTimelineXOffset = exports.ensureFrameIsInViewport = exports.isCursorInViewport = exports.getFrameWhileScrollingLeft = exports.canScrollTimelineIntoDirection = void 0;
3
+ exports.zoomAndPreserveCursor = exports.viewportClientXToScrollContentX = exports.getFrameFromX = exports.getFrameWhileScrollingRight = exports.getFrameIncrementFromWidth = exports.getScrollPositionForCursorOnRightEdge = exports.getScrollPositionForCursorOnLeftEdge = exports.scrollToTimelineXOffset = exports.ensureFrameIsInViewport = exports.isCursorInViewport = exports.getFrameWhileScrollingLeft = exports.canScrollTimelineIntoDirection = void 0;
4
4
  const remotion_1 = require("remotion");
5
5
  const timeline_layout_1 = require("../../helpers/timeline-layout");
6
6
  const timeline_refs_1 = require("./timeline-refs");
@@ -200,9 +200,18 @@ const getFrameFromX = ({ clientX, durationInFrames, width, extrapolate, }) => {
200
200
  return frame;
201
201
  };
202
202
  exports.getFrameFromX = getFrameFromX;
203
- const zoomAndPreserveCursor = ({ oldZoom, newZoom, currentFrame, currentDurationInFrames, }) => {
204
- var _a, _b;
205
- var _c;
203
+ /**
204
+ * Horizontal position inside the scrollable timeline content (0 … scrollWidth)
205
+ * for a viewport `clientX`, so pinch-anchoring matches the pointer (not a
206
+ * rounded frame index).
207
+ */
208
+ const viewportClientXToScrollContentX = ({ clientX, scrollEl, }) => {
209
+ const rect = scrollEl.getBoundingClientRect();
210
+ const clampedClientX = Math.min(Math.max(clientX, rect.left), rect.right);
211
+ return clampedClientX + scrollEl.scrollLeft - rect.left;
212
+ };
213
+ exports.viewportClientXToScrollContentX = viewportClientXToScrollContentX;
214
+ const zoomAndPreserveCursor = ({ oldZoom, newZoom, currentFrame, currentDurationInFrames, anchorFrame, anchorContentX, }) => {
206
215
  const ratio = newZoom / oldZoom;
207
216
  if (ratio === 1) {
208
217
  return;
@@ -212,9 +221,14 @@ const zoomAndPreserveCursor = ({ oldZoom, newZoom, currentFrame, currentDuration
212
221
  return;
213
222
  }
214
223
  const frameIncrement = getFrameIncrement(currentDurationInFrames);
215
- const prevCursorPosition = frameIncrement * currentFrame + timeline_layout_1.TIMELINE_PADDING;
224
+ const frameForScroll = anchorFrame !== null && anchorFrame !== void 0 ? anchorFrame : currentFrame;
225
+ const prevCursorPosition = anchorContentX !== null
226
+ ? Math.min(Math.max(anchorContentX, 0), current.scrollWidth)
227
+ : frameIncrement * frameForScroll + timeline_layout_1.TIMELINE_PADDING;
216
228
  const newCursorPosition = ratio * (prevCursorPosition - timeline_layout_1.TIMELINE_PADDING) + timeline_layout_1.TIMELINE_PADDING;
217
229
  current.scrollLeft += newCursorPosition - prevCursorPosition;
218
- (_a = TimelineSlider_1.redrawTimelineSliderFast.current) === null || _a === void 0 ? void 0 : _a.draw(currentFrame, ((_c = (_b = timeline_refs_1.scrollableRef.current) === null || _b === void 0 ? void 0 : _b.clientWidth) !== null && _c !== void 0 ? _c : 0) * ratio);
230
+ // Playhead position is synced in `TimelineSlider` `useLayoutEffect` using
231
+ // measured `sliderAreaRef.clientWidth` so it matches layout after zoom
232
+ // (avoids fighting React `style` with stale `timelineWidth` during pinch).
219
233
  };
220
234
  exports.zoomAndPreserveCursor = zoomAndPreserveCursor;
@@ -1,4 +1,4 @@
1
1
  import type { SequenceNodePath } from '@remotion/studio-shared';
2
2
  import type { TSequence } from 'remotion';
3
3
  import type { OriginalPosition } from '../../error-overlay/react-overlay/utils/get-source-map';
4
- export declare const useSequencePropsSubscription: (sequence: TSequence, originalLocation: OriginalPosition | null) => SequenceNodePath | null;
4
+ export declare const useSequencePropsSubscription: (sequence: TSequence, originalLocation: OriginalPosition | null, visualModeEnabled: boolean) => SequenceNodePath | null;
@@ -6,7 +6,7 @@ const remotion_1 = require("remotion");
6
6
  const client_id_1 = require("../../helpers/client-id");
7
7
  const timeline_layout_1 = require("../../helpers/timeline-layout");
8
8
  const call_api_1 = require("../call-api");
9
- const useSequencePropsSubscription = (sequence, originalLocation) => {
9
+ const useSequencePropsSubscription = (sequence, originalLocation, visualModeEnabled) => {
10
10
  var _a;
11
11
  var _b, _c, _d, _e;
12
12
  const { setCodeValues } = (0, react_1.useContext)(remotion_1.Internals.VisualModeOverridesContext);
@@ -44,7 +44,12 @@ const useSequencePropsSubscription = (sequence, originalLocation) => {
44
44
  const currentLocationColumn = (0, react_1.useRef)(locationColumn);
45
45
  currentLocationColumn.current = locationColumn;
46
46
  const nodePathRef = (0, react_1.useRef)(null);
47
+ const [nodePath, setNodePath] = (0, react_1.useState)(null);
47
48
  const isMountedRef = (0, react_1.useRef)(true);
49
+ const setNodePathBoth = (0, react_1.useCallback)((next) => {
50
+ nodePathRef.current = next;
51
+ setNodePath(next);
52
+ }, []);
48
53
  (0, react_1.useEffect)(() => {
49
54
  isMountedRef.current = true;
50
55
  return () => {
@@ -52,12 +57,18 @@ const useSequencePropsSubscription = (sequence, originalLocation) => {
52
57
  };
53
58
  }, []);
54
59
  (0, react_1.useEffect)(() => {
60
+ if (!visualModeEnabled) {
61
+ setPropStatusesForSequence(null);
62
+ setNodePathBoth(null);
63
+ return;
64
+ }
55
65
  if (!clientId ||
56
66
  !locationSource ||
57
67
  !locationLine ||
58
68
  locationColumn === null ||
59
69
  !schemaKeysString) {
60
70
  setPropStatusesForSequence(null);
71
+ setNodePathBoth(null);
61
72
  return;
62
73
  }
63
74
  const keys = schemaKeysString.split(',');
@@ -75,16 +86,16 @@ const useSequencePropsSubscription = (sequence, originalLocation) => {
75
86
  return;
76
87
  }
77
88
  if (result.canUpdate) {
78
- nodePathRef.current = result.nodePath;
89
+ setNodePathBoth(result.nodePath);
79
90
  setPropStatusesForSequence(result.props);
80
91
  }
81
92
  else {
82
- nodePathRef.current = null;
93
+ setNodePathBoth(null);
83
94
  setPropStatusesForSequence(null);
84
95
  }
85
96
  })
86
97
  .catch((err) => {
87
- nodePathRef.current = null;
98
+ setNodePathBoth(null);
88
99
  remotion_1.Internals.Log.error(err);
89
100
  setPropStatusesForSequence(null);
90
101
  });
@@ -95,7 +106,7 @@ const useSequencePropsSubscription = (sequence, originalLocation) => {
95
106
  if (!isMountedRef.current) {
96
107
  setPropStatusesForSequence(null);
97
108
  }
98
- nodePathRef.current = null;
109
+ setNodePathBoth(null);
99
110
  if (currentNodePath) {
100
111
  (0, call_api_1.callApi)('/api/unsubscribe-from-sequence-props', {
101
112
  fileName: locationSource,
@@ -107,14 +118,19 @@ const useSequencePropsSubscription = (sequence, originalLocation) => {
107
118
  }
108
119
  };
109
120
  }, [
121
+ visualModeEnabled,
110
122
  clientId,
111
123
  locationSource,
112
124
  locationLine,
113
125
  locationColumn,
114
126
  schemaKeysString,
115
127
  setPropStatusesForSequence,
128
+ setNodePathBoth,
116
129
  ]);
117
130
  (0, react_1.useEffect)(() => {
131
+ if (!visualModeEnabled) {
132
+ return;
133
+ }
118
134
  if (!locationSource || !locationLine || locationColumn === null) {
119
135
  return;
120
136
  }
@@ -132,6 +148,7 @@ const useSequencePropsSubscription = (sequence, originalLocation) => {
132
148
  }
133
149
  else {
134
150
  setPropStatusesForSequence(null);
151
+ setNodePathBoth(null);
135
152
  }
136
153
  };
137
154
  const unsub = subscribeToEvent('sequence-props-updated', listener);
@@ -139,12 +156,14 @@ const useSequencePropsSubscription = (sequence, originalLocation) => {
139
156
  unsub();
140
157
  };
141
158
  }, [
159
+ visualModeEnabled,
142
160
  locationSource,
143
161
  locationLine,
144
162
  locationColumn,
145
163
  subscribeToEvent,
146
164
  setPropStatusesForSequence,
165
+ setNodePathBoth,
147
166
  ]);
148
- return nodePathRef.current;
167
+ return nodePath;
149
168
  };
150
169
  exports.useSequencePropsSubscription = useSequencePropsSubscription;
@@ -48,11 +48,7 @@ const crashWithFrames = (crash) => (error) => {
48
48
  function listenToRuntimeErrors(crash) {
49
49
  const crashWithFramesRunTime = crashWithFrames(crash);
50
50
  (0, unhandled_error_1.register)(window, (error) => {
51
- return crashWithFramesRunTime({
52
- message: error.message,
53
- stack: error.stack,
54
- name: error.name,
55
- });
51
+ return crashWithFramesRunTime(error);
56
52
  });
57
53
  (0, unhandled_rejection_1.register)(window, (error) => {
58
54
  return crashWithFramesRunTime(error);
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ export declare const CopyStackTrace: React.FC<{
3
+ readonly canHaveKeyboardShortcuts: boolean;
4
+ readonly errorText: string;
5
+ }>;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CopyStackTrace = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const Button_1 = require("../../components/Button");
7
+ const use_keybinding_1 = require("../../helpers/use-keybinding");
8
+ const ShortcutHint_1 = require("./ShortcutHint");
9
+ const CopyStackTrace = ({ canHaveKeyboardShortcuts, errorText }) => {
10
+ const [copyState, setCopyState] = (0, react_1.useState)('idle');
11
+ const handleCopyToClipboard = (0, react_1.useCallback)(() => {
12
+ navigator.clipboard
13
+ .writeText(errorText)
14
+ .then(() => {
15
+ setCopyState('copied');
16
+ setTimeout(() => setCopyState('idle'), 2000);
17
+ })
18
+ .catch(() => {
19
+ setCopyState('failed');
20
+ setTimeout(() => setCopyState('idle'), 2000);
21
+ });
22
+ }, [errorText]);
23
+ const { registerKeybinding } = (0, use_keybinding_1.useKeybinding)();
24
+ (0, react_1.useEffect)(() => {
25
+ if (!canHaveKeyboardShortcuts) {
26
+ return;
27
+ }
28
+ const { unregister } = registerKeybinding({
29
+ event: 'keydown',
30
+ key: 't',
31
+ callback: handleCopyToClipboard,
32
+ commandCtrlKey: true,
33
+ preventDefault: true,
34
+ triggerIfInputFieldFocused: false,
35
+ keepRegisteredWhenNotHighestContext: false,
36
+ });
37
+ return () => unregister();
38
+ }, [canHaveKeyboardShortcuts, handleCopyToClipboard, registerKeybinding]);
39
+ const label = (0, react_1.useMemo)(() => {
40
+ if (copyState === 'copied') {
41
+ return 'Copied!';
42
+ }
43
+ if (copyState === 'failed') {
44
+ return 'Failed!';
45
+ }
46
+ return 'Copy Stacktrace';
47
+ }, [copyState]);
48
+ return (jsx_runtime_1.jsxs(Button_1.Button, { onClick: handleCopyToClipboard, children: [label, ' ', copyState === 'idle' && canHaveKeyboardShortcuts ? (jsx_runtime_1.jsx(ShortcutHint_1.ShortcutHint, { cmdOrCtrl: true, keyToPress: "t" })) : null] }));
49
+ };
50
+ exports.CopyStackTrace = CopyStackTrace;