import { useEffect, useRef, useState, useCallback } from 'react' import { Box, useTheme } from '@mui/material' import { useRedisStateStore } from '../stores/redis-state.store' import ConsoleComponent from '../pages/console/ConsoleComponent' const HEIGHT_KEY = 'p3xr-console-drawer-height' const MIN_VH = 15 const MAX_VH = 66 const FOOTER_HEIGHT = 48 /** * Global bottom console drawer — always renders the full ConsoleComponent. * The welcome banner inside the console adapts to connectionState (connected * vs limited-AI). This keeps the toolbar, input, Clear / Commands always * usable — disconnected users can still type `ai: what is ZADD?` or eventually * `connect ` without losing the chrome. * * Top 5px grab strip resizes the drawer between MIN_VH and MAX_VH, persisted * to localStorage. A ResizeObserver dispatches window.resize on every frame * the drawer's height changes so pages that read --p3xr-console-drawer-height-active * re-layout live during drag and during open/close transition. */ export default function ConsoleDrawer() { const isOpen = useRedisStateStore(s => s.consoleDrawerOpen) const setConsoleDrawerOpen = useRedisStateStore(s => s.setConsoleDrawerOpen) const muiTheme = useTheme() const isDark = muiTheme.palette.mode === 'dark' const [resizeClicked, setResizeClicked] = useState(false) const [sizerHover, setSizerHover] = useState(false) const drawerRef = useRef(null) const sizerRef = useRef(null) const dragStyleElRef = useRef(null) const applyDragCursor = useCallback((cursor: 'ns-resize' | 'not-allowed') => { let el = dragStyleElRef.current if (!el) { el = document.createElement('style') el.setAttribute('data-p3xr-console-drawer-drag', '') document.head.appendChild(el) dragStyleElRef.current = el } el.textContent = `*, *::before, *::after { cursor: ${cursor} !important; }` }, []) const clearDragCursor = useCallback(() => { dragStyleElRef.current?.remove() dragStyleElRef.current = null }, []) // Saved height is applied at bootstrap (src/core/console-drawer-height.ts) // so it's in place before this component mounts. // Observe the drawer element — fires on every size change frame // (open/close height transition + live drag). Listeners on window.resize // (profiler/pubsub page height calc) pick it up. useEffect(() => { if (!drawerRef.current || typeof ResizeObserver === 'undefined') return const obs = new ResizeObserver(() => { window.dispatchEvent(new Event('resize')) }) obs.observe(drawerRef.current) return () => obs.disconnect() }, []) // Drag handlers — document-level so the drag continues outside the sizer useEffect(() => { if (!resizeClicked) return const handleMouseMove = (e: MouseEvent) => { const minPx = (MIN_VH / 100) * window.innerHeight const maxPx = (MAX_VH / 100) * window.innerHeight let newHeight = window.innerHeight - e.clientY - FOOTER_HEIGHT const outOfBounds = newHeight < minPx || newHeight > maxPx if (newHeight < minPx) newHeight = minPx if (newHeight > maxPx) newHeight = maxPx applyDragCursor(outOfBounds ? 'not-allowed' : 'ns-resize') document.documentElement.style.setProperty('--p3xr-console-drawer-height', `${Math.round(newHeight)}px`) } const handleMouseUp = () => { setResizeClicked(false) clearDragCursor() document.body.classList.remove('p3xr-not-selectable') document.documentElement.classList.remove('p3xr-console-drawer-resizing') const current = document.documentElement.style.getPropertyValue('--p3xr-console-drawer-height') if (current && current.endsWith('px')) { localStorage.setItem(HEIGHT_KEY, current) } } document.addEventListener('mousemove', handleMouseMove) document.addEventListener('mouseup', handleMouseUp) return () => { document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mouseup', handleMouseUp) } }, [resizeClicked, applyDragCursor, clearDragCursor]) const handleSizerMouseDown = useCallback((e: React.MouseEvent) => { e.preventDefault() e.stopPropagation() setResizeClicked(true) applyDragCursor('ns-resize') document.body.classList.add('p3xr-not-selectable') document.documentElement.classList.add('p3xr-console-drawer-resizing') }, [applyDragCursor]) const sizerFilter = resizeClicked ? (isDark ? 'brightness(2)' : 'brightness(0.5)') : sizerHover ? (isDark ? 'brightness(1.5)' : 'brightness(0.75)') : 'none' return ( setSizerHover(true)} onMouseLeave={() => setSizerHover(false)} sx={{ position: 'absolute', top: 0, left: 0, right: 0, height: '5px', cursor: 'ns-resize', zIndex: 3, bgcolor: (sizerHover || resizeClicked) ? (muiTheme as any).p3xr?.accordionBg : 'transparent', filter: sizerFilter, transition: 'background-color 0.15s ease, filter 0.15s ease', }} /> setConsoleDrawerOpen(false)} /> ) }