@remotion/studio 4.0.520 → 4.0.521

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 (70) hide show
  1. package/dist/api/shut-down-studio.d.ts +6 -0
  2. package/dist/api/shut-down-studio.js +23 -0
  3. package/dist/components/AudioWaveform.js +10 -6
  4. package/dist/components/Canvas.js +84 -12
  5. package/dist/components/CaptionInspector.d.ts +1 -0
  6. package/dist/components/CaptionInspector.js +43 -2
  7. package/dist/components/CaptionTextEditor.js +1 -1
  8. package/dist/components/CompositionSelector.js +41 -30
  9. package/dist/components/CompositionSelectorItem.d.ts +7 -0
  10. package/dist/components/CompositionSelectorItem.js +343 -67
  11. package/dist/components/ElementInstallConfirmation.js +74 -64
  12. package/dist/components/ElementLibraryModal.js +3 -1
  13. package/dist/components/InlineCaptionInspector.js +37 -1
  14. package/dist/components/InspectorPanel/ElementLibraryButton.js +3 -18
  15. package/dist/components/InspectorSequenceSection.js +1 -4
  16. package/dist/components/Menu/MenuSubItem.js +1 -1
  17. package/dist/components/ModalContainer.d.ts +1 -0
  18. package/dist/components/ModalContainer.js +2 -2
  19. package/dist/components/ModalHeader.js +5 -1
  20. package/dist/components/QuickSwitcher/asset-search.d.ts +0 -1
  21. package/dist/components/QuickSwitcher/asset-search.js +1 -16
  22. package/dist/components/RenderButton.js +2 -2
  23. package/dist/components/RenderModal/RenderStatusModal.js +7 -1
  24. package/dist/components/RenderModal/ServerRenderModal.js +1 -1
  25. package/dist/components/RenderModal/WebRenderModal.js +1 -1
  26. package/dist/components/RenderQueue/ClientRenderQueueProcessor.js +1 -1
  27. package/dist/components/SettingsModal.js +8 -5
  28. package/dist/components/TimeValue.js +36 -7
  29. package/dist/components/Timeline/Timeline.d.ts +1 -1
  30. package/dist/components/Timeline/Timeline.js +31 -19
  31. package/dist/components/Timeline/TimelineAssetField.d.ts +0 -1
  32. package/dist/components/Timeline/TimelineAssetField.js +16 -9
  33. package/dist/components/Timeline/TimelineCollapseToggle.js +1 -1
  34. package/dist/components/Timeline/TimelineFontWeightField.d.ts +11 -0
  35. package/dist/components/Timeline/TimelineFontWeightField.js +65 -0
  36. package/dist/components/Timeline/TimelineLayerChildren.d.ts +21 -0
  37. package/dist/components/Timeline/TimelineLayerChildren.js +129 -0
  38. package/dist/components/Timeline/TimelineNumberField.js +13 -5
  39. package/dist/components/Timeline/TimelinePrimitiveFieldValue.js +4 -0
  40. package/dist/components/Timeline/TimelineSequence.js +38 -32
  41. package/dist/components/Timeline/TimelineSequenceItem.js +2 -1
  42. package/dist/components/Timeline/TimelineSequenceRightEdgeDragHandle.js +19 -7
  43. package/dist/components/Timeline/TimelineTickFormatProvider.d.ts +8 -0
  44. package/dist/components/Timeline/TimelineTickFormatProvider.js +16 -0
  45. package/dist/components/Timeline/TimelineTimeIndicators.js +7 -3
  46. package/dist/components/Timeline/TimelineVideoInfo.js +7 -3
  47. package/dist/components/Timeline/timeline-field-display-utils.js +1 -0
  48. package/dist/components/UpdateStatusContext.d.ts +5 -0
  49. package/dist/components/UpdateStatusContext.js +53 -2
  50. package/dist/components/UpdatesSettings.js +25 -4
  51. package/dist/components/WebMcp.js +26 -0
  52. package/dist/components/composition-selector-drag-data.d.ts +27 -0
  53. package/dist/components/composition-selector-drag-data.js +80 -0
  54. package/dist/components/import-assets.d.ts +2 -1
  55. package/dist/components/import-assets.js +11 -1
  56. package/dist/components/parse-caption-file.d.ts +5 -0
  57. package/dist/components/parse-caption-file.js +63 -0
  58. package/dist/esm/chunk-emw4k5b0.js +99527 -0
  59. package/dist/esm/{chunk-np6q13k5.js → chunk-vkrvnx2h.js} +5913 -4699
  60. package/dist/esm/index.mjs +23 -8
  61. package/dist/esm/internals.mjs +6729 -5366
  62. package/dist/esm/previewEntry.mjs +6779 -5416
  63. package/dist/esm/renderEntry.mjs +1 -1
  64. package/dist/helpers/get-preview-file-type.js +22 -3
  65. package/dist/helpers/inserted-element-selection.d.ts +2 -1
  66. package/dist/helpers/use-menu-structure.js +2 -2
  67. package/dist/index.d.ts +1 -0
  68. package/dist/index.js +3 -1
  69. package/dist/state/modals.d.ts +0 -2
  70. package/package.json +16 -16
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @description Shuts down the Remotion Studio.
3
+ * @see [Documentation](https://www.remotion.dev/docs/studio/shut-down-studio)
4
+ */
5
+ import type { ShutdownStudioResponse } from '@remotion/studio-shared';
6
+ export declare const shutDownStudio: () => Promise<ShutdownStudioResponse>;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /**
3
+ * @description Shuts down the Remotion Studio.
4
+ * @see [Documentation](https://www.remotion.dev/docs/studio/shut-down-studio)
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.shutDownStudio = void 0;
8
+ const remotion_1 = require("remotion");
9
+ const call_api_1 = require("../components/call-api");
10
+ const browser_studio_operations_1 = require("../helpers/browser-studio-operations");
11
+ const shutDownStudio = () => {
12
+ if (!(0, remotion_1.getRemotionEnvironment)().isStudio) {
13
+ throw new Error('shutDownStudio() is only available in the Studio');
14
+ }
15
+ if ((0, browser_studio_operations_1.getBrowserStudioOperations)() !== null) {
16
+ throw new Error('shutDownStudio() is not supported in Browser Studio');
17
+ }
18
+ if (window.remotion_isReadOnlyStudio) {
19
+ throw new Error('shutDownStudio() is not available in read-only Studio');
20
+ }
21
+ return (0, call_api_1.callApi)('/api/shutdown-studio', {});
22
+ };
23
+ exports.shutDownStudio = shutDownStudio;
@@ -50,17 +50,15 @@ const getContainerStyle = (height) => {
50
50
  position: 'relative',
51
51
  width: '100%',
52
52
  height,
53
+ overflow: 'hidden',
53
54
  };
54
55
  };
55
56
  const waveformCanvasStyle = {
56
57
  pointerEvents: 'none',
57
- width: '100%',
58
- height: '100%',
58
+ flexShrink: 0,
59
59
  };
60
60
  const volumeCanvasStyle = {
61
61
  position: 'absolute',
62
- width: '100%',
63
- height: '100%',
64
62
  };
65
63
  const parseVolume = (volume) => {
66
64
  if (typeof volume === 'number') {
@@ -146,14 +144,17 @@ const AudioWaveformInner = ({ src, height, startFrom, durationInFrames, displayO
146
144
  const pixelRatio = window.devicePixelRatio;
147
145
  const h = Math.ceil(height * pixelRatio);
148
146
  const w = Math.ceil(visualizationWidth * pixelRatio);
147
+ const drawingWidth = visualizationWidth * pixelRatio;
149
148
  canvasElement.width = w;
150
149
  canvasElement.height = h;
150
+ canvasElement.style.width = w / pixelRatio + 'px';
151
+ canvasElement.style.height = h / pixelRatio + 'px';
151
152
  (0, timeline_utils_1.drawBars)({
152
153
  canvas: canvasElement,
153
154
  peaks: portionPeaks !== null && portionPeaks !== void 0 ? portionPeaks : EMPTY_PEAKS,
154
155
  color: colors_1.WHITE_ALPHA_60,
155
156
  volume: visibleVolume,
156
- width: w,
157
+ width: drawingWidth,
157
158
  });
158
159
  }, [height, portionPeaks, visibleVolume, visualizationWidth]);
159
160
  (0, react_1.useLayoutEffect)(() => {
@@ -167,12 +168,15 @@ const AudioWaveformInner = ({ src, height, startFrom, durationInFrames, displayO
167
168
  const pixelRatio = window.devicePixelRatio;
168
169
  const h = Math.ceil(height * pixelRatio);
169
170
  const w = Math.ceil(visualizationWidth * pixelRatio);
171
+ const drawingWidth = visualizationWidth * pixelRatio;
170
172
  const context = volumeCanvasElement.getContext('2d');
171
173
  if (!context) {
172
174
  return;
173
175
  }
174
176
  volumeCanvasElement.width = w;
175
177
  volumeCanvasElement.height = h;
178
+ volumeCanvasElement.style.width = w / pixelRatio + 'px';
179
+ volumeCanvasElement.style.height = h / pixelRatio + 'px';
176
180
  context.clearRect(0, 0, w, h);
177
181
  if (!Array.isArray(visibleVolume)) {
178
182
  return;
@@ -182,7 +186,7 @@ const AudioWaveformInner = ({ src, height, startFrom, durationInFrames, displayO
182
186
  visibleVolume.forEach((v, index) => {
183
187
  const x = visibleVolume.length <= 1
184
188
  ? 0
185
- : (index / (visibleVolume.length - 1)) * w;
189
+ : (index / (visibleVolume.length - 1)) * drawingWidth;
186
190
  const y = (1 - v) * (h - timeline_layout_1.TIMELINE_BORDER * 2 * pixelRatio) + pixelRatio;
187
191
  if (index === 0) {
188
192
  context.moveTo(x, y);
@@ -47,7 +47,6 @@ const ResetZoomButton_1 = require("./ResetZoomButton");
47
47
  const SvgImportDialog_1 = require("./SvgImportDialog");
48
48
  const imperative_state_1 = require("./Timeline/imperative-state");
49
49
  const use_resolved_stack_1 = require("./Timeline/use-resolved-stack");
50
- const elementInstallDependencyIgnoreList = ['react', 'react-dom', 'remotion'];
51
50
  const getCanvasDragPreviewMetadata = (mimeTypes) => {
52
51
  var _a, _b, _c;
53
52
  const composition = (0, composition_drag_data_1.getCompositionDragPreviewMetadata)(mimeTypes);
@@ -624,24 +623,103 @@ const Canvas = ({ canvasContent, size }) => {
624
623
  (0, element_install_request_1.enqueueElementInstallRequest)(event.request);
625
624
  });
626
625
  }, [previewServerClientId, subscribeToEvent]);
626
+ (0, react_1.useEffect)(() => {
627
+ const onMessage = (event) => {
628
+ var _a, _b;
629
+ const elementLibrary = document.querySelector('iframe[data-remotion-element-library]');
630
+ if (event.source !== (elementLibrary === null || elementLibrary === void 0 ? void 0 : elementLibrary.contentWindow) ||
631
+ !studio_protocol_1.StudioProtocolInternals.isAllowedStudioProtocolPageOrigin(event.origin)) {
632
+ return;
633
+ }
634
+ const payload = studio_protocol_1.StudioProtocolInternals.parseStudioProtocolIframeInstallRequest(event.data);
635
+ const responsePort = event.ports[0];
636
+ if (payload === null || responsePort === undefined) {
637
+ return;
638
+ }
639
+ const canInstall = canReceiveElementInstallRequest &&
640
+ compositionFile !== null &&
641
+ currentCompositionId !== null &&
642
+ previewServerClientId !== null;
643
+ const request = canInstall
644
+ ? {
645
+ clientId: previewServerClientId,
646
+ compositionFile,
647
+ compositionId: currentCompositionId,
648
+ createdAt: Date.now(),
649
+ element: {
650
+ ...payload.element,
651
+ durationInFrames: (_a = payload.element.durationInFrames) !== null && _a !== void 0 ? _a : null,
652
+ installationMode: (_b = payload.element.installationMode) !== null && _b !== void 0 ? _b : null,
653
+ },
654
+ from: null,
655
+ id: crypto.randomUUID(),
656
+ position: null,
657
+ source: { origin: event.origin, type: 'studio-protocol' },
658
+ }
659
+ : null;
660
+ const result = canInstall
661
+ ? {
662
+ success: true,
663
+ status: 'awaiting-confirmation',
664
+ target: {
665
+ compositionId: currentCompositionId,
666
+ projectName: window.remotion_projectName,
667
+ studioOrigin: window.location.origin,
668
+ studioVersion: window.remotion_version,
669
+ },
670
+ }
671
+ : {
672
+ success: false,
673
+ code: 'no-installable-target',
674
+ message: 'Focus a composition in a Remotion Studio that is not read-only, then try again.',
675
+ };
676
+ const timeout = window.setTimeout(() => responsePort.close(), 1000);
677
+ responsePort.onmessage = () => {
678
+ window.clearTimeout(timeout);
679
+ responsePort.close();
680
+ if (request !== null) {
681
+ (0, element_install_request_1.enqueueElementInstallRequest)(request);
682
+ }
683
+ };
684
+ responsePort.postMessage(result);
685
+ };
686
+ window.addEventListener('message', onMessage);
687
+ return () => window.removeEventListener('message', onMessage);
688
+ }, [
689
+ canReceiveElementInstallRequest,
690
+ compositionFile,
691
+ currentCompositionId,
692
+ previewServerClientId,
693
+ ]);
627
694
  (0, react_1.useEffect)(() => {
628
695
  return (0, element_install_request_1.subscribeToElementInstallRequests)((request) => {
629
- const requestWithFrom = request.source.type === 'drag-and-drop' || request.from !== null
696
+ var _a, _b;
697
+ const isDragAndDrop = request.source.type === 'drag-and-drop';
698
+ const requestWithDefaults = isDragAndDrop
630
699
  ? request
631
700
  : {
632
701
  ...request,
633
- from: (0, import_assets_1.getFromForDrop)({
702
+ from: (_a = request.from) !== null && _a !== void 0 ? _a : (0, import_assets_1.getFromForDrop)({
634
703
  durationInFrames: request.element.durationInFrames,
635
704
  from: (0, imperative_state_1.getCurrentFrame)(),
636
705
  preferCompositionStart: true,
637
706
  }),
707
+ position: (_b = request.position) !== null && _b !== void 0 ? _b : (0, import_assets_1.getElementPositionForDrop)({
708
+ dimensions: request.element.dimensions,
709
+ dropPosition: contentDimensions === null || contentDimensions === 'none'
710
+ ? null
711
+ : {
712
+ centerX: contentDimensions.width / 2,
713
+ centerY: contentDimensions.height / 2,
714
+ },
715
+ }),
638
716
  };
639
717
  setPendingElementInstallRequests((requests) => [
640
718
  ...requests,
641
- requestWithFrom,
719
+ requestWithDefaults,
642
720
  ]);
643
721
  });
644
- }, []);
722
+ }, [contentDimensions]);
645
723
  (0, react_1.useEffect)(() => {
646
724
  var _a;
647
725
  var _b;
@@ -719,12 +797,7 @@ const Canvas = ({ canvasContent, size }) => {
719
797
  return;
720
798
  }
721
799
  const declaredDependencies = Array.from(new Map(activeElementInstallRequest.element.dependencies.map((dependency) => [dependency.name, dependency])).values());
722
- const missingPackages = (0, install_required_package_1.getMissingPackages)(declaredDependencies).map((dependency) => dependency.name);
723
- const ignoredDependencies = declaredDependencies.filter((dependency) => elementInstallDependencyIgnoreList.includes(dependency.name) &&
724
- !missingPackages.includes(dependency.name));
725
- const dependenciesToReview = declaredDependencies
726
- .filter((dependency) => !ignoredDependencies.includes(dependency))
727
- .map((dependency) => dependency.version === null
800
+ const missingPackages = (0, install_required_package_1.getMissingPackages)(declaredDependencies).map((dependency) => dependency.version === null
728
801
  ? dependency.name
729
802
  : `${dependency.name}@${dependency.version}`);
730
803
  const { source } = activeElementInstallRequest;
@@ -741,7 +814,6 @@ const Canvas = ({ canvasContent, size }) => {
741
814
  setSelectedModal({
742
815
  type: 'element-install',
743
816
  currentPlan,
744
- dependenciesToReview,
745
817
  missingPackages,
746
818
  newPlan: newPreflight.plan,
747
819
  onClose: closeElementInstallDialog,
@@ -6,6 +6,7 @@ export declare const CaptionInspector: React.FC<{
6
6
  readonly onTextChange: (captions: Caption[]) => void;
7
7
  readonly onTextSave: ((captions: Caption[]) => void) | null;
8
8
  readonly onTextCancel: (() => void) | null;
9
+ readonly onReplaceCaptions: ((captions: Caption[]) => void) | null;
9
10
  readonly onToggle: () => void;
10
11
  readonly readOnly: boolean;
11
12
  readonly readOnlyTitle: string | null;
@@ -2,11 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CaptionInspector = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
5
6
  const colors_1 = require("../helpers/colors");
7
+ const upload_1 = require("../icons/upload");
8
+ const Button_1 = require("./Button");
6
9
  const CaptionTextEditor_1 = require("./CaptionTextEditor");
7
10
  const CollapsibleInspectorSectionHeader_1 = require("./InspectorPanel/CollapsibleInspectorSectionHeader");
8
11
  const common_1 = require("./InspectorPanel/common");
9
12
  const styles_1 = require("./InspectorPanel/styles");
13
+ const NotificationCenter_1 = require("./Notifications/NotificationCenter");
14
+ const parse_caption_file_1 = require("./parse-caption-file");
15
+ const importTooltip = `Import captions
16
+
17
+ Supports Remotion Caption[] JSON. Files are processed locally.`;
10
18
  const readOnlyStatus = {
11
19
  color: colors_1.LIGHT_TEXT,
12
20
  fontFamily: 'sans-serif',
@@ -15,8 +23,41 @@ const readOnlyStatus = {
15
23
  lineHeight: '16px',
16
24
  marginLeft: 12,
17
25
  };
18
- const CaptionInspector = ({ captions, expanded, onTextChange, onTextSave, onTextCancel, onToggle, readOnly, readOnlyTitle, }) => {
26
+ const CaptionInspector = ({ captions, expanded, onTextChange, onTextSave, onTextCancel, onReplaceCaptions, onToggle, readOnly, readOnlyTitle, }) => {
27
+ const fileInput = (0, react_1.useRef)(null);
28
+ const [isImporting, setIsImporting] = (0, react_1.useState)(false);
29
+ const importCaptions = (0, react_1.useCallback)(async (event) => {
30
+ var _a;
31
+ const file = (_a = event.currentTarget.files) === null || _a === void 0 ? void 0 : _a[0];
32
+ event.currentTarget.value = '';
33
+ if (!file || onReplaceCaptions === null) {
34
+ return;
35
+ }
36
+ setIsImporting(true);
37
+ try {
38
+ onReplaceCaptions((0, parse_caption_file_1.parseCaptionFile)({
39
+ fileName: file.name,
40
+ contents: await file.text(),
41
+ }));
42
+ }
43
+ catch (error) {
44
+ (0, NotificationCenter_1.showNotification)(`Could not import ${file.name}: ${error instanceof Error ? error.message : String(error)}`, 5000);
45
+ }
46
+ finally {
47
+ setIsImporting(false);
48
+ }
49
+ }, [onReplaceCaptions]);
19
50
  return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
20
- jsx_runtime_1.jsx(common_1.InspectorSectionHeader, { children: jsx_runtime_1.jsx(CollapsibleInspectorSectionHeader_1.CollapsibleInspectorSectionHeader, { action: jsx_runtime_1.jsx("div", { style: styles_1.sectionHeaderEnd, children: readOnly ? (jsx_runtime_1.jsx("div", { style: readOnlyStatus, title: readOnlyTitle !== null && readOnlyTitle !== void 0 ? readOnlyTitle : undefined, children: "Read only" })) : null }), expanded: expanded, label: "Captions", onToggle: onToggle }) }), expanded ? (jsx_runtime_1.jsx(CaptionTextEditor_1.CaptionTextEditor, { captions: captions, onChange: onTextChange, onSave: onTextSave, onCancel: onTextCancel, readOnly: readOnly })) : null] }));
51
+ jsx_runtime_1.jsx(common_1.InspectorSectionHeader, { children: jsx_runtime_1.jsx(CollapsibleInspectorSectionHeader_1.CollapsibleInspectorSectionHeader, { action: jsx_runtime_1.jsxs("div", { style: styles_1.sectionHeaderEnd, children: [onReplaceCaptions === null ? null : (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
52
+ jsx_runtime_1.jsx("input", { ref: fileInput, accept: ".json", "aria-label": "Import captions file", hidden: true, onChange: importCaptions, type: "file" }), jsx_runtime_1.jsxs(Button_1.Button, { buttonContainerStyle: {
53
+ alignItems: 'center',
54
+ display: 'flex',
55
+ gap: 4,
56
+ }, disabled: isImporting, onClick: () => {
57
+ var _a;
58
+ return (_a = fileInput.current) === null || _a === void 0 ? void 0 : _a.click();
59
+ }, size: "condensed", title: importTooltip, children: [
60
+ jsx_runtime_1.jsx(upload_1.UploadIcon, { "aria-hidden": "true", color: colors_1.CURRENT_COLOR, focusable: "false", style: { height: 12, width: 12 } }), isImporting ? 'Importing…' : 'Import'] })
61
+ ] })), readOnly ? (jsx_runtime_1.jsx("div", { style: readOnlyStatus, title: readOnlyTitle !== null && readOnlyTitle !== void 0 ? readOnlyTitle : undefined, children: "Read only" })) : null] }), expanded: expanded, label: "Captions", onToggle: onToggle }) }), expanded ? (jsx_runtime_1.jsx(CaptionTextEditor_1.CaptionTextEditor, { captions: captions, onChange: onTextChange, onSave: onTextSave, onCancel: onTextCancel, readOnly: readOnly })) : null] }));
21
62
  };
22
63
  exports.CaptionInspector = CaptionInspector;
@@ -119,7 +119,7 @@ const CaptionTextEditor = ({ captions, onChange, onSave, onCancel, readOnly }) =
119
119
  ? `Remove page break after caption ${index + 1}`
120
120
  : `Add page break after caption ${index + 1}`;
121
121
  return (jsx_runtime_1.jsxs("div", { style: row, children: [
122
- jsx_runtime_1.jsxs("div", { style: timing, children: [formatMilliseconds(caption.startMs), " \u2192", ' ', formatMilliseconds(caption.endMs), " ms"] }), jsx_runtime_1.jsx(RemInput_1.RemotionInput, { "data-caption-index": index, disabled: readOnly, onBlur: (event) => {
122
+ jsx_runtime_1.jsxs("div", { style: timing, children: [formatMilliseconds(caption.startMs), " \u2192", ' ', formatMilliseconds(caption.endMs), " ms"] }), jsx_runtime_1.jsx(RemInput_1.RemotionInput, { "aria-label": `Caption ${index + 1}`, "data-caption-index": index, disabled: readOnly, onBlur: (event) => {
123
123
  if (cancelledBlurIndexes.current.delete(index)) {
124
124
  return;
125
125
  }
@@ -11,7 +11,7 @@ const persist_open_folders_1 = require("../helpers/persist-open-folders");
11
11
  const folders_1 = require("../state/folders");
12
12
  const modals_1 = require("../state/modals");
13
13
  const z_index_1 = require("../state/z-index");
14
- const composition_drag_data_1 = require("./composition-drag-data");
14
+ const composition_selector_drag_data_1 = require("./composition-selector-drag-data");
15
15
  const CompositionSelectorItem_1 = require("./CompositionSelectorItem");
16
16
  const ContextMenu_1 = require("./ContextMenu");
17
17
  const InitialCompositionLoader_1 = require("./InitialCompositionLoader");
@@ -63,6 +63,19 @@ const container = {
63
63
  overflow: 'hidden',
64
64
  backgroundColor: colors_1.BACKGROUND,
65
65
  };
66
+ const list = {
67
+ flex: 1,
68
+ overflowY: 'auto',
69
+ paddingTop: 4,
70
+ paddingBottom: 4,
71
+ backgroundColor: colors_1.BACKGROUND,
72
+ };
73
+ const rootReorderLine = {
74
+ backgroundColor: colors_1.TIMELINE_BLUE,
75
+ height: 2,
76
+ pointerEvents: 'none',
77
+ width: '100%',
78
+ };
66
79
  const autoScrollThreshold = 70;
67
80
  const maxAutoScrollSpeed = 18;
68
81
  const getAutoScrollSpeed = ({ clientY, element, }) => {
@@ -105,6 +118,7 @@ const CompositionSelector = () => {
105
118
  setSelectedModal,
106
119
  ]);
107
120
  const [rootDragHovered, setRootDragHovered] = (0, react_1.useState)(false);
121
+ const activeDragRef = (0, react_1.useRef)(null);
108
122
  const listRef = (0, react_1.useRef)(null);
109
123
  const autoScrollAnimation = (0, react_1.useRef)(null);
110
124
  const autoScrollSpeed = (0, react_1.useRef)(0);
@@ -116,15 +130,6 @@ const CompositionSelector = () => {
116
130
  ? (0, create_folder_tree_1.sortFolderTreeAlphabetically)(tree)
117
131
  : tree;
118
132
  }, [compositionSortOrder, compositions, folders, foldersExpanded]);
119
- const list = (0, react_1.useMemo)(() => {
120
- return {
121
- flex: 1,
122
- overflowY: 'auto',
123
- paddingTop: 4,
124
- paddingBottom: 4,
125
- backgroundColor: rootDragHovered ? colors_1.WHITE_ALPHA_12 : colors_1.BACKGROUND,
126
- };
127
- }, [rootDragHovered]);
128
133
  const toggleFolder = (0, react_1.useCallback)((folderName, parentName) => {
129
134
  var _a;
130
135
  (_a = remotion_1.Internals.compositionSelectorRef.current) === null || _a === void 0 ? void 0 : _a.toggleFolder(folderName, parentName);
@@ -166,7 +171,7 @@ const CompositionSelector = () => {
166
171
  }, [runCompositionListAutoScroll, stopCompositionListAutoScroll]);
167
172
  const onCompositionListDragOverCapture = (0, react_1.useCallback)((event) => {
168
173
  if (window.remotion_isReadOnlyStudio ||
169
- (0, composition_drag_data_1.getCompositionDragPreviewMetadata)(event.dataTransfer.types) === null) {
174
+ !(0, composition_selector_drag_data_1.hasCompositionSelectorDragData)(event.dataTransfer.types)) {
170
175
  stopCompositionListAutoScroll();
171
176
  return;
172
177
  }
@@ -182,7 +187,12 @@ const CompositionSelector = () => {
182
187
  }, [stopCompositionListAutoScroll]);
183
188
  const onRootDragOver = (0, react_1.useCallback)((event) => {
184
189
  if (window.remotion_isReadOnlyStudio ||
185
- (0, composition_drag_data_1.getCompositionDragPreviewMetadata)(event.dataTransfer.types) === null) {
190
+ !(0, composition_selector_drag_data_1.hasCompositionSelectorDragData)(event.dataTransfer.types)) {
191
+ return;
192
+ }
193
+ if (activeDragRef.current === null ||
194
+ activeDragRef.current.parentFolderPath === null) {
195
+ setRootDragHovered(false);
186
196
  return;
187
197
  }
188
198
  event.preventDefault();
@@ -202,31 +212,32 @@ const CompositionSelector = () => {
202
212
  if (window.remotion_isReadOnlyStudio) {
203
213
  return;
204
214
  }
205
- const compositionDragData = (0, composition_drag_data_1.parseCompositionDragData)(event.dataTransfer);
206
- if (compositionDragData === null) {
215
+ const dragData = (0, composition_selector_drag_data_1.parseCompositionSelectorDragData)(event.dataTransfer);
216
+ if (dragData === null) {
217
+ return;
218
+ }
219
+ if (activeDragRef.current === null ||
220
+ activeDragRef.current.parentFolderPath === null) {
221
+ event.stopPropagation();
222
+ setRootDragHovered(false);
207
223
  return;
208
224
  }
209
225
  event.preventDefault();
210
226
  event.stopPropagation();
211
227
  stopCompositionListAutoScroll();
212
228
  setRootDragHovered(false);
213
- const composition = compositions.find((c) => c.id === compositionDragData.compositionId);
214
- if (!composition || composition.folderName === null) {
215
- return;
216
- }
217
- const notification = (0, NotificationCenter_1.showNotification)(`Moving ${compositionDragData.compositionId}...`, null);
229
+ const notification = (0, NotificationCenter_1.showNotification)(`Moving ${dragData.item.type === 'composition' ? dragData.item.compositionId : dragData.item.folderName}...`, null);
218
230
  const controller = new AbortController();
219
231
  try {
220
232
  const result = await (0, actions_1.applyCodemod)({
221
233
  codemod: {
222
- type: 'move-composition-to-folder',
223
- idToMove: compositionDragData.compositionId,
224
- folderName: null,
225
- parentName: null,
234
+ type: 'move-composition-or-folder',
235
+ source: dragData.item,
236
+ destination: { type: 'root' },
226
237
  },
227
238
  dryRun: false,
228
239
  signal: controller.signal,
229
- symbolicatedStack: (0, composition_drag_data_1.compositionDragDataToSymbolicatedStack)(compositionDragData),
240
+ symbolicatedStack: (0, composition_selector_drag_data_1.compositionSelectorDragDataToSymbolicatedStack)(dragData),
230
241
  });
231
242
  if (result.success) {
232
243
  notification.dismiss();
@@ -238,13 +249,13 @@ const CompositionSelector = () => {
238
249
  catch (err) {
239
250
  notification.replaceContent(err instanceof Error ? err.message : String(err), 4000);
240
251
  }
241
- }, [compositions, stopCompositionListAutoScroll]);
252
+ }, [stopCompositionListAutoScroll]);
242
253
  return (jsx_runtime_1.jsxs("div", { style: container, children: [
243
- jsx_runtime_1.jsx(ContextMenu_1.ContextMenuForTarget, { triggerRef: listRef, getItems: getRootContextMenuItems }), jsx_runtime_1.jsx(ExplorerQuickSwitcherTrigger_1.ExplorerQuickSwitcherTrigger, { mode: "compositions", showShortcut: true, tabIndex: tabIndex, getActions: getRootContextMenuItems }), jsx_runtime_1.jsx("div", { ref: listRef, className: "__remotion-vertical-scrollbar", style: list, onDragOverCapture: onCompositionListDragOverCapture, onDragEndCapture: stopCompositionListAutoScroll, onDragOver: onRootDragOver, onDragLeave: onRootDragLeave, onDropCapture: stopCompositionListAutoScroll, onDrop: onRootDrop, children: items.map((c) => {
244
- return (jsx_runtime_1.jsx(CompositionSelectorItem_1.CompositionSelectorItem, { level: 0, currentComposition: canvasContent && canvasContent.type === 'composition'
245
- ? canvasContent.compositionId
246
- : null, selectComposition: selectComposition, toggleFolder: toggleFolder, clearRootDragHover: clearRootDragHover, tabIndex: tabIndex, item: c }, c.key + c.type));
247
- }) })
254
+ jsx_runtime_1.jsx(ContextMenu_1.ContextMenuForTarget, { triggerRef: listRef, getItems: getRootContextMenuItems }), jsx_runtime_1.jsx(ExplorerQuickSwitcherTrigger_1.ExplorerQuickSwitcherTrigger, { mode: "compositions", showShortcut: true, tabIndex: tabIndex, getActions: getRootContextMenuItems }), jsx_runtime_1.jsxs("div", { ref: listRef, className: "__remotion-vertical-scrollbar", style: list, onDragOverCapture: onCompositionListDragOverCapture, onDragEndCapture: stopCompositionListAutoScroll, onDragOver: onRootDragOver, onDragLeave: onRootDragLeave, onDropCapture: stopCompositionListAutoScroll, onDrop: onRootDrop, children: [items.map((c, index) => {
255
+ return (jsx_runtime_1.jsx(CompositionSelectorItem_1.CompositionSelectorItem, { level: 0, currentComposition: canvasContent && canvasContent.type === 'composition'
256
+ ? canvasContent.compositionId
257
+ : null, selectComposition: selectComposition, toggleFolder: toggleFolder, clearRootDragHover: clearRootDragHover, canReorder: compositionSortOrder === 'registration', tabIndex: tabIndex, item: c, activeDragRef: activeDragRef, parentFolderPath: null, previousSibling: index === 0 ? null : items[index - 1], nextSibling: index === items.length - 1 ? null : items[index + 1] }, c.key + c.type));
258
+ }), rootDragHovered ? (jsx_runtime_1.jsx("div", { "data-composition-root-reorder-line": true, style: rootReorderLine })) : null] })
248
259
  ] }));
249
260
  };
250
261
  exports.CompositionSelector = CompositionSelector;
@@ -1,5 +1,7 @@
1
+ import type { MutableRefObject } from 'react';
1
2
  import React from 'react';
2
3
  import { type _InternalTypes } from 'remotion';
4
+ import { type CompositionSelectorActiveDrag } from './composition-selector-drag-data';
3
5
  export type CompositionSelectorItemType = {
4
6
  key: string;
5
7
  type: 'composition';
@@ -20,5 +22,10 @@ export declare const CompositionSelectorItem: React.FC<{
20
22
  readonly selectComposition: (c: _InternalTypes['AnyComposition'], push: boolean) => void;
21
23
  readonly toggleFolder: (folderName: string, parentName: string | null) => void;
22
24
  readonly clearRootDragHover: () => void;
25
+ readonly canReorder: boolean;
23
26
  readonly level: number;
27
+ readonly activeDragRef: MutableRefObject<CompositionSelectorActiveDrag | null>;
28
+ readonly parentFolderPath: string | null;
29
+ readonly previousSibling: CompositionSelectorItemType | null;
30
+ readonly nextSibling: CompositionSelectorItemType | null;
24
31
  }>;