263 lines
7.2 KiB
TypeScript
263 lines
7.2 KiB
TypeScript
import React from "react";
|
|
import { useTerminalStore } from "../store/useTerminalStore";
|
|
import { MdClose, MdClearAll } from "react-icons/md";
|
|
import { FiTerminal } from "react-icons/fi";
|
|
|
|
export const TerminalOutput: React.FC = () => {
|
|
const {
|
|
jobs,
|
|
isOpen,
|
|
activeJobId,
|
|
closeTerminal,
|
|
setActiveJob,
|
|
clearJobs,
|
|
removeJob,
|
|
} = useTerminalStore();
|
|
|
|
if (!isOpen) return null;
|
|
|
|
const activeJob = jobs.find((j) => j.id === activeJobId) || jobs[jobs.length - 1];
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
height: "100%",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
backgroundColor: "#1e1e1e",
|
|
borderTop: "1px solid #3e3e42",
|
|
}}
|
|
>
|
|
{/* Terminal header */}
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
padding: "0 12px",
|
|
height: "35px",
|
|
borderBottom: "1px solid #3e3e42",
|
|
backgroundColor: "#252526",
|
|
}}
|
|
>
|
|
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
|
<FiTerminal size={14} color="#bbbbbb" />
|
|
<span
|
|
style={{
|
|
color: "#bbbbbb",
|
|
fontWeight: 500,
|
|
fontSize: "11px",
|
|
letterSpacing: "0.8px",
|
|
}}
|
|
>
|
|
TERMINAL
|
|
</span>
|
|
{jobs.length > 0 && (
|
|
<span
|
|
style={{
|
|
color: "#858585",
|
|
fontSize: "11px",
|
|
backgroundColor: "#3c3c3c",
|
|
padding: "2px 8px",
|
|
borderRadius: "10px",
|
|
}}
|
|
>
|
|
{jobs.length}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div style={{ display: "flex", gap: "4px", alignItems: "center" }}>
|
|
{jobs.length > 0 && (
|
|
<button
|
|
onClick={clearJobs}
|
|
style={{
|
|
background: "transparent",
|
|
border: "none",
|
|
color: "#858585",
|
|
cursor: "pointer",
|
|
padding: "4px",
|
|
borderRadius: "4px",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
}}
|
|
title="Clear all"
|
|
>
|
|
<MdClearAll size={14} />
|
|
</button>
|
|
)}
|
|
<button
|
|
onClick={closeTerminal}
|
|
style={{
|
|
background: "transparent",
|
|
border: "none",
|
|
color: "#858585",
|
|
cursor: "pointer",
|
|
padding: "4px",
|
|
borderRadius: "4px",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
}}
|
|
title="Close"
|
|
>
|
|
<MdClose size={14} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Job tabs */}
|
|
{jobs.length > 1 && (
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
backgroundColor: "#2d2d2d",
|
|
borderBottom: "1px solid #3e3e42",
|
|
overflowX: "auto",
|
|
}}
|
|
>
|
|
{jobs.map((job) => (
|
|
<button
|
|
key={job.id}
|
|
onClick={() => setActiveJob(job.id)}
|
|
style={{
|
|
padding: "6px 16px",
|
|
backgroundColor:
|
|
job.id === activeJobId ? "#1e1e1e" : "transparent",
|
|
border: "none",
|
|
borderBottom:
|
|
job.id === activeJobId
|
|
? "2px solid #0e639c"
|
|
: "2px solid transparent",
|
|
color: job.isRunning ? "#cccccc" : "#858585",
|
|
fontSize: "12px",
|
|
cursor: "pointer",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "6px",
|
|
whiteSpace: "nowrap",
|
|
}}
|
|
>
|
|
<span
|
|
style={{
|
|
width: "8px",
|
|
height: "8px",
|
|
borderRadius: "50%",
|
|
backgroundColor: job.isRunning ? "#4ec9b0" : "#858585",
|
|
display: "inline-block",
|
|
}}
|
|
/>
|
|
{job.scriptPath.split("/").pop()}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Terminal output */}
|
|
<div
|
|
style={{
|
|
flex: 1,
|
|
overflowY: "auto",
|
|
padding: "12px",
|
|
fontFamily: "'Consolas', 'Courier New', monospace",
|
|
fontSize: "13px",
|
|
lineHeight: "1.5",
|
|
}}
|
|
>
|
|
{activeJob ? (
|
|
<>
|
|
{/* Command header */}
|
|
<div style={{ marginBottom: "8px" }}>
|
|
<span style={{ color: "#6a9955" }}>$ </span>
|
|
<span style={{ color: "#cccccc" }}>
|
|
{activeJob.command.join(" ")}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Stdin if provided */}
|
|
{activeJob.stdin && (
|
|
<div
|
|
style={{
|
|
marginBottom: "8px",
|
|
padding: "8px",
|
|
backgroundColor: "#2d2d2d",
|
|
borderRadius: "4px",
|
|
borderLeft: "3px solid #0e639c",
|
|
}}
|
|
>
|
|
<span style={{ color: "#858585" }}>stdin: </span>
|
|
<pre
|
|
style={{
|
|
margin: 0,
|
|
color: "#cccccc",
|
|
whiteSpace: "pre-wrap",
|
|
wordBreak: "break-word",
|
|
}}
|
|
>
|
|
{activeJob.stdin}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
|
|
{/* Stdout */}
|
|
{activeJob.stdout && (
|
|
<pre
|
|
style={{
|
|
margin: "0 0 8px 0",
|
|
color: "#cccccc",
|
|
whiteSpace: "pre-wrap",
|
|
wordBreak: "break-word",
|
|
}}
|
|
>
|
|
{activeJob.stdout}
|
|
</pre>
|
|
)}
|
|
|
|
{/* Stderr */}
|
|
{activeJob.stderr && (
|
|
<pre
|
|
style={{
|
|
margin: "0 0 8px 0",
|
|
color: "#f44747",
|
|
whiteSpace: "pre-wrap",
|
|
wordBreak: "break-word",
|
|
}}
|
|
>
|
|
{activeJob.stderr}
|
|
</pre>
|
|
)}
|
|
|
|
{/* Status */}
|
|
{activeJob.isRunning ? (
|
|
<div style={{ color: "#4ec9b0" }}>⏳ Running...</div>
|
|
) : activeJob.status !== null ? (
|
|
<div
|
|
style={{
|
|
color: activeJob.status === 0 ? "#4ec9b0" : "#f44747",
|
|
}}
|
|
>
|
|
{activeJob.status === 0
|
|
? "✓ Process exited with code 0"
|
|
: `✗ Process exited with code ${activeJob.status}`}
|
|
</div>
|
|
) : null}
|
|
</>
|
|
) : (
|
|
<div
|
|
style={{
|
|
color: "#858585",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
height: "100%",
|
|
flexDirection: "column",
|
|
gap: "8px",
|
|
}}
|
|
>
|
|
<FiTerminal size={32} />
|
|
<span>No active jobs</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|