@remotion/studio 4.0.500 → 4.0.501

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 (108) 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 +5 -2
  10. package/dist/components/Canvas.js +6 -1
  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/CurrentAsset.js +3 -2
  16. package/dist/components/EditorContexts.d.ts +0 -1
  17. package/dist/components/EditorContexts.js +2 -2
  18. package/dist/components/FpsCounter.js +3 -3
  19. package/dist/components/FullscreenToggle.js +2 -1
  20. package/dist/components/GlobalKeybindings.js +2 -0
  21. package/dist/components/InitialCompositionLoader.d.ts +1 -1
  22. package/dist/components/InlineAction.js +1 -0
  23. package/dist/components/InlineCaptionInspector.d.ts +11 -0
  24. package/dist/components/InlineCaptionInspector.js +108 -0
  25. package/dist/components/InspectorPanel/CompositionInspector.js +5 -3
  26. package/dist/components/InspectorPanel/SelectedInspector.d.ts +1 -0
  27. package/dist/components/InspectorPanel/SelectedInspector.js +2 -2
  28. package/dist/components/InspectorPanel/SequenceSelectionInspector.d.ts +1 -0
  29. package/dist/components/InspectorPanel/SequenceSelectionInspector.js +4 -4
  30. package/dist/components/InspectorPanel/inspector-section-collapse.d.ts +22 -0
  31. package/dist/components/InspectorPanel/inspector-section-collapse.js +165 -0
  32. package/dist/components/InspectorPanel/styles.js +7 -1
  33. package/dist/components/InspectorPanel/use-composition-actions.d.ts +3 -0
  34. package/dist/components/InspectorPanel/use-composition-actions.js +91 -6
  35. package/dist/components/InspectorPanel.js +2 -2
  36. package/dist/components/InspectorSequenceSection.d.ts +2 -1
  37. package/dist/components/InspectorSequenceSection.js +238 -8
  38. package/dist/components/MenuToolbar.js +8 -1
  39. package/dist/components/Modals.js +1 -1
  40. package/dist/components/QuickSwitcher/ExplorerQuickSwitcherTrigger.js +1 -0
  41. package/dist/components/QuickSwitcher/QuickSwitcher.d.ts +5 -0
  42. package/dist/components/QuickSwitcher/QuickSwitcher.js +2 -2
  43. package/dist/components/QuickSwitcher/QuickSwitcherContent.d.ts +5 -0
  44. package/dist/components/QuickSwitcher/QuickSwitcherContent.js +26 -8
  45. package/dist/components/RenderQueue/ClientRenderQueueProcessor.d.ts +0 -1
  46. package/dist/components/RenderQueue/ClientRenderQueueProcessor.js +3 -14
  47. package/dist/components/RenderQueue/RenderQueueDownloadItem.js +2 -2
  48. package/dist/components/SelectedOutlineElement.js +1 -4
  49. package/dist/components/SelectedOutlineOverlay.js +17 -8
  50. package/dist/components/ShowOutlinesProvider.js +4 -1
  51. package/dist/components/Timeline/Timeline.js +19 -5
  52. package/dist/components/Timeline/TimelineAssetField.js +1 -0
  53. package/dist/components/Timeline/TimelineExpandedKeyframeRow.js +8 -2
  54. package/dist/components/Timeline/TimelinePrimitiveFieldValue.d.ts +7 -2
  55. package/dist/components/Timeline/TimelinePrimitiveFieldValue.js +3 -1
  56. package/dist/components/Timeline/TimelineRowChrome.d.ts +3 -0
  57. package/dist/components/Timeline/TimelineRowChrome.js +4 -3
  58. package/dist/components/Timeline/TimelineSelection.d.ts +4 -2
  59. package/dist/components/Timeline/TimelineSelection.js +9 -6
  60. package/dist/components/Timeline/TimelineSequence.js +1 -4
  61. package/dist/components/Timeline/TimelineSequenceItem.js +4 -5
  62. package/dist/components/Timeline/TimelineSequencePropItem.js +9 -10
  63. package/dist/components/Timeline/TimelineTrack.js +4 -2
  64. package/dist/components/Timeline/border-radius-representation.d.ts +16 -0
  65. package/dist/components/Timeline/border-radius-representation.js +60 -0
  66. package/dist/components/Timeline/delete-selected-timeline-item.js +1 -6
  67. package/dist/components/Timeline/get-sequence-prop-reset-changes.d.ts +10 -0
  68. package/dist/components/Timeline/get-sequence-prop-reset-changes.js +33 -0
  69. package/dist/components/Timeline/reset-selected-timeline-props.js +2 -8
  70. package/dist/components/Timeline/save-sequence-prop.d.ts +12 -1
  71. package/dist/components/Timeline/save-sequence-prop.js +25 -1
  72. package/dist/components/Timeline/timeline-field-display-utils.js +1 -0
  73. package/dist/components/Timeline/use-timeline-asset-drop.js +4 -0
  74. package/dist/components/UndoRedoButtons.js +15 -6
  75. package/dist/components/handle-drop.d.ts +3 -2
  76. package/dist/components/handle-drop.js +2 -1
  77. package/dist/components/import-assets.d.ts +4 -2
  78. package/dist/components/import-assets.js +20 -7
  79. package/dist/components/use-element-overwrite-confirmation.d.ts +2 -0
  80. package/dist/components/use-element-overwrite-confirmation.js +73 -0
  81. package/dist/components/use-static-files.js +7 -2
  82. package/dist/error-overlay/react-overlay/effects/resolve-file-source.js +13 -2
  83. package/dist/esm/{chunk-dr1y1vm3.js → chunk-n1kj09yv.js} +7634 -6308
  84. package/dist/esm/index.mjs +24 -8
  85. package/dist/esm/internals.mjs +7634 -6308
  86. package/dist/esm/previewEntry.mjs +7646 -6320
  87. package/dist/esm/renderEntry.mjs +1 -1
  88. package/dist/helpers/browser-studio-operations.d.ts +3 -0
  89. package/dist/helpers/browser-studio-operations.js +14 -0
  90. package/dist/helpers/client-id.d.ts +0 -1
  91. package/dist/helpers/client-id.js +15 -10
  92. package/dist/helpers/download-blob.d.ts +1 -0
  93. package/dist/helpers/download-blob.js +15 -0
  94. package/dist/helpers/inject-css.js +5 -3
  95. package/dist/helpers/open-in-editor.js +26 -5
  96. package/dist/helpers/preview-server-events.js +46 -9
  97. package/dist/helpers/use-create-composition.js +8 -6
  98. package/dist/helpers/use-menu-structure.js +29 -0
  99. package/dist/hot-middleware-client/client.js +3 -3
  100. package/dist/icons/border-radius.d.ts +4 -0
  101. package/dist/icons/border-radius.js +8 -0
  102. package/dist/icons/fullscreen.d.ts +4 -0
  103. package/dist/icons/fullscreen.js +6 -0
  104. package/dist/previewEntry.js +1 -1
  105. package/dist/state/modals.d.ts +4 -0
  106. package/dist/state/timeline-sequence-hover.d.ts +17 -0
  107. package/dist/state/timeline-sequence-hover.js +33 -0
  108. 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-n1kj09yv.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;
@@ -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
  });
@@ -164,6 +164,10 @@ export type ModalState = {
164
164
  initialQuery: string;
165
165
  onSelected: (asset: StaticFile) => void;
166
166
  } | null;
167
+ compositionSelection: {
168
+ excludeCompositionId: string;
169
+ onSelected: (composition: _InternalTypes['AnyComposition']) => void;
170
+ } | null;
167
171
  } | AddEffectModalState | ConfirmationDialogState | SvgImportDialogState;
168
172
  export type ModalContextType = {
169
173
  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.501",
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/drag-and-drop": "4.0.501",
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.501",
30
+ "@remotion/canvas-capture": "4.0.501",
31
+ "@remotion/captions": "4.0.501",
32
+ "@remotion/player": "4.0.501",
33
+ "@remotion/media-utils": "4.0.501",
34
+ "@remotion/renderer": "4.0.501",
35
+ "@remotion/web-renderer": "4.0.501",
36
+ "@remotion/studio-shared": "4.0.501",
37
+ "@remotion/timeline-utils": "4.0.501",
38
+ "@remotion/zod-types": "4.0.501",
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.501",
49
50
  "eslint": "9.19.0",
50
51
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
51
52
  },