11import { appOutputAtom , frontendTerminalOutputAtom , backendTerminalOutputAtom , selectedAppIdAtom } from "@/atoms/appAtoms" ;
22import { useAtomValue } from "jotai" ;
33import { useLoadApp } from "@/hooks/useLoadApp" ;
4+ import { useState , useRef , useCallback , useEffect } from "react" ;
5+ import * as React from "react" ;
46
5- // Console component with side-by-side terminal support
7+ // Console component with side-by-side terminal support and resizable panels
68export const Console = ( ) => {
79 const appOutput = useAtomValue ( appOutputAtom ) ;
810 const frontendTerminalOutput = useAtomValue ( frontendTerminalOutputAtom ) ;
911 const backendTerminalOutput = useAtomValue ( backendTerminalOutputAtom ) ;
1012 const selectedAppId = useAtomValue ( selectedAppIdAtom ) ;
1113 const { app } = useLoadApp ( selectedAppId ) ;
1214
15+ // State for panel sizes (percentages)
16+ const [ panelSizes , setPanelSizes ] = useState ( {
17+ left : 50 ,
18+ right : 50 ,
19+ left2 : 33.33 ,
20+ middle : 33.33 ,
21+ right2 : 33.34 ,
22+ } ) ;
23+
24+ // Refs for drag handles
25+ const dragHandleRef = useRef < HTMLDivElement > ( null ) ;
26+ const dragHandle2Ref = useRef < HTMLDivElement > ( null ) ;
27+ const dragHandle3Ref = useRef < HTMLDivElement > ( null ) ;
28+
29+ // Drag state
30+ const [ isDragging , setIsDragging ] = useState < string | null > ( null ) ;
31+ const [ startX , setStartX ] = useState ( 0 ) ;
32+ const [ startSizes , setStartSizes ] = useState ( panelSizes ) ;
33+
34+ const handleMouseDown = useCallback ( ( handleId : string , event : React . MouseEvent ) => {
35+ setIsDragging ( handleId ) ;
36+ setStartX ( event . clientX ) ;
37+ setStartSizes ( { ...panelSizes } ) ;
38+ event . preventDefault ( ) ;
39+ } , [ panelSizes ] ) ;
40+
41+ const handleMouseMove = useCallback ( ( event : MouseEvent ) => {
42+ if ( ! isDragging ) return ;
43+
44+ const container = document . querySelector ( '[data-terminal-container]' ) ;
45+ if ( ! container ) return ;
46+
47+ const rect = container . getBoundingClientRect ( ) ;
48+ const deltaX = event . clientX - startX ;
49+ const containerWidth = rect . width ;
50+
51+ if ( isDragging === 'left-right' ) {
52+ // Two panel resize
53+ const deltaPercent = ( deltaX / containerWidth ) * 100 ;
54+ const newLeft = Math . max ( 20 , Math . min ( 80 , startSizes . left + deltaPercent ) ) ;
55+ const newRight = 100 - newLeft ;
56+ setPanelSizes ( prev => ( { ...prev , left : newLeft , right : newRight } ) ) ;
57+ } else if ( isDragging === 'left-middle' ) {
58+ // Three panel resize (left-middle)
59+ const deltaPercent = ( deltaX / containerWidth ) * 100 ;
60+ const newLeft2 = Math . max ( 15 , Math . min ( 50 , startSizes . left2 + deltaPercent ) ) ;
61+ const totalMiddleRight = 100 - newLeft2 ;
62+ const newMiddle = totalMiddleRight * ( startSizes . middle / ( startSizes . middle + startSizes . right2 ) ) ;
63+ const newRight2 = totalMiddleRight - newMiddle ;
64+ setPanelSizes ( prev => ( { ...prev , left2 : newLeft2 , middle : newMiddle , right2 : newRight2 } ) ) ;
65+ } else if ( isDragging === 'middle-right' ) {
66+ // Three panel resize (middle-right)
67+ const deltaPercent = ( deltaX / containerWidth ) * 100 ;
68+ const newRight2 = Math . max ( 15 , Math . min ( 50 , startSizes . right2 - deltaPercent ) ) ;
69+ const totalLeftMiddle = 100 - newRight2 ;
70+ const newMiddle = totalLeftMiddle * ( startSizes . middle / ( startSizes . left2 + startSizes . middle ) ) ;
71+ const newLeft2 = totalLeftMiddle - newMiddle ;
72+ setPanelSizes ( prev => ( { ...prev , left2 : newLeft2 , middle : newMiddle , right2 : newRight2 } ) ) ;
73+ }
74+ } , [ isDragging , startX , startSizes ] ) ;
75+
76+ const handleMouseUp = useCallback ( ( ) => {
77+ setIsDragging ( null ) ;
78+ } , [ ] ) ;
79+
80+ // Add global mouse event listeners
81+ useEffect ( ( ) => {
82+ if ( isDragging ) {
83+ document . addEventListener ( 'mousemove' , handleMouseMove ) ;
84+ document . addEventListener ( 'mouseup' , handleMouseUp ) ;
85+ return ( ) => {
86+ document . removeEventListener ( 'mousemove' , handleMouseMove ) ;
87+ document . removeEventListener ( 'mouseup' , handleMouseUp ) ;
88+ } ;
89+ }
90+ } , [ isDragging , handleMouseMove , handleMouseUp ] ) ;
91+
1392 // Determine which terminals to show
1493 const hasFrontendOutput = frontendTerminalOutput . length > 0 ;
1594 const hasBackendOutput = backendTerminalOutput . length > 0 ;
@@ -24,7 +103,7 @@ export const Console = () => {
24103 const hasBackend = hasBackendOutput || hasBackendFolder ;
25104
26105 // Show all terminals if any terminal has content (to ensure Frontend is visible when Backend/System have content)
27- const totalTerminals = hasFrontend + hasBackend + hasMain ;
106+ const totalTerminals = ( hasFrontend ? 1 : 0 ) + ( hasBackend ? 1 : 0 ) + ( hasMain ? 1 : 0 ) ;
28107 const showAllTerminals = totalTerminals > 0 ;
29108
30109 // Count active terminals
@@ -57,6 +136,22 @@ export const Console = () => {
57136 ) ;
58137 } ;
59138
139+ // Drag handle component
140+ const DragHandle = ( { onMouseDown, className = "" } : { onMouseDown : ( e : React . MouseEvent ) => void ; className ?: string } ) => (
141+ < div
142+ className = { `w-1 bg-border hover:bg-accent cursor-col-resize active:bg-primary transition-colors ${ className } ` }
143+ onMouseDown = { onMouseDown }
144+ style = { { userSelect : 'none' } }
145+ />
146+ ) ;
147+
148+ // Resizable container wrapper
149+ const ResizableContainer = ( { children, className = "" } : { children : React . ReactNode ; className ?: string } ) => (
150+ < div data-terminal-container className = { `flex h-full ${ className } ` } >
151+ { children }
152+ </ div >
153+ ) ;
154+
60155 // Single terminal layout
61156 if ( terminalCount === 1 ) {
62157 if ( hasFrontend ) {
@@ -73,58 +168,63 @@ export const Console = () => {
73168 if ( hasFrontend && hasBackend ) {
74169 // Frontend and Backend side by side
75170 return (
76- < div className = "flex h-full" >
77- < div className = "flex-1 border-r border-border" >
171+ < ResizableContainer >
172+ < div className = "h-full" style = { { width : ` ${ panelSizes . left } %` } } >
78173 < TerminalPanel title = "Frontend" outputs = { frontendTerminalOutput } color = "green" />
79174 </ div >
80- < div className = "flex-1" >
175+ < DragHandle onMouseDown = { ( e ) => handleMouseDown ( 'left-right' , e ) } />
176+ < div className = "h-full" style = { { width : `${ panelSizes . right } %` } } >
81177 < TerminalPanel title = "Backend" outputs = { backendTerminalOutput } color = "orange" />
82178 </ div >
83- </ div >
179+ </ ResizableContainer >
84180 ) ;
85181 }
86182 if ( hasMain && hasFrontend ) {
87183 // System and Frontend side by side
88184 return (
89- < div className = "flex h-full" >
90- < div className = "flex-1 border-r border-border" >
185+ < ResizableContainer >
186+ < div className = "h-full" style = { { width : ` ${ panelSizes . left } %` } } >
91187 < TerminalPanel title = "System" outputs = { appOutput } color = "blue" />
92188 </ div >
93- < div className = "flex-1" >
189+ < DragHandle onMouseDown = { ( e ) => handleMouseDown ( 'left-right' , e ) } />
190+ < div className = "h-full" style = { { width : `${ panelSizes . right } %` } } >
94191 < TerminalPanel title = "Frontend" outputs = { frontendTerminalOutput } color = "green" />
95192 </ div >
96- </ div >
193+ </ ResizableContainer >
97194 ) ;
98195 }
99196 if ( hasMain && hasBackend ) {
100197 // System and Backend side by side
101198 return (
102- < div className = "flex h-full" >
103- < div className = "flex-1 border-r border-border" >
199+ < ResizableContainer >
200+ < div className = "h-full" style = { { width : ` ${ panelSizes . left } %` } } >
104201 < TerminalPanel title = "System" outputs = { appOutput } color = "blue" />
105202 </ div >
106- < div className = "flex-1" >
203+ < DragHandle onMouseDown = { ( e ) => handleMouseDown ( 'left-right' , e ) } />
204+ < div className = "h-full" style = { { width : `${ panelSizes . right } %` } } >
107205 < TerminalPanel title = "Backend" outputs = { backendTerminalOutput } color = "orange" />
108206 </ div >
109- </ div >
207+ </ ResizableContainer >
110208 ) ;
111209 }
112210 }
113211
114- // Three terminals layout - show in a 3-column layout
212+ // Three terminals layout - show in a 3-column layout with resizable panels
115213 if ( terminalCount === 3 ) {
116214 return (
117- < div className = "flex h-full" >
118- < div className = "flex-1 border-r border-border" >
215+ < ResizableContainer >
216+ < div className = "h-full" style = { { width : ` ${ panelSizes . left2 } %` } } >
119217 < TerminalPanel title = "System" outputs = { appOutput } color = "blue" />
120218 </ div >
121- < div className = "flex-1 border-r border-border" >
219+ < DragHandle onMouseDown = { ( e ) => handleMouseDown ( 'left-middle' , e ) } />
220+ < div className = "h-full" style = { { width : `${ panelSizes . middle } %` } } >
122221 < TerminalPanel title = "Frontend" outputs = { frontendTerminalOutput } color = "green" />
123222 </ div >
124- < div className = "flex-1" >
223+ < DragHandle onMouseDown = { ( e ) => handleMouseDown ( 'middle-right' , e ) } />
224+ < div className = "h-full" style = { { width : `${ panelSizes . right2 } %` } } >
125225 < TerminalPanel title = "Backend" outputs = { backendTerminalOutput } color = "orange" />
126226 </ div >
127- </ div >
227+ </ ResizableContainer >
128228 ) ;
129229 }
130230
0 commit comments