From 250d5bf5315645acc1c110d7c8315e1038570ea8 Mon Sep 17 00:00:00 2001 From: rspruel Date: Mon, 17 Nov 2025 10:14:29 +0000 Subject: [PATCH] VNC-8 Adding upload icon and fixing file paths --- app/images/upload.svg | 22 ++++++ app/ui.js | 169 +++++++++++++++++++++++++++++++++++------- 2 files changed, 163 insertions(+), 28 deletions(-) create mode 100644 app/images/upload.svg diff --git a/app/images/upload.svg b/app/images/upload.svg new file mode 100644 index 00000000..1d22b95f --- /dev/null +++ b/app/images/upload.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/app/ui.js b/app/ui.js index df7edc41..62a60173 100644 --- a/app/ui.js +++ b/app/ui.js @@ -78,6 +78,8 @@ const UI = { monitorStartX: 0, monitorStartY: 0, + currentDownloadPath: '', // Track current folder path for downloads + supportsBroadcastChannel: (typeof BroadcastChannel !== "undefined"), prime() { @@ -535,17 +537,25 @@ const UI = { }, addUploadHandlers() { - UI.addClickHandle('noVNC_upload_button', UI.toggleUploadPanel); + if (document.getElementById('noVNC_upload_button')) { + UI.addClickHandle('noVNC_upload_button', UI.toggleUploadPanel); + } - document.getElementById("noVNC_file_input") - .addEventListener('change', UI.handleFileSelect); + const fileInput = document.getElementById("noVNC_file_input"); + if (fileInput) { + fileInput.addEventListener('change', UI.handleFileSelect); + } }, addDownloadHandlers() { - UI.addClickHandle('noVNC_download_button', UI.toggleDownloadPanel); + if (document.getElementById('noVNC_download_button')) { + UI.addClickHandle('noVNC_download_button', UI.toggleDownloadPanel); + } - document.getElementById("noVNC_refresh_downloads_button") - .addEventListener('click', UI.refreshDownloadsList); + const refreshButton = document.getElementById("noVNC_refresh_downloads_button"); + if (refreshButton) { + refreshButton.addEventListener('click', UI.refreshDownloadsList); + } }, // Add a call to save settings when the element changes, @@ -1549,20 +1559,33 @@ const UI = { UI.closeAllPanels(); UI.openControlbar(); - document.getElementById('noVNC_download_panel') - .classList.add("noVNC_open"); - document.getElementById('noVNC_download_button') - .classList.add("noVNC_selected"); + const panel = document.getElementById('noVNC_download_panel'); + const button = document.getElementById('noVNC_download_button'); + + if (panel) { + panel.classList.add("noVNC_open"); + } + if (button) { + button.classList.add("noVNC_selected"); + } + + // Reset to root folder when opening + UI.currentDownloadPath = ''; // Refresh file list when opening UI.refreshDownloadsList(); }, closeDownloadPanel() { - document.getElementById('noVNC_download_panel') - .classList.remove("noVNC_open"); - document.getElementById('noVNC_download_button') - .classList.remove("noVNC_selected"); + const panel = document.getElementById('noVNC_download_panel'); + const button = document.getElementById('noVNC_download_button'); + + if (panel) { + panel.classList.remove("noVNC_open"); + } + if (button) { + button.classList.remove("noVNC_selected"); + } }, toggleDownloadPanel(e) { @@ -1570,8 +1593,8 @@ const UI = { return false; } - if (document.getElementById('noVNC_download_panel') - .classList.contains("noVNC_open")) { + const panel = document.getElementById('noVNC_download_panel'); + if (panel && panel.classList.contains("noVNC_open")) { UI.closeDownloadPanel(); } else { UI.openDownloadPanel(); @@ -1581,24 +1604,77 @@ const UI = { refreshDownloadsList() { const downloadsList = document.getElementById('noVNC_download_files_list'); + if (!downloadsList) { + console.log('Download files list element not found'); + return; + } + // Show loading message downloadsList.innerHTML = '
Loading files...
'; + // Build API URL with path parameter if we're in a subfolder + let apiUrl = '/api/downloads'; + if (UI.currentDownloadPath) { + // Server expects path to start with / + apiUrl += '?path=' + encodeURIComponent('/' + UI.currentDownloadPath); + } + + console.log('Fetching downloads from:', apiUrl, 'currentPath:', UI.currentDownloadPath); + // Fetch file list from API const xhr = new XMLHttpRequest(); - xhr.open('GET', '/api/downloads', true); + xhr.open('GET', apiUrl, true); xhr.addEventListener('load', () => { if (xhr.status === 200) { try { const response = JSON.parse(xhr.responseText); - if (response.success && response.downloads && response.downloads.length > 0) { - // Clear loading message - downloadsList.innerHTML = ''; + // Handle both old format (success/downloads) and new format (files) + const files = response.files || response.downloads || []; + // Clear loading message + downloadsList.innerHTML = ''; + + // Add breadcrumb navigation + const breadcrumb = document.createElement('div'); + breadcrumb.style.padding = '8px'; + breadcrumb.style.marginBottom = '8px'; + breadcrumb.style.borderBottom = '1px solid #444'; + breadcrumb.style.display = 'flex'; + breadcrumb.style.alignItems = 'center'; + breadcrumb.style.gap = '8px'; + + // Add "up" button if not at root + if (UI.currentDownloadPath) { + const upBtn = document.createElement('button'); + upBtn.textContent = '← Back'; + upBtn.style.padding = '5px 10px'; + upBtn.style.fontSize = '12px'; + upBtn.style.cursor = 'pointer'; + upBtn.addEventListener('click', () => { + // Go up one level + const pathParts = UI.currentDownloadPath.split('/').filter(p => p); + pathParts.pop(); + UI.currentDownloadPath = pathParts.join('/'); + UI.refreshDownloadsList(); + }); + breadcrumb.appendChild(upBtn); + } + + // Show current path + const pathLabel = document.createElement('span'); + pathLabel.textContent = UI.currentDownloadPath ? '/' + UI.currentDownloadPath : '/Downloads'; + pathLabel.style.color = '#cccccc'; + pathLabel.style.fontSize = '12px'; + pathLabel.style.fontWeight = 'bold'; + breadcrumb.appendChild(pathLabel); + + downloadsList.appendChild(breadcrumb); + + if (files.length > 0) { // Display each file - response.downloads.forEach(file => { + files.forEach(file => { const fileItem = document.createElement('div'); fileItem.className = 'noVNC_download_item'; fileItem.style.marginBottom = '8px'; @@ -1614,7 +1690,8 @@ const UI = { fileInfo.style.minWidth = '0'; const fileName = document.createElement('div'); - fileName.textContent = file.filename; + // Add folder icon for directories + fileName.textContent = (file.is_dir ? '📁 ' : '') + file.filename; fileName.style.fontSize = '13px'; fileName.style.fontWeight = 'bold'; fileName.style.color = '#ffffff'; @@ -1626,7 +1703,23 @@ const UI = { fileDetails.style.marginTop = '3px'; if (file.is_dir) { - fileDetails.textContent = 'Directory'; + fileDetails.textContent = 'Folder - Click to open'; + // Make directories clickable + fileItem.style.cursor = 'pointer'; + fileItem.style.transition = 'background-color 0.2s'; + fileItem.addEventListener('mouseenter', () => { + fileItem.style.backgroundColor = 'rgba(255, 255, 255, 0.1)'; + }); + fileItem.addEventListener('mouseleave', () => { + fileItem.style.backgroundColor = ''; + }); + fileItem.addEventListener('click', () => { + // Navigate into directory + UI.currentDownloadPath = UI.currentDownloadPath + ? UI.currentDownloadPath + '/' + file.filename + : file.filename; + UI.refreshDownloadsList(); + }); } else { fileDetails.textContent = UI.formatFileSize(file.size); } @@ -1642,7 +1735,8 @@ const UI = { downloadBtn.style.padding = '5px 10px'; downloadBtn.style.fontSize = '12px'; downloadBtn.style.cursor = 'pointer'; - downloadBtn.addEventListener('click', () => { + downloadBtn.addEventListener('click', (e) => { + e.stopPropagation(); // Prevent any parent click handlers UI.downloadFile(file.filename); }); fileItem.appendChild(fileInfo); @@ -1657,14 +1751,27 @@ const UI = { downloadsList.innerHTML = '
No files available
'; } } catch (e) { - downloadsList.innerHTML = '
Error parsing response
'; + console.error('Error parsing downloads response:', e, xhr.responseText); + downloadsList.innerHTML = '
Error parsing response: ' + e.message + '
'; } } else { - downloadsList.innerHTML = '
Failed to load files
'; + // Try to parse error message from server + let errorMsg = 'Failed to load files (HTTP ' + xhr.status + ')'; + try { + const errorData = JSON.parse(xhr.responseText); + if (errorData.error) { + errorMsg = errorData.error; + } + } catch (e) { + // Ignore parse errors, use default message + } + console.error('Downloads API error:', xhr.status, xhr.responseText); + downloadsList.innerHTML = '
' + errorMsg + '
'; } }); xhr.addEventListener('error', () => { + console.error('Network error loading downloads'); downloadsList.innerHTML = '
Network error
'; }); @@ -1674,7 +1781,13 @@ const UI = { downloadFile(filename) { // Create a temporary anchor element and trigger download const a = document.createElement('a'); - a.href = '/downloads/' + encodeURIComponent(filename); + + // Build the full path including current directory + let fullPath = UI.currentDownloadPath + ? UI.currentDownloadPath + '/' + filename + : filename; + + a.href = '/Downloads/' + encodeURIComponent(fullPath); a.download = filename; document.body.appendChild(a); a.click(); @@ -3409,4 +3522,4 @@ if (l10n.language === "en" || l10n.dictionary !== undefined) { .then(UI.prime); } -export default UI; +export default UI; \ No newline at end of file