@remotion/studio 4.0.456 → 4.0.458

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 (35) hide show
  1. package/dist/components/FramePersistor.js +1 -1
  2. package/dist/components/PlaybackRatePersistor.js +1 -1
  3. package/dist/components/PreviewToolbar.js +1 -1
  4. package/dist/components/Timeline/Padder.d.ts +4 -0
  5. package/dist/components/Timeline/Padder.js +14 -0
  6. package/dist/components/Timeline/Timeline.js +10 -3
  7. package/dist/components/Timeline/TimelineEnumField.d.ts +11 -0
  8. package/dist/components/Timeline/TimelineEnumField.js +34 -0
  9. package/dist/components/Timeline/TimelineExpandArrowButton.d.ts +0 -1
  10. package/dist/components/Timeline/TimelineExpandArrowButton.js +3 -4
  11. package/dist/components/Timeline/TimelineExpandedRow.d.ts +16 -0
  12. package/dist/components/Timeline/TimelineExpandedRow.js +49 -0
  13. package/dist/components/Timeline/TimelineExpandedSection.d.ts +2 -2
  14. package/dist/components/Timeline/TimelineExpandedSection.js +11 -63
  15. package/dist/components/Timeline/TimelineFieldRow.d.ts +3 -1
  16. package/dist/components/Timeline/TimelineFieldRow.js +13 -12
  17. package/dist/components/Timeline/TimelineListItem.d.ts +1 -1
  18. package/dist/components/Timeline/TimelineListItem.js +6 -15
  19. package/dist/components/Timeline/TimelinePlayCursorSyncer.js +5 -5
  20. package/dist/components/Timeline/TimelineSchemaField.js +4 -0
  21. package/dist/components/Timeline/TimelineSequence.d.ts +1 -1
  22. package/dist/components/Timeline/TimelineSequence.js +93 -51
  23. package/dist/components/Timeline/TimelineStack/index.js +1 -1
  24. package/dist/components/Timeline/TimelineTimeIndicators.js +10 -3
  25. package/dist/components/Timeline/TimelineTracks.js +5 -4
  26. package/dist/components/Timeline/sequence-props-subscription-store.d.ts +19 -0
  27. package/dist/components/Timeline/sequence-props-subscription-store.js +151 -0
  28. package/dist/components/Timeline/use-sequence-props-subscription.js +35 -112
  29. package/dist/esm/{chunk-0y1jhm8s.js → chunk-261b3aa0.js} +2168 -1959
  30. package/dist/esm/internals.mjs +2168 -1959
  31. package/dist/esm/previewEntry.mjs +2176 -1967
  32. package/dist/esm/renderEntry.mjs +1 -1
  33. package/dist/helpers/timeline-layout.d.ts +15 -17
  34. package/dist/helpers/timeline-layout.js +32 -52
  35. package/package.json +9 -9
@@ -1,8 +1,41 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.TimelineSequence = void 0;
4
37
  const jsx_runtime_1 = require("react/jsx-runtime");
5
- const react_1 = require("react");
38
+ const react_1 = __importStar(require("react"));
6
39
  const remotion_1 = require("remotion");
7
40
  const colors_1 = require("../../helpers/colors");
8
41
  const get_timeline_sequence_layout_1 = require("../../helpers/get-timeline-sequence-layout");
@@ -17,40 +50,81 @@ const TimelineWidthProvider_1 = require("./TimelineWidthProvider");
17
50
  const AUDIO_GRADIENT = 'linear-gradient(rgb(16 171 58), rgb(43 165 63) 60%)';
18
51
  const VIDEO_GRADIENT = 'linear-gradient(to top, #8e44ad, #9b59b6)';
19
52
  const IMAGE_GRADIENT = 'linear-gradient(to top, #2980b9, #3498db)';
