<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>📁 SD Browser</title> <script src="./jquery.min.js"></script> <link rel="stylesheet" href="./main.css"/> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> body{ background-color: rgb(238, 238, 238); } </style> </head> <body> <!-- Main contents of the webapp --> <div id="menu"> <div id="menu"> <button onclick="back()" class="back button"> <img class="btnicon" src="img/left.svg"> </button> </div> </div> <div class="container"> <p class="addrbar">📁 /<span id="currentPath"></span></p> <div class="sdcontent"> <ul id="folderContent" style="list-style-type:none;"> </ul> </div> <button onclick="parentDir();" class="inverse button" style="margin-top: 0.4em;">↩ Parent Dir</button> <button onclick="refreshDir();" class="inverse button" style="margin-top: 0.4em;">⟳ Refresh</button> <div class="fileopr disabled segment"> <p>Selected file: <span id="selectedFilename"></span></p> <button class="inverse button" onclick="openFile();">Open</button> <button class="inverse button" onclick="deleteFile();">Delete</button> </div> <div class="uploadwrapper segment"> <div class="upload-container"> <input type="file" id="fileInput" multiple> <button class="inverse button" onclick="uploadFiles()">Upload Files</button> </div> <div id="uploadProgress"> <div class="bar"></div> </div> </div> <div class="ui divider"></div> <small style="color: #1f1f1f;">Development SD Browser | imuslab</small> </div> <div id="messagebox"> <p>Hello World</p> </div> <script> let currentPath = ""; //Do not need prefix or suffix slash let selectedFile = undefined; function formatBytes(bytes,decimals) { if(bytes == 0) return '0 Bytes'; var k = 1024, dm = decimals || 2, sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } function listDir(path=""){ $("#currentPath").text(path); currentPath = path; selectedFile = undefined; $("#selectedFilename").text(""); $(".fileopr").addClass('disabled'); $("#folderContent").html("<small>Loading...</small>"); $.ajax({ url: "/api/fs/listDir?dir=" + path, method: "GET", success: function(data){ $("#folderContent").html(""); if (path != ""){ //Not at root $("#folderContent").append(`<li><a class="fileobject" onclick="parentDir();">↼ Back</a></li>`); } data.forEach(sdfile => { let filename = sdfile.Filename; let filesize = sdfile.Filesize; let IsDir = sdfile.IsDir; $("#folderContent").append(`<li><a class="fileobject" isdir="${IsDir}" filename="${filename}" onclick="openthis(this);">${IsDir?"📁":"📄"} ${filename} (${formatBytes(filesize, 2)})</a></li>`); }) }, error: function(){ $("#folderContent").html("❌ Path Error"); } }); } listDir(); function refreshDir(){ listDir(currentPath); } function msgbox(message, succ=true){ if (succ){ $("#messagebox").removeClass("failed"); }else{ $("#messagebox").addClass("failed"); } $("#messagebox p").text(message); $("#messagebox").stop().finish().fadeIn("fast").delay(5000).fadeOut("fast"); } function openFile(){ if (selectedFile == undefined){ alert("No file selected"); return; } window.open("/api/fs/download?path=/" + selectedFile.Filepath); } function deleteFile(){ if (selectedFile == undefined){ alert("No file selected"); return; } $.ajax({ url: "/api/fs/delete?path=/" +selectedFile.Filepath, method: "GET", success: function(data){ msgbox("File removed"); refreshDir(); }, error: function(){ msgbox("File remove failed", false); } }); } function openthis(target){ let filename = $(target).attr("filename"); let isDir = $(target).attr("isdir") == "true"; if (isDir){ //Open directory if (currentPath == ""){ //Do not need prefix slash listDir(filename); }else{ listDir(currentPath + "/" + filename); } }else{ //Selected a file selectedFile = { Filename: filename, Filepath: currentPath + "/" + filename } $("#selectedFilename").text(filename); $(".fileopr").removeClass('disabled'); } } function parentDir(){ let pathChunks = currentPath.split("/"); pathChunks.pop(); pathChunks = pathChunks.join("/"); listDir(pathChunks); } function back(){ window.location.href = "index.html"; } function updateProgressBar(progress){ $("#uploadProgress .bar").css({ "width": progress + "%", }); $("#uploadProgress .bar").text(progress + "%"); } function uploadFiles() { var fileInput = document.getElementById('fileInput'); var files = fileInput.files; if (files.length === 0) { alert('No files selected.'); return; } // Create a FormData object to send files as multipart/form-data var formData = new FormData(); // Append each file to the FormData object for (var i = 0; i < files.length; i++) { formData.append('files[]', files[i]); } // Send the FormData object via XMLHttpRequest var xhr = new XMLHttpRequest(); xhr.open('POST', '/upload?dir=' + currentPath, true); // Replace '/upload' with your ESP32 server endpoint // Track upload progress xhr.upload.addEventListener('progress', function(event) { if (event.lengthComputable) { var percentComplete = (event.loaded / event.total) * 100; console.log('Upload progress: ' + percentComplete.toFixed(2) + '%'); updateProgressBar(percentComplete.toFixed(2)); } else { console.log('Upload progress: unknown'); } }); xhr.onload = function() { if (xhr.status === 200) { msgbox('File uploaded successfully.'); } else { msgbox('Error writing files to disk.', false); } refreshDir(); }; xhr.onerror = function() { msgbox('Error uploading files.'); }; xhr.send(formData); } </script> </body>