@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
@@ -1,14 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useCompositionActions = void 0;
4
+ const studio_protocol_1 = require("@remotion/studio-protocol");
4
5
  const react_1 = require("react");
5
6
  const remotion_1 = require("remotion");
7
+ const browser_studio_operations_1 = require("../../helpers/browser-studio-operations");
6
8
  const client_id_1 = require("../../helpers/client-id");
7
9
  const interactivity_enabled_1 = require("../../helpers/interactivity-enabled");
8
10
  const open_in_editor_1 = require("../../helpers/open-in-editor");
11
+ const modals_1 = require("../../state/modals");
9
12
  const call_api_1 = require("../call-api");
10
13
  const import_assets_1 = require("../import-assets");
11
14
  const NotificationCenter_1 = require("../Notifications/NotificationCenter");
15
+ const get_stack_1 = require("../Timeline/TimelineStack/get-stack");
12
16
  const use_resolved_stack_1 = require("../Timeline/use-resolved-stack");
13
17
  const useCompositionActions = () => {
14
18
  var _a, _b;
@@ -16,9 +20,13 @@ const useCompositionActions = () => {
16
20
  const videoConfig = remotion_1.Internals.useUnsafeVideoConfig();
17
21
  const [isAddingSolid, setIsAddingSolid] = (0, react_1.useState)(false);
18
22
  const [isAddingAsset, setIsAddingAsset] = (0, react_1.useState)(false);
23
+ const [isAddingComposition, setIsAddingComposition] = (0, react_1.useState)(false);
24
+ const { setSelectedModal } = (0, react_1.useContext)(modals_1.ModalsContext);
19
25
  const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
20
26
  const previewConnected = previewServerState.type === 'connected';
21
27
  const previewInteractive = previewConnected && (0, interactivity_enabled_1.isStudioInteractivityEnabled)();
28
+ const browserStudioOperations = (0, browser_studio_operations_1.getBrowserStudioOperations)();
29
+ const browserStudioCanInsertSolid = browserStudioOperations !== null;
22
30
  const currentCompositionId = (canvasContent === null || canvasContent === void 0 ? void 0 : canvasContent.type) === 'composition' ? canvasContent.compositionId : null;
23
31
  const currentComposition = (0, react_1.useMemo)(() => {
24
32
  var _a;
@@ -28,13 +36,15 @@ const useCompositionActions = () => {
28
36
  return ((_a = compositions.find((composition) => composition.id === currentCompositionId)) !== null && _a !== void 0 ? _a : null);
29
37
  }, [compositions, currentCompositionId]);
30
38
  const resolvedCompositionLocation = (0, use_resolved_stack_1.useResolvedStack)((_a = currentComposition === null || currentComposition === void 0 ? void 0 : currentComposition.stack) !== null && _a !== void 0 ? _a : null);
31
- const compositionFile = (_b = resolvedCompositionLocation === null || resolvedCompositionLocation === void 0 ? void 0 : resolvedCompositionLocation.source) !== null && _b !== void 0 ? _b : null;
39
+ const compositionFile = (_b = resolvedCompositionLocation === null || resolvedCompositionLocation === void 0 ? void 0 : resolvedCompositionLocation.source) !== null && _b !== void 0 ? _b : (currentCompositionId && browserStudioOperations
40
+ ? browserStudioOperations.getCompositionFile(currentCompositionId)
41
+ : null);
32
42
  const compositionComponentInfo = (0, open_in_editor_1.useCachedCompositionComponentInfo)({
33
43
  compositionFile,
34
44
  compositionId: currentCompositionId,
35
45
  });
36
- const canShowInsertSolid = previewInteractive &&
37
- !window.remotion_isReadOnlyStudio &&
46
+ const canShowInsertSolid = (previewInteractive || browserStudioCanInsertSolid) &&
47
+ (!window.remotion_isReadOnlyStudio || browserStudioCanInsertSolid) &&
38
48
  (compositionComponentInfo === null || compositionComponentInfo === void 0 ? void 0 : compositionComponentInfo.canAddSequence) === true &&
39
49
  currentCompositionId !== null &&
40
50
  compositionFile !== null &&
@@ -46,6 +56,8 @@ const useCompositionActions = () => {
46
56
  currentCompositionId !== null &&
47
57
  compositionFile !== null;
48
58
  const canInsertAsset = canShowInsertAsset && !isAddingAsset;
59
+ const canShowInsertComposition = canShowInsertAsset;
60
+ const canInsertComposition = canShowInsertComposition && !isAddingComposition;
49
61
  const insertSolid = (0, react_1.useCallback)(async () => {
50
62
  if (!canInsertSolid ||
51
63
  currentCompositionId === null ||
@@ -55,7 +67,7 @@ const useCompositionActions = () => {
55
67
  }
56
68
  setIsAddingSolid(true);
57
69
  try {
58
- const result = await (0, call_api_1.callApi)('/api/insert-jsx-element', {
70
+ const request = {
59
71
  compositionFile,
60
72
  compositionId: currentCompositionId,
61
73
  from: null,
@@ -65,7 +77,10 @@ const useCompositionActions = () => {
65
77
  height: videoConfig.height,
66
78
  position: null,
67
79
  },
68
- });
80
+ };
81
+ const result = browserStudioOperations
82
+ ? await browserStudioOperations.insertSolid(request)
83
+ : await (0, call_api_1.callApi)('/api/insert-jsx-element', request);
69
84
  if (result.success) {
70
85
  (0, NotificationCenter_1.showNotification)('Added <Solid> to source file', 2000);
71
86
  return;
@@ -78,8 +93,14 @@ const useCompositionActions = () => {
78
93
  finally {
79
94
  setIsAddingSolid(false);
80
95
  }
81
- }, [canInsertSolid, compositionFile, currentCompositionId, videoConfig]);
82
- const insertAsset = (0, react_1.useCallback)(async () => {
96
+ }, [
97
+ browserStudioOperations,
98
+ canInsertSolid,
99
+ compositionFile,
100
+ currentCompositionId,
101
+ videoConfig,
102
+ ]);
103
+ const onFilesSelected = (0, react_1.useCallback)(async () => {
83
104
  if (!canInsertAsset ||
84
105
  currentCompositionId === null ||
85
106
  compositionFile === null ||
@@ -107,12 +128,119 @@ const useCompositionActions = () => {
107
128
  setIsAddingAsset(false);
108
129
  }
109
130
  }, [canInsertAsset, compositionFile, currentCompositionId, videoConfig]);
131
+ const onAssetSelected = (0, react_1.useCallback)(async (asset) => {
132
+ if (!canInsertAsset ||
133
+ currentCompositionId === null ||
134
+ compositionFile === null ||
135
+ videoConfig === null) {
136
+ return;
137
+ }
138
+ setIsAddingAsset(true);
139
+ try {
140
+ await (0, import_assets_1.insertExistingAssets)({
141
+ assetPaths: [asset.name],
142
+ fps: videoConfig.fps,
143
+ compositionFile,
144
+ compositionId: currentCompositionId,
145
+ destinationDimensions: null,
146
+ dropPosition: null,
147
+ from: null,
148
+ });
149
+ }
150
+ finally {
151
+ setIsAddingAsset(false);
152
+ }
153
+ }, [canInsertAsset, compositionFile, currentCompositionId, videoConfig]);
154
+ const insertAsset = (0, react_1.useCallback)(() => {
155
+ if (!canInsertAsset) {
156
+ return;
157
+ }
158
+ setSelectedModal({
159
+ type: 'quick-switcher',
160
+ mode: 'assets',
161
+ invocationTimestamp: Date.now(),
162
+ assetSelection: {
163
+ initialQuery: '',
164
+ onSelectFile: () => {
165
+ onFilesSelected().catch(() => undefined);
166
+ },
167
+ onSelected: (asset) => {
168
+ onAssetSelected(asset).catch(() => undefined);
169
+ },
170
+ },
171
+ compositionSelection: null,
172
+ });
173
+ }, [canInsertAsset, onAssetSelected, onFilesSelected, setSelectedModal]);
174
+ const onCompositionSelected = (0, react_1.useCallback)(async (composition) => {
175
+ var _a, _b, _c, _d, _e;
176
+ if (!canInsertComposition ||
177
+ currentCompositionId === null ||
178
+ compositionFile === null) {
179
+ return;
180
+ }
181
+ setIsAddingComposition(true);
182
+ try {
183
+ const resolvedLocation = composition.stack
184
+ ? await (0, get_stack_1.getOriginalLocationFromStack)(composition.stack, 'sequence')
185
+ : null;
186
+ const selectedCompositionFile = (_b = (_a = resolvedLocation === null || resolvedLocation === void 0 ? void 0 : resolvedLocation.source) !== null && _a !== void 0 ? _a : browserStudioOperations === null || browserStudioOperations === void 0 ? void 0 : browserStudioOperations.getCompositionFile(composition.id)) !== null && _b !== void 0 ? _b : null;
187
+ const compositionDragData = studio_protocol_1.StudioProtocolInternals.makeDragData({
188
+ type: 'composition',
189
+ compositionFile: selectedCompositionFile,
190
+ compositionId: composition.id,
191
+ width: (_c = composition.width) !== null && _c !== void 0 ? _c : null,
192
+ height: (_d = composition.height) !== null && _d !== void 0 ? _d : null,
193
+ durationInFrames: (_e = composition.durationInFrames) !== null && _e !== void 0 ? _e : null,
194
+ }).data;
195
+ await (0, import_assets_1.insertComposition)({
196
+ composition: compositionDragData,
197
+ compositionFile,
198
+ compositionId: currentCompositionId,
199
+ dropPosition: null,
200
+ from: null,
201
+ });
202
+ }
203
+ catch (error) {
204
+ (0, NotificationCenter_1.showNotification)(`Could not add composition: ${error instanceof Error ? error.message : String(error)}`, 4000);
205
+ }
206
+ finally {
207
+ setIsAddingComposition(false);
208
+ }
209
+ }, [
210
+ browserStudioOperations,
211
+ canInsertComposition,
212
+ compositionFile,
213
+ currentCompositionId,
214
+ ]);
215
+ const insertComposition = (0, react_1.useCallback)(() => {
216
+ if (!canInsertComposition) {
217
+ return;
218
+ }
219
+ setSelectedModal({
220
+ type: 'quick-switcher',
221
+ mode: 'compositions',
222
+ invocationTimestamp: Date.now(),
223
+ assetSelection: null,
224
+ compositionSelection: {
225
+ excludeCompositionId: currentCompositionId,
226
+ onSelected: onCompositionSelected,
227
+ },
228
+ });
229
+ }, [
230
+ canInsertComposition,
231
+ currentCompositionId,
232
+ onCompositionSelected,
233
+ setSelectedModal,
234
+ ]);
110
235
  return {
111
236
  canInsertAsset,
237
+ canInsertComposition,
112
238
  canInsertSolid,
113
239
  canShowInsertAsset,
240
+ canShowInsertComposition,
114
241
  canShowInsertSolid,
115
242
  insertAsset,
243
+ insertComposition,
116
244
  insertSolid,
117
245
  };
118
246
  };
@@ -26,10 +26,10 @@ const InspectorPanel = ({ composition, currentDefaultProps, readOnlyStudio, setD
26
26
  }
27
27
  if (selectedItems.length > 1) {
28
28
  if (sameSequenceInspectorSelection) {
29
- return (jsx_runtime_1.jsx("div", { style: styles_1.container, children: jsx_runtime_1.jsx(SelectedInspector_1.SelectedInspector, { selection: sameSequenceInspectorSelection }) }));
29
+ return (jsx_runtime_1.jsx("div", { style: styles_1.container, children: jsx_runtime_1.jsx(SelectedInspector_1.SelectedInspector, { selection: sameSequenceInspectorSelection, readOnlyStudio: readOnlyStudio }) }));
30
30
  }
31
31
  return (jsx_runtime_1.jsx("div", { style: styles_1.container, children: jsx_runtime_1.jsxs(common_1.InspectorMessage, { children: [selectedItems.length, " items selected"] }) }));
32
32
  }
33
- return (jsx_runtime_1.jsx("div", { style: styles_1.container, children: jsx_runtime_1.jsx(SelectedInspector_1.SelectedInspector, { selection: selectedItems[0] }) }));
33
+ return (jsx_runtime_1.jsx("div", { style: styles_1.container, children: jsx_runtime_1.jsx(SelectedInspector_1.SelectedInspector, { selection: selectedItems[0], readOnlyStudio: readOnlyStudio }) }));
34
34
  };
35
35
  exports.InspectorPanel = InspectorPanel;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import type { TSequence } from 'remotion';
2
+ import { type TSequence } from 'remotion';
3
3
  import type { CodePosition } from '../error-overlay/react-overlay/utils/get-source-map';
4
4
  import type { SequenceNodePathInfo } from '../helpers/get-timeline-sequence-sort-key';
5
5
  import { type FlatTreeRow } from '../helpers/timeline-layout';
@@ -11,6 +11,7 @@ export declare const getInspectorSelectableItems: (rows: readonly FlatTreeRow[])
11
11
  export declare const hasSequenceControls: (sequence: TSequence) => sequence is SequenceWithControls;
12
12
  export declare const InspectorSequenceSection: React.FC<{
13
13
  readonly sequence: SequenceWithControls;
14
+ readonly readOnlyStudio: boolean;
14
15
  readonly validatedLocation: CodePosition;
15
16
  readonly nodePathInfo: SequenceNodePathInfo;
16
17
  readonly keyframeDisplayOffset: number;
@@ -3,18 +3,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.InspectorSequenceSection = exports.hasSequenceControls = exports.getInspectorSelectableItems = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
+ const remotion_1 = require("remotion");
6
7
  const client_id_1 = require("../helpers/client-id");
7
8
  const colors_1 = require("../helpers/colors");
8
9
  const get_preview_file_type_1 = require("../helpers/get-preview-file-type");
9
10
  const interactivity_enabled_1 = require("../helpers/interactivity-enabled");
10
11
  const timeline_layout_1 = require("../helpers/timeline-layout");
12
+ const border_radius_1 = require("../icons/border-radius");
13
+ const fullscreen_1 = require("../icons/fullscreen");
11
14
  const plus_1 = require("../icons/plus");
12
15
  const modals_1 = require("../state/modals");
13
16
  const AssetFileIcon_1 = require("./AssetFileIcon");
14
17
  const InlineAction_1 = require("./InlineAction");
18
+ const InlineCaptionInspector_1 = require("./InlineCaptionInspector");
15
19
  const common_1 = require("./InspectorPanel/common");
20
+ const inspector_section_collapse_1 = require("./InspectorPanel/inspector-section-collapse");
16
21
  const styles_1 = require("./InspectorPanel/styles");
17
22
  const asset_search_1 = require("./QuickSwitcher/asset-search");
23
+ const border_radius_representation_1 = require("./Timeline/border-radius-representation");
24
+ const save_sequence_prop_1 = require("./Timeline/save-sequence-prop");
18
25
  const timeline_asset_link_1 = require("./Timeline/timeline-asset-link");
19
26
  const TimelineAssetField_1 = require("./Timeline/TimelineAssetField");
20
27
  const TimelineExpandedRow_1 = require("./Timeline/TimelineExpandedRow");
@@ -44,6 +51,43 @@ const plusIcon = {
44
51
  width: 15,
45
52
  height: 15,
46
53
  };
54
+ const borderRadiusToggleIcon = {
55
+ flexShrink: 0,
56
+ height: 15,
57
+ width: 15,
58
+ };
59
+ const collapsibleSectionHeaderButton = {
60
+ appearance: 'none',
61
+ backgroundColor: 'transparent',
62
+ border: 'none',
63
+ borderRadius: 3,
64
+ cursor: 'default',
65
+ display: 'block',
66
+ flex: 1,
67
+ fontFamily: 'Arial, Helvetica, sans-serif',
68
+ fontSize: 12,
69
+ fontWeight: 'bold',
70
+ lineHeight: '16px',
71
+ margin: 0,
72
+ minWidth: 0,
73
+ overflow: 'hidden',
74
+ padding: '4px 0',
75
+ textAlign: 'left',
76
+ textOverflow: 'ellipsis',
77
+ userSelect: 'none',
78
+ whiteSpace: 'nowrap',
79
+ };
80
+ const CollapsibleInspectorSectionHeader = ({ action, expanded, label, onToggle }) => {
81
+ const [hovered, setHovered] = (0, react_1.useState)(false);
82
+ const style = (0, react_1.useMemo)(() => {
83
+ return {
84
+ ...collapsibleSectionHeaderButton,
85
+ color: hovered ? colors_1.WHITE : colors_1.LIGHT_TEXT,
86
+ };
87
+ }, [hovered]);
88
+ return (jsx_runtime_1.jsxs("div", { style: styles_1.sectionHeaderRow, children: [
89
+ jsx_runtime_1.jsx("button", { type: "button", "aria-expanded": expanded, "aria-label": `${expanded ? 'Collapse' : 'Expand'} ${label}`, className: "__remotion-inspector-section-title", onClick: onToggle, onPointerEnter: () => setHovered(true), onPointerLeave: () => setHovered(false), style: style, children: label }), action] }));
90
+ };
47
91
  const assetSelectorIcon = {
48
92
  flexShrink: 0,
49
93
  height: 18,
@@ -88,6 +132,7 @@ const isEffectsRoot = (node) => {
88
132
  return auxiliaryKeys[auxiliaryKeys.length - 1] === 'effects';
89
133
  };
90
134
  const INSPECTOR_COLLAPSED_ROWS_SESSION_STORAGE_KEY = 'remotion.editor.inspectorCollapsedRows';
135
+ const INSPECTOR_SECTION_EXPANSION_OVERRIDES_SESSION_STORAGE_KEY = 'remotion.editor.inspectorSectionExpansionOverrides';
91
136
  const getInspectorExpansionKey = (nodePathInfo) => {
92
137
  return JSON.stringify(nodePathInfo);
93
138
  };
@@ -121,6 +166,48 @@ const persistInspectorCollapsedKeys = (keys) => {
121
166
  // Ignore quota errors or disabled storage.
122
167
  }
123
168
  };
169
+ const loadInspectorSectionExpansionOverrides = () => {
170
+ if (typeof window === 'undefined') {
171
+ return {};
172
+ }
173
+ try {
174
+ const raw = window.sessionStorage.getItem(INSPECTOR_SECTION_EXPANSION_OVERRIDES_SESSION_STORAGE_KEY);
175
+ if (raw === null) {
176
+ return {};
177
+ }
178
+ const parsed = JSON.parse(raw);
179
+ if (parsed === null ||
180
+ typeof parsed !== 'object' ||
181
+ Array.isArray(parsed)) {
182
+ return {};
183
+ }
184
+ return Object.fromEntries(Object.entries(parsed).filter((entry) => {
185
+ return typeof entry[1] === 'boolean';
186
+ }));
187
+ }
188
+ catch (_a) {
189
+ return {};
190
+ }
191
+ };
192
+ const persistInspectorSectionExpansionOverrides = (overrides) => {
193
+ if (typeof window === 'undefined') {
194
+ return;
195
+ }
196
+ try {
197
+ window.sessionStorage.setItem(INSPECTOR_SECTION_EXPANSION_OVERRIDES_SESSION_STORAGE_KEY, JSON.stringify(overrides));
198
+ }
199
+ catch (_a) {
200
+ // Ignore quota errors or disabled storage.
201
+ }
202
+ };
203
+ const getInspectorSectionExpansionKey = ({ nodePathInfo, groupId, }) => {
204
+ return JSON.stringify([
205
+ nodePathInfo.sequenceSubscriptionKey.absolutePath,
206
+ nodePathInfo.sequenceSubscriptionKey.nodePath,
207
+ nodePathInfo.index,
208
+ groupId,
209
+ ]);
210
+ };
124
211
  const getInspectorControlGroups = (rows) => {
125
212
  return timeline_layout_1.SCHEMA_FIELD_GROUPS.map((group) => ({
126
213
  ...group,
@@ -141,15 +228,20 @@ const hasSequenceControls = (sequence) => {
141
228
  return sequence.controls !== null;
142
229
  };
143
230
  exports.hasSequenceControls = hasSequenceControls;
144
- const InspectorSequenceSection = ({ sequence, validatedLocation, nodePathInfo, keyframeDisplayOffset, renderTransformControls, }) => {
145
- const { tree } = (0, use_timeline_expanded_tree_1.useTimelineExpandedTree)({
231
+ const InspectorSequenceSection = ({ sequence, readOnlyStudio, validatedLocation, nodePathInfo, keyframeDisplayOffset, renderTransformControls, }) => {
232
+ var _a;
233
+ const { tree, propStatuses } = (0, use_timeline_expanded_tree_1.useTimelineExpandedTree)({
146
234
  sequence,
147
235
  nodePathInfo,
148
236
  includeTextContent: true,
149
237
  includeSourceControls: true,
150
238
  });
151
239
  const [collapsedKeys, setCollapsedKeys] = (0, react_1.useState)(loadInspectorCollapsedKeys);
240
+ const [sectionExpansionOverrides, setSectionExpansionOverrides] = (0, react_1.useState)(loadInspectorSectionExpansionOverrides);
241
+ const [automaticSectionExpansion, setAutomaticSectionExpansion] = (0, react_1.useState)({});
152
242
  const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
243
+ const { getDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeDragOverridesContext);
244
+ const { setPropStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
153
245
  const { setSelectedModal } = (0, react_1.useContext)(modals_1.ModalsContext);
154
246
  const selectAsset = (0, use_select_asset_1.useSelectAsset)();
155
247
  const mediaSrc = (0, timeline_asset_link_1.getTimelineAssetSrcFromSchema)(sequence.controls);
@@ -229,15 +321,102 @@ const InspectorSequenceSection = ({ sequence, validatedLocation, nodePathInfo, k
229
321
  }),
230
322
  };
231
323
  }, [getIsExpanded, tree]);
232
- const controlSelectableItems = (0, react_1.useMemo)(() => (0, exports.getInspectorSelectableItems)(controlRows.filter(({ node }) => {
233
- var _a;
234
- return node.kind !== 'field' || ((_a = node.field) === null || _a === void 0 ? void 0 : _a.key) !== 'src';
235
- })), [controlRows]);
236
324
  const effectSelectableItems = (0, react_1.useMemo)(() => (0, exports.getInspectorSelectableItems)(effectRows), [effectRows]);
237
325
  const controlGroups = (0, react_1.useMemo)(() => getInspectorControlGroups(controlRows), [controlRows]);
238
326
  const layoutGroup = controlGroups.find((group) => group.id === 'layout');
239
327
  const controlGroupsWithoutLayout = controlGroups.filter((group) => group.id !== 'layout');
328
+ const sequencePropStatuses = remotion_1.Internals.getPropStatusesCtx(propStatuses, nodePathInfo.sequenceSubscriptionKey);
240
329
  const { schema } = sequence.controls;
330
+ const getControlGroupActivity = (0, react_1.useCallback)((group) => {
331
+ if (!(0, inspector_section_collapse_1.isSmartCollapsibleInspectorGroup)(group.id)) {
332
+ return 'active';
333
+ }
334
+ return (0, inspector_section_collapse_1.getInspectorSectionActivity)({
335
+ group: group.id,
336
+ propStatuses: sequencePropStatuses,
337
+ schema,
338
+ });
339
+ }, [schema, sequencePropStatuses]);
340
+ (0, react_1.useEffect)(() => {
341
+ setAutomaticSectionExpansion((previous) => {
342
+ let changed = false;
343
+ const next = { ...previous };
344
+ for (const group of controlGroups) {
345
+ if (!(0, inspector_section_collapse_1.isSmartCollapsibleInspectorGroup)(group.id)) {
346
+ continue;
347
+ }
348
+ const expansionKey = getInspectorSectionExpansionKey({
349
+ nodePathInfo,
350
+ groupId: group.id,
351
+ });
352
+ const activity = getControlGroupActivity(group);
353
+ if (activity === 'active' && next[expansionKey] !== true) {
354
+ next[expansionKey] = true;
355
+ changed = true;
356
+ }
357
+ else if (activity === 'inactive' &&
358
+ next[expansionKey] === undefined) {
359
+ next[expansionKey] = false;
360
+ changed = true;
361
+ }
362
+ }
363
+ return changed ? next : previous;
364
+ });
365
+ }, [controlGroups, getControlGroupActivity, nodePathInfo]);
366
+ const isControlGroupExpanded = (0, react_1.useCallback)((group) => {
367
+ if (!(0, inspector_section_collapse_1.isSmartCollapsibleInspectorGroup)(group.id)) {
368
+ return true;
369
+ }
370
+ const expansionKey = getInspectorSectionExpansionKey({
371
+ nodePathInfo,
372
+ groupId: group.id,
373
+ });
374
+ const override = sectionExpansionOverrides[expansionKey];
375
+ if (override !== undefined) {
376
+ return override;
377
+ }
378
+ const automaticExpansion = automaticSectionExpansion[expansionKey];
379
+ if (automaticExpansion !== undefined) {
380
+ return automaticExpansion;
381
+ }
382
+ return getControlGroupActivity(group) !== 'inactive';
383
+ }, [
384
+ automaticSectionExpansion,
385
+ getControlGroupActivity,
386
+ nodePathInfo,
387
+ sectionExpansionOverrides,
388
+ ]);
389
+ const toggleControlGroup = (0, react_1.useCallback)((group) => {
390
+ const expansionKey = getInspectorSectionExpansionKey({
391
+ nodePathInfo,
392
+ groupId: group.id,
393
+ });
394
+ const nextExpanded = !isControlGroupExpanded(group);
395
+ setSectionExpansionOverrides((previous) => {
396
+ const next = { ...previous, [expansionKey]: nextExpanded };
397
+ persistInspectorSectionExpansionOverrides(next);
398
+ return next;
399
+ });
400
+ }, [isControlGroupExpanded, nodePathInfo]);
401
+ const visibleControlRows = controlGroups.flatMap((group) => {
402
+ return isControlGroupExpanded(group) ? group.rows : [];
403
+ });
404
+ const controlSelectableItems = (0, react_1.useMemo)(() => (0, exports.getInspectorSelectableItems)(visibleControlRows.filter(({ node }) => {
405
+ var _a;
406
+ return node.kind !== 'field' || ((_a = node.field) === null || _a === void 0 ? void 0 : _a.key) !== 'src';
407
+ })), [visibleControlRows]);
408
+ const borderRadiusGroup = controlGroups.find((group) => group.id === 'border-radius');
409
+ const borderRadiusUsesShorthand = borderRadiusGroup === null || borderRadiusGroup === void 0 ? void 0 : borderRadiusGroup.rows.some(({ node }) => {
410
+ var _a;
411
+ return node.kind === 'field' && ((_a = node.field) === null || _a === void 0 ? void 0 : _a.key) === border_radius_representation_1.BORDER_RADIUS_SHORTHAND_KEY;
412
+ });
413
+ const borderRadiusConversion = (0, border_radius_representation_1.getBorderRadiusConversion)(sequencePropStatuses, getDragOverrides(nodePathInfo.sequenceSubscriptionKey));
414
+ const inlineCaptionValue = ((_a = schema.captions) === null || _a === void 0 ? void 0 : _a.type) === 'remotion-captions'
415
+ ? sequence.controls.currentRuntimeValueDotNotation.captions
416
+ : null;
417
+ const inlineCaptions = Array.isArray(inlineCaptionValue)
418
+ ? inlineCaptionValue
419
+ : null;
241
420
  const showEffectsSection = nodePathInfo.supportsEffects || effectRows.length > 0;
242
421
  const canAddEffect = nodePathInfo.supportsEffects &&
243
422
  previewServerState.type === 'connected' &&
@@ -260,14 +439,65 @@ const InspectorSequenceSection = ({ sequence, validatedLocation, nodePathInfo, k
260
439
  setSelectedModal,
261
440
  validatedLocation.source,
262
441
  ]);
442
+ const onConvertBorderRadius = (0, react_1.useCallback)(() => {
443
+ if (borderRadiusConversion === null ||
444
+ previewServerState.type !== 'connected') {
445
+ return;
446
+ }
447
+ const common = {
448
+ fileName: validatedLocation.source,
449
+ nodePath: nodePathInfo.sequenceSubscriptionKey,
450
+ defaultValue: null,
451
+ schema,
452
+ };
453
+ const changes = (0, border_radius_representation_1.getBorderRadiusConversionChanges)(borderRadiusConversion).map((change) => ({ ...common, ...change }));
454
+ (0, save_sequence_prop_1.saveSequenceProps)({
455
+ changes,
456
+ addedKeyframes: null,
457
+ movedKeyframes: null,
458
+ setPropStatuses,
459
+ clientId: previewServerState.clientId,
460
+ undoLabel: 'Change border radius representation',
461
+ redoLabel: 'Change border radius representation again',
462
+ });
463
+ }, [
464
+ borderRadiusConversion,
465
+ nodePathInfo.sequenceSubscriptionKey,
466
+ previewServerState,
467
+ schema,
468
+ setPropStatuses,
469
+ validatedLocation.source,
470
+ ]);
471
+ const borderRadiusAction = borderRadiusGroup ? (jsx_runtime_1.jsx(InlineAction_1.InlineAction, { disabled: borderRadiusConversion === null ||
472
+ previewServerState.type !== 'connected', onClick: onConvertBorderRadius, title: borderRadiusConversion === null
473
+ ? borderRadiusUsesShorthand
474
+ ? 'A static border radius is required to use individual corners'
475
+ : 'All four corners must have the same static value'
476
+ : borderRadiusUsesShorthand
477
+ ? 'Use individual corner radii'
478
+ : 'Use one border radius value', renderAction: (color) => borderRadiusUsesShorthand ? (jsx_runtime_1.jsx(fullscreen_1.FullscreenIcon, { color: color, style: borderRadiusToggleIcon })) : (jsx_runtime_1.jsx(border_radius_1.BorderRadiusIcon, { color: color, style: borderRadiusToggleIcon })) })) : null;
263
479
  const effectsHeader = (jsx_runtime_1.jsxs("div", { style: styles_1.sectionHeaderRow, children: [
264
480
  jsx_runtime_1.jsx("div", { style: effectsHeaderTitle, children: "Effects" }), jsx_runtime_1.jsx(InlineAction_1.InlineAction, { disabled: !canAddEffect, onClick: onAddEffect, title: canAddEffect ? 'Add effect' : undefined, renderAction: (color) => jsx_runtime_1.jsx(plus_1.Plus, { color: color, style: plusIcon }) })] }));
265
481
  const renderRow = ({ node, depth }) => {
266
482
  return (jsx_runtime_1.jsx(TimelineRowLayoutContext_1.TimelineRowLayoutContext.Provider, { value: TimelineRowLayoutContext_1.INSPECTOR_TIMELINE_ROW_LAYOUT, children: jsx_runtime_1.jsx(TimelineExpandedRow_1.TimelineExpandedRow, { node: node, depth: depth, nestedDepth: 0, rowDepthBase: 0, getIsExpanded: getIsExpanded, toggleTrack: toggleTrack, validatedLocation: validatedLocation, nodePath: nodePathInfo.sequenceSubscriptionKey, schema: schema, keyframeDisplayOffset: keyframeDisplayOffset, keyframeControlsMode: "inspector" }) }, JSON.stringify(node.nodePathInfo)));
267
483
  };
268
- if (controlRows.length === 0 && !showEffectsSection) {
484
+ const renderControlGroupHeader = (group) => {
485
+ const collapsible = (0, inspector_section_collapse_1.isSmartCollapsibleInspectorGroup)(group.id);
486
+ const expanded = isControlGroupExpanded(group);
487
+ if (collapsible) {
488
+ return (jsx_runtime_1.jsx(CollapsibleInspectorSectionHeader, { action: group.id === 'border-radius' && expanded ? borderRadiusAction : null, expanded: expanded, label: group.label, onToggle: () => toggleControlGroup(group) }));
489
+ }
490
+ return (jsx_runtime_1.jsx("div", { style: styles_1.sectionHeaderRow, children: jsx_runtime_1.jsx("div", { style: effectsHeaderTitle, children: group.label }) }));
491
+ };
492
+ if (controlRows.length === 0 &&
493
+ !showEffectsSection &&
494
+ inlineCaptions === null) {
269
495
  return (jsx_runtime_1.jsx("div", { style: container, children: jsx_runtime_1.jsx(common_1.InspectorSection, { header: "Controls", children: jsx_runtime_1.jsx("div", { style: emptyState, children: "No schema" }) }) }));
270
496
  }
271
- return (jsx_runtime_1.jsx(TimelineAssetField_1.AssetSelectionContext.Provider, { value: assetSelectionContextValue, children: jsx_runtime_1.jsxs("div", { style: container, children: [controlRows.length > 0 ? (jsx_runtime_1.jsx(TimelineSelection_1.TimelineSelectionOrderProvider, { items: controlSelectableItems, children: controlGroupsWithoutLayout.map((group) => (jsx_runtime_1.jsxs(common_1.InspectorSection, { header: group.label, children: [group.id === 'transforms' ? renderTransformControls() : null, group.rows.map(renderRow)] }, group.id))) })) : null, showEffectsSection ? (jsx_runtime_1.jsx(common_1.InspectorSection, { header: effectsHeader, children: effectRows.length > 0 ? (jsx_runtime_1.jsx(TimelineSelection_1.TimelineSelectionOrderProvider, { items: effectSelectableItems, children: effectRows.map(renderRow) })) : null })) : null, layoutGroup ? (jsx_runtime_1.jsx(TimelineSelection_1.TimelineSelectionOrderProvider, { items: controlSelectableItems, children: jsx_runtime_1.jsx(common_1.InspectorSection, { header: layoutGroup.label, children: layoutGroup.rows.map(renderRow) }) })) : null] }) }));
497
+ return (jsx_runtime_1.jsx(TimelineAssetField_1.AssetSelectionContext.Provider, { value: assetSelectionContextValue, children: jsx_runtime_1.jsxs("div", { style: container, children: [controlRows.length > 0 ? (jsx_runtime_1.jsx(TimelineSelection_1.TimelineSelectionOrderProvider, { items: controlSelectableItems, children: controlGroupsWithoutLayout.map((group) => (jsx_runtime_1.jsx(common_1.InspectorSection, { header: renderControlGroupHeader(group), children: isControlGroupExpanded(group) ? (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [group.id === 'transforms'
498
+ ? renderTransformControls()
499
+ : null, group.rows.map(renderRow)] })) : null }, group.id))) })) : null, inlineCaptions ? (jsx_runtime_1.jsx(InlineCaptionInspector_1.InlineCaptionInspector, { captions: inlineCaptions, controls: sequence.controls, nodePath: nodePathInfo.sequenceSubscriptionKey, readOnlyStudio: readOnlyStudio, validatedLocation: validatedLocation })) : null, showEffectsSection ? (jsx_runtime_1.jsx(common_1.InspectorSection, { header: effectsHeader, children: effectRows.length > 0 ? (jsx_runtime_1.jsx(TimelineSelection_1.TimelineSelectionOrderProvider, { items: effectSelectableItems, children: effectRows.map(renderRow) })) : null })) : null, layoutGroup ? (jsx_runtime_1.jsx(TimelineSelection_1.TimelineSelectionOrderProvider, { items: controlSelectableItems, children: jsx_runtime_1.jsx(common_1.InspectorSection, { header: renderControlGroupHeader(layoutGroup), children: isControlGroupExpanded(layoutGroup)
500
+ ? layoutGroup.rows.map(renderRow)
501
+ : null }) })) : null] }) }));
272
502
  };
273
503
  exports.InspectorSequenceSection = InspectorSequenceSection;
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ export declare const toggleLoop: (setLoop: React.Dispatch<React.SetStateAction<boolean>>) => void;
2
3
  export declare const LoopToggle: React.FC<{
3
4
  readonly loop: boolean;
4
5
  readonly setLoop: React.Dispatch<React.SetStateAction<boolean>>;
@@ -1,18 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LoopToggle = void 0;
3
+ exports.LoopToggle = exports.toggleLoop = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const colors_1 = require("../helpers/colors");
7
7
  const loop_1 = require("../state/loop");
8
8
  const ControlButton_1 = require("./ControlButton");
9
9
  const accessibilityLabel = 'Loop video';
10
+ const toggleLoop = (setLoop) => {
11
+ setLoop((currentLoop) => {
12
+ (0, loop_1.persistLoopOption)(!currentLoop);
13
+ return !currentLoop;
14
+ });
15
+ };
16
+ exports.toggleLoop = toggleLoop;
10
17
  const LoopToggle = ({ loop, setLoop }) => {
11
18
  const onClick = (0, react_1.useCallback)(() => {
12
- setLoop((c) => {
13
- (0, loop_1.persistLoopOption)(!c);
14
- return !c;
15
- });
19
+ (0, exports.toggleLoop)(setLoop);
16
20
  }, [setLoop]);
17
21
  return (jsx_runtime_1.jsx(ControlButton_1.ControlButton, { title: accessibilityLabel, "aria-label": accessibilityLabel, onClick: onClick, children: jsx_runtime_1.jsx("svg", { viewBox: "0 0 512 512", style: { width: 18, height: 18 }, children: jsx_runtime_1.jsx("path", { fill: loop ? colors_1.BLUE : colors_1.WHITE, d: "M493.544 181.463c11.956 22.605 18.655 48.4 18.452 75.75C511.339 345.365 438.56 416 350.404 416H192v47.495c0 22.475-26.177 32.268-40.971 17.475l-80-80c-9.372-9.373-9.372-24.569 0-33.941l80-80C166.138 271.92 192 282.686 192 304v48h158.875c52.812 0 96.575-42.182 97.12-94.992.155-15.045-3.17-29.312-9.218-42.046-4.362-9.185-2.421-20.124 4.8-27.284 4.745-4.706 8.641-8.555 11.876-11.786 11.368-11.352 30.579-8.631 38.091 5.571zM64.005 254.992c.545-52.81 44.308-94.992 97.12-94.992H320v47.505c0 22.374 26.121 32.312 40.971 17.465l80-80c9.372-9.373 9.372-24.569 0-33.941l-80-80C346.014 16.077 320 26.256 320 48.545V96H161.596C73.44 96 .661 166.635.005 254.788c-.204 27.35 6.495 53.145 18.452 75.75 7.512 14.202 26.723 16.923 38.091 5.57 3.235-3.231 7.13-7.08 11.876-11.786 7.22-7.16 9.162-18.098 4.8-27.284-6.049-12.735-9.374-27.001-9.219-42.046z" }) }) }));
18
22
  };
@@ -90,9 +90,17 @@ const MenuSubItem = ({ label, leaveLeftSpace, leftItem, onActionChosen, id, sele
90
90
  return null;
91
91
  }
92
92
  const left = size.left + size.width + styles_1.SUBMENU_LEFT_INSET;
93
+ const minSpaceRequired = mobileLayout
94
+ ? styles_1.MAX_MOBILE_MENU_WIDTH
95
+ : styles_1.MAX_MENU_WIDTH;
96
+ const canOpenOnRight = size.windowSize.width - size.left - size.width >= minSpaceRequired;
93
97
  return {
94
98
  ...styles_1.menuContainerTowardsBottom,
95
- left: mobileLayout ? left * 0.7 : left,
99
+ ...(canOpenOnRight
100
+ ? { left }
101
+ : {
102
+ right: size.windowSize.width - size.left + styles_1.SUBMENU_LEFT_INSET,
103
+ }),
96
104
  top: size.top - styles_1.MENU_VERTICAL_PADDING,
97
105
  };
98
106
  }, [mobileLayout, selected, size, subMenu, subMenuActivated]);
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MenuToolbar = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
+ const browser_studio_operations_1 = require("../helpers/browser-studio-operations");
7
+ const client_id_1 = require("../helpers/client-id");
6
8
  const colors_1 = require("../helpers/colors");
7
9
  const mobile_layout_1 = require("../helpers/mobile-layout");
8
10
  const use_menu_structure_1 = require("../helpers/use-menu-structure");
@@ -31,6 +33,11 @@ const flex = {
31
33
  };
32
34
  const MenuToolbar = ({ readOnlyStudio }) => {
33
35
  const [selected, setSelected] = (0, react_1.useState)(null);
36
+ const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
37
+ const browserStudioOperations = (0, browser_studio_operations_1.getBrowserStudioOperations)();
38
+ const canUndoAndRedo = !readOnlyStudio ||
39
+ (previewServerState.type === 'connected' &&
40
+ browserStudioOperations !== null);
34
41
  const mobileLayout = (0, mobile_layout_1.useMobileLayout)();
35
42
  const fixedWidthRight = (0, react_1.useMemo)(() => {
36
43
  return {
@@ -101,7 +108,7 @@ const MenuToolbar = ({ readOnlyStudio }) => {
101
108
  jsx_runtime_1.jsxs("div", { style: fixedWidthLeft, children: [
102
109
  jsx_runtime_1.jsx(SidebarCollapserControls_1.SidebarCollapserControl, { side: "left" }), structure.map((s) => {
103
110
  return (jsx_runtime_1.jsx(MenuItem_1.MenuItem, { selected: selected === s.id, onItemSelected: itemClicked, onItemHovered: itemHovered, id: s.id, label: s.label, onItemQuit: onItemQuit, menu: s, onPreviousMenu: onPreviousMenu, onNextMenu: onNextMenu, leaveLeftPadding: s.leaveLeftPadding }, s.id));
104
- }), readOnlyStudio ? null : jsx_runtime_1.jsx(UpdateCheck_1.UpdateCheck, {})] }), jsx_runtime_1.jsx("div", { style: flex }), jsx_runtime_1.jsx(MenuBuildIndicator_1.MenuBuildIndicator, {}), jsx_runtime_1.jsx("div", { style: flex }), jsx_runtime_1.jsxs("div", { style: fixedWidthRight, children: [readOnlyStudio ? null : jsx_runtime_1.jsx(UndoRedoButtons_1.UndoRedoButtons, {}), jsx_runtime_1.jsx(SidebarCollapserControls_1.SidebarCollapserControl, { side: "right" })
111
+ }), readOnlyStudio ? null : jsx_runtime_1.jsx(UpdateCheck_1.UpdateCheck, {})] }), jsx_runtime_1.jsx("div", { style: flex }), jsx_runtime_1.jsx(MenuBuildIndicator_1.MenuBuildIndicator, {}), jsx_runtime_1.jsx("div", { style: flex }), jsx_runtime_1.jsxs("div", { style: fixedWidthRight, children: [canUndoAndRedo ? jsx_runtime_1.jsx(UndoRedoButtons_1.UndoRedoButtons, {}) : null, jsx_runtime_1.jsx(SidebarCollapserControls_1.SidebarCollapserControl, { side: "right" })
105
112
  ] })
106
113
  ] }));
107
114
  };