@remotion/studio 4.0.500 → 4.0.502

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 (156) hide show
  1. package/dist/Studio.js +1 -1
  2. package/dist/api/delete-static-file.js +8 -3
  3. package/dist/api/rename-static-file.js +8 -0
  4. package/dist/api/watch-public-folder.js +2 -1
  5. package/dist/api/watch-static-file.js +2 -1
  6. package/dist/api/write-static-file.js +8 -3
  7. package/dist/components/AssetSelector.js +3 -1
  8. package/dist/components/AssetSelectorItem.d.ts +2 -1
  9. package/dist/components/AssetSelectorItem.js +7 -4
  10. package/dist/components/Canvas.js +9 -4
  11. package/dist/components/CaptionInspector.d.ts +10 -0
  12. package/dist/components/CaptionInspector.js +24 -0
  13. package/dist/components/CaptionTextEditor.d.ts +9 -0
  14. package/dist/components/CaptionTextEditor.js +132 -0
  15. package/dist/components/CompositionSelector.js +4 -4
  16. package/dist/components/CompositionSelectorItem.js +5 -5
  17. package/dist/components/CurrentAsset.js +3 -2
  18. package/dist/components/EditorContent.js +1 -1
  19. package/dist/components/EditorContexts.d.ts +0 -1
  20. package/dist/components/EditorContexts.js +2 -2
  21. package/dist/components/FpsCounter.js +3 -3
  22. package/dist/components/FullscreenToggle.d.ts +3 -1
  23. package/dist/components/FullscreenToggle.js +3 -2
  24. package/dist/components/GlobalKeybindings.js +2 -0
  25. package/dist/components/InitialCompositionLoader.d.ts +1 -1
  26. package/dist/components/InlineAction.js +2 -1
  27. package/dist/components/InlineCaptionInspector.d.ts +11 -0
  28. package/dist/components/InlineCaptionInspector.js +108 -0
  29. package/dist/components/InspectorPanel/CompositionInspector.js +5 -3
  30. package/dist/components/InspectorPanel/SelectedInspector.d.ts +1 -0
  31. package/dist/components/InspectorPanel/SelectedInspector.js +2 -2
  32. package/dist/components/InspectorPanel/SequenceSelectionInspector.d.ts +1 -0
  33. package/dist/components/InspectorPanel/SequenceSelectionInspector.js +4 -4
  34. package/dist/components/InspectorPanel/inspector-section-collapse.d.ts +22 -0
  35. package/dist/components/InspectorPanel/inspector-section-collapse.js +165 -0
  36. package/dist/components/InspectorPanel/styles.js +7 -1
  37. package/dist/components/InspectorPanel/use-composition-actions.d.ts +4 -1
  38. package/dist/components/InspectorPanel/use-composition-actions.js +135 -7
  39. package/dist/components/InspectorPanel.js +2 -2
  40. package/dist/components/InspectorSequenceSection.d.ts +2 -1
  41. package/dist/components/InspectorSequenceSection.js +238 -8
  42. package/dist/components/LoopToggle.d.ts +1 -0
  43. package/dist/components/LoopToggle.js +9 -5
  44. package/dist/components/Menu/MenuSubItem.js +9 -1
  45. package/dist/components/MenuToolbar.js +8 -1
  46. package/dist/components/Modals.js +1 -1
  47. package/dist/components/ObserveDefaultPropsContext.js +7 -4
  48. package/dist/components/PlayPause.d.ts +1 -0
  49. package/dist/components/PlayPause.js +2 -4
  50. package/dist/components/PlaybackRateSelector.d.ts +9 -2
  51. package/dist/components/PlaybackRateSelector.js +21 -10
  52. package/dist/components/PreviewToolbar.js +19 -109
  53. package/dist/components/PreviewToolbarOverflowButton.d.ts +12 -0
  54. package/dist/components/PreviewToolbarOverflowButton.js +217 -0
  55. package/dist/components/QuickSwitcher/ExplorerQuickSwitcherTrigger.js +1 -0
  56. package/dist/components/QuickSwitcher/QuickSwitcher.d.ts +6 -0
  57. package/dist/components/QuickSwitcher/QuickSwitcher.js +2 -2
  58. package/dist/components/QuickSwitcher/QuickSwitcherContent.d.ts +6 -0
  59. package/dist/components/QuickSwitcher/QuickSwitcherContent.js +42 -9
  60. package/dist/components/QuickSwitcher/QuickSwitcherResult.d.ts +2 -0
  61. package/dist/components/QuickSwitcher/QuickSwitcherResult.js +7 -1
  62. package/dist/components/RenderButton.d.ts +1 -0
  63. package/dist/components/RenderButton.js +6 -4
  64. package/dist/components/RenderQueue/ClientRenderQueueProcessor.d.ts +0 -1
  65. package/dist/components/RenderQueue/ClientRenderQueueProcessor.js +3 -14
  66. package/dist/components/RenderQueue/RenderQueueDownloadItem.js +2 -2
  67. package/dist/components/SelectedOutlineElement.js +1 -4
  68. package/dist/components/SelectedOutlineOverlay.js +17 -8
  69. package/dist/components/ShowOutlinesProvider.js +4 -1
  70. package/dist/components/SizeSelector.d.ts +6 -0
  71. package/dist/components/SizeSelector.js +17 -8
  72. package/dist/components/Splitter/SplitterContainer.d.ts +1 -0
  73. package/dist/components/Splitter/SplitterContainer.js +23 -3
  74. package/dist/components/Splitter/SplitterContext.d.ts +14 -0
  75. package/dist/components/Splitter/SplitterContext.js +16 -1
  76. package/dist/components/Splitter/SplitterHandle.js +8 -6
  77. package/dist/components/Timeline/Timeline.js +21 -6
  78. package/dist/components/Timeline/TimelineAssetField.d.ts +1 -1
  79. package/dist/components/Timeline/TimelineAssetField.js +37 -1
  80. package/dist/components/Timeline/TimelineDragHandler.js +1 -1
  81. package/dist/components/Timeline/TimelineExpandedKeyframeRow.js +8 -2
  82. package/dist/components/Timeline/TimelinePrimitiveFieldValue.d.ts +7 -2
  83. package/dist/components/Timeline/TimelinePrimitiveFieldValue.js +3 -1
  84. package/dist/components/Timeline/TimelineRowChrome.d.ts +3 -0
  85. package/dist/components/Timeline/TimelineRowChrome.js +4 -3
  86. package/dist/components/Timeline/TimelineSelection.d.ts +4 -2
  87. package/dist/components/Timeline/TimelineSelection.js +9 -6
  88. package/dist/components/Timeline/TimelineSequence.js +1 -4
  89. package/dist/components/Timeline/TimelineSequenceItem.js +4 -5
  90. package/dist/components/Timeline/TimelineSequencePropItem.js +9 -10
  91. package/dist/components/Timeline/TimelineStack/get-stack.js +8 -0
  92. package/dist/components/Timeline/TimelineTrack.js +4 -2
  93. package/dist/components/Timeline/border-radius-representation.d.ts +16 -0
  94. package/dist/components/Timeline/border-radius-representation.js +60 -0
  95. package/dist/components/Timeline/delete-selected-timeline-item.js +1 -6
  96. package/dist/components/Timeline/get-sequence-prop-reset-changes.d.ts +10 -0
  97. package/dist/components/Timeline/get-sequence-prop-reset-changes.js +33 -0
  98. package/dist/components/Timeline/reset-selected-timeline-props.js +2 -8
  99. package/dist/components/Timeline/save-sequence-prop.d.ts +12 -1
  100. package/dist/components/Timeline/save-sequence-prop.js +28 -4
  101. package/dist/components/Timeline/sequence-props-subscription-store.d.ts +2 -2
  102. package/dist/components/Timeline/sequence-props-subscription-store.js +3 -3
  103. package/dist/components/Timeline/timeline-field-display-utils.js +1 -0
  104. package/dist/components/Timeline/use-timeline-asset-drop.js +4 -0
  105. package/dist/components/TopPanel.js +1 -1
  106. package/dist/components/UndoRedoButtons.js +15 -6
  107. package/dist/components/UpdateCheck.js +11 -4
  108. package/dist/components/default-props-api.d.ts +3 -0
  109. package/dist/components/default-props-api.js +19 -0
  110. package/dist/components/drop-handler-data.d.ts +1 -1
  111. package/dist/components/drop-handler-data.js +6 -6
  112. package/dist/components/effect-drag-and-drop.d.ts +1 -1
  113. package/dist/components/effect-drag-and-drop.js +3 -3
  114. package/dist/components/element-drag-and-drop.d.ts +1 -1
  115. package/dist/components/element-drag-and-drop.js +3 -3
  116. package/dist/components/handle-drop.d.ts +3 -2
  117. package/dist/components/handle-drop.js +2 -1
  118. package/dist/components/import-assets.d.ts +8 -6
  119. package/dist/components/import-assets.js +20 -7
  120. package/dist/components/sequence-props-api.d.ts +4 -0
  121. package/dist/components/sequence-props-api.js +26 -0
  122. package/dist/components/use-element-overwrite-confirmation.d.ts +2 -0
  123. package/dist/components/use-element-overwrite-confirmation.js +73 -0
  124. package/dist/components/use-static-files.js +7 -2
  125. package/dist/error-overlay/react-overlay/effects/resolve-file-source.js +13 -2
  126. package/dist/error-overlay/remotion-overlay/ErrorDisplay.d.ts +1 -0
  127. package/dist/error-overlay/remotion-overlay/ErrorDisplay.js +29 -5
  128. package/dist/error-overlay/remotion-overlay/ErrorLoader.js +4 -20
  129. package/dist/esm/{chunk-dr1y1vm3.js → chunk-fjv75ty4.js} +8478 -6609
  130. package/dist/esm/index.mjs +24 -8
  131. package/dist/esm/internals.mjs +8478 -6609
  132. package/dist/esm/previewEntry.mjs +8535 -6666
  133. package/dist/esm/renderEntry.mjs +1 -1
  134. package/dist/helpers/browser-studio-operations.d.ts +3 -0
  135. package/dist/helpers/browser-studio-operations.js +14 -0
  136. package/dist/helpers/client-id.d.ts +0 -1
  137. package/dist/helpers/client-id.js +15 -10
  138. package/dist/helpers/download-blob.d.ts +1 -0
  139. package/dist/helpers/download-blob.js +15 -0
  140. package/dist/helpers/inject-css.js +5 -3
  141. package/dist/helpers/open-in-editor.js +26 -5
  142. package/dist/helpers/preview-server-events.js +46 -9
  143. package/dist/helpers/use-create-composition.js +8 -6
  144. package/dist/helpers/use-menu-structure.js +29 -0
  145. package/dist/hot-middleware-client/client.js +3 -3
  146. package/dist/icons/border-radius.d.ts +4 -0
  147. package/dist/icons/border-radius.js +8 -0
  148. package/dist/icons/fullscreen.d.ts +4 -0
  149. package/dist/icons/fullscreen.js +6 -0
  150. package/dist/icons/upload.d.ts +4 -0
  151. package/dist/icons/upload.js +8 -0
  152. package/dist/previewEntry.js +1 -1
  153. package/dist/state/modals.d.ts +5 -0
  154. package/dist/state/timeline-sequence-hover.d.ts +17 -0
  155. package/dist/state/timeline-sequence-hover.js +33 -0
  156. package/package.json +13 -12
