Skip to content

Commit 074d6f3

Browse files
authored
Merge pull request #26 from chechojgb/terminal
style: cambios estilos terminal
2 parents bbcfdcc + 738d4ea commit 074d6f3

File tree

6 files changed

+108
-22
lines changed

6 files changed

+108
-22
lines changed

microservicio-ssh/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ wss.on("connection", (ws) => {
3030
}
3131

3232
shellStream = stream;
33-
ws.send(JSON.stringify({ output: "🟢 Sesión iniciada\n" }));
33+
// ws.send(JSON.stringify({ output: "🟢 Sesión iniciada\n" }));
3434

3535
stream.on("data", (data) => {
3636
ws.send(JSON.stringify({ output: data.toString() }));

package-lock.json

Lines changed: 23 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@
6868
"typescript": "^5.7.2",
6969
"vite": "^6.0",
7070
"xterm": "^5.3.0",
71-
"xterm-addon-fit": "^0.8.0"
71+
"xterm-addon-fit": "^0.8.0",
72+
"xterm-addon-search": "^0.13.0",
73+
"xterm-addon-web-links": "^0.9.0"
7274
},
7375
"optionalDependencies": {
7476
"@rollup/rollup-linux-x64-gnu": "4.9.5",
Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
// components/XTermSSH.jsx
2-
import { useEffect, useRef } from "react";
1+
import { useEffect, useRef, useState } from "react";
32
import { Terminal } from "xterm";
43
import { FitAddon } from "xterm-addon-fit";
54
import "xterm/css/xterm.css";
@@ -9,29 +8,40 @@ export default function XTermSSH() {
98
const term = useRef(null);
109
const fitAddon = useRef(new FitAddon());
1110
const socketRef = useRef(null);
11+
const [showToast, setShowToast] = useState(false);
12+
13+
const getTheme = () => {
14+
const isDark = document.documentElement.classList.contains("dark");
15+
return {
16+
background: isDark ? "#" : "#ffffff",
17+
foreground: isDark ? "#00ff00" : "#000000",
18+
cursor: isDark ? "#00ff00" : "#000000",
19+
selection: isDark ? "rgba(255, 255, 255, 0.3)" : "rgba(0,0,0,0.3)",
20+
};
21+
};
22+
23+
const applyTheme = () => {
24+
if (term.current) {
25+
const theme = getTheme();
26+
term.current.options.theme = theme;
27+
term.current.refresh(0, term.current.rows - 1);
28+
}
29+
};
1230

1331
useEffect(() => {
1432
term.current = new Terminal({
1533
cursorBlink: true,
1634
fontSize: 14,
1735
fontFamily: "monospace",
18-
theme: {
19-
background: "#1e1e1e",
20-
foreground: "#00ff00",
21-
},
36+
theme: getTheme(),
2237
});
2338

2439
term.current.loadAddon(fitAddon.current);
2540
term.current.open(terminalRef.current);
2641
fitAddon.current.fit();
2742

28-
// Conexión WebSocket
2943
socketRef.current = new WebSocket("ws://localhost:3001");
3044

31-
socketRef.current.onopen = () => {
32-
term.current.write("🟢 Conectado al servidor\n");
33-
};
34-
3545
socketRef.current.onmessage = (event) => {
3646
const data = JSON.parse(event.data);
3747
if (data.output) {
@@ -41,33 +51,59 @@ export default function XTermSSH() {
4151
}
4252
};
4353

44-
socketRef.current.onclose = () => {
45-
term.current.write("\n🔌 Conexión cerrada");
46-
};
47-
4854
socketRef.current.onerror = (err) => {
4955
term.current.write(`\n❌ Error: ${err.message}`);
5056
};
5157

52-
// Captura de entrada del usuario
5358
term.current.onData((data) => {
5459
socketRef.current.send(JSON.stringify({ input: data }));
5560
});
5661

57-
// Resize terminal si la ventana cambia de tamaño
62+
const observer = new MutationObserver(applyTheme);
63+
observer.observe(document.documentElement, {
64+
attributes: true,
65+
attributeFilter: ["class"],
66+
});
67+
5868
const handleResize = () => fitAddon.current.fit();
5969
window.addEventListener("resize", handleResize);
6070

71+
// ✅ Copiar automáticamente lo seleccionado (una sola vez)
72+
let lastCopiedSelection = "";
73+
term.current.onSelectionChange(() => {
74+
const selection = term.current.getSelection();
75+
if (selection && selection !== lastCopiedSelection) {
76+
navigator.clipboard.writeText(selection).then(() => {
77+
setShowToast(true);
78+
setTimeout(() => setShowToast(false), 1500);
79+
});
80+
lastCopiedSelection = selection;
81+
}
82+
});
83+
84+
// ✅ Pegar con clic derecho
85+
terminalRef.current.addEventListener("contextmenu", async (e) => {
86+
e.preventDefault();
87+
const text = await navigator.clipboard.readText();
88+
if (text) term.current.paste(text);
89+
});
90+
6191
return () => {
6292
term.current.dispose();
6393
socketRef.current.close();
94+
observer.disconnect();
6495
window.removeEventListener("resize", handleResize);
6596
};
6697
}, []);
6798

6899
return (
69-
<div className="h-screen bg-black">
100+
<div className="relative w-full h-full">
70101
<div ref={terminalRef} className="w-full h-full" />
102+
{showToast && (
103+
<div className="absolute top-2 right-2 bg-green-600 text-white px-4 py-2 rounded shadow-lg text-sm animate-fade-in-out z-50">
104+
Copiado al portapapeles
105+
</div>
106+
)}
71107
</div>
72108
);
73109
}

resources/js/hooks/useDarkMode.jsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
// import { useEffect, useState } from 'react';
2+
3+
// export default function useDarkMode() {
4+
// const [darkMode, setDarkMode] = useState(() => {
5+
// if (typeof window !== 'undefined') {
6+
// const saved = localStorage.getItem('darkMode');
7+
// if (saved !== null) return JSON.parse(saved);
8+
// return window.matchMedia('(prefers-color-scheme: dark)').matches;
9+
// }
10+
// return false;
11+
// });
12+
13+
// useEffect(() => {
14+
// localStorage.setItem('darkMode', JSON.stringify(darkMode));
15+
// document.documentElement.classList.toggle('dark', darkMode);
16+
// }, [darkMode]);
17+
18+
// return [darkMode, setDarkMode];
19+
// }
20+
21+
122
import { useEffect, useState } from 'react';
223

324
export default function useDarkMode() {
@@ -13,6 +34,9 @@ export default function useDarkMode() {
1334
useEffect(() => {
1435
localStorage.setItem('darkMode', JSON.stringify(darkMode));
1536
document.documentElement.classList.toggle('dark', darkMode);
37+
38+
// ✅ Esta línea notifica a otros componentes (como XTerm) del cambio
39+
window.dispatchEvent(new Event("storage"));
1640
}, [darkMode]);
1741

1842
return [darkMode, setDarkMode];

resources/js/pages/TestTerminal.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ export default function TableAgents() {
1717
return (
1818
<AppLayout breadcrumbs={breadcrumbs}>
1919
<Head title="terminal" />
20-
<XTermSSH/>
20+
<div className='p-8 space-y-8 w-full h-[calc(100vh-4rem)]'>
21+
<XTermSSH/>
22+
</div>
2123
</AppLayout>
2224
);
2325
}

0 commit comments

Comments
 (0)