11import { useOwnPreviewIframe , usePreviewIframeStore } from "./player/store/previewIframeStore" ;
22import { buildProjectApiPath } from "./utils/projectRouting" ;
3- import { useState , useCallback , useRef , useMemo , useLayoutEffect } from "react" ;
3+ import { useState , useCallback , useRef , useMemo } from "react" ;
4+ import { useStableHandlers } from "./hooks/useStableHandlers" ;
5+ import { useHistoryFlags , useToolbarSession } from "./hooks/useShellSlices" ;
46import { useDismissingTabSetter , useRightPanelIntent } from "./hooks/useRightPanelIntents" ;
57import { useRenderQueue } from "./components/renders/useRenderQueue" ;
68import { usePlayerStore } from "./player" ;
@@ -46,10 +48,9 @@ import {
4648import type { DomEditSelection } from "./components/editor/domEditing" ;
4749import { StudioHeader } from "./components/StudioHeader" ;
4850import { useGestureCommit } from "./hooks/useGestureCommit" ;
49- import { GestureTrailOverlay } from "./components/editor/GestureTrailOverlay" ;
50- import { StudioLeftPanels } from "./components/StudioLeftPanels" ;
51+ import { useGestureTrailOverlay } from "./hooks/useGestureTrailOverlay" ;
5152import { EditorShell } from "./components/EditorShell" ;
52- import { StudioRightPanels } from "./components/StudioRightPanels " ;
53+ import { useStudioSidePanels } from "./hooks/useStudioSidePanels " ;
5354import { TimelineToolbar } from "./components/TimelineToolbar" ;
5455import { StudioPlaybackProvider , StudioShellProvider } from "./contexts/StudioContext" ;
5556import { PanelLayoutProvider } from "./contexts/PanelLayoutContext" ;
@@ -99,17 +100,18 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
99100 rightCollapsed : initialUrlStateRef . current . rightCollapsed ,
100101 rightPanelTab : initialUrlStateRef . current . rightPanelTab ,
101102 } ) ;
102- const editHistory = usePersistentEditHistory ( { projectId } ) ;
103+ const editHistory = useStableHandlers ( usePersistentEditHistory ( { projectId } ) , projectId ) ;
103104 const handleDomZIndexReorderCommitRef = useRef < TimelineZIndexReorderCommit | null > ( null ) ;
104105 const pendingTimelineEditPathRef = useRef ( new Set < string > ( ) ) ;
105106 const isGestureRecordingRef = useRef ( false ) ;
106107 const reloadPreview = useCallback ( ( ) => setRefreshKey ( ( k ) => k + 1 ) , [ ] ) ;
107- const fileManager = useFileManager ( {
108+ const fileManagerResult = useFileManager ( {
108109 projectId,
109110 showToast,
110111 recordEdit : editHistory . recordEdit ,
111112 setRefreshKey,
112113 } ) ;
114+ const fileManager = useStableHandlers ( fileManagerResult , projectId ) ;
113115 const masterCompPath = useMemo (
114116 ( ) => resolveMasterCompositionPath ( fileManager . compositions ) ,
115117 [ fileManager . compositions ] ,
@@ -134,8 +136,8 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
134136 ) ;
135137 const activeCompPathRef = useRef ( activeCompPath ) ;
136138 activeCompPathRef . current = activeCompPath ;
137- const renderQueue = useRenderQueue ( projectId , activeCompPathRef ) ;
138- const previewPersistence = usePreviewPersistence ( {
139+ const renderQueue = useStableHandlers ( useRenderQueue ( projectId , activeCompPathRef ) , projectId ) ;
140+ const previewPersistenceResult = usePreviewPersistence ( {
139141 showToast,
140142 readOptionalProjectFile : fileManager . readOptionalProjectFile ,
141143 writeProjectFile : fileManager . writeProjectFile ,
@@ -144,6 +146,7 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
144146 activeCompPathRef,
145147 reloadPreview : ( ) => setRefreshKey ( ( k ) => k + 1 ) ,
146148 } ) ;
149+ const previewPersistence = useStableHandlers ( previewPersistenceResult , projectId ) ;
147150 const externalFileChanges = useStudioExternalFileChanges ( {
148151 projectId,
149152 activeCompPath,
@@ -184,15 +187,9 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
184187 } = useTimelineAddAtPlayhead (
185188 timelineEditing . handleTimelineAssetDrop ,
186189 timelineEditing . handleTimelineCompositionDrop ,
190+ projectId ,
187191 ) ;
188- const {
189- activeBlockParams,
190- setActiveBlockParams,
191- handleAddBlock,
192- handleTimelineBlockDrop,
193- handleAddMediaOverlay,
194- handlePreviewBlockDrop,
195- } = useBlockHandlers ( {
192+ const blockHandlersResult = useBlockHandlers ( {
196193 projectId,
197194 blockCtxDeps : {
198195 activeCompPath,
@@ -209,6 +206,14 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
209206 setRightCollapsed : panelLayout . setRightCollapsed ,
210207 setRightPanelTab : panelLayout . setRightPanelTab ,
211208 } ) ;
209+ const {
210+ activeBlockParams,
211+ setActiveBlockParams,
212+ handleAddBlock,
213+ handleTimelineBlockDrop,
214+ handleAddMediaOverlay,
215+ handlePreviewBlockDrop,
216+ } = useStableHandlers ( blockHandlersResult , projectId ) ;
212217 const dismissBlockParams = useCallback ( ( ) => setActiveBlockParams ( null ) , [ setActiveBlockParams ] ) ;
213218 const setRightPanelTab = useDismissingTabSetter ( panelLayout . setRightPanelTab , dismissBlockParams ) ;
214219 const layout = useMemo (
@@ -237,6 +242,7 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
237242 waitForPendingDomEditSaves : previewPersistence . waitForPendingDomEditSaves ,
238243 } ) ;
239244 const appHotkeys = useAppHotkeys ( {
245+ projectId,
240246 handleTimelineElementsDelete : timelineEditing . handleTimelineElementsDelete ,
241247 handleLinkEdit : timelineEditing . handleLinkEdit ,
242248 handleTimelineElementDeleteOnly : timelineEditing . handleTimelineElementDeleteOnly ,
@@ -269,7 +275,7 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
269275 onToggleRecording : ( ) => handleToggleRecordingRef . current ( ) ,
270276 readOnlyPreview,
271277 } ) ;
272- const domEditSession = useDomEditSession ( {
278+ const domEditSessionResult = useDomEditSession ( {
273279 projectId,
274280 activeCompPath,
275281 compIdToSrc,
@@ -309,6 +315,7 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
309315 handleTimelineElementsDelete : timelineEditing . handleTimelineElementsDelete ,
310316 readOnlyPreview,
311317 } ) ;
318+ const domEditSession = useStableHandlers ( domEditSessionResult , projectId ) ;
312319 domEditSelectionBridgeRef . current = domEditSession . domEditSelection ;
313320 const { handleDomZIndexReorderCommit : zCommit , handleDomEditElementDelete : del } = domEditSession ;
314321 handleDomZIndexReorderCommitRef . current = trackedStudioEdit ( zCommit ) ;
@@ -342,13 +349,14 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
342349 effectiveTimelineDuration,
343350 } ) ;
344351 const compositionDimensions = useCompositionDimensions ( previewIframeRef ) ;
345- const lint = useLintModal ( projectId , refreshKey ) ;
346- const frameCapture = useFrameCapture ( {
352+ const lint = useStableHandlers ( useLintModal ( projectId , refreshKey ) , projectId ) ;
353+ const frameCaptureResult = useFrameCapture ( {
347354 projectId,
348355 activeCompPath,
349356 showToast,
350357 waitForPendingDomEditSaves : previewPersistence . waitForPendingDomEditSaves ,
351358 } ) ;
359+ const frameCapture = useStableHandlers ( frameCaptureResult , projectId ) ;
352360 const {
353361 consoleErrors,
354362 setConsoleErrors,
@@ -359,21 +367,19 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
359367 const domEditSessionRef = useRef ( domEditSession ) ;
360368 domEditSessionRef . current = domEditSession ;
361369 const { gestureState, gestureRecording, handleToggleRecording } = useGestureCommit ( {
370+ projectId,
362371 domEditSessionRef,
363372 previewIframeRef,
364373 showToast,
365374 isGestureRecordingRef,
366375 readOnlyPreview,
367376 } ) ;
368377 handleToggleRecordingRef . current = handleToggleRecording ;
369- const canvasRectRef = useRef < DOMRect | null > ( null ) ;
370- useLayoutEffect ( ( ) => {
371- if ( gestureState !== "recording" || ! previewIframe ) {
372- canvasRectRef . current = null ;
373- return ;
374- }
375- canvasRectRef . current = previewIframe . getBoundingClientRect ( ) ;
376- } , [ gestureState , previewIframe ] ) ;
378+ const gestureOverlay = useGestureTrailOverlay (
379+ { gestureState, gestureRecording } ,
380+ previewIframe ,
381+ compositionDimensions ,
382+ ) ;
377383 const handlePreviewIframeRef = useCallback (
378384 ( iframe : HTMLIFrameElement | null ) => {
379385 previewIframeRef . current = iframe ;
@@ -415,6 +421,8 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
415421 setRightPanelTab,
416422 initialState : initialUrlStateRef . current ,
417423 } ) ;
424+ const historyFlags = useHistoryFlags ( editHistory ) ;
425+ const toolbarSession = useToolbarSession ( domEditSession ) ;
418426 const studioCtxValue = buildStudioContextValue ( {
419427 projectId : projectId ! ,
420428 activeCompPath,
@@ -427,7 +435,7 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
427435 setRefreshKey,
428436 timelineElements,
429437 isPlaying,
430- editHistory,
438+ editHistory : historyFlags ,
431439 handleUndo : appHotkeys . handleUndo ,
432440 handleRedo : appHotkeys . handleRedo ,
433441 renderQueue,
@@ -441,11 +449,51 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
441449 const timelineToolbar = useMemo (
442450 ( ) => (
443451 < TimelineToolbar
444- domEditSession = { domEditSession }
452+ domEditSession = { toolbarSession }
445453 onSplitElement = { timelineEditing . handleTimelineElementSplit }
446454 />
447455 ) ,
448- [ domEditSession , timelineEditing . handleTimelineElementSplit ] ,
456+ [ toolbarSession , timelineEditing . handleTimelineElementSplit ] ,
457+ ) ;
458+ const handleExport = useCallback ( ( ) => {
459+ void previewPersistence
460+ . waitForPendingDomEditSaves ( )
461+ . then ( ( ) => renderQueue . startRender ( undefined ) ) ;
462+ } , [ previewPersistence , renderQueue ] ) ;
463+ const clearConsoleErrors = useCallback ( ( ) => setConsoleErrors ( null ) , [ setConsoleErrors ] ) ;
464+ const panels = useStudioSidePanels (
465+ {
466+ onSelectComposition : handleSelectComposition ,
467+ onAddBlock : handleAddBlock ,
468+ onPreviewBlock : setBlockPreview ,
469+ onLint : lint . handleLint ,
470+ linting : lint . linting ,
471+ lintFindingCount : lint . lintFindingCount ,
472+ lintFindingsByFile : lint . findingsByFile ,
473+ lintHasError : lint . hasLintError ,
474+ onAddAssetToTimeline : handleAddAssetAtPlayhead ,
475+ onAddCompositionToTimeline : handleAddCompositionAtPlayhead ,
476+ } ,
477+ {
478+ activeBlockParams,
479+ onDismissBlockParams : dismissBlockParams ,
480+ onCloseBlockParams : ( ) => {
481+ setActiveBlockParams ( null ) ;
482+ panelLayout . setRightPanelTab ( "design" ) ;
483+ } ,
484+ recordingState : gestureState ,
485+ recordingDuration : gestureRecording . recordingDuration ,
486+ onToggleRecording : handleToggleRecording ,
487+ sdkSession : sdkHandle . session ,
488+ publishSdkSession : sdkHandle . publish ,
489+ forceReloadSdkSession : sdkHandle . forceReload ,
490+ reloadPreview,
491+ recordEdit : editHistory . recordEdit ,
492+ onToggleElementHidden : timelineEditing . handleToggleElementHidden ,
493+ onAutoGroupCarveSources : timelineEditing . handleAutoGroupCarveSources ,
494+ onAddMediaOverlay : handleAddMediaOverlay ,
495+ } ,
496+ projectId ,
449497 ) ;
450498 if ( resolving || waitingForServer || ! projectId )
451499 return < StudioSplash waiting = { waitingForServer } /> ;
@@ -468,12 +516,7 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
468516 capturing = { frameCapture . capturing }
469517 inspectorButtonActive = { inspectorButtonActive }
470518 inspectorPanelActive = { inspectorPanelActive }
471- onExport = { ( ) => {
472- void ( async ( ) => {
473- await previewPersistence . waitForPendingDomEditSaves ( ) ;
474- await renderQueue . startRender ( undefined ) ;
475- } ) ( ) ;
476- } }
519+ onExport = { handleExport }
477520 />
478521 { previewPersistence . domEditSaveQueuePaused && ! externalFileChanges . blocked && (
479522 < SaveQueuePausedBanner
@@ -491,41 +534,7 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
491534 < EditorShell
492535 readOnlyPreview = { readOnlyPreview }
493536 readOnlyPreviewReason = { readOnlyPreviewReason }
494- panels = {
495- < >
496- < StudioLeftPanels
497- onSelectComposition = { handleSelectComposition }
498- onAddBlock = { handleAddBlock }
499- onPreviewBlock = { setBlockPreview }
500- onLint = { lint . handleLint }
501- linting = { lint . linting }
502- lintFindingCount = { lint . lintFindingCount }
503- lintFindingsByFile = { lint . findingsByFile }
504- lintHasError = { lint . hasLintError }
505- onAddAssetToTimeline = { handleAddAssetAtPlayhead }
506- onAddCompositionToTimeline = { handleAddCompositionAtPlayhead }
507- />
508- < StudioRightPanels
509- activeBlockParams = { activeBlockParams }
510- onDismissBlockParams = { dismissBlockParams }
511- onCloseBlockParams = { ( ) => {
512- setActiveBlockParams ( null ) ;
513- panelLayout . setRightPanelTab ( "design" ) ;
514- } }
515- recordingState = { gestureState }
516- recordingDuration = { gestureRecording . recordingDuration }
517- onToggleRecording = { handleToggleRecording }
518- sdkSession = { sdkHandle . session }
519- publishSdkSession = { sdkHandle . publish }
520- forceReloadSdkSession = { sdkHandle . forceReload }
521- reloadPreview = { reloadPreview }
522- recordEdit = { editHistory . recordEdit }
523- onToggleElementHidden = { timelineEditing . handleToggleElementHidden }
524- onAutoGroupCarveSources = { timelineEditing . handleAutoGroupCarveSources }
525- onAddMediaOverlay = { handleAddMediaOverlay }
526- />
527- </ >
528- }
537+ panels = { panels }
529538 timelineToolbar = { timelineToolbar }
530539 renderClipContent = { renderClipContent }
531540 handleTimelineElementDelete = { timelineEditing . handleTimelineElementDelete }
@@ -561,26 +570,15 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
561570 recordingState = { gestureState }
562571 onToggleRecording = { handleToggleRecording }
563572 blockPreview = { blockPreview }
564- gestureOverlay = {
565- gestureState === "recording" && previewIframe ? (
566- < GestureTrailOverlay
567- samples = { gestureRecording . samplesRef . current }
568- sampleCount = { gestureRecording . samplesRef . current . length }
569- trail = { gestureRecording . trailRef . current }
570- canvasRect = { canvasRectRef . current ! }
571- compositionSize = { compositionDimensions ?? undefined }
572- mode = "recording"
573- />
574- ) : undefined
575- }
573+ gestureOverlay = { gestureOverlay }
576574 />
577575 < StudioOverlays
578576 projectId = { projectId }
579577 projectDir = { fileManager . projectDir }
580578 lintModal = { lint . lintModal }
581579 closeLintModal = { lint . closeLintModal }
582580 consoleErrors = { consoleErrors }
583- clearConsoleErrors = { ( ) => setConsoleErrors ( null ) }
581+ clearConsoleErrors = { clearConsoleErrors }
584582 domEditSession = { domEditSession }
585583 activeCompPath = { activeCompPath }
586584 toasts = { toasts }
0 commit comments