@@ -54,18 +54,12 @@ import { hasFiredSessionStart, markSessionStartFired } from "./telemetry/config"
5454export function StudioApp ( ) {
5555 const { projectId, resolving, waitingForServer } = useServerConnection ( ) ;
5656 const initialUrlStateRef = useRef ( readStudioUrlStateFromWindow ( ) ) ;
57-
58- // Fire once per browser tab session — sessionStorage-backed so HMR
59- // remounts, route changes, and any future StudioApp remount within the
60- // same tab don't refire `studio_session_start`. `has_project` lets us
61- // tell scratch-open from project-context-open.
6257 useEffect ( ( ) => {
6358 if ( resolving || waitingForServer ) return ;
6459 if ( hasFiredSessionStart ( ) ) return ;
6560 markSessionStartFired ( ) ;
6661 trackStudioSessionStart ( { has_project : projectId != null } ) ;
6762 } , [ projectId , resolving , waitingForServer ] ) ;
68-
6963 const [ activeCompPath , setActiveCompPath ] = useState < string | null > ( null ) ;
7064 const [ activeCompPathHydrated , setActiveCompPathHydrated ] = useState (
7165 ( ) => initialUrlStateRef . current . activeCompPath == null ,
@@ -82,7 +76,6 @@ export function StudioApp() {
8276 compositionPath : string ;
8377 } | null > ( null ) ;
8478 const [ blockPreview , setBlockPreview ] = useState < BlockPreviewInfo | null > ( null ) ;
85-
8679 const previewIframeRef = useRef < HTMLIFrameElement | null > ( null ) ;
8780 const activeCompPathRef = useRef ( activeCompPath ) ;
8881 activeCompPathRef . current = activeCompPath ;
@@ -112,7 +105,6 @@ export function StudioApp() {
112105 window . setTimeout ( ( ) => setPreviewDocumentVersion ( ( v ) => v + 1 ) , 80 ) ;
113106 window . setTimeout ( ( ) => setPreviewDocumentVersion ( ( v ) => v + 1 ) , 300 ) ;
114107 } , [ ] ) ;
115-
116108 const [ timelineVisible , setTimelineVisible ] = useState (
117109 ( ) =>
118110 initialUrlStateRef . current . timelineVisible ??
@@ -136,27 +128,23 @@ export function StudioApp() {
136128 const reloadPreview = useCallback ( ( ) => {
137129 setRefreshKey ( ( k ) => k + 1 ) ;
138130 } , [ ] ) ;
139-
140131 const fileManager = useFileManager ( {
141132 projectId,
142133 showToast,
143134 recordEdit : editHistory . recordEdit ,
144135 domEditSaveTimestampRef,
145136 setRefreshKey,
146137 } ) ;
147-
148138 useEffect ( ( ) => {
149139 if ( activeCompPathHydrated ) return ;
150140 if ( ! fileManager . fileTreeLoaded ) return ;
151-
152141 const nextCompPath = normalizeStudioCompositionPath (
153142 initialUrlStateRef . current . activeCompPath ,
154143 fileManager . fileTree ,
155144 ) ;
156145 setActiveCompPath ( ( current ) => ( current === nextCompPath ? current : nextCompPath ) ) ;
157146 setActiveCompPathHydrated ( true ) ;
158147 } , [ activeCompPathHydrated , fileManager . fileTree , fileManager . fileTreeLoaded ] ) ;
159-
160148 const previewPersistence = usePreviewPersistence ( {
161149 projectId,
162150 showToast,
@@ -169,7 +157,6 @@ export function StudioApp() {
169157 reloadPreview : ( ) => setRefreshKey ( ( k ) => k + 1 ) ,
170158 pendingTimelineEditPathRef,
171159 } ) ;
172-
173160 const timelineEditing = useTimelineEditing ( {
174161 projectId,
175162 activeCompPath,
@@ -183,25 +170,42 @@ export function StudioApp() {
183170 pendingTimelineEditPathRef,
184171 uploadProjectFiles : fileManager . uploadProjectFiles ,
185172 } ) ;
186-
173+ const blockBaseOpts = useMemo (
174+ ( ) => ( {
175+ activeCompPath,
176+ readProjectFile : fileManager . readProjectFile ,
177+ writeProjectFile : fileManager . writeProjectFile ,
178+ recordEdit : editHistory . recordEdit ,
179+ refreshFileTree : fileManager . refreshFileTree ,
180+ reloadPreview,
181+ showToast,
182+ } ) ,
183+ [
184+ activeCompPath ,
185+ fileManager . readProjectFile ,
186+ fileManager . writeProjectFile ,
187+ fileManager . refreshFileTree ,
188+ editHistory . recordEdit ,
189+ reloadPreview ,
190+ showToast ,
191+ ] ,
192+ ) ;
193+ const blockCallOpts = useCallback (
194+ ( blockName : string ) => ( {
195+ ...blockBaseOpts ,
196+ projectId : projectId ! ,
197+ blockName,
198+ previewIframe : previewIframeRef . current ,
199+ currentTime : usePlayerStore . getState ( ) . currentTime ,
200+ timelineElements,
201+ } ) ,
202+ [ blockBaseOpts , projectId , timelineElements ] ,
203+ ) ;
187204 const handleAddBlock = useCallback (
188205 ( blockName : string ) => {
189206 if ( ! projectId ) return ;
190207 void ( async ( ) => {
191- const result = await addBlockToProject ( {
192- projectId,
193- blockName,
194- activeCompPath,
195- previewIframe : previewIframeRef . current ,
196- currentTime : usePlayerStore . getState ( ) . currentTime ,
197- timelineElements,
198- readProjectFile : fileManager . readProjectFile ,
199- writeProjectFile : fileManager . writeProjectFile ,
200- recordEdit : editHistory . recordEdit ,
201- refreshFileTree : fileManager . refreshFileTree ,
202- reloadPreview,
203- showToast,
204- } ) ;
208+ const result = await addBlockToProject ( blockCallOpts ( blockName ) ) ;
205209 const params = result ?. block . type === "hyperframes:block" ? result . block . params : undefined ;
206210 if ( params ?. length ) {
207211 setActiveBlockParams ( {
@@ -215,84 +219,22 @@ export function StudioApp() {
215219 }
216220 } ) ( ) ;
217221 } ,
218- [
219- projectId ,
220- activeCompPath ,
221- timelineElements ,
222- fileManager . readProjectFile ,
223- fileManager . writeProjectFile ,
224- fileManager . refreshFileTree ,
225- editHistory . recordEdit ,
226- reloadPreview ,
227- showToast ,
228- panelLayout ,
229- ] ,
222+ [ projectId , blockCallOpts , panelLayout ] ,
230223 ) ;
231-
232224 const handleTimelineBlockDrop = useCallback (
233225 ( blockName : string , placement : { start : number ; track : number } ) => {
234226 if ( ! projectId ) return ;
235- void addBlockToProject ( {
236- projectId,
237- blockName,
238- activeCompPath,
239- placement,
240- previewIframe : previewIframeRef . current ,
241- currentTime : usePlayerStore . getState ( ) . currentTime ,
242- timelineElements,
243- readProjectFile : fileManager . readProjectFile ,
244- writeProjectFile : fileManager . writeProjectFile ,
245- recordEdit : editHistory . recordEdit ,
246- refreshFileTree : fileManager . refreshFileTree ,
247- reloadPreview,
248- showToast,
249- } ) ;
227+ void addBlockToProject ( { ...blockCallOpts ( blockName ) , placement } ) ;
250228 } ,
251- [
252- projectId ,
253- activeCompPath ,
254- timelineElements ,
255- fileManager . readProjectFile ,
256- fileManager . writeProjectFile ,
257- fileManager . refreshFileTree ,
258- editHistory . recordEdit ,
259- reloadPreview ,
260- showToast ,
261- ] ,
229+ [ projectId , blockCallOpts ] ,
262230 ) ;
263-
264231 const handlePreviewBlockDrop = useCallback (
265232 ( blockName : string , position : { left : number ; top : number } ) => {
266233 if ( ! projectId ) return ;
267- void addBlockToProject ( {
268- projectId,
269- blockName,
270- activeCompPath,
271- visualPosition : position ,
272- previewIframe : previewIframeRef . current ,
273- currentTime : usePlayerStore . getState ( ) . currentTime ,
274- timelineElements,
275- readProjectFile : fileManager . readProjectFile ,
276- writeProjectFile : fileManager . writeProjectFile ,
277- recordEdit : editHistory . recordEdit ,
278- refreshFileTree : fileManager . refreshFileTree ,
279- reloadPreview,
280- showToast,
281- } ) ;
234+ void addBlockToProject ( { ...blockCallOpts ( blockName ) , visualPosition : position } ) ;
282235 } ,
283- [
284- projectId ,
285- activeCompPath ,
286- timelineElements ,
287- fileManager . readProjectFile ,
288- fileManager . writeProjectFile ,
289- fileManager . refreshFileTree ,
290- editHistory . recordEdit ,
291- reloadPreview ,
292- showToast ,
293- ] ,
236+ [ projectId , blockCallOpts ] ,
294237 ) ;
295-
296238 const clearDomSelectionRef = useRef < ( ) => void > ( ( ) => { } ) ;
297239 const domEditSelectionBridgeRef = useRef < DomEditSelection | null > ( null ) ;
298240 const handleDomEditElementDeleteRef = useRef < ( s : DomEditSelection ) => Promise < void > > (
0 commit comments