diff options
Diffstat (limited to 'html/file-system')
-rw-r--r-- | html/file-system/index.html | 1210 |
1 files changed, 1210 insertions, 0 deletions
diff --git a/html/file-system/index.html b/html/file-system/index.html new file mode 100644 index 0000000..75a7daa --- /dev/null +++ b/html/file-system/index.html @@ -0,0 +1,1210 @@ +<!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> \ No newline at end of file |