This commit is contained in:
2025-06-02 14:40:40 -06:00
parent 608e47b2fa
commit 1fd4f8edab

View File

@ -116,6 +116,175 @@ const TerminalComponent: React.FC<TerminalProps> = ({
xtermRef.current.write(`\r\nls: cannot access '${currentPath.current}': Not a directory`);
}
break;
case 'pwd':
xtermRef.current.write('\r\n' + currentPath.current);
break;
case 'find':
if (args.length === 0) {
xtermRef.current.write('\r\nfind: missing path\r\nUsage: find <path> [-name <pattern>]');
break;
}
const findPath = args[0];
const nameIndex = args.indexOf('-name');
const namePattern = nameIndex !== -1 && args[nameIndex + 1] ? args[nameIndex + 1] : '';
const findResults: string[] = [];
// Simple recursive search through filesystem
const searchInPath = (searchPath: string, depth: number = 0) => {
if (depth > 10) return; // Prevent infinite recursion
const pathContent = fileSystem[searchPath];
if (Array.isArray(pathContent)) {
pathContent.forEach(item => {
const itemPath = searchPath === '/' ? '/' + item : searchPath + '/' + item;
// Check if item matches pattern (if specified)
if (!namePattern || item.includes(namePattern.replace(/\*/g, ''))) {
findResults.push(itemPath);
}
// Recursively search subdirectories
if (Array.isArray(fileSystem[itemPath])) {
searchInPath(itemPath, depth + 1);
}
});
}
};
if (fileSystem[findPath] !== undefined) {
if (!namePattern) {
findResults.push(findPath);
}
searchInPath(findPath);
if (findResults.length > 0) {
xtermRef.current.write('\r\n' + findResults.join('\r\n'));
} else {
xtermRef.current.write(`\r\nfind: no files found`);
}
} else {
xtermRef.current.write(`\r\nfind: ${findPath}: No such file or directory`);
}
break;
case 'head':
const headLines = args[0] === '-n' && args[1] ? parseInt(args[1]) : 10;
const headFile = args[0] === '-n' ? args[2] : args[0];
if (!headFile) {
xtermRef.current.write('\r\nhead: missing file operand');
break;
}
let headFilePath: string;
if (headFile.startsWith('/')) {
headFilePath = headFile;
} else {
headFilePath = (currentPath.current === '/' ? '' : currentPath.current) + '/' + headFile;
}
const headContent = fileSystem[headFilePath];
if (headContent === undefined) {
xtermRef.current.write(`\r\nhead: ${headFile}: No such file or directory`);
} else if (Array.isArray(headContent)) {
xtermRef.current.write(`\r\nhead: ${headFile}: Is a directory`);
} else {
const lines = headContent.split('\n');
const displayLines = lines.slice(0, headLines);
xtermRef.current.write('\r\n' + displayLines.join('\r\n'));
}
break;
case 'tail':
const tailLines = args[0] === '-n' && args[1] ? parseInt(args[1]) : 10;
const tailFile = args[0] === '-n' ? args[2] : args[0];
if (!tailFile) {
xtermRef.current.write('\r\ntail: missing file operand');
break;
}
let tailFilePath: string;
if (tailFile.startsWith('/')) {
tailFilePath = tailFile;
} else {
tailFilePath = (currentPath.current === '/' ? '' : currentPath.current) + '/' + tailFile;
}
const tailContent = fileSystem[tailFilePath];
if (tailContent === undefined) {
xtermRef.current.write(`\r\ntail: ${tailFile}: No such file or directory`);
} else if (Array.isArray(tailContent)) {
xtermRef.current.write(`\r\ntail: ${tailFile}: Is a directory`);
} else {
const lines = tailContent.split('\n');
const displayLines = lines.slice(-tailLines);
xtermRef.current.write('\r\n' + displayLines.join('\r\n'));
}
break;
case 'sl':
// Easter egg: Steam Locomotive animation
xtermRef.current.write('\r\n');
const trainFrames = [
" ==== ________ ___________",
" _D _| |_______/ \\__I_I_____===__|_________|",
" |(_)--- | H\\________/ | | =|___ ___|",
" / | | H | | | | ||_| |_||",
" | | | H |__--------------------| [___] |",
" | ________|___H__/__|_____/[][]~\\_______| |",
" |/ | |-----------I_____I [][] [] D |=======|__",
"__/ =| o |=-~~\\ /~~\\ /~~\\ /~~\\ ____Y___________|__|",
" |/-=|___|= || || || |_____/~\\___/",
" \\_/ \\O=====O=====O=====O_/ \\_/"
];
// Calculate train width (length of longest line)
const trainWidth = Math.max(...trainFrames.map(line => line.length));
// Start position: completely off-screen to the right
let position = xtermRef.current.cols + 10;
const animateTrain = () => {
// Stop when train is completely off-screen to the left
if (position < -trainWidth - 5) {
xtermRef.current?.write('\x1b[2J\x1b[H'); // Clear screen one final time
prompt();
return;
}
// Clear screen and position cursor at top
xtermRef.current?.write('\x1b[2J\x1b[H');
// Draw each line of the train with proper spacing
trainFrames.forEach((line) => {
if (position >= 0) {
// Train is partially or fully on screen from the right
const spaces = ' '.repeat(position);
const visiblePart = line.substring(0, xtermRef.current!.cols - position);
xtermRef.current?.write(spaces + visiblePart + '\r\n');
} else if (position + line.length > 0) {
// Train is partially off-screen to the left
const startIdx = Math.abs(position);
const visiblePart = line.substring(startIdx, startIdx + xtermRef.current!.cols);
xtermRef.current?.write(visiblePart + '\r\n');
} else {
// Train is completely off-screen to the left, just write empty line
xtermRef.current?.write('\r\n');
}
});
position -= 3; // Move train left by 3 characters each frame
setTimeout(animateTrain, 120); // Slightly slower for smoother animation
};
animateTrain();
return; // Don't call prompt() immediately
case 'cd':
const targetDir = args[0];
if (!targetDir) {
@ -188,8 +357,12 @@ const TerminalComponent: React.FC<TerminalProps> = ({
const helpText =
"Available Commands:\r\n" +
" ls List directory contents\r\n" +
" pwd Print working directory\r\n" +
" cd <directory> Change directory\r\n" +
" cat <file> Display file contents\r\n" +
" find <path> [-name <pattern>] Search for files\r\n" +
" head [-n <num>] <file> Show first lines of file\r\n" +
" tail [-n <num>] <file> Show last lines of file\r\n" +
" clear Clear the screen\r\n" +
" help Display this help message\r\n\r\n" +
"Tips:\r\n" +