// File Browser Component const fileBrowser = { currentPath: '', selectedPath: '', mode: 'directory', // 'directory' or 'file' callback: null, init() { const modal = document.getElementById('file-browser-modal'); const closeBtn = document.getElementById('file-browser-close'); const cancelBtn = document.getElementById('file-browser-cancel'); const selectBtn = document.getElementById('file-browser-select'); const parentBtn = document.getElementById('file-browser-parent'); closeBtn.addEventListener('click', () => this.close()); cancelBtn.addEventListener('click', () => this.close()); selectBtn.addEventListener('click', () => this.select()); parentBtn.addEventListener('click', () => this.goToParent()); // Close on overlay click modal.querySelector('.modal-overlay').addEventListener('click', () => this.close()); }, async open(options = {}) { this.mode = options.mode || 'directory'; this.callback = options.callback; this.currentPath = options.initialPath || '/Users'; this.selectedPath = ''; // Update title const title = this.mode === 'directory' ? 'Select Directory' : 'Select File'; document.getElementById('file-browser-title').textContent = title; // Show modal document.getElementById('file-browser-modal').classList.remove('hidden'); // Load initial directory await this.loadDirectory(this.currentPath); }, close() { document.getElementById('file-browser-modal').classList.add('hidden'); this.callback = null; }, select() { if (this.selectedPath && this.callback) { this.callback(this.selectedPath); } this.close(); }, async goToParent() { const parts = this.currentPath.split('/').filter(p => p); if (parts.length > 0) { parts.pop(); const parentPath = '/' + parts.join('/'); await this.loadDirectory(parentPath); } }, async loadDirectory(path) { const listContainer = document.getElementById('file-browser-list'); listContainer.innerHTML = '

Loading...

'; try { const data = await api.browseFilesystem(path, this.mode); this.currentPath = data.current_path; this.selectedPath = this.mode === 'directory' ? this.currentPath : ''; // Update current path display document.getElementById('file-browser-current-path').value = this.currentPath; // Render items this.renderItems(data.entries); } catch (error) { console.error('Failed to load directory:', error); listContainer.innerHTML = `
Failed to load directory: ${error.message}
`; } }, renderItems(entries) { const listContainer = document.getElementById('file-browser-list'); if (entries.length === 0) { listContainer.innerHTML = '
Empty directory
'; return; } // Sort: directories first, then files, alphabetically entries.sort((a, b) => { if (a.is_dir !== b.is_dir) { return a.is_dir ? -1 : 1; } return a.name.localeCompare(b.name); }); let html = ''; for (const entry of entries) { const icon = entry.is_dir ? '📁' : '📄'; const className = entry.is_dir ? 'directory' : 'file'; const isSelected = entry.path === this.selectedPath; // Only show files if in file mode, always show directories if (this.mode === 'file' && !entry.is_dir) { html += `
${icon} ${entry.name}
`; } else if (entry.is_dir) { html += `
${icon} ${entry.name}
`; } } listContainer.innerHTML = html; // Add click handlers listContainer.querySelectorAll('.file-browser-item').forEach(item => { item.addEventListener('click', () => this.handleItemClick(item)); }); }, async handleItemClick(item) { const path = item.dataset.path; const isDir = item.dataset.isDir === 'true'; if (isDir) { // Double-click to navigate into directory if (this.selectedPath === path) { await this.loadDirectory(path); } else { // Single click to select directory this.selectedPath = path; // Update UI document.querySelectorAll('.file-browser-item').forEach(i => { i.classList.remove('selected'); }); item.classList.add('selected'); } } else { // Select file this.selectedPath = path; // Update UI document.querySelectorAll('.file-browser-item').forEach(i => { i.classList.remove('selected'); }); item.classList.add('selected'); } } }; // Expose to window window.fileBrowser = fileBrowser;