()
| 60 | type CanvasRect = { left: number; top: number; width: number; height: number }; |
| 61 | // fallow-ignore-next-line complexity |
| 62 | export function StudioApp() { |
| 63 | const { projectId, resolving, waitingForServer } = useServerConnection(); |
| 64 | const initialUrlStateRef = useRef(readStudioUrlStateFromWindow()); |
| 65 | const viewModeValue = useViewModeState(); |
| 66 | |
| 67 | // sessionStorage-backed: fires once per tab, survives HMR remounts |
| 68 | useEffect(() => { |
| 69 | if (resolving || waitingForServer) return; |
| 70 | if (hasFiredSessionStart()) return; |
| 71 | markSessionStartFired(); |
| 72 | trackStudioSessionStart({ has_project: projectId != null }); |
| 73 | }, [projectId, resolving, waitingForServer]); |
| 74 | |
| 75 | const [activeCompPath, setActiveCompPath] = useState<string | null>(null); |
| 76 | const [activeCompPathHydrated, setActiveCompPathHydrated] = useState( |
| 77 | () => initialUrlStateRef.current.activeCompPath == null, |
| 78 | ); |
| 79 | const [compIdToSrc, setCompIdToSrc] = useState<Map<string, string>>(new Map()); |
| 80 | const [previewIframe, setPreviewIframe] = useState<HTMLIFrameElement | null>(null); |
| 81 | const [compositionLoading, setCompositionLoading] = useState(true); |
| 82 | const [refreshKey, setRefreshKey] = useState(0); |
| 83 | const [previewDocumentVersion, setPreviewDocumentVersion] = useState(0); |
| 84 | const [blockPreview, setBlockPreview] = useState<BlockPreviewInfo | null>(null); |
| 85 | const previewIframeRef = useRef<HTMLIFrameElement | null>(null); |
| 86 | const activeCompPathRef = useRef(activeCompPath); |
| 87 | activeCompPathRef.current = activeCompPath; |
| 88 | const leftSidebarRef = useRef<LeftSidebarHandle>(null); |
| 89 | const renderQueue = useRenderQueue(projectId); |
| 90 | const captionEditMode = useCaptionStore((s) => s.isEditMode); |
| 91 | const captionHasSelection = useCaptionStore((s) => s.selectedSegmentIds.size > 0); |
| 92 | const captionSync = useCaptionSync(projectId); |
| 93 | const timelineElements = usePlayerStore((s) => s.elements); |
| 94 | const setSelectedTimelineElementId = usePlayerStore((s) => s.setSelectedElementId); |
| 95 | const timelineDuration = usePlayerStore((s) => s.duration); |
| 96 | const isPlaying = usePlayerStore((s) => s.isPlaying); |
| 97 | const isMasterView = !activeCompPath || activeCompPath === "index.html"; |
| 98 | const activePreviewUrl = activeCompPath |
| 99 | ? `/api/projects/${projectId}/preview/comp/${activeCompPath}` |
| 100 | : null; |
| 101 | const effectiveTimelineDuration = useMemo(() => { |
| 102 | const maxEnd = |
| 103 | timelineElements.length > 0 |
| 104 | ? Math.max(...timelineElements.map((el) => el.start + el.duration)) |
| 105 | : 0; |
| 106 | return Math.max(timelineDuration, maxEnd); |
| 107 | }, [timelineDuration, timelineElements]); |
| 108 | const refreshTimersRef = useRef<number[]>([]); |
| 109 | const refreshPreviewDocumentVersion = useCallback(() => { |
| 110 | for (const id of refreshTimersRef.current) clearTimeout(id); |
| 111 | refreshTimersRef.current = []; |
| 112 | setPreviewDocumentVersion((v) => v + 1); |
| 113 | refreshTimersRef.current.push( |
| 114 | window.setTimeout(() => setPreviewDocumentVersion((v) => v + 1), 80), |
| 115 | window.setTimeout(() => setPreviewDocumentVersion((v) => v + 1), 300), |
| 116 | ); |
| 117 | }, []); |
| 118 | useEffect( |
| 119 | () => () => { |
nothing calls this directly
no test coverage detected