@remotion/studio 4.0.488 → 4.0.490

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 (59) hide show
  1. package/dist/components/Canvas.js +30 -12
  2. package/dist/components/Editor.js +2 -5
  3. package/dist/components/GlobalKeybindings.js +2 -1
  4. package/dist/components/InspectorPanel/AlignmentControls.d.ts +5 -0
  5. package/dist/components/InspectorPanel/AlignmentControls.js +205 -0
  6. package/dist/components/InspectorPanel/EasingInspector.js +8 -2
  7. package/dist/components/InspectorPanel/KeyframeSettings.d.ts +5 -0
  8. package/dist/components/InspectorPanel/KeyframeSettings.js +142 -0
  9. package/dist/components/InspectorPanel/SequenceSelectionInspector.js +2 -1
  10. package/dist/components/InspectorPanel/alignment-controls.d.ts +12 -0
  11. package/dist/components/InspectorPanel/alignment-controls.js +42 -0
  12. package/dist/components/InspectorSequenceSection.d.ts +1 -0
  13. package/dist/components/InspectorSequenceSection.js +2 -2
  14. package/dist/components/KeyboardShortcutsExplainer.js +2 -1
  15. package/dist/components/MobilePanel.js +9 -3
  16. package/dist/components/Modals.js +3 -3
  17. package/dist/components/NewComposition/CancelButton.d.ts +1 -0
  18. package/dist/components/NewComposition/CancelButton.js +6 -2
  19. package/dist/components/NewComposition/MenuContent.js +2 -2
  20. package/dist/components/Preview.js +1 -1
  21. package/dist/components/SelectedOutlineOverlay.d.ts +6 -1
  22. package/dist/components/SelectedOutlineOverlay.js +247 -9
  23. package/dist/components/Timeline/MaxTimelineTracks.js +2 -5
  24. package/dist/components/Timeline/TimelineClipboardKeybindings.js +3 -0
  25. package/dist/components/Timeline/TimelineEffectPropItem.js +2 -50
  26. package/dist/components/Timeline/TimelineSequenceItem.js +8 -10
  27. package/dist/components/Timeline/TimelineSequenceName.js +1 -3
  28. package/dist/components/Timeline/TimelineSequencePropItem.js +2 -49
  29. package/dist/components/import-assets.d.ts +1 -0
  30. package/dist/components/import-assets.js +1 -0
  31. package/dist/esm/{chunk-nn36hyt1.js → chunk-16nwrbrm.js} +4972 -4346
  32. package/dist/esm/index.mjs +21 -1
  33. package/dist/esm/internals.mjs +4972 -4346
  34. package/dist/esm/previewEntry.mjs +5010 -4384
  35. package/dist/esm/renderEntry.mjs +1 -1
  36. package/dist/helpers/install-required-package.d.ts +1 -0
  37. package/dist/helpers/install-required-package.js +3 -2
  38. package/dist/helpers/interactivity-enabled.js +2 -4
  39. package/dist/helpers/show-browser-rendering.js +2 -1
  40. package/dist/helpers/studio-runtime-config.d.ts +7 -0
  41. package/dist/helpers/studio-runtime-config.js +46 -0
  42. package/dist/helpers/use-keybinding.js +4 -3
  43. package/dist/helpers/use-menu-structure.js +2 -1
  44. package/dist/icons/align-bottom.d.ts +3 -0
  45. package/dist/icons/align-bottom.js +12 -0
  46. package/dist/icons/align-center-horizontal.d.ts +3 -0
  47. package/dist/icons/align-center-horizontal.js +12 -0
  48. package/dist/icons/align-center-vertical.d.ts +3 -0
  49. package/dist/icons/align-center-vertical.js +12 -0
  50. package/dist/icons/align-left.d.ts +3 -0
  51. package/dist/icons/align-left.js +12 -0
  52. package/dist/icons/align-right.d.ts +3 -0
  53. package/dist/icons/align-right.js +12 -0
  54. package/dist/icons/align-top.d.ts +3 -0
  55. package/dist/icons/align-top.js +12 -0
  56. package/dist/state/modals.d.ts +1 -2
  57. package/package.json +11 -11
  58. package/dist/components/Timeline/KeyframeSettingsModal.d.ts +0 -15
  59. package/dist/components/Timeline/KeyframeSettingsModal.js +0 -151
@@ -12,6 +12,7 @@ const client_id_1 = require("../helpers/client-id");
12
12
  const colors_1 = require("../helpers/colors");
13
13
  const get_asset_metadata_1 = require("../helpers/get-asset-metadata");
14
14
  const get_effective_translation_1 = require("../helpers/get-effective-translation");
15
+ const install_required_package_1 = require("../helpers/install-required-package");
15
16
  const open_in_editor_1 = require("../helpers/open-in-editor");
16
17
  const remote_asset_drag_1 = require("../helpers/remote-asset-drag");
17
18
  const smooth_zoom_1 = require("../helpers/smooth-zoom");
@@ -35,6 +36,18 @@ const elementInstallCompositionIdStyle = {
35
36
  fontFamily: 'monospace',
36
37
  fontSize: 13,
37
38
  };
