import { useCallback, type RefObject } from "react"; import { SourceEditor } from "./editor/SourceEditor"; import { LeftSidebar, type LeftSidebarHandle } from "./sidebar/LeftSidebar"; import { MediaPreview } from "./MediaPreview"; import { isMediaFile } from "../utils/mediaTypes"; import { usePanelLayoutContext } from "../contexts/PanelLayoutContext"; import { useStudioShellContext } from "../contexts/StudioContext"; import { useFileManagerContext } from "../contexts/FileManagerContext"; import { getPersistedRenderSettings } from "./renders/renderSettings"; import type { BlockPreviewInfo } from "./sidebar/BlocksTab"; export interface StudioLeftSidebarProps { leftSidebarRef: RefObject; onSelectComposition: (comp: string) => void; onAddBlock: (blockName: string) => void; onPreviewBlock?: (preview: BlockPreviewInfo | null) => void; onLint: () => void; linting: boolean; lintFindingCount?: number; lintFindingsByFile?: Map; onAddAssetToTimeline?: (path: string) => void; } // fallow-ignore-next-line complexity export function StudioLeftSidebar({ leftSidebarRef, onSelectComposition, onAddBlock, onPreviewBlock, onLint, linting, lintFindingCount, lintFindingsByFile, onAddAssetToTimeline, }: StudioLeftSidebarProps) { const { leftCollapsed, leftWidth, setLeftWidth, toggleLeftSidebar, handlePanelResizeStart, handlePanelResizeMove, handlePanelResizeEnd, } = usePanelLayoutContext(); const { projectId, renderQueue, waitForPendingDomEditSaves } = useStudioShellContext(); const { compositions, assets, editingFile, fileTree, revealSourceOffset, handleFileSelect, handleCreateFile, handleCreateFolder, handleDeleteFile, handleRenameFile, handleDuplicateFile, handleMoveFile, handleImportFiles, handleContentChange, } = useFileManagerContext(); const handleRenderComposition = useCallback( async (comp: string) => { await waitForPendingDomEditSaves(); const { format, quality, fps } = getPersistedRenderSettings(); await renderQueue.startRender({ composition: comp, format, quality, fps }); }, [renderQueue, waitForPendingDomEditSaves], ); if (leftCollapsed) { return (
); } return ( <> { await handleImportFiles(files, dir); }} codeChildren={ editingFile ? ( isMediaFile(editingFile.path) ? ( ) : editingFile.content == null ? ( // Never mount the editor on unloaded content: a keystroke would // autosave an empty document over the real file.
Loading {editingFile.path}…
) : ( ) ) : undefined } onRenderComposition={handleRenderComposition} isRendering={renderQueue.isRendering} onLint={onLint} linting={linting} lintFindingCount={lintFindingCount} lintFindingsByFile={lintFindingsByFile} onToggleCollapse={toggleLeftSidebar} onAddBlock={onAddBlock} onPreviewBlock={onPreviewBlock} onAddAssetToTimeline={onAddAssetToTimeline} /> {/* Vertical resize divider: 3px visible seam, 8px pointer-capture zone via the absolutely-positioned inner hit area. The outer element is w-[3px] so it contributes only 3px of gap in the flex row; the inner -left-[2.5px] element widens the hit area to 8px without affecting layout. */}
handlePanelResizeStart("left", e)} onPointerMove={handlePanelResizeMove} onPointerUp={handlePanelResizeEnd} onPointerCancel={handlePanelResizeEnd} onKeyDown={(e) => { if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return; e.preventDefault(); const delta = e.key === "ArrowLeft" ? -16 : 16; const maxLeft = Math.floor(window.innerWidth * 0.5); setLeftWidth(Math.max(160, Math.min(maxLeft, leftWidth + delta))); }} > {/* Expanded hit zone: 8px wide, centered on the 3px seam */}
{/* Visible hairline */}
); }