`
},
'style.css': {
type: 'file',
content: `body {
font-family: sans-serif;
background-color: #f0f0f0;
color: #333;
text-align: center;
padding: 2rem;
}
h1 {
color: #2c3e50;
}
#demo-btn {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin-top: 20px;
}
#demo-btn:hover {
background-color: #2980b9;
}`
},
'script.js': {
type: 'file',
content: `document.getElementById('demo-btn').addEventListener('click', function() {
alert('Button clicked! This is working!');
});`
},
'src': {
type: 'folder',
children: {
'main.js': {
type: 'file',
content: `// Main application logic`
},
'components': {
type: 'folder',
children: {
'button.js': {
type: 'file',
content: `// A custom button component`
}
}
}
}
}
};
// DOM elements
const sidebar = document.getElementById('sidebar');
const fileTree = document.getElementById('file-tree');
const tabBar = document.getElementById('tab-bar');
const editor = document.getElementById('editor');
const lineNumbers = document.getElementById('line-numbers');
const addTabBtn = document.getElementById('add-tab-btn');
const newFileBtn = document.getElementById('new-file-btn');
const newFolderBtn = document.getElementById('new-folder-btn');
const managerTab = document.getElementById('manager-tab');
const contextMenu = document.getElementById('context-menu');
const importFileInput = document.getElementById('import-file-input');
const runBtn = document.getElementById('run-btn');
// State variables
let activeFile = null;
let fileCounter = 1;
// Function to toggle the sidebar's visibility on small screens
function toggleSidebar() {
// Only toggle on screens smaller than large breakpoint (lg)
if (window.innerWidth < 1024) {
sidebar.classList.toggle('-translate-x-full');
}
}
// Function to render the file tree recursively
function renderFileTree(container, files, path = '') {
container.innerHTML = '';
for (const name in files) {
const item = files[name];
const fullPath = path + name;
const element = document.createElement('div');
element.classList.add('pl-4', 'py-1', 'group', 'flex', 'items-center', 'justify-between', 'text-gray-300', 'rounded', 'transition-colors', 'duration-200');
// Content for the file/folder, including the icon and name
const contentDiv = document.createElement('div');
contentDiv.classList.add('flex', 'items-center', 'space-x-2', 'flex-1', 'cursor-pointer');
if (item.type === 'folder') {
// Folder icon and name
contentDiv.innerHTML = `${name}`;
contentDiv.onclick = (e) => {
e.stopPropagation();
// Toggle folder visibility
const childrenContainer = element.nextElementSibling;
if (childrenContainer) {
childrenContainer.classList.toggle('hidden');
contentDiv.querySelector('i').classList.toggle('fa-caret-right');
contentDiv.querySelector('i').classList.toggle('fa-caret-down');
}
};
element.appendChild(contentDiv);
// Context menu icon for the folder
const menuIcon = document.createElement('i');
menuIcon.classList.add('fas', 'fa-ellipsis-v', 'text-gray-500', 'cursor-pointer', 'p-1', 'opacity-0', 'group-hover:opacity-100', 'transition-opacity');
menuIcon.onclick = (e) => showContextMenu(e, fullPath, 'folder');
element.appendChild(menuIcon);
container.appendChild(element);
const childrenContainer = document.createElement('div');
childrenContainer.classList.add('pl-4', 'hidden');
renderFileTree(childrenContainer, item.children, fullPath + '/');
container.appendChild(childrenContainer);
} else {
// File icon and name
contentDiv.innerHTML = `${name}`;
contentDiv.onclick = (e) => {
e.stopPropagation();
openFile(fullPath, name);
// Hide sidebar on mobile after clicking a file
if (window.innerWidth < 1024) {
toggleSidebar();
}
};
element.appendChild(contentDiv);
// Context menu icon for the file
const menuIcon = document.createElement('i');
menuIcon.classList.add('fas', 'fa-ellipsis-v', 'text-gray-500', 'cursor-pointer', 'p-1', 'opacity-0', 'group-hover:opacity-100', 'transition-opacity');
menuIcon.onclick = (e) => showContextMenu(e, fullPath, 'file');
element.appendChild(menuIcon);
container.appendChild(element);
}
}
}
// Function to show the context menu
function showContextMenu(event, path, type) {
event.stopPropagation();
hideContextMenu(); // Hide any existing menu
// Set the menu position
contextMenu.style.left = `${event.clientX}px`;
contextMenu.style.top = `${event.clientY}px`;
// Determine menu items based on type
const menuItems = [];
if (type === 'file') {
menuItems.push(
{ name: 'Rename', icon: 'fas fa-pen-to-square', action: () => renameItem(path) },
{ name: 'Copy', icon: 'fas fa-copy', action: () => copyFile(path) },
{ name: 'Delete', icon: 'fas fa-trash-alt', action: () => deleteItem(path) }
);
} else if (type === 'folder') {
menuItems.push(
{ name: 'Rename', icon: 'fas fa-pen-to-square', action: () => renameItem(path) },
{ name: 'New File', icon: 'fas fa-file-alt', action: () => createNewItemInFolder(path, 'file') },
{ name: 'New Folder', icon: 'fas fa-folder-plus', action: () => createNewItemInFolder(path, 'folder') },
{ name: 'Import', icon: 'fas fa-upload', action: () => importFile(path) },
{ name: 'Delete', icon: 'fas fa-trash-alt', action: () => deleteItem(path) }
);
}
// Populate the menu
contextMenu.innerHTML = '';
menuItems.forEach(item => {
const menuItem = document.createElement('div');
menuItem.classList.add('context-menu-item');
menuItem.innerHTML = `${item.name}`;
menuItem.onclick = (e) => {
e.stopPropagation();
item.action();
hideContextMenu();
};
contextMenu.appendChild(menuItem);
});
contextMenu.classList.remove('hidden');
}
// Function to hide the context menu
function hideContextMenu() {
contextMenu.classList.add('hidden');
}
// Function to open a file and update the editor
function openFile(path, name) {
// Find the file content in the file system object
const pathParts = path.split('/');
let current = fileSystem;
for (const part of pathParts) {
if (current[part] && current[part].type === 'folder') {
current = current[part].children;
} else if (current[part] && current[part].type === 'file') {
current = current[part];
}
}
const content = current.content;
if (content !== undefined) {
editor.value = content;
activeFile = path;
updateTabs(path, name);
updateLineNumbers(); // Call to update line numbers
editor.focus();
}
}
// Function to update the tabs at the top
function updateTabs(path, name) {
const existingTab = document.getElementById(`tab-${path}`);
// If tab already exists, just make it active
if (existingTab) {
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('bg-gray-600', 'border-gray-500'));
existingTab.classList.add('bg-gray-600', 'border-gray-500');
return;
}
// Create a new tab element
const newTab = document.createElement('div');
newTab.id = `tab-${path}`;
newTab.classList.add('tab', 'px-4', 'py-2', 'flex', 'items-center', 'space-x-2', 'cursor-pointer', 'bg-gray-700', 'border-r', 'border-gray-700', 'hover:bg-gray-600', 'transition-colors', 'duration-200');
newTab.innerHTML = `${name}
`;
newTab.onclick = () => openFile(path, name);
// Remove active state from other tabs
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('bg-gray-600', 'border-gray-500'));
newTab.classList.add('bg-gray-600', 'border-gray-500');
// Add new tab to the bar
tabBar.appendChild(newTab);
}
// Function to close a tab
function closeTab(event, path) {
event.stopPropagation();
const tabToRemove = document.getElementById(`tab-${path}`);
tabBar.removeChild(tabToRemove);
// If the closed tab was the active one, open the last remaining tab
if (activeFile === path) {
const remainingTabs = tabBar.querySelectorAll('.tab');
if (remainingTabs.length > 0) {
const lastTab = remainingTabs[remainingTabs.length - 1];
const newPath = lastTab.id.replace('tab-', '');
const newName = lastTab.querySelector('span').textContent;
openFile(newPath, newName);
} else {
editor.value = '';
activeFile = null;
updateLineNumbers();
}
}
}
// Function to save the editor content to the file system
function saveContent() {
if (activeFile) {
const pathParts = activeFile.split('/');
let current = fileSystem;
for (let i = 0; i < pathParts.length - 1; i++) {
current = current[pathParts[i]].children;
}
const fileName = pathParts[pathParts.length - 1];
current[fileName].content = editor.value;
}
}
// Function to get the parent folder object and item name from a path
function getParentAndItem(path) {
const pathParts = path.split('/').filter(p => p);
const itemName = pathParts.pop();
let parent = fileSystem;
for (const part of pathParts) {
parent = parent[part].children;
}
return { parent, itemName };
}
// --- Context Menu Action Functions ---
// Handles renaming a file or folder
function renameItem(path) {
const { parent, itemName } = getParentAndItem(path);
const newName = prompt(`Rename '${itemName}' to:`, itemName);
if (newName && newName !== itemName) {
if (parent[newName]) {
alert('An item with this name already exists!');
return;
}
parent[newName] = parent[itemName];
delete parent[itemName];
// If the renamed item was active, update the path
if (activeFile === path) {
activeFile = path.replace(itemName, newName);
}
// Update tabs if a tab for this file exists
const existingTab = document.getElementById(`tab-${path}`);
if (existingTab) {
existingTab.id = `tab-${path.replace(itemName, newName)}`;
existingTab.querySelector('span').textContent = newName;
existingTab.onclick = () => openFile(activeFile, newName);
}
renderFileTree(fileTree, fileSystem);
}
}
// Handles deleting a file or folder
function deleteItem(path) {
const { parent, itemName } = getParentAndItem(path);
const isConfirmed = confirm(`Are you sure you want to delete '${itemName}'? This cannot be undone.`);
if (isConfirmed) {
delete parent[itemName];
if (activeFile === path) {
activeFile = null;
editor.value = '';
}
closeTab(new Event('click'), path);
renderFileTree(fileTree, fileSystem);
}
}
// Handles creating a new file or folder in a folder
function createNewItemInFolder(path, type) {
const { parent, itemName } = getParentAndItem(path);
const newName = prompt(`Enter new ${type} name:`);
if (newName) {
const currentFolder = parent[itemName].children;
if (currentFolder[newName]) {
alert('An item with this name already exists!');
return;
}
if (type === 'file') {
currentFolder[newName] = { type: 'file', content: '' };
openFile(`${path}/${newName}`, newName);
} else if (type === 'folder') {
currentFolder[newName] = { type: 'folder', children: {} };
}
renderFileTree(fileTree, fileSystem);
}
}
// Handles copying a file
function copyFile(path) {
const { parent, itemName } = getParentAndItem(path);
const fileToCopy = parent[itemName];
const newName = prompt(`Enter new file name:`, `copy_of_${itemName}`);
if (newName) {
if (parent[newName]) {
alert('An item with this name already exists!');
return;
}
parent[newName] = { ...fileToCopy }; // Shallow copy
renderFileTree(fileTree, fileSystem);
}
}
// Handles importing a file from the user's computer
function importFile(path) {
importFileInput.onclick = null; // Clear previous listeners
importFileInput.onchange = (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const content = e.target.result;
const { parent, itemName } = getParentAndItem(path);
const currentFolder = parent[itemName].children;
const newFileName = file.name;
if (currentFolder[newFileName]) {
alert('An item with this name already exists!');
return;
}
currentFolder[newFileName] = { type: 'file', content: content };
renderFileTree(fileTree, fileSystem);
openFile(`${path}/${newFileName}`, newFileName);
};
reader.readAsText(file);
}
};
importFileInput.click(); // Trigger the file selection dialog
}
// --- Line Number Logic ---
function updateLineNumbers() {
const numberOfLines = editor.value.split('\n').length;
lineNumbers.innerHTML = ''; // Clear existing numbers
let lineNumbersHTML = '';
for (let i = 1; i <= numberOfLines; i++) {
lineNumbersHTML += `
${i}
`;
}
lineNumbers.innerHTML = lineNumbersHTML;
}
// Function to run the code
function runCode() {
// Save all content before running
saveContent();
// Find the HTML file (prefer index.html if it exists)
let htmlFile = fileSystem['index.html'];
if (!htmlFile) {
// Look for any HTML file in the root
for (const fileName in fileSystem) {
if (fileSystem[fileName].type === 'file' && fileName.endsWith('.html')) {
htmlFile = fileSystem[fileName];
break;
}
}
}
if (!htmlFile) {
alert('No HTML file found to run. Please create an index.html file first.');
return;
}
// Create a new window with the HTML content
const previewWindow = window.open('', '_blank');
// Get all CSS and JS files that might be linked
let cssContent = '';
let jsContent = '';
// Collect all CSS files
for (const fileName in fileSystem) {
if (fileSystem[fileName].type === 'file' && fileName.endsWith('.css')) {
cssContent += `\n`;
}
}
// Collect all JS files
for (const fileName in fileSystem) {
if (fileSystem[fileName].type === 'file' && fileName.endsWith('.js')) {
jsContent += `\n`;
}
}
// Combine all the content
let fullHtml = htmlFile.content;
// Inject CSS and JS if they're not already linked in the HTML
if (!fullHtml.includes('');
if (headEnd !== -1) {
fullHtml = fullHtml.slice(0, headEnd) + cssContent + fullHtml.slice(headEnd);
} else {
fullHtml = `${cssContent}` + fullHtml;
}
}
if (!fullHtml.includes('