20
- const TimelineSequence = ({ s }) => {
53
+ const TimelineSequenceFn = ({ s }) => {
21
54
  const windowWidth = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
22
55
  if (windowWidth === null) {
23
56
  return null;
24
57
  }
25
58
  return jsx_runtime_1.jsx(Inner, { windowWidth: windowWidth, s: s });
26
59
  };
27
- exports.TimelineSequence = TimelineSequence;
60
+ const TimelineSequenceCurrentFrame = ({ s, displayDurationInFrames, premountWidth, postmountWidth, style, children, }) => {
61
+ var _a, _b;
62
+ const frame = (0, remotion_1.useCurrentFrame)();
63
+ const relativeFrame = frame - s.from;
64
+ const relativeFrameWithPremount = relativeFrame + ((_a = s.premountDisplay) !== null && _a !== void 0 ? _a : 0);
65
+ const relativeFrameWithPostmount = relativeFrame - displayDurationInFrames;
66
+ const roundedFrame = Math.round(relativeFrame * 100) / 100;
67
+ const isInRange = relativeFrame >= 0 && relativeFrame < displayDurationInFrames;
68
+ const isPremounting = relativeFrameWithPremount >= 0 &&
69
+ relativeFrameWithPremount < displayDurationInFrames &&
70
+ !isInRange;
71
+ const isPostmounting = relativeFrameWithPostmount >= 0 &&
72
+ relativeFrameWithPostmount < ((_b = s.postmountDisplay) !== null && _b !== void 0 ? _b : 0) &&
73
+ !isInRange;
74
+ const actualStyle = (0, react_1.useMemo)(() => {
75
+ return {
76
+ ...style,
77
+ opacity: isInRange ? 1 : 0.5,
78
+ };
79
+ }, [isInRange, style]);
80
+ return (jsx_runtime_1.jsxs("div", { style: actualStyle, title: s.displayName, children: [premountWidth ? (jsx_runtime_1.jsx("div", { style: {
81
+ width: premountWidth,
82
+ height: '100%',
83
+ background: `repeating-linear-gradient(
84
+ -45deg,
85
+ transparent,
86
+ transparent 2px,
87
+ rgba(255, 255, 255, ${isPremounting ? 0.5 : 0.2}) 2px,
88
+ rgba(255, 255, 255, ${isPremounting ? 0.5 : 0.2}) 4px
89
+ )`,
90
+ position: 'absolute',
91
+ } })) : null, postmountWidth ? (jsx_runtime_1.jsx("div", { style: {
92
+ width: postmountWidth,
93
+ height: '100%',
94
+ background: `repeating-linear-gradient(
95
+ -45deg,
96
+ transparent,
97
+ transparent 2px,
98
+ rgba(255, 255, 255, ${isPostmounting ? 0.5 : 0.2}) 2px,
99
+ rgba(255, 255, 255, ${isPostmounting ? 0.5 : 0.2}) 4px
100
+ )`,
101
+ position: 'absolute',
102
+ right: 0,
103
+ } })) : null, children, s.type !== 'audio' &&
104
+ s.type !== 'video' &&
105
+ s.type !== 'image' &&
106
+ s.loopDisplay === undefined &&
107
+ (isInRange || isPremounting || isPostmounting) ? (jsx_runtime_1.jsx("div", { style: {
108
+ paddingLeft: 5 + (premountWidth !== null && premountWidth !== void 0 ? premountWidth : 0),
109
+ height: '100%',
110
+ display: 'flex',
111
+ alignItems: 'center',
112
+ }, children: jsx_runtime_1.jsx(TimelineSequenceFrame_1.TimelineSequenceFrame, { premounted: isPremounting, postmounted: isPostmounting ? s.duration - 1 : null, roundedFrame: roundedFrame }) })) : null] }));
113
+ };
28
114
  const Inner = ({ s, windowWidth }) => {
29
115
  // If a duration is 1, it is essentially a still and it should have width 0
30
116
  // Some compositions may not be longer than their media duration,
31
117
  // if that is the case, it needs to be asynchronously determined
32
- var _a, _b, _c;
118
+ var _a;
33
119
  const video = remotion_1.Internals.useVideo();
34
120
  const maxMediaDuration = (0, use_max_media_duration_1.useMaxMediaDuration)(s, (_a = video === null || video === void 0 ? void 0 : video.fps) !== null && _a !== void 0 ? _a : 30);
35
121
  const effectiveMaxMediaDuration = s.loopDisplay ? null : maxMediaDuration;
36
122
  if (!video) {
37
123
  throw new TypeError('Expected video config');
38
124
  }
39
- const frame = (0, remotion_1.useCurrentFrame)();
40
- const relativeFrame = frame - s.from;
41
125
  const displayDurationInFrames = s.loopDisplay
42
126
  ? s.loopDisplay.durationInFrames * s.loopDisplay.numberOfTimes
43
127
  : s.duration;
44
- const relativeFrameWithPremount = relativeFrame + ((_b = s.premountDisplay) !== null && _b !== void 0 ? _b : 0);
45
- const relativeFrameWithPostmount = relativeFrame - displayDurationInFrames;
46
- const roundedFrame = Math.round(relativeFrame * 100) / 100;
47
- const isInRange = relativeFrame >= 0 && relativeFrame < displayDurationInFrames;
48
- const isPremounting = relativeFrameWithPremount >= 0 &&
49
- relativeFrameWithPremount < displayDurationInFrames &&
50
- !isInRange;
51
- const isPostmounting = relativeFrameWithPostmount >= 0 &&
52
- relativeFrameWithPostmount < ((_c = s.postmountDisplay) !== null && _c !== void 0 ? _c : 0) &&
53
- !isInRange;
54
128
  const { marginLeft, width, naturalWidth, premountWidth, postmountWidth } = (0, react_1.useMemo)(() => {
55
129
  return (0, get_timeline_sequence_layout_1.getTimelineSequenceLayout)({
56
130
  durationInFrames: displayDurationInFrames,
@@ -86,43 +160,11 @@ const Inner = ({ s, windowWidth }) => {
86
160
  width,
87
161
  color: 'white',
88
162
  overflow: 'hidden',
89
- opacity: isInRange ? 1 : 0.5,
90
163
  };
91
- }, [isInRange, marginLeft, s.type, width]);
164
+ }, [marginLeft, s.type, width]);
92
165
  if (maxMediaDuration === null && !s.loopDisplay) {
93
166
  return null;
94
167
  }
95
- return (jsx_runtime_1.jsxs("div", { style: style, title: s.displayName, children: [premountWidth ? (jsx_runtime_1.jsx("div", { style: {
96
- width: premountWidth,
97
- height: '100%',
98
- background: `repeating-linear-gradient(
99
- -45deg,
100
- transparent,
101
- transparent 2px,
102
- rgba(255, 255, 255, ${isPremounting ? 0.5 : 0.2}) 2px,
103
- rgba(255, 255, 255, ${isPremounting ? 0.5 : 0.2}) 4px
104
- )`,
105
- position: 'absolute',
106
- } })) : null, postmountWidth ? (jsx_runtime_1.jsx("div", { style: {
107
- width: postmountWidth,
108
- height: '100%',
109
- background: `repeating-linear-gradient(
110
- -45deg,
111
- transparent,
112
- transparent 2px,
113
- rgba(255, 255, 255, ${isPostmounting ? 0.5 : 0.2}) 2px,
114
- rgba(255, 255, 255, ${isPostmounting ? 0.5 : 0.2}) 4px
115
- )`,
116
- position: 'absolute',
117
- right: 0,
118
- } })) : null, s.type === 'audio' ? (jsx_runtime_1.jsx(AudioWaveform_1.AudioWaveform, { src: s.src, doesVolumeChange: s.doesVolumeChange, visualizationWidth: width, startFrom: s.startMediaFrom, durationInFrames: s.duration, volume: s.volume, playbackRate: s.playbackRate, loopDisplay: s.loopDisplay })) : null, s.type === 'video' ? (jsx_runtime_1.jsx(TimelineVideoInfo_1.TimelineVideoInfo, { src: s.src, visualizationWidth: width, naturalWidth: naturalWidth, trimBefore: s.startMediaFrom, durationInFrames: s.duration, playbackRate: s.playbackRate, volume: s.volume, doesVolumeChange: s.doesVolumeChange, premountWidth: premountWidth !== null && premountWidth !== void 0 ? premountWidth : 0, postmountWidth: postmountWidth !== null && postmountWidth !== void 0 ? postmountWidth : 0, loopDisplay: s.loopDisplay })) : null, s.type === 'image' ? (jsx_runtime_1.jsx(TimelineImageInfo_1.TimelineImageInfo, { src: s.src, visualizationWidth: width })) : null, s.loopDisplay === undefined ? null : (jsx_runtime_1.jsx(LoopedTimelineIndicators_1.LoopedTimelineIndicator, { loops: s.loopDisplay.numberOfTimes })), s.type !== 'audio' &&
119
- s.type !== 'video' &&
120
- s.type !== 'image' &&
121
- s.loopDisplay === undefined &&
122
- (isInRange || isPremounting || isPostmounting) ? (jsx_runtime_1.jsx("div", { style: {
123
- paddingLeft: 5 + (premountWidth !== null && premountWidth !== void 0 ? premountWidth : 0),
124
- height: '100%',
125
- display: 'flex',
126
- alignItems: 'center',
127
- }, children: jsx_runtime_1.jsx(TimelineSequenceFrame_1.TimelineSequenceFrame, { premounted: isPremounting, postmounted: isPostmounting ? s.duration - 1 : null, roundedFrame: roundedFrame }) })) : null] }, s.id));
168
+ return (jsx_runtime_1.jsxs(TimelineSequenceCurrentFrame, { s: s, displayDurationInFrames: displayDurationInFrames, premountWidth: premountWidth, postmountWidth: postmountWidth, style: style, children: [s.type === 'audio' ? (jsx_runtime_1.jsx(AudioWaveform_1.AudioWaveform, { src: s.src, doesVolumeChange: s.doesVolumeChange, visualizationWidth: width, startFrom: s.startMediaFrom, durationInFrames: s.duration, volume: s.volume, playbackRate: s.playbackRate, loopDisplay: s.loopDisplay })) : null, s.type === 'video' ? (jsx_runtime_1.jsx(TimelineVideoInfo_1.TimelineVideoInfo, { src: s.src, visualizationWidth: width, naturalWidth: naturalWidth, trimBefore: s.startMediaFrom, durationInFrames: s.duration, playbackRate: s.playbackRate, volume: s.volume, doesVolumeChange: s.doesVolumeChange, premountWidth: premountWidth !== null && premountWidth !== void 0 ? premountWidth : 0, postmountWidth: postmountWidth !== null && postmountWidth !== void 0 ? postmountWidth : 0, loopDisplay: s.loopDisplay })) : null, s.type === 'image' ? (jsx_runtime_1.jsx(TimelineImageInfo_1.TimelineImageInfo, { src: s.src, visualizationWidth: width })) : null, s.loopDisplay === undefined ? null : (jsx_runtime_1.jsx(LoopedTimelineIndicators_1.LoopedTimelineIndicator, { loops: s.loopDisplay.numberOfTimes }))] }));
128
169
  };
170
+ exports.TimelineSequence = react_1.default.memo(TimelineSequenceFn);
@@ -127,7 +127,7 @@ const TimelineStack = ({ isCompact, sequence, originalLocation }) => {
127
127
  : stackHovered && stackHoverable
128
128
  ? colors_1.LIGHT_TEXT
129
129
  : colors_1.VERY_LIGHT_TEXT,
130
- marginLeft: 10,
130
+ marginLeft: 5,
131
131
  cursor: stackHoverable ? 'pointer' : undefined,
132
132
  display: 'flex',
133
133
  flexDirection: 'row',
@@ -30,9 +30,12 @@ const secondTick = {
30
30
  ...tick,
31
31
  height: 15,
32
32
  };
33
+ const TICK_LABEL_FONT_SIZE = 12;
34
+ const TICK_LABEL_MARGIN_LEFT = 8;
35
+ const TICK_LABEL_MIN_GAP = 16;
33
36
  const tickLabel = {
34
- fontSize: 12,
35
- marginLeft: 8,
37
+ fontSize: TICK_LABEL_FONT_SIZE,
38
+ marginLeft: TICK_LABEL_MARGIN_LEFT,
36
39
  marginTop: 7,
37
40
  color: colors_1.LIGHT_TEXT,
38
41
  };
@@ -101,8 +104,12 @@ const Inner = ({ windowWidth, durationInFrames, fps }) => {
101
104
  const ticks = (0, react_1.useMemo)(() => {
102
105
  const frameInterval = (0, timeline_scroll_logic_1.getFrameIncrementFromWidth)(durationInFrames, windowWidth);
103
106
  const MIN_SPACING_BETWEEN_TICKS_PX = 5;
107
+ const maxTickLabelWidth = (0, render_frame_1.renderFrame)(durationInFrames - 1, fps).length *
108
+ TICK_LABEL_FONT_SIZE *
109
+ 0.6;
110
+ const minSpacingBetweenTickLabelsPx = TICK_LABEL_MARGIN_LEFT + maxTickLabelWidth + TICK_LABEL_MIN_GAP;
104
111
  const seconds = Math.floor(durationInFrames / fps);
105
- const secondMarkerEveryNth = Math.ceil((MIN_SPACING_BETWEEN_TICKS_PX * fps) / (frameInterval * fps));
112
+ const secondMarkerEveryNth = Math.ceil(minSpacingBetweenTickLabelsPx / (frameInterval * fps));
106
113
  const frameMarkerEveryNth = Math.ceil(MIN_SPACING_BETWEEN_TICKS_PX / frameInterval);
107
114
  // Big ticks showing for every second
108
115
  const secondTicks = new Array(seconds)
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TimelineTracks = 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 timeline_layout_1 = require("../../helpers/timeline-layout");
8
9
  const ExpandedTracksProvider_1 = require("../ExpandedTracksProvider");
@@ -18,13 +19,13 @@ const content = {
18
19
  const timelineContent = {
19
20
  minHeight: '100%',
20
21
  };
21
- const getExpandedPlaceholderStyle = (sequence, expandedTracks) => ({
22
- height: (0, timeline_layout_1.getExpandedTrackHeight)(sequence, expandedTracks) +
23
- timeline_layout_1.TIMELINE_ITEM_BORDER_BOTTOM,
22
+ const getExpandedPlaceholderStyle = (sequence, expandedTracks, dragOverrides, codeValues) => ({
23
+ height: (0, timeline_layout_1.getExpandedTrackHeight)(sequence, expandedTracks, dragOverrides, codeValues) + timeline_layout_1.TIMELINE_ITEM_BORDER_BOTTOM,
24
24
  });
25
25
  const TimelineTracks = ({ timeline, hasBeenCut }) => {
26
26
  const { expandedTracks } = (0, react_1.useContext)(ExpandedTracksProvider_1.ExpandedTracksContext);
27
27
  const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
28
+ const { dragOverrides, codeValues } = (0, react_1.useContext)(remotion_1.Internals.VisualModeOverridesContext);
28
29
  const visualModeEnabled = Boolean(process.env.EXPERIMENTAL_VISUAL_MODE_ENABLED) &&
29
30
  previewServerState.type === 'connected';
30
31
  const timelineStyle = (0, react_1.useMemo)(() => {
@@ -45,7 +46,7 @@ const TimelineTracks = ({ timeline, hasBeenCut }) => {
45
46
  jsx_runtime_1.jsx("div", { style: {
46
47
  height: (0, timeline_layout_1.getTimelineLayerHeight)(track.sequence.type),
47
48
  marginBottom: timeline_layout_1.TIMELINE_ITEM_BORDER_BOTTOM,
48
- }, children: jsx_runtime_1.jsx(TimelineSequence_1.TimelineSequence, { s: track.sequence }) }), visualModeEnabled && isExpanded ? (jsx_runtime_1.jsx("div", { style: getExpandedPlaceholderStyle(track.sequence, expandedTracks) })) : null] }, track.sequence.id));
49
+ }, children: jsx_runtime_1.jsx(TimelineSequence_1.TimelineSequence, { s: track.sequence }) }), visualModeEnabled && isExpanded ? (jsx_runtime_1.jsx("div", { style: getExpandedPlaceholderStyle(track.sequence, expandedTracks, dragOverrides, codeValues) })) : null] }, track.sequence.id));
49
50
  })] }), hasBeenCut ? jsx_runtime_1.jsx(MaxTimelineTracks_1.MaxTimelineTracksReached, {}) : null] }));
