feat: Emulador de terminal con cursor para mongosh
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Terminal, Download, Upload, FileText, Send, AlertCircle, RefreshCw } from 'lucide-react';
|
||||
import { Terminal, Download, Upload, FileText, AlertCircle, RefreshCw } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export function SeccionSistema() {
|
||||
@@ -12,6 +12,9 @@ export function SeccionSistema() {
|
||||
const [isExecuting, setIsExecuting] = useState(false);
|
||||
const historyCounter = useRef(0);
|
||||
const consoleEndRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const commandHistoryRef = useRef<string[]>([]);
|
||||
const historyIndexRef = useRef<number>(-1);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
consoleEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
@@ -19,7 +22,7 @@ export function SeccionSistema() {
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [consoleHistory]);
|
||||
}, [consoleHistory, mongoshCommand]);
|
||||
|
||||
const addHistory = (type: 'input' | 'output' | 'error', content: string) => {
|
||||
setConsoleHistory(prev => [...prev, { id: historyCounter.current++, type, content }]);
|
||||
@@ -28,8 +31,14 @@ export function SeccionSistema() {
|
||||
const handleExecuteMongosh = async () => {
|
||||
if (!mongoshCommand.trim()) return;
|
||||
|
||||
addHistory('input', mongoshCommand);
|
||||
const commandToRun = mongoshCommand;
|
||||
addHistory('input', commandToRun);
|
||||
|
||||
if (commandHistoryRef.current[commandHistoryRef.current.length - 1] !== commandToRun) {
|
||||
commandHistoryRef.current.push(commandToRun);
|
||||
}
|
||||
historyIndexRef.current = commandHistoryRef.current.length;
|
||||
|
||||
setMongoshCommand('');
|
||||
setIsExecuting(true);
|
||||
|
||||
@@ -61,6 +70,23 @@ export function SeccionSistema() {
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleExecuteMongosh();
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
if (historyIndexRef.current > 0) {
|
||||
const newIndex = historyIndexRef.current - 1;
|
||||
historyIndexRef.current = newIndex;
|
||||
setMongoshCommand(commandHistoryRef.current[newIndex]);
|
||||
}
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
if (historyIndexRef.current < commandHistoryRef.current.length - 1) {
|
||||
const newIndex = historyIndexRef.current + 1;
|
||||
historyIndexRef.current = newIndex;
|
||||
setMongoshCommand(commandHistoryRef.current[newIndex]);
|
||||
} else if (historyIndexRef.current === commandHistoryRef.current.length - 1) {
|
||||
historyIndexRef.current = commandHistoryRef.current.length;
|
||||
setMongoshCommand('');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -141,39 +167,46 @@ export function SeccionSistema() {
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="bg-gray-900 text-green-400 font-mono text-sm p-4 rounded-md h-[400px] overflow-y-auto mb-4 flex flex-col gap-2 relative">
|
||||
<div
|
||||
className="bg-gray-900 text-green-400 font-mono text-sm p-4 rounded-md h-[400px] overflow-y-auto flex flex-col gap-1 relative cursor-text group"
|
||||
onClick={() => inputRef.current?.focus()}
|
||||
>
|
||||
{consoleHistory.length === 0 && (
|
||||
<div className="text-gray-500 italic">Conectado. Esperando comandos... Ej: db.pacientes.find()</div>
|
||||
<div className="text-gray-500 italic mb-2">Conectado. Esperando comandos... Ej: db.pacientes.find()</div>
|
||||
)}
|
||||
{consoleHistory.map((item) => (
|
||||
<div key={item.id} className={`${item.type === 'error' ? 'text-red-400' : item.type === 'input' ? 'text-blue-300' : 'text-gray-300'}`}>
|
||||
<div key={item.id} className={`break-words ${item.type === 'error' ? 'text-red-400' : item.type === 'input' ? 'text-blue-300' : 'text-gray-300'}`}>
|
||||
{item.type === 'input' && <span className="text-blue-500 mr-2">{'>'}</span>}
|
||||
{item.type === 'output' && <span className="text-gray-500 mr-2">{'<-'}</span>}
|
||||
{item.type === 'error' && <span className="text-red-500 mr-2">{'!'}</span>}
|
||||
<pre className="inline-block whitespace-pre-wrap font-inherit m-0">{item.content}</pre>
|
||||
<pre className="inline whitespace-pre-wrap font-inherit m-0">{item.content}</pre>
|
||||
</div>
|
||||
))}
|
||||
<div ref={consoleEndRef} />
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
|
||||
<div className="flex items-center text-blue-300 mt-1">
|
||||
<span className="text-blue-500 mr-2">{'>'}</span>
|
||||
<span className="whitespace-pre-wrap break-all">{mongoshCommand}</span>
|
||||
<span className="w-2 bg-green-400 animate-pulse ml-[1px] inline-block h-4 opacity-0 group-focus-within:opacity-100 transition-opacity"></span>
|
||||
</div>
|
||||
|
||||
<div ref={consoleEndRef} className="h-1" />
|
||||
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
className="flex-1 px-3 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono"
|
||||
placeholder="Escribe un comando... (ej: db.usuarios.find())"
|
||||
className="opacity-0 absolute inset-0 w-full h-full pointer-events-none"
|
||||
value={mongoshCommand}
|
||||
onChange={(e) => setMongoshCommand(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={isExecuting}
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
/>
|
||||
<Button onClick={handleExecuteMongosh} disabled={isExecuting}>
|
||||
<Send className="h-4 w-4 mr-2" />
|
||||
Ejecutar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 text-xs text-gray-500 flex items-center gap-1">
|
||||
<AlertCircle className="h-3 w-3" />
|
||||
Soporta asincronía. Variables globales: `db`, `ObjectId`. Las promesas se resuelven automáticamente.
|
||||
Soporta asincronía. Variables globales: `db`, `ObjectId`. Flechas arriba/abajo para navegar el historial.
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user