39
+ const elementInstallDependencyListStyle = {
40
+ marginTop: 8,
41
+ marginBottom: 0,
42
+ paddingLeft: 24,
43
+ listStyleType: 'disc',
44
+ };
45
+ const elementInstallDependencyStyle = {
46
+ color: 'inherit',
47
+ fontFamily: 'monospace',
48
+ fontSize: 13,
49
+ lineHeight: 1.5,
50
+ };
38
51
  const elementInstallCodeDetailsStyle = {
39
52
  marginTop: 12,
40
53
  fontSize: 13,
@@ -611,11 +624,12 @@ const Canvas = ({ canvasContent, size }) => {
611
624
  (0, react_1.useEffect)(() => {
612
625
  fetchMetadata();
613
626
  }, [fetchMetadata]);
614
- const updateElementInstallTarget = (0, react_1.useCallback)(() => {
627
+ const updateElementInstallTarget = (0, react_1.useCallback)((requestId) => {
615
628
  if (previewServerClientId === null) {
616
629
  return;
617
630
  }
618
631
  (0, call_api_1.callApi)('/api/update-element-install-target', {
632
+ requestId,
619
633
  clientId: previewServerClientId,
620
634
  compositionFile: canInstallElements ? compositionFile : null,
621
635
  compositionId: canInstallElements ? currentCompositionId : null,
@@ -629,17 +643,9 @@ const Canvas = ({ canvasContent, size }) => {
629
643
  currentCompositionId,
630
644
  previewServerClientId,
631
645
  ]);
632
- (0, react_1.useEffect)(() => {
633
- updateElementInstallTarget();
634
- const interval = window.setInterval(updateElementInstallTarget, 2000);
635
- return () => {
636
- window.clearInterval(interval);
637
- };
638
- }, [updateElementInstallTarget]);
639
646
  (0, react_1.useEffect)(() => {
640
647
  const markFocused = () => {
641
648
  lastFocusedAtRef.current = Date.now();
642
- updateElementInstallTarget();
643
649
  };
644
650
  window.addEventListener('focus', markFocused);
645
651
  document.addEventListener('pointerdown', markFocused, { capture: true });
@@ -647,7 +653,15 @@ const Canvas = ({ canvasContent, size }) => {
647
653
  window.removeEventListener('focus', markFocused);
648
654
  document.removeEventListener('pointerdown', markFocused, { capture: true });
649
655
  };
650
- }, [updateElementInstallTarget]);
656
+ }, []);
657
+ (0, react_1.useEffect)(() => {
658
+ return subscribeToEvent('request-element-install-target', (event) => {
659
+ if (event.type !== 'request-element-install-target') {
660
+ return;
661
+ }
662
+ updateElementInstallTarget(event.requestId);
663
+ });
664
+ }, [subscribeToEvent, updateElementInstallTarget]);
651
665
  (0, react_1.useEffect)(() => {
652
666
  if (installingElementName === null) {
653
667
  return;
@@ -692,10 +706,14 @@ const Canvas = ({ canvasContent, size }) => {
692
706
  let canceled = false;
693
707
  const handleInstallRequest = async () => {
694
708
  setInstallingElementName(activeElementInstallRequest.element.displayName);
709
+ const missingPackages = (0, install_required_package_1.getMissingPackages)(activeElementInstallRequest.element.dependencies);
695
710
  const accepted = await confirm({
696
711
  title: 'Install Element',
697
- message: (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: ["Install \u201C", activeElementInstallRequest.element.displayName, "\u201D into", ' ', jsx_runtime_1.jsx("code", { style: elementInstallCompositionIdStyle, children: activeElementInstallRequest.compositionId }), ' ', "composition? This will create an Element source file and update the composition source.",
698
- jsx_runtime_1.jsxs("details", { style: elementInstallCodeDetailsStyle, children: [
712
+ message: (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: ["Install \u201C", activeElementInstallRequest.element.displayName, "\u201D into", ' ', jsx_runtime_1.jsx("code", { style: elementInstallCompositionIdStyle, children: activeElementInstallRequest.compositionId }), ' ', "composition? This will create an Element source file and update the composition source.", missingPackages.length > 0 ? (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
713
+ jsx_runtime_1.jsx("br", {}), jsx_runtime_1.jsx("br", {}),
714
+ "The following dependencies will also be installed:",
715
+ jsx_runtime_1.jsx("ul", { style: elementInstallDependencyListStyle, children: missingPackages.map((packageName) => (jsx_runtime_1.jsx("li", { style: elementInstallDependencyStyle, children: packageName }, packageName))) })
716
+ ] })) : null, jsx_runtime_1.jsxs("details", { style: elementInstallCodeDetailsStyle, children: [
699
717
  jsx_runtime_1.jsx("summary", { style: elementInstallCodeSummaryStyle, children: "Preview Element source" }), jsx_runtime_1.jsx("pre", { style: elementInstallCodeBlockStyle, children: jsx_runtime_1.jsx("code", { children: activeElementInstallRequest.element.sourceCode }) })
700
718
  ] })
701
719
  ] })),
@@ -40,6 +40,7 @@ const react_1 = __importStar(require("react"));
40
40
  const remotion_1 = require("remotion");
41
41
  const colors_1 = require("../helpers/colors");
42
42
  const noop_1 = require("../helpers/noop");
43
+ const studio_runtime_config_1 = require("../helpers/studio-runtime-config");
43
44
  const canvas_ref_1 = require("../state/canvas-ref");
44
45
  const scale_lock_1 = require("../state/scale-lock");
45
46
  const timeline_zoom_1 = require("../state/timeline-zoom");
@@ -61,11 +62,7 @@ const background = {
61
62
  flexDirection: 'column',
62
63
  position: 'absolute',
63
64
  };
64
- const DEFAULT_BUFFER_STATE_DELAY_IN_MILLISECONDS = 300;
65
- exports.BUFFER_STATE_DELAY_IN_MILLISECONDS = typeof process.env.BUFFER_STATE_DELAY_IN_MILLISECONDS === 'undefined' ||
66
- process.env.BUFFER_STATE_DELAY_IN_MILLISECONDS === null
67
- ? DEFAULT_BUFFER_STATE_DELAY_IN_MILLISECONDS
68
- : Number(process.env.BUFFER_STATE_DELAY_IN_MILLISECONDS);
65
+ exports.BUFFER_STATE_DELAY_IN_MILLISECONDS = (0, studio_runtime_config_1.getStudioBufferStateDelayInMilliseconds)();
69
66
  const Editor = ({ Root, readOnlyStudio }) => {
70
67
  const [drawElement, setDrawElement] = (0, react_1.useState)(null);
71
68
  const size = player_1.PlayerInternals.useElementSize(drawElement, {
@@ -6,6 +6,7 @@ const remotion_1 = require("remotion");
6
6
  const calculate_timeline_1 = require("../helpers/calculate-timeline");
7
7
  const client_id_1 = require("../helpers/client-id");
8
8
  const show_browser_rendering_1 = require("../helpers/show-browser-rendering");
9
+ const studio_runtime_config_1 = require("../helpers/studio-runtime-config");
9
10
  const timeline_node_path_key_1 = require("../helpers/timeline-node-path-key");
10
11
  const use_keybinding_1 = require("../helpers/use-keybinding");
11
12
  const checkerboard_1 = require("../state/checkerboard");
@@ -134,7 +135,7 @@ const GlobalKeybindings = ({ readOnlyStudio }) => {
134
135
  commandCtrlKey: true,
135
136
  preventDefault: true,
136
137
  });
137
- const cmdIKey = process.env.ASK_AI_ENABLED
138
+ const cmdIKey = (0, studio_runtime_config_1.getStudioAskAIEnabled)()
138
139
  ? keybindings.registerKeybinding({
139
140
  event: 'keydown',
140
141
  key: 'i',
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import type { TrackWithHash } from '../../helpers/get-timeline-sequence-sort-key';
3
+ export declare const AlignmentControls: React.FC<{
4
+ readonly track: TrackWithHash;
5
+ }>;
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AlignmentControls = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const remotion_1 = require("remotion");
7
+ const client_id_1 = require("../../helpers/client-id");
8
+ const align_bottom_1 = require("../../icons/align-bottom");
9
+ const align_center_horizontal_1 = require("../../icons/align-center-horizontal");
10
+ const align_center_vertical_1 = require("../../icons/align-center-vertical");
11
+ const align_left_1 = require("../../icons/align-left");
12
+ const align_right_1 = require("../../icons/align-right");
13
+ const align_top_1 = require("../../icons/align-top");
14
+ const InlineAction_1 = require("../InlineAction");
15
+ const InspectorPanelLayout_1 = require("../InspectorPanelLayout");
16
+ const selected_outline_drag_1 = require("../selected-outline-drag");
17
+ const selected_outline_types_1 = require("../selected-outline-types");
18
+ const call_add_keyframe_1 = require("../Timeline/call-add-keyframe");
19
+ const save_sequence_prop_1 = require("../Timeline/save-sequence-prop");
20
+ const timeline_translate_utils_1 = require("../Timeline/timeline-translate-utils");
21
+ const alignment_controls_1 = require("./alignment-controls");
22
+ const isPropStatusDraggable = (propStatus) => {
23
+ return (!propStatus ||
24
+ propStatus.status === 'static' ||
25
+ (propStatus.status === 'keyframed' &&
26
+ propStatus.interpolationFunction === 'interpolate'));
27
+ };
28
+ const container = {
29
+ display: 'flex',
30
+ flexDirection: 'row',
31
+ gap: 4,
32
+ padding: `0 ${InspectorPanelLayout_1.INSPECTOR_PANEL_HORIZONTAL_PADDING}px 12px`,
33
+ alignItems: 'center',
34
+ };
35
+ const iconStyle = {
36
+ width: 16,
37
+ height: 16,
38
+ };
39
+ const verticalDivider = {
40
+ width: 1,
41
+ height: 16,
42
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
43
+ margin: '0 8px',
44
+ };
45
+ const AlignmentButton = ({ onClick, title, Icon, disabled }) => {
46
+ return (jsx_runtime_1.jsx(InlineAction_1.InlineAction, { title: title, onClick: onClick, renderAction: (color) => jsx_runtime_1.jsx(Icon, { style: iconStyle, color: color }), disabled: disabled }));
47
+ };
48
+ const AlignmentControls = ({ track }) => {
49
+ var _a;
50
+ const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
51
+ const { propStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModePropStatusesContext);
52
+ const { getDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeDragOverridesContext);
53
+ const { setPropStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
54
+ const { canvasContent, compositions } = (0, react_1.useContext)(remotion_1.Internals.CompositionManager);
55
+ const currentCompositionMetadata = (0, react_1.useMemo)(() => {
56
+ var _a;
57
+ if ((canvasContent === null || canvasContent === void 0 ? void 0 : canvasContent.type) !== 'composition')
58
+ return null;
59
+ return ((_a = compositions.find((c) => c.id === canvasContent.compositionId)) !== null && _a !== void 0 ? _a : null);
60
+ }, [canvasContent, compositions]);
61
+ const timelinePosition = remotion_1.Internals.Timeline.useTimelinePosition();
62
+ const handleAlign = (0, react_1.useCallback)((direction) => {
63
+ var _a;
64
+ var _b, _c, _d, _e;
65
+ if (previewServerState.type !== 'connected' ||
66
+ !track.nodePathInfo ||
67
+ !track.sequence.controls) {
68
+ return;
69
+ }
70
+ const ref = (_a = track.sequence.refForOutline) === null || _a === void 0 ? void 0 : _a.current;
71
+ if (!ref) {
72
+ return;
73
+ }
74
+ const nodePath = track.nodePathInfo.sequenceSubscriptionKey;
75
+ const nodePropStatuses = remotion_1.Internals.getPropStatusesCtx(propStatuses, nodePath);
76
+ const sourceFrame = timelinePosition - track.keyframeDisplayOffset;
77
+ const dragOverrides = (_b = getDragOverrides(nodePath)) !== null && _b !== void 0 ? _b : {};
78
+ const activeSchema = (0, selected_outline_drag_1.getSelectedOutlineActiveSchema)({
79
+ schema: track.sequence.controls.schema,
80
+ currentRuntimeValueDotNotation: track.sequence.controls.currentRuntimeValueDotNotation,
81
+ dragOverrides,
82
+ propStatus: nodePropStatuses,
83
+ frame: sourceFrame,
84
+ });
85
+ const fieldSchema = activeSchema === null || activeSchema === void 0 ? void 0 : activeSchema[selected_outline_types_1.translateFieldKey];
86
+ const propStatus = nodePropStatuses === null || nodePropStatuses === void 0 ? void 0 : nodePropStatuses[selected_outline_types_1.translateFieldKey];
87
+ if ((fieldSchema === null || fieldSchema === void 0 ? void 0 : fieldSchema.type) !== 'translate') {
88
+ return;
89
+ }
90
+ if (!isPropStatusDraggable(propStatus)) {
91
+ return;
92
+ }
93
+ let elementRect;
94
+ try {
95
+ elementRect = ref.getBoundingClientRect();
96
+ }
97
+ catch (_f) {
98
+ return;
99
+ }
100
+ if (!(currentCompositionMetadata === null || currentCompositionMetadata === void 0 ? void 0 : currentCompositionMetadata.width)) {
101
+ return;
102
+ }
103
+ const compositionRect = remotion_1.Internals.portalNode().getBoundingClientRect();
104
+ if (compositionRect.width === 0 || compositionRect.height === 0) {
105
+ return;
106
+ }
107
+ const scale = compositionRect.width / currentCompositionMetadata.width;
108
+ const currentTranslate = propStatus
109
+ ? String((_d = (_c = remotion_1.Internals.getEffectiveVisualModeValue({
110
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
+ propStatus: propStatus,
112
+ dragOverrideValue: dragOverrides[selected_outline_types_1.translateFieldKey],
113
+ defaultValue: fieldSchema.default,
114
+ frame: sourceFrame,
115
+ shouldResortToDefaultValueIfUndefined: true,
116
+ })) !== null && _c !== void 0 ? _c : fieldSchema.default) !== null && _d !== void 0 ? _d : '0px 0px')
117
+ : String((_e = fieldSchema.default) !== null && _e !== void 0 ? _e : '0px 0px');
118
+ const [currentX, currentY] = (0, timeline_translate_utils_1.parseTranslate)(currentTranslate);
119
+ const { x: newX, y: newY } = (0, alignment_controls_1.computeAlignedTranslate)({
120
+ direction,
121
+ elementRect,
122
+ compositionRect,
123
+ currentTranslateX: currentX,
124
+ currentTranslateY: currentY,
125
+ scale,
126
+ });
127
+ const newValue = (0, timeline_translate_utils_1.serializeTranslate)(newX, newY);
128
+ const undoLabels = {
129
+ left: 'Align left',
130
+ 'center-h': 'Align center horizontally',
131
+ right: 'Align right',
132
+ top: 'Align top',
133
+ 'center-v': 'Align center vertically',
134
+ bottom: 'Align bottom',
135
+ };
136
+ const label = undoLabels[direction];
137
+ if (!propStatus || propStatus.status === 'static') {
138
+ (0, save_sequence_prop_1.saveSequenceProps)({
139
+ changes: [
140
+ {
141
+ fileName: nodePath.absolutePath,
142
+ nodePath,
143
+ fieldKey: selected_outline_types_1.translateFieldKey,
144
+ value: newValue,
145
+ defaultValue: fieldSchema.default !== undefined
146
+ ? JSON.stringify(fieldSchema.default)
147
+ : null,
148
+ schema: track.sequence.controls.schema,
149
+ },
150
+ ],
151
+ setPropStatuses,
152
+ clientId: previewServerState.clientId,
153
+ undoLabel: label,
154
+ redoLabel: label,
155
+ });
156
+ }
157
+ else {
158
+ (0, call_add_keyframe_1.callAddSequenceKeyframe)({
159
+ fileName: nodePath.absolutePath,
160
+ nodePath,
161
+ fieldKey: selected_outline_types_1.translateFieldKey,
162
+ sourceFrame,
163
+ value: newValue,
164
+ schema: track.sequence.controls.schema,
165
+ setPropStatuses,
166
+ clientId: previewServerState.clientId,
167
+ });
168
+ }
169
+ }, [
170
+ previewServerState,
171
+ track,
172
+ currentCompositionMetadata,
173
+ timelinePosition,
174
+ propStatuses,
175
+ getDragOverrides,
176
+ setPropStatuses,
177
+ ]);
178
+ if (previewServerState.type !== 'connected' ||
179
+ !track.nodePathInfo ||
180
+ !track.sequence.controls ||
181
+ !currentCompositionMetadata) {
182
+ return null;
183
+ }
184
+ const renderNodePath = track.nodePathInfo.sequenceSubscriptionKey;
185
+ const renderNodePropStatuses = remotion_1.Internals.getPropStatusesCtx(propStatuses, renderNodePath);
186
+ const renderSourceFrame = timelinePosition - track.keyframeDisplayOffset;
187
+ const renderDragOverrides = (_a = getDragOverrides(renderNodePath)) !== null && _a !== void 0 ? _a : {};
188
+ const renderActiveSchema = (0, selected_outline_drag_1.getSelectedOutlineActiveSchema)({
189
+ schema: track.sequence.controls.schema,
190
+ currentRuntimeValueDotNotation: track.sequence.controls.currentRuntimeValueDotNotation,
191
+ dragOverrides: renderDragOverrides,
192
+ propStatus: renderNodePropStatuses,
193
+ frame: renderSourceFrame,
194
+ });
195
+ const renderFieldSchema = renderActiveSchema === null || renderActiveSchema === void 0 ? void 0 : renderActiveSchema[selected_outline_types_1.translateFieldKey];
196
+ const renderPropStatus = renderNodePropStatuses === null || renderNodePropStatuses === void 0 ? void 0 : renderNodePropStatuses[selected_outline_types_1.translateFieldKey];
197
+ if ((renderFieldSchema === null || renderFieldSchema === void 0 ? void 0 : renderFieldSchema.type) !== 'translate') {
198
+ return null;
199
+ }
200
+ const alignmentDisabled = !isPropStatusDraggable(renderPropStatus);
201
+ return (jsx_runtime_1.jsxs("div", { style: container, children: [
202
+ jsx_runtime_1.jsx(AlignmentButton, { title: "Align left", onClick: () => handleAlign('left'), Icon: align_left_1.AlignLeftIcon, disabled: alignmentDisabled }), jsx_runtime_1.jsx(AlignmentButton, { title: "Align center horizontally", onClick: () => handleAlign('center-h'), Icon: align_center_horizontal_1.AlignCenterHorizontalIcon, disabled: alignmentDisabled }), jsx_runtime_1.jsx(AlignmentButton, { title: "Align right", onClick: () => handleAlign('right'), Icon: align_right_1.AlignRightIcon, disabled: alignmentDisabled }), jsx_runtime_1.jsx("div", { style: verticalDivider }), jsx_runtime_1.jsx(AlignmentButton, { title: "Align top", onClick: () => handleAlign('top'), Icon: align_top_1.AlignTopIcon, disabled: alignmentDisabled }), jsx_runtime_1.jsx(AlignmentButton, { title: "Align center vertically", onClick: () => handleAlign('center-v'), Icon: align_center_vertical_1.AlignCenterVerticalIcon, disabled: alignmentDisabled }), jsx_runtime_1.jsx(AlignmentButton, { title: "Align bottom", onClick: () => handleAlign('bottom'), Icon: align_bottom_1.AlignBottomIcon, disabled: alignmentDisabled })
203
+ ] }));
204
+ };
205
+ exports.AlignmentControls = AlignmentControls;
@@ -16,6 +16,7 @@ const update_selected_easing_1 = require("../Timeline/update-selected-easing");
16
16
  const common_1 = require("./common");
17
17
  const easing_inspector_selection_1 = require("./easing-inspector-selection");
18
18
  const KeyframeEasingNavigator_1 = require("./KeyframeEasingNavigator");
19
+ const KeyframeSettings_1 = require("./KeyframeSettings");
19
20
  const SequenceInspectorHeader_1 = require("./SequenceInspectorHeader");
20
21
  const styles_1 = require("./styles");
21
22
  const use_track_for_selection_1 = require("./use-track-for-selection");
@@ -206,10 +207,15 @@ const EasingInspector = ({ selection }) => {
206
207
  selection,
207
208
  track,
208
209
  ]);
209
- if (state === null || track === null || currentEasingSelection === null) {
210
+ if (state === null ||
211
+ track === null ||
212
+ currentEasingSelection === null ||
213
+ easingUpdate === null) {
210
214
  return jsx_runtime_1.jsx(common_1.InspectorMessage, { children: "Easing unavailable" });
211
215
  }
212
216
  return (jsx_runtime_1.jsxs("div", { style: styles_1.selectedContainer, className: is_menu_item_1.VERTICAL_SCROLLBAR_CLASSNAME, children: [
213
- jsx_runtime_1.jsx(SequenceInspectorHeader_1.SequenceInspectorHeaderWithDivider, { track: track }), jsx_runtime_1.jsx(EasingEditorModal_1.EasingEditor, { state: state, renderHeader: renderHeader }, (0, TimelineSelection_1.getTimelineSelectionKey)(currentEasingSelection)), canAddKeyframeAtPlayhead ? (jsx_runtime_1.jsx("div", { style: styles_1.detailsContainer, children: jsx_runtime_1.jsx(common_1.InspectorInlineAction, { disabled: addKeyframeDisabled, onClick: onAddKeyframeAtPlayhead, renderIcon: (color) => (jsx_runtime_1.jsx(plus_1.Plus, { color: color, style: addKeyframeIcon })), children: `Add keyframe at ${addKeyframeTime}` }) })) : null] }));
217
+ jsx_runtime_1.jsx(SequenceInspectorHeader_1.SequenceInspectorHeaderWithDivider, { track: track }), jsx_runtime_1.jsx(EasingEditorModal_1.EasingEditor, { state: state, renderHeader: renderHeader }, (0, TimelineSelection_1.getTimelineSelectionKey)(currentEasingSelection)), jsx_runtime_1.jsx(KeyframeSettings_1.KeyframeSettings, { update: easingUpdate }), canAddKeyframeAtPlayhead ? (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
218
+ jsx_runtime_1.jsx("div", { style: styles_1.inspectorSectionDivider }), jsx_runtime_1.jsx("div", { style: styles_1.detailsContainer, children: jsx_runtime_1.jsx(common_1.InspectorInlineAction, { disabled: addKeyframeDisabled, onClick: onAddKeyframeAtPlayhead, renderIcon: (color) => (jsx_runtime_1.jsx(plus_1.Plus, { color: color, style: addKeyframeIcon })), children: `Add keyframe at ${addKeyframeTime}` }) })
219
+ ] })) : null] }));
214
220
  };
215
221
  exports.EasingInspector = EasingInspector;
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import type { SelectedEasingUpdate } from '../Timeline/update-selected-easing';
3
+ export declare const KeyframeSettings: React.FC<{
4
+ readonly update: SelectedEasingUpdate;
5
+ }>;
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeyframeSettings = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const remotion_1 = require("remotion");
7
+ const client_id_1 = require("../../helpers/client-id");
8
+ const ComboBox_1 = require("../NewComposition/ComboBox");
9
+ const InputDragger_1 = require("../NewComposition/InputDragger");
10
+ const call_update_keyframe_settings_1 = require("../Timeline/call-update-keyframe-settings");
11
+ const common_1 = require("./common");
12
+ const styles_1 = require("./styles");
13
+ const comboStyle = {
14
+ minWidth: 120,
15
+ };
16
+ const keyframeSettingsContainer = {
17
+ ...styles_1.detailsContainer,
18
+ paddingBottom: 0,
19
+ };
20
+ const extrapolateOptions = [
21
+ 'extend',
22
+ 'clamp',
23
+ 'identity',
24
+ 'wrap',
25
+ ];
26
+ const labelForExtrapolate = (value) => value[0].toUpperCase() + value.slice(1);
27
+ const getExtrapolateValues = (onSelect, disabled) => {
28
+ return extrapolateOptions.map((value) => ({
29
+ type: 'item',
30
+ id: value,
31
+ keyHint: null,
32
+ label: labelForExtrapolate(value),
33
+ leftItem: null,
34
+ disabled,
35
+ onClick: () => onSelect(value),
36
+ quickSwitcherLabel: null,
37
+ subMenu: null,
38
+ value,
39
+ }));
40
+ };
41
+ const outputOptions = [
42
+ 'linear',
43
+ 'perceptual-scale',
44
+ ];
45
+ const labelForOutput = (value) => {
46
+ return value === 'perceptual-scale' ? 'Perceptual scale' : 'Linear';
47
+ };
48
+ const getOutputValues = (onSelect, disabled) => {
49
+ return outputOptions.map((value) => ({
50
+ type: 'item',
51
+ id: value,
52
+ keyHint: null,
53
+ label: labelForOutput(value),
54
+ leftItem: null,
55
+ disabled,
56
+ onClick: () => onSelect(value),
57
+ quickSwitcherLabel: null,
58
+ subMenu: null,
59
+ value,
60
+ }));
61
+ };
62
+ const KeyframeSettings = ({ update }) => {
63
+ var _a, _b;
64
+ const { setPropStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
65
+ const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
66
+ const { propStatus } = update;
67
+ const [posterize, setPosterize] = (0, react_1.useState)((_a = propStatus.posterize) !== null && _a !== void 0 ? _a : 0);
68
+ const canEditInterpolationSettings = propStatus.interpolationFunction === 'interpolate';
69
+ const disabled = previewServerState.type !== 'connected';
70
+ (0, react_1.useEffect)(() => {
71
+ var _a;
72
+ setPosterize((_a = propStatus.posterize) !== null && _a !== void 0 ? _a : 0);
73
+ }, [propStatus.posterize]);
74
+ const save = (0, react_1.useCallback)((settings) => {
75
+ if (previewServerState.type !== 'connected') {
76
+ return;
77
+ }
78
+ if (update.type === 'sequence') {
79
+ (0, call_update_keyframe_settings_1.callUpdateSequenceKeyframeSettings)({
80
+ fileName: update.fileName,
81
+ nodePath: update.nodePath,
82
+ fieldKey: update.fieldKey,
83
+ settings,
84
+ schema: update.schema,
85
+ setPropStatuses,
86
+ clientId: previewServerState.clientId,
87
+ }).catch(() => undefined);
88
+ return;
89
+ }
90
+ (0, call_update_keyframe_settings_1.callUpdateEffectKeyframeSettings)({
91
+ fileName: update.fileName,
92
+ nodePath: update.nodePath,
93
+ effectIndex: update.effectIndex,
94
+ fieldKey: update.fieldKey,
95
+ settings,
96
+ schema: update.schema,
97
+ setPropStatuses,
98
+ clientId: previewServerState.clientId,
99
+ }).catch(() => undefined);
100
+ }, [previewServerState, setPropStatuses, update]);
101
+ const saveSettings = (0, react_1.useCallback)((_a) => {
102
+ var _b, _c;
103
+ var { left = propStatus.clamping.left, output = (_b = propStatus.output) !== null && _b !== void 0 ? _b : 'linear', posterize: nextPosterize = (_c = propStatus.posterize) !== null && _c !== void 0 ? _c : 0, right = propStatus.clamping.right, } = _a;
104
+ save({
105
+ type: 'settings',
106
+ clamping: canEditInterpolationSettings ? { left, right } : undefined,
107
+ output: canEditInterpolationSettings ? output : undefined,
108
+ posterize: nextPosterize <= 0 ? undefined : nextPosterize,
109
+ });
110
+ }, [
111
+ canEditInterpolationSettings,
112
+ propStatus.clamping.left,
113
+ propStatus.clamping.right,
114
+ propStatus.output,
115
+ propStatus.posterize,
116
+ save,
117
+ ]);
118
+ const onSelectLeft = (0, react_1.useCallback)((left) => saveSettings({ left }), [saveSettings]);
119
+ const onSelectRight = (0, react_1.useCallback)((right) => saveSettings({ right }), [saveSettings]);
120
+ const onSelectOutput = (0, react_1.useCallback)((output) => saveSettings({ output }), [saveSettings]);
121
+ const leftValues = (0, react_1.useMemo)(() => getExtrapolateValues(onSelectLeft, disabled), [disabled, onSelectLeft]);
122
+ const rightValues = (0, react_1.useMemo)(() => getExtrapolateValues(onSelectRight, disabled), [disabled, onSelectRight]);
123
+ const outputValues = (0, react_1.useMemo)(() => getOutputValues(onSelectOutput, disabled), [disabled, onSelectOutput]);
124
+ const onPosterizeChange = (0, react_1.useCallback)((value) => {
125
+ setPosterize(Math.max(0, Math.round(value)));
126
+ }, []);
127
+ const onPosterizeChangeEnd = (0, react_1.useCallback)((value) => {
128
+ const nextPosterize = Math.max(0, Math.round(value));
129
+ setPosterize(nextPosterize);
130
+ saveSettings({ posterize: nextPosterize });
131
+ }, [saveSettings]);
132
+ const posterizeFormatter = (0, react_1.useCallback)((value) => {
133
+ return String(Math.round(Number(value)));
134
+ }, []);
135
+ return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
136
+ jsx_runtime_1.jsx("div", { style: styles_1.inspectorSectionDivider }), jsx_runtime_1.jsx(common_1.InspectorSectionHeader, { children: "Keyframe settings" }), jsx_runtime_1.jsxs("div", { style: keyframeSettingsContainer, children: [canEditInterpolationSettings ? (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
137
+ jsx_runtime_1.jsx(common_1.InspectorDetailRow, { label: "Extrapolate left", children: jsx_runtime_1.jsx(ComboBox_1.Combobox, { values: leftValues, selectedId: propStatus.clamping.left, title: "Extrapolate left", style: comboStyle, small: true }) }), jsx_runtime_1.jsx(common_1.InspectorDetailRow, { label: "Extrapolate right", children: jsx_runtime_1.jsx(ComboBox_1.Combobox, { values: rightValues, selectedId: propStatus.clamping.right, title: "Extrapolate right", style: comboStyle, small: true }) }), jsx_runtime_1.jsx(common_1.InspectorDetailRow, { label: "Output", children: jsx_runtime_1.jsx(ComboBox_1.Combobox, { values: outputValues, selectedId: (_b = propStatus.output) !== null && _b !== void 0 ? _b : 'linear', title: "Output", style: comboStyle, small: true }) })
138
+ ] })) : null, jsx_runtime_1.jsx(common_1.InspectorDetailRow, { label: "Posterize", children: jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: posterize, status: "ok", onValueChange: onPosterizeChange, onValueChangeEnd: onPosterizeChangeEnd, onTextChange: () => undefined, min: 0, step: 1, formatter: posterizeFormatter, rightAlign: true, small: true, disabled: disabled }) })
139
+ ] })
140
+ ] }));
141
+ };
142
+ exports.KeyframeSettings = KeyframeSettings;
@@ -7,6 +7,7 @@ const client_id_1 = require("../../helpers/client-id");
7
7
  const InspectorSequenceSection_1 = require("../InspectorSequenceSection");
8
8
  const is_menu_item_1 = require("../Menu/is-menu-item");
9
9
  const TimelineSelection_1 = require("../Timeline/TimelineSelection");
10
+ const AlignmentControls_1 = require("./AlignmentControls");
10
11
  const common_1 = require("./common");
11
12
  const SequenceInspectorHeader_1 = require("./SequenceInspectorHeader");
12
13
  const styles_1 = require("./styles");
@@ -51,7 +52,7 @@ const SequenceExpandedInspector = ({ track }) => {
51
52
  return jsx_runtime_1.jsx(common_1.InspectorMessage, { children: "Sequence inspector unavailable" });
52
53
  }
53
54
  return (jsx_runtime_1.jsxs("div", { style: styles_1.selectedContainer, className: is_menu_item_1.VERTICAL_SCROLLBAR_CLASSNAME, onPointerDown: selectSequenceOnInspectorPointerDown, children: [
54
- jsx_runtime_1.jsx(SequenceInspectorHeader_1.SequenceInspectorHeader, { sourceLocation: sourceLocation, track: track }), jsx_runtime_1.jsx(InspectorSequenceSection_1.InspectorSequenceSection, { sequence: track.sequence, validatedLocation: validatedLocation, nodePathInfo: track.nodePathInfo, keyframeDisplayOffset: track.keyframeDisplayOffset, renderSectionHeader: (children) => (jsx_runtime_1.jsx(common_1.InspectorSectionHeader, { children: children })) })] }));
55
+ jsx_runtime_1.jsx(SequenceInspectorHeader_1.SequenceInspectorHeader, { sourceLocation: sourceLocation, track: track }), jsx_runtime_1.jsx(InspectorSequenceSection_1.InspectorSequenceSection, { sequence: track.sequence, validatedLocation: validatedLocation, nodePathInfo: track.nodePathInfo, keyframeDisplayOffset: track.keyframeDisplayOffset, renderSectionHeader: (children) => (jsx_runtime_1.jsx(common_1.InspectorSectionHeader, { children: children })), renderTransformControls: () => jsx_runtime_1.jsx(AlignmentControls_1.AlignmentControls, { track: track }) })] }));
55
56
  };
