+ );
+};
+
+export default ActivityBar;
\ No newline at end of file
diff --git a/Frontend/src/components/EditorArea.jsx b/Frontend/src/components/EditorArea.jsx
new file mode 100644
index 0000000..896f736
--- /dev/null
+++ b/Frontend/src/components/EditorArea.jsx
@@ -0,0 +1,890 @@
+import React from "react";
+import { useState, useRef, useEffect } from "react";
+import Editor from "@monaco-editor/react";
+import {
+ X, Plus, Save, FileCode, FileText, Folder, ChevronDown, ChevronRight,
+ File, FilePlus, FolderPlus, Trash2, Edit, MoreHorizontal, Play,
+ Terminal, Loader
+} from "lucide-react";
+import Sidebar from "./Sidebar";
+import Panel from "./Panel"; // Import Panel component
+
+const EditorArea = ({
+ sidebarVisible = true,
+ activeView = "explorer",
+ panelVisible,
+ setPanelVisible
+}) => {
+ // Store files with their content in state - start with just README.md
+ const [files, setFiles] = useState([
+ { id: "README.md", language: "markdown", content: getDefaultCode("README.md") },
+ ]);
+
+ const [activeTab, setActiveTab] = useState(files[0]?.id || "");
+ const [isNewFileModalOpen, setIsNewFileModalOpen] = useState(false);
+ const [newFileName, setNewFileName] = useState("");
+ const [newFileType, setNewFileType] = useState("javascript");
+ const [unsavedChanges, setUnsavedChanges] = useState({});
+
+ // Sidebar state - now receives visibility from props
+ const [sidebarWidth, setSidebarWidth] = useState(250);
+ const [expandedFolders, setExpandedFolders] = useState({});
+ const [fileStructure, setFileStructure] = useState({
+ 'src': {
+ type: 'folder',
+ children: {}
+ },
+ 'README.md': {
+ type: 'file',
+ language: 'markdown',
+ id: 'README.md'
+ }
+ });
+
+ // Context menu state
+ const [showContextMenu, setShowContextMenu] = useState(false);
+ const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 });
+ const [contextMenuTarget, setContextMenuTarget] = useState(null);
+
+ const editorRef = useRef(null);
+ const newFileInputRef = useRef(null);
+ const renameInputRef = useRef(null);
+ const [isRenaming, setIsRenaming] = useState(false);
+ const [renamePath, setRenamePath] = useState('');
+ const [renameValue, setRenameValue] = useState('');
+
+ // Replace terminal states with panel states
+ const [isRunning, setIsRunning] = useState(false);
+ const [showPanel, setShowPanel] = useState(false);
+ const [panelHeight, setPanelHeight] = useState(200);
+ const [terminalOutput, setTerminalOutput] = useState([]);
+ const [activeRunningFile, setActiveRunningFile] = useState(null);
+
+ // Focus the input when new file modal opens
+ useEffect(() => {
+ if (isNewFileModalOpen && newFileInputRef.current) {
+ newFileInputRef.current.focus();
+ }
+ }, [isNewFileModalOpen]);
+
+ // Focus rename input when renaming
+ useEffect(() => {
+ if (isRenaming && renameInputRef.current) {
+ renameInputRef.current.focus();
+ }
+ }, [isRenaming]);
+
+ // Load files and file structure from localStorage on component mount
+ useEffect(() => {
+ const savedFiles = localStorage.getItem("vscode-clone-files");
+ const savedFileStructure = localStorage.getItem("vscode-clone-structure");
+
+ if (savedFiles) {
+ try {
+ const parsedFiles = JSON.parse(savedFiles);
+ setFiles(parsedFiles);
+ if (parsedFiles.length > 0) {
+ setActiveTab(parsedFiles[0].id);
+ }
+ } catch (error) {
+ console.error("Failed to load saved files:", error);
+ }
+ }
+
+ if (savedFileStructure) {
+ try {
+ const parsedStructure = JSON.parse(savedFileStructure);
+ setFileStructure(parsedStructure);
+ } catch (error) {
+ console.error("Failed to load file structure:", error);
+ }
+ }
+ }, []);
+
+ // Save files and file structure to localStorage whenever they change
+ useEffect(() => {
+ localStorage.setItem("vscode-clone-files", JSON.stringify(files));
+ }, [files]);
+
+ useEffect(() => {
+ localStorage.setItem("vscode-clone-structure", JSON.stringify(fileStructure));
+ }, [fileStructure]);
+
+ // Add this effect to handle editor resize when sidebar changes
+ useEffect(() => {
+ // Force editor to readjust layout when sidebar visibility changes
+ if (editorRef.current) {
+ setTimeout(() => {
+ editorRef.current.layout();
+ }, 300); // Small delay to allow transition to complete
+ }
+ }, [sidebarVisible]);
+
+ // Add this effect to sync the panel state with parent component
+ useEffect(() => {
+ if (panelVisible !== undefined) {
+ setShowPanel(panelVisible);
+ }
+ }, [panelVisible]);
+
+ const handleEditorDidMount = (editor) => {
+ editorRef.current = editor;
+ };
+
+ const handleEditorChange = (value) => {
+ // Mark the current file as having unsaved changes
+ setUnsavedChanges(prev => ({
+ ...prev,
+ [activeTab]: true
+ }));
+
+ // Update the file content in the files array
+ setFiles(files.map(file =>
+ file.id === activeTab ? { ...file, content: value } : file
+ ));
+ };
+
+ const handleCloseTab = (e, fileId) => {
+ e.stopPropagation();
+
+ if (unsavedChanges[fileId]) {
+ if (!confirm(`You have unsaved changes in ${fileId}. Close anyway?`)) {
+ return;
+ }
+ }
+
+ // Remove the file from the files array
+ const newFiles = files.filter(file => file.id !== fileId);
+ setFiles(newFiles);
+
+ // Update unsavedChanges
+ const newUnsavedChanges = { ...unsavedChanges };
+ delete newUnsavedChanges[fileId];
+ setUnsavedChanges(newUnsavedChanges);
+
+ // If the active tab is closed, set a new active tab
+ if (activeTab === fileId && newFiles.length > 0) {
+ setActiveTab(newFiles[0].id);
+ }
+ };
+
+ const handleCreateNewFile = (e, path = '') => {
+ e?.preventDefault();
+
+ if (!newFileName) return;
+
+ const filePath = path ? `${path}/${newFileName}` : newFileName;
+
+ // Check if file already exists
+ if (files.some(file => file.id === filePath)) {
+ alert(`A file named "${filePath}" already exists.`);
+ return;
+ }
+
+ // Determine language based on file extension
+ let language = newFileType;
+ const extension = newFileName.split('.').pop().toLowerCase();
+ if (['jsx', 'js', 'ts', 'tsx'].includes(extension)) {
+ language = 'javascript';
+ } else if (['css', 'scss', 'less'].includes(extension)) {
+ language = 'css';
+ } else if (['html', 'htm'].includes(extension)) {
+ language = 'html';
+ } else if (['json'].includes(extension)) {
+ language = 'json';
+ } else if (['md', 'markdown'].includes(extension)) {
+ language = 'markdown';
+ }
+
+ // Create new file
+ const newFile = {
+ id: filePath,
+ language,
+ content: ''
+ };
+
+ setFiles([...files, newFile]);
+ setActiveTab(filePath);
+
+ // Update file structure
+ updateFileStructure(filePath, 'file', language);
+
+ setNewFileName('');
+ setIsNewFileModalOpen(false);
+ };
+
+ const updateFileStructure = (path, type, language = null) => {
+ const parts = path.split('/');
+ const fileName = parts.pop();
+ let current = fileStructure;
+
+ // Navigate to the correct folder
+ if (parts.length > 0) {
+ for (const part of parts) {
+ if (!current[part]) {
+ current[part] = { type: 'folder', children: {} };
+ }
+ current = current[part].children;
+ }
+ }
+
+ // Add the new item to the structure
+ if (type === 'file') {
+ current[fileName] = { type: 'file', language, id: path };
+ } else if (type === 'folder') {
+ current[fileName] = { type: 'folder', children: {} };
+ }
+
+ // Update the state with the new structure
+ setFileStructure({...fileStructure});
+ };
+
+ const createNewFolder = (path = '') => {
+ const folderName = prompt("Enter folder name:");
+ if (!folderName) return;
+
+ const folderPath = path ? `${path}/${folderName}` : folderName;
+ updateFileStructure(folderPath, 'folder');
+
+ // If the folder is inside another folder, expand the parent
+ if (path) {
+ setExpandedFolders({
+ ...expandedFolders,
+ [path]: true
+ });
+ }
+ };
+
+ const handleSaveFile = () => {
+ // Mark current file as saved
+ setUnsavedChanges(prev => ({
+ ...prev,
+ [activeTab]: false
+ }));
+
+ // In a real app, you would save to the server here
+ console.log(`File ${activeTab} saved!`);
+ };
+
+ const toggleFolder = (folderPath) => {
+ setExpandedFolders({
+ ...expandedFolders,
+ [folderPath]: !expandedFolders[folderPath]
+ });
+ };
+
+ const openFile = (fileId) => {
+ // Check if file exists in files array
+ const fileExists = files.some(file => file.id === fileId);
+
+ if (!fileExists) {
+ // Determine language from file structure
+ let language = 'text';
+ const parts = fileId.split('/');
+ const fileName = parts.pop();
+ const extension = fileName.split('.').pop().toLowerCase();
+
+ if (['jsx', 'js', 'ts', 'tsx'].includes(extension)) {
+ language = 'javascript';
+ } else if (['css', 'scss', 'less'].includes(extension)) {
+ language = 'css';
+ } else if (['html', 'htm'].includes(extension)) {
+ language = 'html';
+ } else if (['json'].includes(extension)) {
+ language = 'json';
+ } else if (['md', 'markdown'].includes(extension)) {
+ language = 'markdown';
+ }
+
+ // Create new file entry
+ const newFile = {
+ id: fileId,
+ language,
+ content: ''
+ };
+
+ setFiles([...files, newFile]);
+ }
+
+ setActiveTab(fileId);
+ };
+
+ const handleContextMenu = (e, path, type) => {
+ e.preventDefault();
+ setContextMenuPosition({ x: e.clientX, y: e.clientY });
+ setContextMenuTarget({ path, type });
+ setShowContextMenu(true);
+ };
+
+ const closeContextMenu = () => {
+ setShowContextMenu(false);
+ setContextMenuTarget(null);
+ };
+
+ const deleteItem = (path, type) => {
+ const confirmDelete = confirm(`Are you sure you want to delete ${path}?`);
+ if (!confirmDelete) return;
+
+ if (type === 'file') {
+ // Remove from files array
+ setFiles(files.filter(file => file.id !== path));
+
+ // If it was active, set a new active tab
+ if (activeTab === path) {
+ const newActiveTab = files.find(file => file.id !== path)?.id || '';
+ setActiveTab(newActiveTab);
+ }
+
+ // Remove from unsavedChanges
+ const newUnsavedChanges = { ...unsavedChanges };
+ delete newUnsavedChanges[path];
+ setUnsavedChanges(newUnsavedChanges);
+ }
+
+ // Remove from file structure
+ const parts = path.split('/');
+ const itemName = parts.pop();
+ let current = fileStructure;
+ let parent = null;
+
+ // Navigate to the correct folder
+ if (parts.length > 0) {
+ for (const part of parts) {
+ parent = current;
+ current = current[part].children;
+ }
+
+ // Delete the item
+ delete current[itemName];
+ } else {
+ // Delete top-level item
+ delete fileStructure[itemName];
+ }
+
+ // Update the state
+ setFileStructure({...fileStructure});
+ };
+
+ const startRenaming = (path, type) => {
+ setRenamePath(path);
+
+ const parts = path.split('/');
+ const currentName = parts.pop();
+ setRenameValue(currentName);
+
+ setIsRenaming(true);
+ };
+
+ const handleRename = (e) => {
+ e.preventDefault();
+
+ if (!renameValue.trim()) return;
+
+ const parts = renamePath.split('/');
+ const oldName = parts.pop();
+ const parentPath = parts.join('/');
+ const newPath = parentPath ? `${parentPath}/${renameValue}` : renameValue;
+
+ // Check if this would overwrite an existing file or folder
+ const parts2 = newPath.split('/');
+ const newName = parts2.pop();
+ let current = fileStructure;
+
+ // Navigate to parent folder
+ for (let i = 0; i < parts2.length; i++) {
+ current = current[parts2[i]].children;
+ }
+
+ if (current[newName] && renamePath !== newPath) {
+ alert(`An item named "${newName}" already exists at this location.`);
+ return;
+ }
+
+ // Get the object data
+ const pathParts = renamePath.split('/');
+ let curr = fileStructure;
+ for (let i = 0; i < pathParts.length - 1; i++) {
+ curr = curr[pathParts[i]].children;
+ }
+ const item = curr[pathParts[pathParts.length - 1]];
+
+ // Delete from old location
+ delete curr[pathParts[pathParts.length - 1]];
+
+ // Add to new location
+ const newParts = newPath.split('/');
+ curr = fileStructure;
+ for (let i = 0; i < newParts.length - 1; i++) {
+ curr = curr[newParts[i]].children;
+ }
+ curr[newParts[newParts.length - 1]] = item;
+
+ // If it's a file, update the files array
+ if (item.type === 'file') {
+ const fileIndex = files.findIndex(file => file.id === renamePath);
+ if (fileIndex !== -1) {
+ const updatedFiles = [...files];
+ updatedFiles[fileIndex] = {
+ ...updatedFiles[fileIndex],
+ id: newPath
+ };
+ setFiles(updatedFiles);
+
+ // Update active tab if necessary
+ if (activeTab === renamePath) {
+ setActiveTab(newPath);
+ }
+
+ // Update unsavedChanges
+ if (unsavedChanges[renamePath]) {
+ const newUnsavedChanges = { ...unsavedChanges };
+ newUnsavedChanges[newPath] = newUnsavedChanges[renamePath];
+ delete newUnsavedChanges[renamePath];
+ setUnsavedChanges(newUnsavedChanges);
+ }
+ }
+ }
+
+ setFileStructure({...fileStructure});
+ setIsRenaming(false);
+ };
+
+ const cancelRename = () => {
+ setIsRenaming(false);
+ setRenamePath('');
+ setRenameValue('');
+ };
+
+ const getFileIcon = (fileName) => {
+ const extension = fileName.split('.').pop().toLowerCase();
+ if (['jsx', 'js', 'ts', 'tsx'].includes(extension)) {
+ return ;
+ } else if (['css', 'scss', 'less'].includes(extension)) {
+ return ;
+ } else if (['html', 'htm'].includes(extension)) {
+ return ;
+ } else if (['md', 'markdown'].includes(extension)) {
+ return ;
+ }
+ return ;
+ };
+
+ function getDefaultCode(tabId) {
+ switch (tabId) {
+ case "README.md":
+ return `# VS Code Clone Project
+
+## Overview
+This is a simple VS Code clone built with React and Monaco Editor.
+
+## Features
+- File tree navigation
+- Tab management
+- Code editing with Monaco Editor
+- Syntax highlighting
+
+## Getting Started
+1. Create a new file using the + button in the sidebar
+2. Edit your code in the editor
+3. Save changes using the save button
+
+Happy coding!`;
+ default:
+ return "";
+ }
+ }
+
+ const activeFile = files.find(file => file.id === activeTab);
+
+ // Calculate editor area style based on sidebar visibility
+ const editorAreaStyle = {
+ marginLeft: sidebarVisible ? `${sidebarWidth}px` : '0px',
+ width: `calc(100% - ${sidebarVisible ? sidebarWidth : 0}px)`
+ };
+
+ // Update the run code function to work with backend
+ const handleRunCode = async () => {
+ if (!activeFile) return;
+
+ // Show the panel
+ setShowPanel(true);
+ if (setPanelVisible) {
+ setPanelVisible(true);
+ }
+
+ // Set running state
+ setIsRunning(true);
+ setActiveRunningFile(activeFile.id);
+
+ // Clear previous output and add new command
+ const fileExtension = activeFile.id.split('.').pop().toLowerCase();
+ const language = getLanguageFromExtension(fileExtension);
+
+ const newOutput = [
+ { type: 'command', content: `$ run ${activeFile.id}` }
+ ];
+ setTerminalOutput(newOutput);
+
+ try {
+ // Step 1: Submit code to backend
+ const submitResponse = await fetch('http://localhost:8080/submit', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ language: language,
+ code: activeFile.content
+ }),
+ });
+
+ if (!submitResponse.ok) {
+ throw new Error(`Server error: ${submitResponse.status}`);
+ }
+
+ const { id } = await submitResponse.json();
+ setTerminalOutput(prev => [...prev, { type: 'output', content: `Job submitted with ID: ${id}` }]);
+
+ // Step 2: Poll for status until completed or failed
+ let status = 'pending';
+ while (status !== 'completed' && status !== 'failed') {
+ // Add a small delay between polls
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ const statusResponse = await fetch(`http://localhost:8080/status?id=${id}`);
+ if (!statusResponse.ok) {
+ throw new Error(`Status check failed: ${statusResponse.status}`);
+ }
+
+ const statusData = await statusResponse.json();
+ status = statusData.status;
+
+ // Update terminal with status (for any status type)
+ setTerminalOutput(prev => {
+ // Update the last status message or add a new one
+ const hasStatus = prev.some(line => line.content.includes('Status:'));
+ if (hasStatus) {
+ return prev.map(line =>
+ line.content.includes('Status:')
+ ? { ...line, content: `Status: ${status}` }
+ : line
+ );
+ } else {
+ return [...prev, { type: 'output', content: `Status: ${status}` }];
+ }
+ });
+ }
+
+ // Get the result for both completed and failed status
+ const resultResponse = await fetch(`http://localhost:8080/result?id=${id}`);
+ if (!resultResponse.ok) {
+ throw new Error(`Result fetch failed: ${resultResponse.status}`);
+ }
+
+ const { output } = await resultResponse.json();
+
+ // Format and display output
+ const outputLines = output.split('\n').map(line => ({
+ type: status === 'failed' ? 'warning' : 'output',
+ content: line
+ }));
+
+ setTerminalOutput(prev => [
+ ...prev,
+ {
+ type: status === 'failed' ? 'warning' : 'output',
+ content: status === 'failed'
+ ? '------- EXECUTION FAILED -------'
+ : '------- EXECUTION RESULT -------'
+ },
+ ...outputLines
+ ]);
+
+ if (status === 'failed') {
+ console.error('Code execution failed:', output);
+ }
+
+ } catch (error) {
+ setTerminalOutput(prev => [...prev, { type: 'warning', content: `Error: ${error.message}` }]);
+ } finally {
+ // Set running state to false
+ setIsRunning(false);
+ }
+ };
+
+ // Helper function to convert file extension to language identifier for API
+ const getLanguageFromExtension = (extension) => {
+ const languageMap = {
+ 'java': 'java',
+ 'c': 'c',
+ 'cpp': 'cpp',
+ 'py': 'python',
+ 'js': 'javascript',
+ 'jsx': 'javascript',
+ 'ts': 'typescript',
+ 'tsx': 'typescript'
+ };
+
+ return languageMap[extension] || extension;
+ };
+
+ // Update this function to also update parent state
+ const togglePanel = () => {
+ const newState = !showPanel;
+ setShowPanel(newState);
+ if (setPanelVisible) {
+ setPanelVisible(newState);
+ }
+ };
+
+ return (
+
+ {sidebarVisible && (
+
+ )}
+
+
+
+
+ {files.map((file) => {
+ // Extract just the filename without path for display
+ const displayName = file.id.includes('/') ?
+ file.id.split('/').pop() :
+ file.id;
+
+ return (
+
setActiveTab(file.id)}
+ title={file.id} // Show full path on hover
+ >
+
+ {getFileIcon(file.id)}
+ {displayName} {/* Show just filename, not full path */}
+ {unsavedChanges[file.id] && ' •'}
+
+
+
+ );
+ })}
+
+
+
+ {/* Run controls */}
+
+ {activeFile && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+ {activeFile ? (
+
+ ) : (
+
+
No file open. Create a new file or select a file to start editing.
+
+ )}
+
+
+ {/* Use Panel component instead of internal terminal */}
+ {showPanel && (
+ <>
+