feat: Emulador de terminal con cursor para mongosh
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
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';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
export function SeccionSistema() {
|
export function SeccionSistema() {
|
||||||
@@ -12,6 +12,9 @@ export function SeccionSistema() {
|
|||||||
const [isExecuting, setIsExecuting] = useState(false);
|
const [isExecuting, setIsExecuting] = useState(false);
|
||||||
const historyCounter = useRef(0);
|
const historyCounter = useRef(0);
|
||||||
const consoleEndRef = useRef<HTMLDivElement>(null);
|
const consoleEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const commandHistoryRef = useRef<string[]>([]);
|
||||||
|
const historyIndexRef = useRef<number>(-1);
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
consoleEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
consoleEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
@@ -19,7 +22,7 @@ export function SeccionSistema() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}, [consoleHistory]);
|
}, [consoleHistory, mongoshCommand]);
|
||||||
|
|
||||||
const addHistory = (type: 'input' | 'output' | 'error', content: string) => {
|
const addHistory = (type: 'input' | 'output' | 'error', content: string) => {
|
||||||
setConsoleHistory(prev => [...prev, { id: historyCounter.current++, type, content }]);
|
setConsoleHistory(prev => [...prev, { id: historyCounter.current++, type, content }]);
|
||||||
@@ -28,8 +31,14 @@ export function SeccionSistema() {
|
|||||||
const handleExecuteMongosh = async () => {
|
const handleExecuteMongosh = async () => {
|
||||||
if (!mongoshCommand.trim()) return;
|
if (!mongoshCommand.trim()) return;
|
||||||
|
|
||||||
addHistory('input', mongoshCommand);
|
|
||||||
const commandToRun = 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('');
|
setMongoshCommand('');
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
|
|
||||||
@@ -61,6 +70,23 @@ export function SeccionSistema() {
|
|||||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
handleExecuteMongosh();
|
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>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<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 && (
|
{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) => (
|
{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 === 'input' && <span className="text-blue-500 mr-2">{'>'}</span>}
|
||||||
{item.type === 'output' && <span className="text-gray-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>}
|
{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>
|
||||||
))}
|
))}
|
||||||
<div ref={consoleEndRef} />
|
|
||||||
</div>
|
<div className="flex items-center text-blue-300 mt-1">
|
||||||
|
<span className="text-blue-500 mr-2">{'>'}</span>
|
||||||
<div className="flex gap-2">
|
<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
|
<input
|
||||||
|
ref={inputRef}
|
||||||
type="text"
|
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"
|
className="opacity-0 absolute inset-0 w-full h-full pointer-events-none"
|
||||||
placeholder="Escribe un comando... (ej: db.usuarios.find())"
|
|
||||||
value={mongoshCommand}
|
value={mongoshCommand}
|
||||||
onChange={(e) => setMongoshCommand(e.target.value)}
|
onChange={(e) => setMongoshCommand(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
disabled={isExecuting}
|
disabled={isExecuting}
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck="false"
|
||||||
/>
|
/>
|
||||||
<Button onClick={handleExecuteMongosh} disabled={isExecuting}>
|
|
||||||
<Send className="h-4 w-4 mr-2" />
|
|
||||||
Ejecutar
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-2 text-xs text-gray-500 flex items-center gap-1">
|
<div className="mt-2 text-xs text-gray-500 flex items-center gap-1">
|
||||||
<AlertCircle className="h-3 w-3" />
|
<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>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user