56
57
  const SequenceSelectionInspector = ({ selection }) => {
57
58
  const track = (0, use_track_for_selection_1.useTrackForSelection)(selection);
@@ -0,0 +1,12 @@
1
+ export type AlignmentDirection = 'left' | 'center-h' | 'right' | 'top' | 'center-v' | 'bottom';
2
+ export declare const computeAlignedTranslate: ({ direction, elementRect, compositionRect, currentTranslateX, currentTranslateY, scale, }: {
3
+ readonly direction: AlignmentDirection;
4
+ readonly elementRect: DOMRect;
5
+ readonly compositionRect: DOMRect;
6
+ readonly currentTranslateX: number;
7
+ readonly currentTranslateY: number;
8
+ readonly scale: number;
9
+ }) => {
10
+ x: number;
11
+ y: number;
12
+ };
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeAlignedTranslate = void 0;
4
+ const computeAlignedTranslate = ({ direction, elementRect, compositionRect, currentTranslateX, currentTranslateY, scale, }) => {
5
+ let deltaX = 0;
6
+ let deltaY = 0;
7
+ // Note: elementRect and compositionRect are in screen space (pixels).
8
+ // We divide by scale to convert back to composition space.
9
+ switch (direction) {
10
+ case 'left':
11
+ deltaX = compositionRect.left - elementRect.left;
12
+ break;
13
+ case 'center-h': {
14
+ const compCenter = compositionRect.left + compositionRect.width / 2;
15
+ const elemCenter = elementRect.left + elementRect.width / 2;
16
+ deltaX = compCenter - elemCenter;
17
+ break;
18
+ }
19
+ case 'right':
20
+ deltaX = compositionRect.right - elementRect.right;
21
+ break;
22
+ case 'top':
23
+ deltaY = compositionRect.top - elementRect.top;
24
+ break;
25
+ case 'center-v': {
26
+ const compCenter = compositionRect.top + compositionRect.height / 2;
27
+ const elemCenter = elementRect.top + elementRect.height / 2;
28
+ deltaY = compCenter - elemCenter;
29
+ break;
30
+ }
31
+ case 'bottom':
32
+ deltaY = compositionRect.bottom - elementRect.bottom;
33
+ break;
34
+ default:
35
+ break;
36
+ }
37
+ return {
38
+ x: currentTranslateX + deltaX / scale,
39
+ y: currentTranslateY + deltaY / scale,
40
+ };
41
+ };
42
+ exports.computeAlignedTranslate = computeAlignedTranslate;
@@ -15,5 +15,6 @@ export declare const InspectorSequenceSection: React.FC<{
15
15
  readonly nodePathInfo: SequenceNodePathInfo;
16
16
  readonly keyframeDisplayOffset: number;
17
17
  readonly renderSectionHeader: (children: React.ReactNode) => React.ReactNode;
18
+ readonly renderTransformControls: () => React.ReactNode;
18
19
  }>;
19
20
  export {};
@@ -133,7 +133,7 @@ const hasSequenceControls = (sequence) => {
133
133
  return sequence.controls !== null;
134
134
  };
135
135
  exports.hasSequenceControls = hasSequenceControls;
136
- const InspectorSequenceSection = ({ sequence, validatedLocation, nodePathInfo, keyframeDisplayOffset, renderSectionHeader, }) => {
136
+ const InspectorSequenceSection = ({ sequence, validatedLocation, nodePathInfo, keyframeDisplayOffset, renderSectionHeader, renderTransformControls, }) => {
137
137
  const { tree } = (0, use_timeline_expanded_tree_1.useTimelineExpandedTree)({
138
138
  sequence,
139
139
  nodePathInfo,
@@ -222,6 +222,6 @@ const InspectorSequenceSection = ({ sequence, validatedLocation, nodePathInfo, k
222
222
  ] }));
223
223
  }
224
224
  return (jsx_runtime_1.jsxs("div", { style: container, children: [
225
- jsx_runtime_1.jsx("div", { style: styles_1.sequenceHeaderDivider }), controlRows.length > 0 ? (jsx_runtime_1.jsx(TimelineSelection_1.TimelineSelectionOrderProvider, { items: controlSelectableItems, children: controlGroups.map((group, i) => (jsx_runtime_1.jsxs(react_1.default.Fragment, { children: [i === 0 ? null : jsx_runtime_1.jsx("div", { style: controlsEffectsDivider }), renderSectionHeader(group.label), group.rows.map(renderRow)] }, group.id))) })) : null, showEffectsSection ? (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [showControlsEffectsDivider ? (jsx_runtime_1.jsx("div", { style: controlsEffectsDivider })) : null, renderEffectsHeader(), effectRows.length === 0 ? (jsx_runtime_1.jsx("div", { style: emptyState, children: "None" })) : (jsx_runtime_1.jsx(TimelineSelection_1.TimelineSelectionOrderProvider, { items: effectSelectableItems, children: effectRows.map(renderRow) }))] })) : null] }));
225
+ jsx_runtime_1.jsx("div", { style: styles_1.sequenceHeaderDivider }), controlRows.length > 0 ? (jsx_runtime_1.jsx(TimelineSelection_1.TimelineSelectionOrderProvider, { items: controlSelectableItems, children: controlGroups.map((group, i) => (jsx_runtime_1.jsxs(react_1.default.Fragment, { children: [i === 0 ? null : jsx_runtime_1.jsx("div", { style: controlsEffectsDivider }), renderSectionHeader(group.label), group.id === 'transforms' ? renderTransformControls() : null, group.rows.map(renderRow)] }, group.id))) })) : null, showEffectsSection ? (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [showControlsEffectsDivider ? (jsx_runtime_1.jsx("div", { style: controlsEffectsDivider })) : null, renderEffectsHeader(), effectRows.length === 0 ? (jsx_runtime_1.jsx("div", { style: emptyState, children: "None" })) : (jsx_runtime_1.jsx(TimelineSelection_1.TimelineSelectionOrderProvider, { items: effectSelectableItems, children: effectRows.map(renderRow) }))] })) : null] }));
226
226
  };
