diff --git a/AppImage/components/terminal-panel.tsx b/AppImage/components/terminal-panel.tsx index 690550b..99e0847 100644 --- a/AppImage/components/terminal-panel.tsx +++ b/AppImage/components/terminal-panel.tsx @@ -3,7 +3,19 @@ import type React from "react" import { useEffect, useRef, useState, useCallback } from "react" import { API_PORT } from "@/lib/api-config" -import { Activity, Trash2, X, Search, Send, Lightbulb, Terminal, Plus, Split, Grid2X2 } from "lucide-react" +import { + Activity, + Trash2, + X, + Search, + Send, + Lightbulb, + Terminal, + Plus, + Split, + Grid2X2, + GripHorizontal, +} from "lucide-react" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" @@ -124,6 +136,8 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl const [activeTerminalId, setActiveTerminalId] = useState("") const [layout, setLayout] = useState<"single" | "vertical" | "horizontal" | "grid">("single") const [isMobile, setIsMobile] = useState(false) + const [terminalHeight, setTerminalHeight] = useState(500) // altura por defecto en px + const [isResizing, setIsResizing] = useState(false) const [searchModalOpen, setSearchModalOpen] = useState(false) const [searchQuery, setSearchQuery] = useState("") @@ -134,21 +148,61 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl const [useOnline, setUseOnline] = useState(true) const containerRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}) - - const setContainerRef = useCallback( - (id: string) => (el: HTMLDivElement | null) => { - containerRefs.current[id] = el - }, - [], - ) + const resizeStartY = useRef(0) + const resizeStartHeight = useRef(0) useEffect(() => { setIsMobile(window.innerWidth < 768) const handleResize = () => setIsMobile(window.innerWidth < 768) window.addEventListener("resize", handleResize) + + const savedHeight = localStorage.getItem("terminalHeight") + if (savedHeight) { + setTerminalHeight(Number.parseInt(savedHeight, 10)) + } + return () => window.removeEventListener("resize", handleResize) }, []) + const handleResizeStart = useCallback( + (e: React.MouseEvent) => { + if (isMobile) return + setIsResizing(true) + resizeStartY.current = e.clientY + resizeStartHeight.current = terminalHeight + e.preventDefault() + }, + [isMobile, terminalHeight], + ) + + const handleResizeMove = useCallback( + (e: MouseEvent) => { + if (!isResizing) return + + const deltaY = resizeStartY.current - e.clientY + const newHeight = Math.max(200, Math.min(800, resizeStartHeight.current + deltaY)) + setTerminalHeight(newHeight) + }, + [isResizing], + ) + + const handleResizeEnd = useCallback(() => { + if (!isResizing) return + setIsResizing(false) + localStorage.setItem("terminalHeight", terminalHeight.toString()) + }, [isResizing, terminalHeight]) + + useEffect(() => { + if (isResizing) { + document.addEventListener("mousemove", handleResizeMove) + document.addEventListener("mouseup", handleResizeEnd) + return () => { + document.removeEventListener("mousemove", handleResizeMove) + document.removeEventListener("mouseup", handleResizeEnd) + } + } + }, [isResizing, handleResizeMove, handleResizeEnd]) + useEffect(() => { if (terminals.length === 0) { addNewTerminal() @@ -187,18 +241,16 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl console.log("[v0] Received parsed examples from server:", data.examples.length) - // Usar los ejemplos ya parseados del servidor const formattedResults: CheatSheetResult[] = data.examples.map((example: any) => ({ command: example.command, description: example.description || "", - examples: [example.command], // El comando limpio como ejemplo + examples: [example.command], })) setUseOnline(true) setSearchResults(formattedResults) } catch (error) { console.log("[v0] Error fetching from cheat.sh proxy, using offline commands:", error) - // Mostrar resultados offline si falla const filtered = proxmoxCommands.filter( (item) => item.cmd.toLowerCase().includes(query.toLowerCase()) || @@ -228,8 +280,6 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl if (terminals.length >= 4) return const newId = `terminal-${Date.now()}` - // containerRefs.current[newId] = useRef(null) // No longer needed - setTerminals((prev) => [ ...prev, { @@ -238,7 +288,6 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl term: null, ws: null, isConnected: false, - // containerRef: containerRefs.current[newId], // No longer needed }, ]) setActiveTerminalId(newId) @@ -263,7 +312,7 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl return filtered }) - delete containerRefs.current[id] // Clean up the ref + delete containerRefs.current[id] } useEffect(() => { @@ -323,7 +372,6 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl const wsUrl = websocketUrl || getWebSocketUrl() const ws = new WebSocket(wsUrl) - // Ajusta al contenedor y envía tamaño al backend const syncSizeWithBackend = () => { try { fitAddon.fit() @@ -346,7 +394,6 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl ws.onopen = () => { setTerminals((prev) => prev.map((t) => (t.id === terminal.id ? { ...t, isConnected: true, term, ws } : t))) term.writeln("\x1b[32mConnected to ProxMenux terminal.\x1b[0m") - // Tamaño inicial syncSizeWithBackend() } @@ -365,14 +412,12 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl term.writeln("\r\n\x1b[33m[INFO] Connection closed\x1b[0m") } - // Teclas → backend term.onData((data) => { if (ws.readyState === WebSocket.OPEN) { ws.send(data) } }) - // Solo reaccionamos a cambios de tamaño de la ventana const handleResize = () => { syncSizeWithBackend() } @@ -441,11 +486,8 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl const activeTerminal = terminals.find((t) => t.id === activeTerminalId) if (activeTerminal?.ws && activeTerminal.ws.readyState === WebSocket.OPEN) { - // Solo enviamos el comando sin el \n para que no se ejecute automáticamente - // El usuario puede editar el comando y presionar Enter cuando esté listo activeTerminal.ws.send(command) - // Pequeño delay antes de cerrar el modal para asegurar que el comando se envió setTimeout(() => { setSearchModalOpen(false) }, 100) @@ -476,6 +518,18 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl return (
+ {!isMobile && ( +
+ +
+ )} +
@@ -557,7 +611,7 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl
-
+
{isMobile ? ( @@ -581,7 +635,7 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl {terminals.map((terminal) => (
(containerRefs.current[terminal.id] = el)} className="w-full h-full bg-black overflow-hidden" style={{ height: "calc(100vh - 24rem)" }} /> @@ -607,7 +661,10 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl )}
-
+
(containerRefs.current[terminal.id] = el)} + className="w-full h-full bg-black pt-7 overflow-hidden" + />
))}
@@ -702,7 +759,6 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl
))} - {/* Powered by cheat.sh */}