<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Folders in Folders in Folders</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: beige;
display: flex;
flex-direction: column;
align-items: center;
}
#navigation {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin: 0.625em 0;
}
#navigation button {
margin: 0.3125em;
padding: 0.625em 0.9375em;
font-size: 1em;
flex-grow: 1;
min-width: 7.5em;
}
#currentPath {
font-weight: bold;
margin: 0.625em 0;
}
ul {
list-style-type: none;
width: 100%;
max-width: 37.5em;
}
li {
margin: 0.3125em 0;
cursor: pointer;
}
.folder {
display: flex;
align-items: center;
padding: 0.5em;
margin: 0;
border-radius: 0;
transition: background-color 0.2s;
width: 100%;
padding-left: 2em;
}
.folder:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.folder.dragging {
opacity: 0.5;
}
.folder.drag-over {
background-color: rgba(0, 120, 250, 0.1);
border: 2px dashed #0078fa;
}
.folder::before {
content: "📁";
margin-right: 0.5em;
margin-left: -1.5em;
}
ul {
padding-left: 1.5em;
border-left: 1px solid #e0e0e0;
margin: 0;
width: calc(100% - 1.5em);
}
li {
margin: 0;
padding: 0;
width: 100%;
}
#editor {
margin-top: 1.25em;
width: 100%;
max-width: 37.5em;
cursor: move;
min-width: 200px;
min-height: 150px;
resize: both;
overflow: auto;
}
#editor textarea {
width: 100%;
height: calc(100% - 3em);
font-size: 1em;
padding: 0.625em;
box-sizing: border-box;
resize: none;
margin-bottom: 0;
}
h1 {
text-align: center;
font-size: 1.5em;
margin-top: 0.625em;
}
@media (max-width: 37.5em) {
h1 {
font-size: 1.25em;
}
#navigation button {
font-size: 0.875em;
padding: 0.5em 0.625em;
}
.folder {
font-size: 1em;
}
#editor textarea {
height: 7.5em;
font-size: 0.875em;
}
}
#navigation {
display: none; /* Hide the button navigation by default */
}
.context-menu {
position: fixed;
background: white;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 2px 2px 5px rgba(0,0,0,0.2);
padding: 0.5em 0;
min-width: 150px;
z-index: 1000;
display: none;
}
.context-menu.active {
display: block;
}
.context-menu-item {
padding: 0.5em 1em;
cursor: pointer;
display: flex;
align-items: center;
}
.context-menu-item:hover {
background-color: #f0f0f0;
}
.context-menu-item::before {
margin-right: 0.5em;
}
.context-menu-item.new-folder::before {
content: "📁";
}
.context-menu-item.rename::before {
content: "✏️";
}
.context-menu-item.copy::before {
content: "📋";
}
.context-menu-item.paste::before {
content: "📥";
}
.context-menu-item.delete::before {
content: "🗑️";
}
#editor {
display: none; /* Hide editor by default */
position: fixed;
right: 20px;
top: 20px;
background: white;
border: 1px solid #ccc;
border-radius: 4px;
padding: 1em;
box-shadow: 2px 2px 5px rgba(0,0,0,0.2);
}
.folder.selected {
background-color: rgba(0, 120, 250, 0.1);
}
#fileSystemContainer {
width: 100%;
max-width: 37.5em;
min-height: 300px;
background: white;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 1em;
margin-top: 1em;
box-sizing: border-box;
overflow: hidden;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200px;
color: #666;
text-align: center;
}
.empty-state::before {
content: "📁";
font-size: 3em;
margin-bottom: 0.5em;
opacity: 0.5;
}
.empty-state-text {
font-size: 1.1em;
margin-bottom: 1em;
}
.root-folder {
padding: 0.5em;
padding-left: 2em;
margin: 0;
width: 100%;
border-radius: 0;
cursor: pointer;
transition: background-color 0.2s;
}
.root-folder:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.root-folder.selected {
background-color: rgba(0, 120, 250, 0.1);
}
#editorHeader {
padding: 0.5em;
background-color: #f5f5f5;
border-bottom: 1px solid #ccc;
margin: -1em -1em 1em -1em;
border-radius: 4px 4px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.5em;
}
#editorHeader h2 {
margin: 0;
font-size: 1.1em;
}
.editor-controls {
display: flex;
gap: 0.3em;
}
.editor-button {
cursor: pointer;
padding: 0.2em 0.5em;
border-radius: 3px;
font-size: 0.9em;
color: #666;
}
.editor-button:hover {
background-color: #e0e0e0;
}
.resize-handle {
position: absolute;
width: 10px;
height: 10px;
background-color: #f5f5f5;
border: 1px solid #ccc;
}
.resize-handle.se {
right: 0;
bottom: 0;
cursor: se-resize;
}
.resize-handle.sw {
left: 0;
bottom: 0;
cursor: sw-resize;
}
.resize-handle.ne {
right: 0;
top: 0;
cursor: ne-resize;
}
.resize-handle.nw {
left: 0;
top: 0;
cursor: nw-resize;
}
.resize-handle:hover {
background-color: #e0e0e0;
}
/* Update the inchworm styles */
.inchworm {
position: absolute;
top: -16px;
left: 0;
font-size: 22px;
animation: inch 18s infinite linear;
transform-origin: center;
color: green;
}
@keyframes inch {
/* Forward */
0% {
left: 0;
transform: scaleX(1) scaleY(1);
}
5% {
left: 0;
transform: scaleX(0.4) scaleY(1.4);
}
10% {
left: 30px;
transform: scaleX(1.3) scaleY(0.7);
}
15% {
left: 30px;
transform: scaleX(0.4) scaleY(1.4);
}
20% {
left: 60px;
transform: scaleX(1.3) scaleY(0.7);
}
25% {
left: 60px;
transform: scaleX(0.4) scaleY(1.4);
}
30% {
left: 90px;
transform: scaleX(1.3) scaleY(0.7);
}
35% {
left: 90px;
transform: scaleX(0.4) scaleY(1.4);
}
40% {
left: 120px;
transform: scaleX(1.3) scaleY(0.7);
}
/* Rest */
42% {
left: 120px;
transform: scaleX(1) scaleY(1);
}
/* Backwards */
45% {
left: 120px;
transform: scaleX(0.4) scaleY(1.4);
}
50% {
left: 90px;
transform: scaleX(1.3) scaleY(0.7);
}
55% {
left: 90px;
transform: scaleX(0.4) scaleY(1.4);
}
60% {
left: 60px;
transform: scaleX(1.3) scaleY(0.7);
}
65% {
left: 60px;
transform: scaleX(0.4) scaleY(1.4);
}
70% {
left: 30px;
transform: scaleX(1.3) scaleY(0.7);
}
75% {
left: 30px;
transform: scaleX(0.4) scaleY(1.4);
}
80% {
left: 0;
transform: scaleX(1.3) scaleY(0.7);
}
/* Rest */
85% {
left: 0;
transform: scaleX(1) scaleY(1);
}
100% {
left: 0;
transform: scaleX(1) scaleY(1);
}
}
.folder.editing {
padding: 0;
margin: 0;
}
.folder.editing input {
font-size: 1em;
padding: 0.5em;
margin: 0.25em 0;
width: calc(100% - 2em);
border: 1px solid #ccc;
border-radius: 4px;
outline: none;
}
.folder.editing input:focus {
border-color: #0078fa;
}
#viewControls {
position: absolute;
top: 1em;
right: 1em;
}
.view-toggle {
padding: 0.5em;
border: 1px solid #ccc;
border-radius: 4px;
background: white;
cursor: pointer;
transition: background-color 0.2s;
}
.view-toggle:hover {
background-color: #f0f0f0;
}
#breadcrumbs {
padding: 0.5em 1em;
margin: 0;
border-bottom: 1px solid #e0e0e0;
display: none;
text-align: left;
background-color: #f8f8f8;
border-radius: 4px 4px 0 0;
white-space: normal;
word-break: keep-all;
line-height: 1.8;
}
.breadcrumb-item {
display: inline-block;
cursor: pointer;
padding: 0.2em 0.5em;
border-radius: 3px;
color: #666;
white-space: nowrap;
}
.breadcrumb-item:hover {
background-color: rgba(0, 0, 0, 0.05);
color: #000;
}
.breadcrumb-separator {
margin: 0 0.2em;
color: #999;
user-select: none;
display: inline-block;
}
/* Grid view styles */
.grid-view {
display: none;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 1em;
padding: 1em;
}
.grid-folder {
display: flex;
flex-direction: column;
align-items: center;
padding: 1em;
cursor: pointer;
border-radius: 4px;
text-align: center;
}
.grid-folder:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.grid-folder-icon {
font-size: 2em;
margin-bottom: 0.5em;
}
.grid-folder-name {
font-size: 0.9em;
word-break: break-word;
max-width: 100%;
}
#fileSystemContainer {
display: flex;
flex-direction: column;
}
.grid-view {
padding-top: 0;
}
</style>
</head>
<body>
<div id="navigation">
<button onclick="goToRoot()">Go to Root</button>
<button onclick="goUp()">Go Up One Level</button>
<button onclick="createFolderPrompt()">Create Folder</button>
<button onclick="copyItem()">Copy</button>
<button onclick="pasteItem()">Paste</button>
<button onclick="deleteItem()">Delete</button>
</div>
<div id="fileSystemContainer" oncontextmenu="showContextMenu(event, 'root')">
<div id="breadcrumbs"></div>
<div class="root-folder" onclick="selectFolder('root')">~</div>
<ul id="fileSystemTree"></ul>
<div class="grid-view"></div>
<div class="empty-state" style="display: none;">
<div class="empty-state-text">No folders yet.</div>
<div class="empty-state-hint">Right-click and use the context menu to create a folder.</div>
</div>
</div>
<div id="editor">
<div id="editorHeader">
<h2><span id="editorPath">/</span></h2>
<div class="editor-controls">
<span class="editor-button" onclick="setEditorSize('normal')" title="Normal Size">⊡</span>
<span class="editor-button" onclick="setEditorSize('vertical')" title="Vertical Split">⊢</span>
<span class="editor-button" onclick="setEditorSize('horizontal')" title="Horizontal Split">⊤</span>
<span class="editor-button" onclick="setEditorSize('full')" title="Full Screen">⛶</span>
<span class="editor-button" id="closeEditor" onclick="hideEditor()" title="Close">✕</span>
</div>
</div>
<div class="inchworm">O</div>
<textarea id="folderContent" placeholder="Select a folder to edit its contents"></textarea>
</div>
<div class="context-menu" id="contextMenu">
<div class="context-menu-item new-folder" onclick="createFolderPrompt()">New Folder</div>
<div class="context-menu-item rename" onclick="renameFolder()">Rename</div>
<div class="context-menu-item copy" onclick="copyItem()">Copy</div>
<div class="context-menu-item paste" onclick="pasteItem()">Paste</div>
<div class="context-menu-item delete" onclick="deleteItem()">Delete</div>
</div>
<div id="viewControls">
<button class="view-toggle" onclick="toggleView()" title="Toggle View">
<span class="tree-icon">🌳</span>/<span class="grid-icon">📱</span>
</button>
</div>
<script>
const initialState = () => ({
root: { type: 'folder', children: {}, content: '' }
});
const loadFileSystem = () => JSON.parse(localStorage.getItem('fileSystem')) || initialState();
const state = {
fileSystem: loadFileSystem(),
currentFolderPath: 'root',
copiedFolderData: null,
copiedItemPath: null,
draggedPath: null,
viewMode: 'tree' // 'tree' or 'grid'
};
const clone = (obj) => JSON.parse(JSON.stringify(obj));
const updateFileSystem = (newFileSystem) => {
state.fileSystem = newFileSystem;
saveFileSystem(newFileSystem);
};
const getFolder = (path, fileSystem = state.fileSystem) => {
const parts = path.split('/').filter(part => part !== 'root');
return parts.reduce((current, part) => {
if (!current || !current.children[part]) {
return null;
}
return current.children[part];
}, fileSystem.root);
};
const updateFolder = (fileSystem, path, updateFn) => {
const rootCopy = clone(fileSystem);
const folderToUpdate = getFolder(path, rootCopy);
if (!folderToUpdate) {
alert(`Folder not found for path: ${path}`);
return null;
}
updateFn(folderToUpdate);
return rootCopy;
};
const addFolder = (path, name, folderData, fileSystem) => {
return updateFolder(fileSystem, path, (folder) => {
if (folder.children[name]) {
alert(`Folder with name "${name}" already exists.`);
return;
}
folder.children[name] = folderData || { type: 'folder', children: {}, content: '' };
});
};
const deleteFolder = (path, fileSystem) => {
if (path === 'root') {
return fileSystem;
}
if (!path.includes('/')) {
const newFileSystem = clone(fileSystem);
delete newFileSystem.root.children[path];
return newFileSystem;
}
const [parentPath, folderName] = path.split(/\/([^\/]+)$/);
return updateFolder(fileSystem, parentPath, (folder) => {
delete folder.children[folderName];
});
};
const saveFileSystem = (fileSystem) => {
localStorage.setItem('fileSystem', JSON.stringify(fileSystem));
};
const goToRoot = () => {
state.currentFolderPath = 'root';
render();
};
const goUp = () => {
const parts = state.currentFolderPath.split('/');
if (parts.length > 1) {
parts.pop();
state.currentFolderPath = parts.join('/');
render();
}
};
const createFolder = (path, name) => {
const updatedFileSystem = addFolder(path, name, null, state.fileSystem);
if (updatedFileSystem) {
updateFileSystem(updatedFileSystem);
render();
} else {
console.error('Error creating folder', path, name);
alert('Error creating folder');
}
};
const deleteItem = () => {
console.log('Attempting to delete:', state.currentFolderPath); // Debug log
// Basic validation
if (state.currentFolderPath === 'root') {
alert("Can't delete root!");
return;
}
// Get parent path and folder name
const parts = state.currentFolderPath.split('/');
const folderName = parts[parts.length - 1];
const parentPath = parts.slice(0, -1).join('/') || 'root';
// Create new file system and delete the folder
const newFileSystem = clone(state.fileSystem);
const parent = parentPath === 'root' ?
newFileSystem.root :
getFolder(parentPath, newFileSystem);
if (parent && parent.children) {
delete parent.children[folderName];
updateFileSystem(newFileSystem);
state.currentFolderPath = parentPath;
document.getElementById('editor').style.display = 'none';
render();
}
hideContextMenu();
};
const copyItem = () => {
const folder = getFolder(state.currentFolderPath);
if (folder) {
state.copiedFolderData = clone(folder); // Store a deep copy of the folder
state.copiedItemPath = state.currentFolderPath; // Store the path so that we can remove later
}
hideContextMenu();
};
const pasteItem = () => {
if (!state.copiedItemPath || !state.copiedFolderData) {
alert('No item to paste');
hideContextMenu();
return;
}
const [oldPath, folderName] = state.copiedItemPath.split(/\/([^\/]+)$/);
const updatedFileSystem = addFolder(state.currentFolderPath, folderName, state.copiedFolderData, state.fileSystem);
if (updatedFileSystem) {
updateFileSystem(deleteFolder(oldPath, updatedFileSystem)); // Remove original
state.copiedFolderData = null; // Clear the copied data
state.copiedItemPath = null;
render();
} else {
alert('Error pasting item');
}
hideContextMenu();
};
const saveFolderContent = () => {
const updatedFileSystem = updateFolder(state.fileSystem, state.currentFolderPath, (folder) => {
folder.content = document.getElementById('folderContent').value;
});
if (updatedFileSystem) {
updateFileSystem(updatedFileSystem);
}
};
document.getElementById('folderContent').addEventListener('input', () => {
saveFolderContent();
});
const handleDragStart = (e, path) => {
e.target.classList.add('dragging');
e.dataTransfer.setData('text/plain', path);
state.draggedPath = path;
};
const handleDragEnd = (e) => {
e.target.classList.remove('dragging');
document.querySelectorAll('.folder').forEach(f => {
f.classList.remove('drag-over');
});
};
const handleDragOver = (e) => {
e.preventDefault();
e.target.classList.add('drag-over');
};
const handleDragLeave = (e) => {
e.target.classList.remove('drag-over');
};
const handleDrop = (e, targetPath) => {
e.preventDefault();
e.target.classList.remove('drag-over');
if (!state.draggedPath || state.draggedPath === targetPath) return;
const sourcePath = state.draggedPath;
const sourceFolder = getFolder(sourcePath);
if (!sourceFolder) return;
// Create a copy of the dragged folder
const [, folderName] = sourcePath.split(/\/([^\/]+)$/);
const updatedFileSystem = addFolder(targetPath, folderName, clone(sourceFolder), state.fileSystem);
if (updatedFileSystem) {
// Remove the original folder
updateFileSystem(deleteFolder(sourcePath, updatedFileSystem));
state.draggedPath = null;
render();
}
};
const renderFolderTree = (folder, path = 'root') => {
const entries = Object.entries(folder.children);
return entries.length ? entries.map(([name, item]) => {
const fullPath = path === 'root' ? name : `${path}/${name}`;
return `
<li>
<div class="folder"
onclick="selectFolder('${fullPath}')"
oncontextmenu="showContextMenu(event, '${fullPath}')"
draggable="true"
ondragstart="handleDragStart(event, '${fullPath}')"
ondragend="handleDragEnd(event)"
ondragover="handleDragOver(event)"
ondragleave="handleDragLeave(event)"
ondrop="handleDrop(event, '${fullPath}')"
>${name}</div>
<ul>${renderFolderTree(item, fullPath)}</ul>
</li>
`;
}).join('') : '';
};
const selectFolder = (path) => {
// Don't proceed if no path is provided
if (!path) return;
const folder = path === 'root' ? state.fileSystem.root : getFolder(path);
if (folder) {
// Remove previous selection
document.querySelectorAll('.folder.selected, .root-folder.selected').forEach(f => {
f.classList.remove('selected');
});
// Add selection to current folder
if (path === 'root') {
document.querySelector('.root-folder').classList.add('selected');
} else {
const folderElement = document.querySelector(`.folder[onclick*="${path}"]`);
if (folderElement) {
folderElement.classList.add('selected');
}
}
// Set the current path
state.currentFolderPath = path;
// Update editor content
document.getElementById('folderContent').value = folder.content || '';
// Show editor in normal size
editor.style.display = 'block';
setEditorSize('normal');
render();
}
};
const render = () => {
if (!state.fileSystem.root) {
console.error('File system is not initialized correctly', state.fileSystem);
alert('File system is not initialized correctly');
return;
}
// Show/hide breadcrumbs and root folder based on view mode
document.getElementById('breadcrumbs').style.display =
state.viewMode === 'grid' ? 'block' : 'none';
document.querySelector('.root-folder').style.display =
state.viewMode === 'grid' ? 'none' : 'block';
// Update current folder content based on view mode
if (state.viewMode === 'tree') {
document.getElementById('fileSystemTree').style.display = 'block';
document.querySelector('.grid-view').style.display = 'none';
document.getElementById('fileSystemTree').innerHTML =
renderFolderTree(state.fileSystem.root);
} else {
document.getElementById('fileSystemTree').style.display = 'none';
document.querySelector('.grid-view').style.display = 'grid';
const currentFolder = state.currentFolderPath === 'root' ?
state.fileSystem.root :
getFolder(state.currentFolderPath);
document.querySelector('.grid-view').innerHTML =
renderGridView(currentFolder);
}
renderBreadcrumbs();
// Update path displays
const currentPath = state.currentFolderPath.replace('root', '') || '/';
document.title = `Folder: ${currentPath}`;
document.getElementById('editorPath').textContent = currentPath;
// Update root folder selection state
const rootFolder = document.querySelector('.root-folder');
if (state.currentFolderPath === 'root') {
rootFolder.classList.add('selected');
} else {
rootFolder.classList.remove('selected');
}
// Update folder tree and empty state
document.getElementById('fileSystemTree').innerHTML = renderFolderTree(state.fileSystem.root);
const emptyState = document.querySelector('.empty-state');
const hasNoFolders = Object.keys(state.fileSystem.root.children).length === 0;
emptyState.style.display = hasNoFolders ? 'flex' : 'none';
};
const createFolderPrompt = () => {
const name = prompt('Enter folder name:');
if (name) createFolder(state.currentFolderPath, name);
hideContextMenu();
};
const contextMenu = document.getElementById('contextMenu');
// Prevent default context menu
document.addEventListener('contextmenu', (e) => {
e.preventDefault();
});
// Hide context menu when clicking outside
document.addEventListener('click', (e) => {
if (!contextMenu.contains(e.target)) {
contextMenu.classList.remove('active');
}
});
const showContextMenu = (e, path) => {
e.preventDefault();
e.stopPropagation();
// Just update the current path without selecting the folder
if (path) {
state.currentFolderPath = path;
}
// Position the menu
contextMenu.style.left = `${e.pageX}px`;
contextMenu.style.top = `${e.pageY}px`;
// Show the menu
contextMenu.classList.add('active');
// Enable/disable paste option
const pasteOption = contextMenu.querySelector('.paste');
pasteOption.style.opacity = state.copiedFolderData ? '1' : '0.5';
// Prevent menu from going off-screen
const rect = contextMenu.getBoundingClientRect();
if (rect.right > window.innerWidth) {
contextMenu.style.left = `${e.pageX - rect.width}px`;
}
if (rect.bottom > window.innerHeight) {
contextMenu.style.top = `${e.pageY - rect.height}px`;
}
};
// Add double-click handler to show/hide editor
document.addEventListener('dblclick', (e) => {
if (!e.target.classList.contains('folder')) {
document.getElementById('editor').style.display = 'none';
}
});
const editor = document.getElementById('editor');
const editorHeader = document.getElementById('editorHeader');
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 0;
let yOffset = 0;
const dragStart = (e) => {
if (e.target.closest('#closeEditor') || e.target.classList.contains('resize-handle')) return;
if (e.type === "touchstart") {
initialX = e.touches[0].clientX - xOffset;
initialY = e.touches[0].clientY - yOffset;
} else {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
}
if (e.target === editorHeader || e.target.closest('#editorHeader')) {
isDragging = true;
}
};
const dragEnd = () => {
initialX = currentX;
initialY = currentY;
isDragging = false;
};
const drag = (e) => {
if (isDragging) {
e.preventDefault();
if (e.type === "touchmove") {
currentX = e.touches[0].clientX - initialX;
currentY = e.touches[0].clientY - initialY;
} else {
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
}
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, editor);
}
};
const setTranslate = (xPos, yPos, el) => {
el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
};
const hideEditor = () => {
editor.style.display = 'none';
};
// Add event listeners for drag functionality
editorHeader.addEventListener("touchstart", dragStart, false);
editorHeader.addEventListener("touchend", dragEnd, false);
editorHeader.addEventListener("touchmove", drag, false);
editorHeader.addEventListener("mousedown", dragStart, false);
document.addEventListener("mousemove", drag, false);
document.addEventListener("mouseup", dragEnd, false);
const setEditorSize = (mode, editorElement = editor) => {
// Reset any existing transforms and positioning
editorElement.style.transform = 'none';
editorElement.style.transition = 'all 0.3s ease';
// Get viewport dimensions and convert padding to pixels
const vw = window.innerWidth;
const vh = window.innerHeight;
const padding = '2em';
const paddingPx = parseFloat(getComputedStyle(document.documentElement).fontSize) * 2;
const fullModePadding = '4em';
const fullModePaddingPx = parseFloat(getComputedStyle(document.documentElement).fontSize) * 4;
// Reset all positioning first
editorElement.style.position = 'fixed';
editorElement.style.margin = '0';
editorElement.style.maxWidth = 'none';
editorElement.style.bottom = padding;
switch (mode) {
case 'normal':
editorElement.style.width = '37.5em';
editorElement.style.height = '300px';
editorElement.style.left = '20px';
editorElement.style.top = '20px';
editorElement.style.right = 'auto';
editorElement.style.bottom = 'auto';
break;
case 'vertical':
// Take up the left half of the viewport
editorElement.style.width = `${(vw - paddingPx * 3) / 2}px`;
editorElement.style.height = `${vh - paddingPx * 2}px`;
editorElement.style.left = padding;
editorElement.style.top = padding;
editorElement.style.right = 'auto';
break;
case 'horizontal':
// Take up the top half of the viewport
editorElement.style.width = `${vw - paddingPx * 2}px`;
editorElement.style.height = `${(vh - paddingPx * 3) / 2}px`;
editorElement.style.left = padding;
editorElement.style.top = padding;
editorElement.style.right = padding;
break;
case 'full':
// Take up the full viewport with larger padding
editorElement.style.width = `${vw - fullModePaddingPx * 2}px`;
editorElement.style.height = `${vh - fullModePaddingPx * 2}px`;
editorElement.style.top = fullModePadding;
editorElement.style.left = fullModePadding;
editorElement.style.right = fullModePadding;
editorElement.style.bottom = fullModePadding;
break;
}
// Reset offsets used by drag functionality
xOffset = 0;
yOffset = 0;
// Ensure the editor is visible
editorElement.style.display = 'block';
};
const renameFolder = () => {
console.log('Attempting to rename:', state.currentFolderPath); // Debug log
// Basic validation
if (state.currentFolderPath === 'root') {
alert("Can't rename root!");
return;
}
// Get parent path and folder name
const parts = state.currentFolderPath.split('/');
const oldName = parts[parts.length - 1];
const parentPath = parts.slice(0, -1).join('/') || 'root';
const newName = prompt(`Rename "${oldName}" to:`, oldName);
if (!newName || newName === oldName) return;
// Create new file system and rename the folder
const newFileSystem = clone(state.fileSystem);
const parent = parentPath === 'root' ?
newFileSystem.root :
getFolder(parentPath, newFileSystem);
if (parent && parent.children) {
if (parent.children[newName]) {
alert('A folder with this name already exists');
return;
}
parent.children[newName] = parent.children[oldName];
delete parent.children[oldName];
updateFileSystem(newFileSystem);
state.currentFolderPath = parentPath === 'root' ?
newName :
`${parentPath}/${newName}`;
render();
}
hideContextMenu();
};
// Add double-click to rename
const handleFolderDoubleClick = (e) => {
const folderElement = e.target.closest('.folder');
if (folderElement && !folderElement.classList.contains('editing')) {
e.preventDefault();
e.stopPropagation();
renameFolder();
}
};
document.addEventListener('dblclick', handleFolderDoubleClick);
const toggleView = () => {
state.viewMode = state.viewMode === 'tree' ? 'grid' : 'tree';
render();
};
const navigateTo = (path, openDetails = false) => {
state.currentFolderPath = path;
// Only open folder details if explicitly requested (from breadcrumbs)
if (openDetails) {
const folder = path === 'root' ? state.fileSystem.root : getFolder(path);
if (folder) {
document.getElementById('folderContent').value = folder.content || '';
editor.style.display = 'block';
setEditorSize('normal');
}
}
render();
};
const renderBreadcrumbs = () => {
// Start with root, but ensure we don't duplicate it
const parts = state.currentFolderPath.split('/').filter(part => part !== 'root');
const fullPath = ['root', ...parts];
// Generate breadcrumbs from the full path
const breadcrumbs = fullPath.map((part, index) => {
const path = fullPath.slice(0, index + 1).join('/');
const isLastItem = index === fullPath.length - 1;
return `
<span class="breadcrumb-item"
onclick="navigateTo('${path}', ${isLastItem})">${part === 'root' ? '~' : part}</span>
${index < fullPath.length - 1 ? '<span class="breadcrumb-separator">/</span>' : ''}
`;
}).join('');
document.getElementById('breadcrumbs').innerHTML = breadcrumbs;
};
const renderGridView = (folder) => {
const entries = Object.entries(folder.children);
return entries.map(([name, item]) => `
<div class="grid-folder"
onclick="navigateTo('${state.currentFolderPath}/${name}')"
oncontextmenu="showContextMenu(event, '${state.currentFolderPath}/${name}')">
<div class="grid-folder-icon">📁</div>
<div class="grid-folder-name">${name}</div>
</div>
`).join('');
};
const hideContextMenu = () => {
contextMenu.classList.remove('active');
};
render();
</script>
</body>
</html>