227
227
  exports.InspectorSequenceSection = InspectorSequenceSection;
@@ -4,6 +4,7 @@ exports.KeyboardShortcutsExplainer = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const ShortcutHint_1 = require("../error-overlay/remotion-overlay/ShortcutHint");
6
6
  const colors_1 = require("../helpers/colors");
7
+ const studio_runtime_config_1 = require("../helpers/studio-runtime-config");
7
8
  const use_keybinding_1 = require("../helpers/use-keybinding");
8
9
  const keys_1 = require("../icons/keys");
9
10
  const layout_1 = require("./layout");
@@ -174,7 +175,7 @@ const KeyboardShortcutsExplainer = () => {
174
175
  jsx_runtime_1.jsxs("div", { style: left, children: [
175
176
  jsx_runtime_1.jsx("kbd", { style: key, children: "Del" }), jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.3 }), jsx_runtime_1.jsx("kbd", { style: key, children: "\u232B" })
176
177
  ] }), jsx_runtime_1.jsx("div", { style: right, children: "Delete / reset selection" })
177
- ] }), process.env.ASK_AI_ENABLED && (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
178
+ ] }), (0, studio_runtime_config_1.getStudioAskAIEnabled)() && (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
178
179
  jsx_runtime_1.jsx("br", {}), jsx_runtime_1.jsx("div", { style: title, children: "AI" }), jsx_runtime_1.jsxs(layout_1.Row, { align: "center", children: [
179
180
  jsx_runtime_1.jsxs("div", { style: left, children: [
180
181
  jsx_runtime_1.jsx("kbd", { style: key, children: ShortcutHint_1.cmdOrCtrlCharacter }), jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.3 }), jsx_runtime_1.jsx("kbd", { style: key, children: "I" })