50
51
  };
51
52
  exports.TimelineTracks = TimelineTracks;
@@ -0,0 +1,19 @@
1
+ import type { EventSourceEvent, SequenceNodePath } from '@remotion/studio-shared';
2
+ import { type CanUpdateSequencePropStatus } from 'remotion';
3
+ import type { SequenceSchema } from 'remotion';
4
+ export type SequencePropsSnapshot = {
5
+ nodePath: SequenceNodePath | null;
6
+ jsxInMapCallback: boolean;
7
+ props: Record<string, CanUpdateSequencePropStatus> | null;
8
+ };
9
+ type SubscribeToEvent = (type: EventSourceEvent['type'], listener: (event: EventSourceEvent) => void) => () => void;
10
+ export declare const acquireSequencePropsSubscription: ({ clientId, fileName, line, column, schema, subscribeToEvent, onChange, }: {
11
+ clientId: string;
12
+ fileName: string;
13
+ line: number;
14
+ column: number;
15
+ schema: SequenceSchema;
16
+ subscribeToEvent: SubscribeToEvent;
17
+ onChange: (snapshot: SequencePropsSnapshot) => void;
18
+ }) => () => void;
19
+ export {};
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.acquireSequencePropsSubscription = void 0;
4
+ const remotion_1 = require("remotion");
5
+ const call_api_1 = require("../call-api");
6
+ const INITIAL_SNAPSHOT = {
7
+ nodePath: null,
8
+ jsxInMapCallback: false,
9
+ props: null,
10
+ };
11
+ const entries = new Map();
12
+ let globalUnsubscribe = null;
13
+ const makeKey = (fileName, line, column) => `${fileName}|${line}|${column}`;
14
+ const nodePathsEqual = (a, b) => {
15
+ if (!a || !b) {
16
+ return false;
17
+ }
18
+ return JSON.stringify(a) === JSON.stringify(b);
19
+ };
20
+ const notify = (entry) => {
21
+ for (const listener of entry.listeners) {
22
+ listener(entry.snapshot);
23
+ }
24
+ };
25
+ const handleEvent = (event) => {
26
+ if (event.type !== 'sequence-props-updated') {
27
+ return;
28
+ }
29
+ for (const entry of entries.values()) {
30
+ if (entry.fileName !== event.fileName) {
31
+ continue;
32
+ }
33
+ if (!nodePathsEqual(entry.snapshot.nodePath, event.nodePath)) {
34
+ continue;
35
+ }
36
+ if (event.result.canUpdate) {
37
+ entry.snapshot = {
38
+ nodePath: entry.snapshot.nodePath,
39
+ jsxInMapCallback: event.result.jsxInMapCallback,
40
+ props: event.result.props,
41
+ };
42
+ }
43
+ else {
44
+ entry.snapshot = {
45
+ nodePath: entry.snapshot.nodePath,
46
+ jsxInMapCallback: false,
47
+ props: null,
48
+ };
49
+ }
50
+ notify(entry);
51
+ }
52
+ };
53
+ const ensureGlobalListener = (subscribeToEvent) => {
54
+ if (globalUnsubscribe) {
55
+ return;
56
+ }
57
+ globalUnsubscribe = subscribeToEvent('sequence-props-updated', handleEvent);
58
+ };
59
+ const teardownGlobalListenerIfEmpty = () => {
60
+ if (entries.size > 0) {
61
+ return;
62
+ }
63
+ if (globalUnsubscribe) {
64
+ globalUnsubscribe();
65
+ globalUnsubscribe = null;
66
+ }
67
+ };
68
+ const fireUnsubscribe = (fileName, nodePath, clientId) => {
69
+ (0, call_api_1.callApi)('/api/unsubscribe-from-sequence-props', {
70
+ fileName,
71
+ nodePath,
72
+ clientId,
73
+ }).catch(() => {
74
+ // Ignore unsubscribe errors
75
+ });
76
+ };
77
+ const acquireSequencePropsSubscription = ({ clientId, fileName, line, column, schema, subscribeToEvent, onChange, }) => {
78
+ const key = makeKey(fileName, line, column);
79
+ let entry = entries.get(key);
80
+ if (!entry) {
81
+ const newEntry = {
82
+ clientId,
83
+ fileName,
84
+ refCount: 0,
85
+ snapshot: INITIAL_SNAPSHOT,
86
+ listeners: new Set(),
87
+ tornDown: false,
88
+ };
89
+ entries.set(key, newEntry);
90
+ ensureGlobalListener(subscribeToEvent);
91
+ (0, call_api_1.callApi)('/api/subscribe-to-sequence-props', {
92
+ fileName,
93
+ line,
94
+ column,
95
+ schema,
96
+ clientId,
97
+ })
98
+ .then((result) => {
99
+ if (newEntry.tornDown) {
100
+ if (result.canUpdate) {
101
+ fireUnsubscribe(fileName, result.nodePath, clientId);
102
+ }
103
+ return;
104
+ }
105
+ if (result.canUpdate) {
106
+ newEntry.snapshot = {
107
+ nodePath: result.nodePath,
108
+ jsxInMapCallback: result.jsxInMapCallback,
109
+ props: result.props,
110
+ };
111
+ }
112
+ else {
113
+ newEntry.snapshot = INITIAL_SNAPSHOT;
114
+ }
115
+ notify(newEntry);
116
+ })
117
+ .catch((err) => {
118
+ if (newEntry.tornDown) {
119
+ return;
120
+ }
121
+ remotion_1.Internals.Log.error(err);
122
+ newEntry.snapshot = INITIAL_SNAPSHOT;
123
+ notify(newEntry);
124
+ });
125
+ entry = newEntry;
126
+ }
127
+ entry.refCount += 1;
128
+ entry.listeners.add(onChange);
129
+ onChange(entry.snapshot);
130
+ let released = false;
131
+ return () => {
132
+ if (released) {
133
+ return;
134
+ }
135
+ released = true;
136
+ const e = entry;
137
+ e.listeners.delete(onChange);
138
+ e.refCount -= 1;
139
+ if (e.refCount > 0) {
140
+ return;
141
+ }
142
+ e.tornDown = true;
143
+ const resolvedNodePath = e.snapshot.nodePath;
144
+ entries.delete(key);
145
+ if (resolvedNodePath) {
146
+ fireUnsubscribe(e.fileName, resolvedNodePath, e.clientId);
147
+ }
148
+ teardownGlobalListenerIfEmpty();
149
+ };
150
+ };
151
+ exports.acquireSequencePropsSubscription = acquireSequencePropsSubscription;
@@ -4,13 +4,16 @@ exports.useSequencePropsSubscription = void 0;
4
4
  const react_1 = require("react");