@@ -212,7 +212,7 @@ var renderContent = (Root) => {
212
212
  renderToDOM(/* @__PURE__ */ jsx("div", {
213
213
  children: /* @__PURE__ */ jsx(DelayedSpinner, {})
214
214
  }));
215
- import("./chunk-dr1y1vm3.js").then(({ StudioInternals }) => {
215
+ import("./chunk-fjv75ty4.js").then(({ StudioInternals }) => {
216
216
  window.remotion_isStudio = true;
217
217
  window.remotion_isReadOnlyStudio = true;
218
218
  window.remotion_inputProps = "{}";
@@ -0,0 +1,3 @@
1
+ import type { BrowserStudioOperations } from '@remotion/studio-shared';
2
+ export declare const BROWSER_STUDIO_OPERATIONS_READY_EVENT = "remotion-browser-studio-operations-ready";
3
+ export declare const getBrowserStudioOperations: () => BrowserStudioOperations | null;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getBrowserStudioOperations = exports.BROWSER_STUDIO_OPERATIONS_READY_EVENT = void 0;
4
+ // Browser Studio may be hosted by a different studio-shared version, so this
5
+ // handshake intentionally has no shared runtime import.
6
+ exports.BROWSER_STUDIO_OPERATIONS_READY_EVENT = 'remotion-browser-studio-operations-ready';
7
+ const getBrowserStudioOperations = () => {
8
+ var _a;
9
+ if (typeof window === 'undefined') {
10
+ return null;
11
+ }
12
+ return (_a = window.remotion_browserStudio) !== null && _a !== void 0 ? _a : null;
13
+ };
14
+ exports.getBrowserStudioOperations = getBrowserStudioOperations;
@@ -8,6 +8,5 @@ type Context = {
8
8
  export declare const StudioServerConnectionCtx: React.Context<Context>;
9
9
  export declare const PreviewServerConnection: React.FC<{
10
10
  readonly children: React.ReactNode;
11
- readonly readOnlyStudio: boolean;
12
11
  }>;
13
12
  export {};
@@ -53,10 +53,14 @@ exports.StudioServerConnectionCtx = react_1.default.createContext({
53
53
  throw new Error('Context not initalized');
54
54
  },
55
55
  });
56
- const PreviewServerConnection = ({ children, readOnlyStudio }) => {
56
+ const PreviewServerConnection = ({ children }) => {
57
57
  const listeners = (0, react_1.useRef)([]);
58
+ const latestUndoRedoEvent = (0, react_1.useRef)(undefined);
58
59
  const subscribeToEvent = (0, react_1.useCallback)((type, listener) => {
59
60
  listeners.current.push({ type, listener });
61
+ if (type === 'undo-redo-stack-changed' && latestUndoRedoEvent.current) {
62
+ listener(latestUndoRedoEvent.current);
63
+ }
60
64
  return () => {
61
65
  listeners.current = listeners.current.filter((l) => l.type !== type || l.listener !== listener);
62
66
  };
@@ -65,9 +69,6 @@ const PreviewServerConnection = ({ children, readOnlyStudio }) => {
65
69
  type: 'init',
66
70
  });
67
71
  (0, react_1.useEffect)(() => {
68
- if (readOnlyStudio) {
69
- return;
70
- }
71
72
  const handleEvent = (newEvent) => {
72
73
  var _a, _b;
73
74
  if (newEvent.type === 'new-input-props' ||
@@ -76,16 +77,20 @@ const PreviewServerConnection = ({ children, readOnlyStudio }) => {
76
77
  (0, url_state_1.reloadUrl)();
77
78
  }
78
79
  if (newEvent.type === 'init') {
80
+ latestUndoRedoEvent.current = {
81
+ type: 'undo-redo-stack-changed',
82
+ undoFile: newEvent.undoFile,
83
+ redoFile: newEvent.redoFile,
84
+ };
79
85
  listeners.current.forEach((l) => {
80
86
  if (l.type === 'undo-redo-stack-changed') {
81
- l.listener({
82
- type: 'undo-redo-stack-changed',
83
- undoFile: newEvent.undoFile,
84
- redoFile: newEvent.redoFile,
85
- });
87
+ l.listener(latestUndoRedoEvent.current);
86
88
  }
87
89
  });
88
90
  }
91
+ if (newEvent.type === 'undo-redo-stack-changed') {
92
+ latestUndoRedoEvent.current = newEvent;
93
+ }
89
94
  if (newEvent.type === 'render-queue-updated') {
90
95
  (_a = context_1.renderJobsRef.current) === null || _a === void 0 ? void 0 : _a.updateRenderJobs(newEvent.queue);
91
96
  for (const job of newEvent.queue) {
@@ -122,7 +127,7 @@ const PreviewServerConnection = ({ children, readOnlyStudio }) => {
122
127
  unsubscribeFromEvents();
123
128
  unsubscribeFromConnectionState();
124
129
  };
125
- }, [readOnlyStudio]);
130
+ }, []);
126
131
  const context = (0, react_1.useMemo)(() => {
127
132
  return {
128
133
  previewServerState: state,
@@ -0,0 +1 @@
1
+ export declare const downloadBlob: (blob: Blob, filename: string) => void;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.downloadBlob = void 0;
4
+ const downloadBlob = (blob, filename) => {
5
+ const url = URL.createObjectURL(blob);
6
+ const a = document.createElement('a');
7
+ a.href = url;
8
+ const cleanFilename = filename.includes('/')
9
+ ? filename.substring(filename.lastIndexOf('/') + 1)
10
+ : filename;
11
+ a.download = cleanFilename;
12
+ a.click();
13
+ URL.revokeObjectURL(url);
14
+ };
15
+ exports.downloadBlob = downloadBlob;
@@ -83,20 +83,22 @@ const makeDefaultGlobalCSS = () => {
83
83
 
84
84
  input:focus,
85
85
  textarea:focus,
86
- button:focus:not(.__remotion_input_dragger):not(.__remotion_color_swatch),
86
+ button:focus:not(.__remotion_input_dragger):not(.__remotion_color_swatch):not(.__remotion-inspector-section-title),
87
87
  a:focus {
88
88
  outline: none;
89
89
  box-shadow: ${colors_1.FOCUS_BOX_SHADOW};
90
90
  }
91
91
 
92
92
  .__remotion-composition-selector-item:focus,
93
- .__remotion-inspector-inline-action:focus {
93
+ .__remotion-inspector-inline-action:focus,
94
+ .__remotion-inspector-section-title:focus {
94
95
  outline: none;
95
96
  box-shadow: none;
96
97
  }
97
98
 
98
99
  .__remotion-composition-selector-item:focus-visible,
99
- .__remotion-inspector-inline-action:focus-visible {
100
+ .__remotion-inspector-inline-action:focus-visible,
101
+ .__remotion-inspector-section-title:focus-visible {
100
102
  box-shadow: ${colors_1.FOCUS_BOX_SHADOW};
101
103
  }
102
104
 
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.openCompositionComponentInEditor = exports.preloadCompositionComponentInfo = exports.loadCompositionComponentInfo = exports.useCachedCompositionComponentInfo = exports.getCachedCompositionComponentInfo = exports.subscribeToCompositionComponentInfo = exports.openOriginalPositionInEditorAtProperty = exports.openOriginalPositionInEditor = exports.openInEditor = void 0;
4
4
  const react_1 = require("react");
5
5
  const call_api_1 = require("../components/call-api");
6
+ const browser_studio_operations_1 = require("./browser-studio-operations");
6
7
  const openInEditor = (stack) => {
7
8
  const { originalFileName, originalLineNumber, originalColumnNumber, originalFunctionName, originalScriptCode, } = stack;
8
9
  return (0, call_api_1.callApi)('/api/open-in-editor', {
@@ -27,12 +28,16 @@ const openOriginalPositionInEditor = async (originalPosition) => {
27
28
  };
28
29
  exports.openOriginalPositionInEditor = openOriginalPositionInEditor;
29
30
  const openOriginalPositionInEditorAtProperty = async ({ originalPosition, property, }) => {
30
- const position = await (0, call_api_1.callApi)('/api/find-in-file', {
31
+ const request = {
31
32
  fileName: originalPosition.source,
32
33
  lineNumber: originalPosition.line,
33
34
  columnNumber: originalPosition.column,
34
35
  search: property,
35
- });
36
+ };
37
+ const browserStudioOperations = (0, browser_studio_operations_1.getBrowserStudioOperations)();
38
+ const position = browserStudioOperations
39
+ ? await browserStudioOperations.findInFile(request)
40
+ : await (0, call_api_1.callApi)('/api/find-in-file', request);
36
41
  await (0, exports.openOriginalPositionInEditor)({
37
42
  source: originalPosition.source,
38
43
  line: position.lineNumber,
@@ -64,7 +69,7 @@ const getCachedCompositionComponentInfo = ({ compositionFile, compositionId, })
64
69
  };
65
70
  exports.getCachedCompositionComponentInfo = getCachedCompositionComponentInfo;
66
71
  const useCachedCompositionComponentInfo = ({ compositionFile, compositionId, }) => {
67
- return (0, react_1.useSyncExternalStore)(exports.subscribeToCompositionComponentInfo, () => {
72
+ const result = (0, react_1.useSyncExternalStore)(exports.subscribeToCompositionComponentInfo, () => {
68
73
  if (compositionFile === null || compositionId === null) {
69
74
  return null;
70
75
  }
@@ -73,6 +78,18 @@ const useCachedCompositionComponentInfo = ({ compositionFile, compositionId, })
73
78
  compositionId,
74
79
  });
75
80
  }, () => null);
81
+ (0, react_1.useEffect)(() => {
82
+ if (!(0, browser_studio_operations_1.getBrowserStudioOperations)() ||
83
+ compositionFile === null ||
84
+ compositionId === null) {
85
+ return;
86
+ }
87
+ (0, exports.preloadCompositionComponentInfo)({
88
+ compositionFile,
89
+ compositionId,
90
+ });
91
+ }, [compositionFile, compositionId]);
92
+ return result;
76
93
  };
77
94
  exports.useCachedCompositionComponentInfo = useCachedCompositionComponentInfo;
78
95
  const loadCompositionComponentInfo = async ({ compositionFile, compositionId, }) => {
@@ -85,10 +102,14 @@ const loadCompositionComponentInfo = async ({ compositionFile, compositionId, })
85
102
  return existing;
86
103
  }
87
104
  const promise = (async () => {
88
- const body = await (0, call_api_1.callApi)('/api/composition-component-info', {
105
+ const request = {
89
106
  compositionFile,
90
107
  compositionId,
91
- });
108
+ };
109
+ const browserStudioOperations = (0, browser_studio_operations_1.getBrowserStudioOperations)();
110
+ const body = browserStudioOperations
111
+ ? await browserStudioOperations.getCompositionComponentInfo(request)
112
+ : await (0, call_api_1.callApi)('/api/composition-component-info', request);
92
113
  const result = {
93
114
  location: body.location,
94
115
  canAddSequence: body.canAddSequence,
@@ -3,9 +3,13 @@
3
3
  /// <reference lib="dom.iterable" />
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.subscribeToPreviewServerConnectionState = exports.subscribeToPreviewServerEvents = exports.ensurePreviewServerEventSource = void 0;
6
+ const browser_studio_operations_1 = require("./browser-studio-operations");
6
7
  let source = null;
8
+ let unsubscribeFromBrowserStudio = null;
9
+ let browserStudioReadyListenerInstalled = false;
7
10
  let connectionState = { type: 'init' };
8
11
  let lastInitEvent = null;
12
+ let pendingHmrEvent = null;
9
13
  const messageListeners = new Set();
10
14
  const connectionStateListeners = new Set();
11
15
  const notifyConnectionState = () => {
@@ -14,10 +18,37 @@ const notifyConnectionState = () => {
14
18
  }
15
19
  };
16
20
  const dispatch = (event) => {
21
+ if (event.type === 'init') {
22
+ lastInitEvent = event;
23
+ connectionState = {
24
+ type: 'connected',
25
+ clientId: event.clientId,
26
+ };
27
+ notifyConnectionState();
28
+ }
29
+ if (event.type === 'hmr' && messageListeners.size === 0) {
30
+ pendingHmrEvent = event;
31
+ }
17
32
  for (const listener of messageListeners) {
18
33
  listener(event);
19
34
  }
20
35
  };
36
+ const connectToBrowserStudio = () => {
37
+ if (unsubscribeFromBrowserStudio) {
38
+ return true;
39
+ }
40
+ const browserStudioOperations = (0, browser_studio_operations_1.getBrowserStudioOperations)();
41
+ if (!browserStudioOperations) {
42
+ return false;
43
+ }
44
+ if (source) {
45
+ source.close();
46
+ source = null;
47
+ }
48
+ unsubscribeFromBrowserStudio =
49
+ browserStudioOperations.subscribeToEvent(dispatch);
50
+ return true;
51
+ };
21
52
  const openEventSource = () => {
22
53
  if (source) {
23
54
  return;
@@ -26,14 +57,6 @@ const openEventSource = () => {
26
57
  source.addEventListener('message', (event) => {
27
58
  try {
28
59
  const newEvent = JSON.parse(event.data);
29
- if (newEvent.type === 'init') {
30
- lastInitEvent = newEvent;
31
- connectionState = {
32
- type: 'connected',
33
- clientId: newEvent.clientId,
34
- };
35
- notifyConnectionState();
36
- }
37
60
  dispatch(newEvent);
38
61
  }
39
62
  catch (_a) {
@@ -53,7 +76,17 @@ const openEventSource = () => {
53
76
  });
54
77
  };
55
78
  const ensurePreviewServerEventSource = () => {
56
- if (typeof window === 'undefined' || typeof EventSource === 'undefined') {
79
+ if (typeof window === 'undefined') {
80
+ return;
81
+ }
82
+ if (!browserStudioReadyListenerInstalled) {
83
+ browserStudioReadyListenerInstalled = true;
84
+ window.addEventListener(browser_studio_operations_1.BROWSER_STUDIO_OPERATIONS_READY_EVENT, connectToBrowserStudio);
85
+ }
86
+ if (connectToBrowserStudio()) {
87
+ return;
88
+ }
89
+ if (typeof EventSource === 'undefined') {
57
90
  return;
58
91
  }
59
92
  openEventSource();
@@ -65,6 +98,10 @@ const subscribeToPreviewServerEvents = (listener) => {
65
98
  if (lastInitEvent && connectionState.type === 'connected') {
66
99
  listener(lastInitEvent);
67
100
  }
101
+ if (pendingHmrEvent) {
102
+ listener(pendingHmrEvent);
103
+ pendingHmrEvent = null;
104
+ }
68
105
  return () => {
69
106
  messageListeners.delete(listener);
70
107
  };
@@ -2,9 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useCreateComposition = exports.getUniqueCompositionName = void 0;
4
4
  const react_1 = require("react");
5
- const remotion_1 = require("remotion");
5
+ const InitialCompositionLoader_1 = require("../components/InitialCompositionLoader");
6
6
  const actions_1 = require("../components/RenderQueue/actions");
7
- const url_state_1 = require("./url-state");
8
7
  const validate_new_comp_data_1 = require("./validate-new-comp-data");
9
8
  const toPascalCase = (value) => {
10
9
  var _a;
@@ -48,7 +47,7 @@ const getUniqueCompositionName = (compositions) => {
48
47
  };
49
48
  exports.getUniqueCompositionName = getUniqueCompositionName;
50
49
  const useCreateComposition = ({ compositions, durationInFrames, folderName, newId, parentName, selectedFrameRate, size, }) => {
51
- const { setCanvasContent } = (0, react_1.useContext)(remotion_1.Internals.CompositionSetters);
50
+ const selectComposition = (0, InitialCompositionLoader_1.useSelectComposition)();
52
51
  const componentName = (0, react_1.useMemo)(() => toPascalCase(newId), [newId]);
53
52
  const nameValidationMessage = (0, react_1.useMemo)(() => {
54
53
  return (0, validate_new_comp_data_1.validateCompositionName)(newId, compositions);
@@ -94,11 +93,14 @@ const useCreateComposition = ({ compositions, durationInFrames, folderName, newI
94
93
  });
95
94
  if (result.success) {
96
95
  await waitForComposition(newId);
97
- setCanvasContent({ type: 'composition', compositionId: newId });
98
- (0, url_state_1.pushUrl)(`/${newId}`);
96
+ selectComposition({
97
+ id: newId,
98
+ folderName,
99
+ parentFolderName: parentName,
100
+ }, true);
99
101
  }
100
102
  return result;
101
- }, [codemod, newId, setCanvasContent]);
103
+ }, [codemod, folderName, newId, parentName, selectComposition]);
102
104
  return {
103
105
  codemod,
104
106
  createComposition,
@@ -24,9 +24,11 @@ const editor_snapping_1 = require("../state/editor-snapping");
24
24
  const editor_zoom_gestures_1 = require("../state/editor-zoom-gestures");
25
25
  const modals_1 = require("../state/modals");
26
26
  const sidebar_1 = require("../state/sidebar");
27
+ const browser_studio_operations_1 = require("./browser-studio-operations");
27
28
  const check_fullscreen_support_1 = require("./check-fullscreen-support");
28
29
  const client_id_1 = require("./client-id");
29
30
  const colors_1 = require("./colors");
31
+ const download_blob_1 = require("./download-blob");
30
32
  const get_file_manager_name_1 = require("./get-file-manager-name");
31
33
  const get_git_menu_item_1 = require("./get-git-menu-item");
32
34
  const mobile_layout_1 = require("./mobile-layout");
@@ -50,6 +52,8 @@ const iconPath = {
50
52
  const ICON_SIZE = 16;
51
53
  const getFileMenu = ({ readOnlyStudio, closeMenu, previewServerState, setSelectedModal, }) => {
52
54
  const fileManagerName = (0, get_file_manager_name_1.getFileManagerName)(window.remotion_fileSystemPlatform);
55
+ const browserStudioOperations = (0, browser_studio_operations_1.getBrowserStudioOperations)();
56
+ const downloadProject = browserStudioOperations === null || browserStudioOperations === void 0 ? void 0 : browserStudioOperations.downloadProject;
53
57
  const items = [
54
58
  readOnlyStudio
55
59
  ? null
@@ -132,6 +136,29 @@ const getFileMenu = ({ readOnlyStudio, closeMenu, previewServerState, setSelecte
132
136
  subMenu: null,
133
137
  quickSwitcherLabel: 'Render on web...',
134
138
  },
139
+ downloadProject
140
+ ? {
141
+ id: 'download-project',
142
+ value: 'download-project',
143
+ label: 'Download project',
144
+ onClick: async () => {
145
+ closeMenu();
146
+ try {
147
+ const { data, fileName } = await downloadProject();
148
+ const arrayBuffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
149
+ (0, download_blob_1.downloadBlob)(new Blob([arrayBuffer], { type: 'application/zip' }), fileName);
150
+ }
151
+ catch (error) {
152
+ (0, NotificationCenter_1.showNotification)(`Could not download project: ${error instanceof Error ? error.message : String(error)}`, 2000);
153
+ }
154
+ },
155
+ type: 'item',
156
+ keyHint: null,
157
+ leftItem: null,
158
+ subMenu: null,
159
+ quickSwitcherLabel: 'Download project',
160
+ }
161
+ : null,
135
162
  !readOnlyStudio
136
163
  ? {
137
164
  type: 'divider',
@@ -582,6 +609,7 @@ const useMenuStructure = (closeMenu, readOnlyStudio) => {
582
609
  mode: 'compositions',
583
610
  invocationTimestamp: Date.now(),
584
611
  assetSelection: null,
612
+ compositionSelection: null,
585
613
  });
586
614
  },
587
615
  type: 'item',
@@ -771,6 +799,7 @@ const useMenuStructure = (closeMenu, readOnlyStudio) => {
771
799
  mode: 'docs',
772
800
  invocationTimestamp: Date.now(),
773
801
  assetSelection: null,
802
+ compositionSelection: null,
774
803
  });
775
804
  },
776
805
  keyHint: '?',
@@ -107,9 +107,9 @@ const enableHotMiddleware = () => {
107
107
  window.__remotion_processHmrEvent = (hmrEvent) => {
108
108
  processMessage(hmrEvent);
109
109
  };
110
- // Connect to /events immediately so HMR works before React mounts.
111
- // PreviewServerConnection reuses the same EventSource via preview-server-events.
112
- if (typeof window !== 'undefined' && typeof EventSource !== 'undefined') {
110
+ // Connect immediately so HMR works before React mounts. Browser Studio and
111
+ // the preview server share the same transport-neutral event subscription.
112
+ if (typeof window !== 'undefined') {
113
113
  if (!unsubscribeFromPreviewServerEvents) {
114
114
  unsubscribeFromPreviewServerEvents = (0, preview_server_events_1.subscribeToPreviewServerEvents)((event) => {
115
115
  if (event.type === 'hmr') {
@@ -0,0 +1,4 @@
1
+ import type { SVGProps } from 'react';
2
+ export declare const BorderRadiusIcon: ({ color, ...props }: SVGProps<SVGSVGElement> & {
3
+ readonly color: string;
4
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BorderRadiusIcon = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const BorderRadiusIcon = ({ color, ...props }) => {
6
+ return (jsx_runtime_1.jsx("svg", { viewBox: "0 0 16 16", fill: "none", ...props, children: jsx_runtime_1.jsx("path", { d: "M2.5 3.5H6.5C10.4 3.5 12.5 5.6 12.5 9.5V13.5", stroke: color, strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2" }) }));
7
+ };
8
+ exports.BorderRadiusIcon = BorderRadiusIcon;
@@ -0,0 +1,4 @@
1
+ import type { SVGProps } from 'react';
2
+ export declare const FullscreenIcon: ({ color, ...props }: SVGProps<SVGSVGElement> & {
3
+ readonly color: string;
4
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FullscreenIcon = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const FullscreenIcon = ({ color, ...props }) => (jsx_runtime_1.jsx("svg", { viewBox: "0 0 448 512", ...props, children: jsx_runtime_1.jsx("path", { fill: color, d: "M0 180V56c0-13.3 10.7-24 24-24h124c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12H64v84c0 6.6-5.4 12-12 12H12c-6.6 0-12-5.4-12-12zM288 44v40c0 6.6 5.4 12 12 12h84v84c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12V56c0-13.3-10.7-24-24-24H300c-6.6 0-12 5.4-12 12zm148 276h-40c-6.6 0-12 5.4-12 12v84h-84c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h124c13.3 0 24-10.7 24-24V332c0-6.6-5.4-12-12-12zM160 468v-40c0-6.6-5.4-12-12-12H64v-84c0-6.6-5.4-12-12-12H12c-6.6 0-12 5.4-12 12v124c0 13.3 10.7 24 24 24h124c6.6 0 12-5.4 12-12z" }) }));
6
+ exports.FullscreenIcon = FullscreenIcon;
@@ -0,0 +1,4 @@
1
+ import type { SVGProps } from 'react';
2
+ export declare const UploadIcon: React.FC<SVGProps<SVGSVGElement> & {
3
+ readonly color: string;
4
+ }>;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UploadIcon = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const UploadIcon = ({ color, ...props }) => {
6
+ return (jsx_runtime_1.jsx("svg", { ...props, xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 640 640", children: jsx_runtime_1.jsx("path", { fill: color, d: "M192 96L320 96L320 192C320 227.3 348.7 256 384 256L480 256L480 512C480 529.7 465.7 544 448 544L192 544C174.3 544 160 529.7 160 512L160 128C160 110.3 174.3 96 192 96zM352 109.3L466.7 224L384 224C366.3 224 352 209.7 352 192L352 109.3zM192 64C156.7 64 128 92.7 128 128L128 512C128 547.3 156.7 576 192 576L448 576C483.3 576 512 547.3 512 512L512 250.5C512 233.5 505.3 217.2 493.3 205.2L370.7 82.7C358.7 70.7 342.5 64 325.5 64L192 64zM304 488C304 496.8 311.2 504 320 504C328.8 504 336 496.8 336 488L336 432L392 432C400.8 432 408 424.8 408 416C408 407.2 400.8 400 392 400L336 400L336 344C336 335.2 328.8 328 320 328C311.2 328 304 335.2 304 344L304 400L248 400C239.2 400 232 407.2 232 416C232 424.8 239.2 432 248 432L304 432L304 488z" }) }));
7
+ };
8
+ exports.UploadIcon = UploadIcon;
@@ -45,5 +45,5 @@ const renderToDOM = (content) => {
45
45
  };
46
46
  renderToDOM(jsx_runtime_1.jsx(NoRegisterRoot_1.NoRegisterRoot, {}));
47
47
  remotion_1.Internals.waitForRoot((NewRoot) => {
48
- renderToDOM(jsx_runtime_1.jsx(Studio_1.Studio, { readOnly: false, rootComponent: NewRoot }));
48
+ renderToDOM(jsx_runtime_1.jsx(Studio_1.Studio, { readOnly: window.remotion_isReadOnlyStudio, rootComponent: NewRoot }));
49
49
  });
@@ -162,8 +162,13 @@ export type ModalState = {
162
162
  invocationTimestamp: number;
163
163
  assetSelection: {
164
164
  initialQuery: string;
165
+ onSelectFile: () => void;
165
166
  onSelected: (asset: StaticFile) => void;
166
167
  } | null;
168
+ compositionSelection: {
169
+ excludeCompositionId: string;
170
+ onSelected: (composition: _InternalTypes['AnyComposition']) => void;
171
+ } | null;
167
172
  } | AddEffectModalState | ConfirmationDialogState | SvgImportDialogState;
168
173
  export type ModalContextType = {
169
174
  selectedModal: ModalState | null;
@@ -0,0 +1,17 @@
1
+ import type { Dispatch, SetStateAction } from 'react';
2
+ import type { SequenceNodePathInfo } from '../helpers/get-timeline-sequence-sort-key';
3
+ export type TimelineSequenceHover = {
4
+ readonly key: string;
5
+ readonly source: 'canvas' | 'timeline';
6
+ };
7
+ type TimelineSequenceHoverState = {
8
+ readonly hoveredSequence: TimelineSequenceHover | null;
9
+ readonly setHoveredSequence: Dispatch<SetStateAction<TimelineSequenceHover | null>>;
10
+ };
11
+ export declare const TimelineSequenceHoverContext: import("react").Context<TimelineSequenceHoverState>;
12
+ export declare const useTimelineSequenceHover: (nodePathInfo: SequenceNodePathInfo | null) => {
13
+ hovered: boolean;
14
+ onPointerEnter: () => void;
15
+ onPointerLeave: () => void;
16
+ };
17
+ export {};
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useTimelineSequenceHover = exports.TimelineSequenceHoverContext = void 0;
4
+ const react_1 = require("react");
5
+ const timeline_node_path_key_1 = require("../helpers/timeline-node-path-key");
6
+ exports.TimelineSequenceHoverContext = (0, react_1.createContext)({
7
+ hoveredSequence: null,
8
+ setHoveredSequence: () => undefined,
9
+ });
10
+ const useTimelineSequenceHover = (nodePathInfo) => {
11
+ const { hoveredSequence, setHoveredSequence } = (0, react_1.useContext)(exports.TimelineSequenceHoverContext);
12
+ const sequenceKey = (0, react_1.useMemo)(() => nodePathInfo === null
13
+ ? null
14
+ : (0, timeline_node_path_key_1.timelineNodePathInfoToKey)({ ...nodePathInfo, auxiliaryKeys: [] }), [nodePathInfo]);
15
+ const onPointerEnter = (0, react_1.useCallback)(() => {
16
+ if (sequenceKey === null) {
17
+ return;
18
+ }
19
+ setHoveredSequence({ key: sequenceKey, source: 'timeline' });
20
+ }, [sequenceKey, setHoveredSequence]);
21
+ const onPointerLeave = (0, react_1.useCallback)(() => {
22
+ setHoveredSequence((currentHover) => {
23
+ if ((currentHover === null || currentHover === void 0 ? void 0 : currentHover.source) !== 'timeline' ||
24
+ currentHover.key !== sequenceKey) {
25
+ return currentHover;
26
+ }
27
+ return null;
28
+ });
29
+ }, [sequenceKey, setHoveredSequence]);
30
+ const hovered = sequenceKey !== null && (hoveredSequence === null || hoveredSequence === void 0 ? void 0 : hoveredSequence.key) === sequenceKey;
31
+ return (0, react_1.useMemo)(() => ({ hovered, onPointerEnter, onPointerLeave }), [hovered, onPointerEnter, onPointerLeave]);
32
+ };
33
+ exports.useTimelineSequenceHover = useTimelineSequenceHover;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/studio"
4
4
  },
5
5
  "name": "@remotion/studio",
6
- "version": "4.0.500",
6
+ "version": "4.0.502",
7
7
  "description": "APIs for interacting with the Remotion Studio",
8
8
  "main": "dist",
9
9
  "scripts": {
@@ -24,17 +24,18 @@
24
24
  "react-dom": ">=16.8.0"
25
25
  },
26
26
  "dependencies": {
27
- "@remotion/drag-and-drop": "4.0.500",
27
+ "@remotion/studio-protocol": "4.0.502",
28
28
  "semver": "7.5.3",
29
- "remotion": "4.0.500",
30
- "@remotion/canvas-capture": "4.0.500",
31
- "@remotion/player": "4.0.500",
32
- "@remotion/media-utils": "4.0.500",
33
- "@remotion/renderer": "4.0.500",
34
- "@remotion/web-renderer": "4.0.500",
35
- "@remotion/studio-shared": "4.0.500",
36
- "@remotion/timeline-utils": "4.0.500",
37
- "@remotion/zod-types": "4.0.500",
29
+ "remotion": "4.0.502",
30
+ "@remotion/canvas-capture": "4.0.502",
31
+ "@remotion/captions": "4.0.502",
32
+ "@remotion/player": "4.0.502",
33
+ "@remotion/media-utils": "4.0.502",
34
+ "@remotion/renderer": "4.0.502",
35
+ "@remotion/web-renderer": "4.0.502",
36
+ "@remotion/studio-shared": "4.0.502",
37
+ "@remotion/timeline-utils": "4.0.502",
38
+ "@remotion/zod-types": "4.0.502",
38
39
  "@jridgewell/trace-mapping": "0.3.31",
39
40
  "mediabunny": "1.50.8",
40
41
  "memfs": "3.4.3",
@@ -45,7 +46,7 @@
45
46
  "react": "19.2.3",
46
47
  "react-dom": "19.2.3",
47
48
  "@types/semver": "7.5.3",
48
- "@remotion/eslint-config-internal": "4.0.500",
49
+ "@remotion/eslint-config-internal": "4.0.502",
49
50
  "eslint": "9.19.0",
50
51
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
51
52
  },