5
5
  const remotion_1 = require("remotion");
6
6
  const client_id_1 = require("../../helpers/client-id");
7
- const timeline_layout_1 = require("../../helpers/timeline-layout");
8
- const call_api_1 = require("../call-api");
7
+ const sequence_props_subscription_store_1 = require("./sequence-props-subscription-store");
8
+ const EMPTY_STATE = {
9
+ nodePath: null,
10
+ jsxInMapCallback: false,
11
+ };
9
12
  const useSequencePropsSubscription = (sequence, originalLocation, visualModeEnabled) => {
10
- var _a;
11
- var _b, _c, _d, _e;
13
+ var _a, _b;
14
+ var _c, _d, _e, _f;
12
15
  const { setCodeValues } = (0, react_1.useContext)(remotion_1.Internals.VisualModeOverridesContext);
13
- const overrideId = (_b = (_a = sequence.controls) === null || _a === void 0 ? void 0 : _a.overrideId) !== null && _b !== void 0 ? _b : null;
16
+ const overrideId = (_c = (_a = sequence.controls) === null || _a === void 0 ? void 0 : _a.overrideId) !== null && _c !== void 0 ? _c : null;
14
17
  const setPropStatusesForSequence = (0, react_1.useCallback)((statuses) => {
15
18
  if (!overrideId) {
16
19
  return;
@@ -19,8 +22,6 @@ const useSequencePropsSubscription = (sequence, originalLocation, visualModeEnab
19
22
  }, [overrideId, setCodeValues]);
20
23
  const { previewServerState: state, subscribeToEvent } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
21
24
  const clientId = state.type === 'connected' ? state.clientId : undefined;
22
- const schemaFields = (0, react_1.useMemo)(() => (0, timeline_layout_1.getSchemaFields)(sequence.controls), [sequence.controls]);
23
- const schemaKeysString = (0, react_1.useMemo)(() => (schemaFields ? schemaFields.map((f) => f.key).join(',') : null), [schemaFields]);
24
25
  const validatedLocation = (0, react_1.useMemo)(() => {
25
26
  var _a;
26
27
  if (!originalLocation ||
@@ -34,145 +35,67 @@ const useSequencePropsSubscription = (sequence, originalLocation, visualModeEnab
34
35
  column: (_a = originalLocation.column) !== null && _a !== void 0 ? _a : 0,
35
36
  };
36
37
  }, [originalLocation]);
37
- const locationSource = (_c = validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.source) !== null && _c !== void 0 ? _c : null;
38
- const locationLine = (_d = validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.line) !== null && _d !== void 0 ? _d : null;
39
- const locationColumn = (_e = validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.column) !== null && _e !== void 0 ? _e : null;
40
- const currentLocationSource = (0, react_1.useRef)(locationSource);
41
- currentLocationSource.current = locationSource;
42
- const currentLocationLine = (0, react_1.useRef)(locationLine);
43
- currentLocationLine.current = locationLine;
44
- const currentLocationColumn = (0, react_1.useRef)(locationColumn);
45
- currentLocationColumn.current = locationColumn;
46
- const nodePathRef = (0, react_1.useRef)(null);
47
- const [nodePath, setNodePath] = (0, react_1.useState)(null);
48
- const [jsxInMapCallback, setJsxInMapCallback] = (0, react_1.useState)(false);
38
+ const [subscriptionState, setSubscriptionState] = (0, react_1.useState)(EMPTY_STATE);
49
39
  const isMountedRef = (0, react_1.useRef)(true);
50
- const setNodePathBoth = (0, react_1.useCallback)((next) => {
51
- nodePathRef.current = next;
52
- setNodePath(next);
53
- }, []);
54
40
  (0, react_1.useEffect)(() => {
55
41
  isMountedRef.current = true;
56
42
  return () => {
57
43
  isMountedRef.current = false;
58
44
  };
59
45
  }, []);
46
+ const schema = (_b = sequence.controls) === null || _b === void 0 ? void 0 : _b.schema;
47
+ const locationSource = (_d = validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.source) !== null && _d !== void 0 ? _d : null;
48
+ const locationLine = (_e = validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.line) !== null && _e !== void 0 ? _e : null;
49
+ const locationColumn = (_f = validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.column) !== null && _f !== void 0 ? _f : null;
60
50
  (0, react_1.useEffect)(() => {
61
51
  if (!visualModeEnabled) {
62
52
  setPropStatusesForSequence(null);
63
- setNodePathBoth(null);
64
- setJsxInMapCallback(false);
53
+ setSubscriptionState(EMPTY_STATE);
65
54
  return;
66
55
  }
67
56
  if (!clientId ||
68
57
  !locationSource ||
69
58
  !locationLine ||
70
59
  locationColumn === null ||
71
- !schemaKeysString) {
60
+ !schema) {
72
61
  setPropStatusesForSequence(null);
73
- setNodePathBoth(null);
74
- setJsxInMapCallback(false);
62
+ setSubscriptionState(EMPTY_STATE);
75
63
  return;
76
64
  }
77
- const keys = schemaKeysString.split(',');
78
- (0, call_api_1.callApi)('/api/subscribe-to-sequence-props', {
65
+ const onChange = (snapshot) => {
66
+ setSubscriptionState({
67
+ nodePath: snapshot.nodePath,
68
+ jsxInMapCallback: snapshot.jsxInMapCallback,
69
+ });
70
+ setPropStatusesForSequence(snapshot.props);
71
+ };
72
+ const release = (0, sequence_props_subscription_store_1.acquireSequencePropsSubscription)({
73
+ clientId,
79
74
  fileName: locationSource,
80
75
  line: locationLine,
81
76
  column: locationColumn,
82
- keys,
83
- clientId,
84
- })
85
- .then((result) => {
86
- if (currentLocationSource.current !== locationSource ||
87
- currentLocationLine.current !== locationLine ||
88
- currentLocationColumn.current !== locationColumn) {
89
- return;
90
- }
91
- if (result.canUpdate) {
92
- setNodePathBoth(result.nodePath);
93
- setPropStatusesForSequence(result.props);
94
- setJsxInMapCallback(result.jsxInMapCallback);
95
- }
96
- else {
97
- setNodePathBoth(null);
98
- setPropStatusesForSequence(null);
99
- setJsxInMapCallback(false);
100
- }
101
- })
102
- .catch((err) => {
103
- setNodePathBoth(null);
104
- setJsxInMapCallback(false);
105
- remotion_1.Internals.Log.error(err);
106
- setPropStatusesForSequence(null);
77
+ schema,
78
+ subscribeToEvent,
79
+ onChange,
107
80
  });
108
81
  return () => {
109
- const currentNodePath = nodePathRef.current;
82
+ release();
110
83
  // Only clear props on true unmount, not on re-subscribe due to
111
- // line number changes — avoids flicker while re-subscribing.
84
+ // location changes — avoids flicker while re-subscribing.
112
85
  if (!isMountedRef.current) {
113
86
  setPropStatusesForSequence(null);
114
87
  }
115
- setNodePathBoth(null);
116
- setJsxInMapCallback(false);
117
- if (currentNodePath) {
118
- (0, call_api_1.callApi)('/api/unsubscribe-from-sequence-props', {
119
- fileName: locationSource,
120
- nodePath: currentNodePath,
121
- clientId,
122
- }).catch(() => {
123
- // Ignore unsubscribe errors
124
- });
125
- }
126
88
  };
127
89
  }, [
128
- visualModeEnabled,
129
90
  clientId,
130
- locationSource,
131
- locationLine,
132
91
  locationColumn,
133
- schemaKeysString,
134
- setPropStatusesForSequence,
135
- setNodePathBoth,
136
- ]);
137
- (0, react_1.useEffect)(() => {
138
- if (!visualModeEnabled) {
139
- return;
140
- }
141
- if (!locationSource || !locationLine || locationColumn === null) {
142
- return;
143
- }
144
- const listener = (event) => {
145
- if (event.type !== 'sequence-props-updated') {
146
- return;
147
- }
148
- if (event.fileName !== currentLocationSource.current ||
149
- !nodePathRef.current ||
150
- JSON.stringify(event.nodePath) !== JSON.stringify(nodePathRef.current)) {
151
- return;
152
- }
153
- if (event.result.canUpdate) {
154
- setPropStatusesForSequence(event.result.props);
155
- setJsxInMapCallback(event.result.jsxInMapCallback);
156
- }
157
- else {
158
- setPropStatusesForSequence(null);
159
- setNodePathBoth(null);
160
- setJsxInMapCallback(false);
161
- }
162
- };
163
- const unsub = subscribeToEvent('sequence-props-updated', listener);
164
- return () => {
165
- unsub();
166
- };
167
- }, [
168
- visualModeEnabled,
169
- locationSource,
170
92
  locationLine,
171
- locationColumn,
172
- subscribeToEvent,
93
+ locationSource,
94
+ schema,
173
95
  setPropStatusesForSequence,
174
- setNodePathBoth,
96
+ subscribeToEvent,
97
+ visualModeEnabled,
175
98
  ]);
176
- return { nodePath, jsxInMapCallback };
99
+ return subscriptionState;
177
100
  };
178
101
  exports.useSequencePropsSubscription = useSequencePropsSubscription;