1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216 |
- <html>
- <head>
- <title>File Manager</title>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" />
- <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.js"></script>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.6.0/jszip.min.js"></script>
- <link rel="stylesheet" href="fs.css">
- <script>
-
- </script>
- <style>
- body{
- background-color: white;
- }
- #uploadProgressBar{
- position: fixed;
- bottom: 0;
- left: 0;
- width: 100%;
- }
- .fileOprBtn.disabled{
- opacity: 0.5;
- user-select: none;
- pointer-events: none;
- }
- </style>
- </head>
- <body class="whiteTheme">
- <div id="navibar" class="navibar">
- <!-- File Opr Group-->
- <button class="fileOprBtn" title="Open" onclick="openViaButton(event);"><img class="opricon" src="img/opr/open.svg"><p class="oprtxt" locale="fileopr/Open">Open</p></button>
- <button id="copyButton" class="fileOprBtn" title="Copy" onclick="copy();"><img class="opricon" src="img/opr/copy.svg"><p class="oprtxt" locale="fileopr/Copy">Copy</p></button>
- <button id="pasteButton" class="fileOprBtn" title="Paste" onclick="paste();"><img class="opricon" src="img/opr/paste.svg"><p class="oprtxt" locale="fileopr/Paste">Paste</p></button>
- <div class="fileoprGroupDivider" style="display: inline-block; vertical-align: top;">
- <button class="fileoprSmallBtn" title="Refresh" onclick="refresh();"><i class="green refresh icon"></i> <span locale="fileopr/Refresh">Refresh</span></button><br>
- <button class="fileoprSmallBtn" title="Cut" onclick="cut();"><i class="blue cut icon"></i> <span locale="fileopr/Cut">Cut</span></button><br>
- <button class="fileoprSmallBtn" title="Rename" onclick="rename();"><i class="teal i cursor icon"></i> <span locale="fileopr/Rename">Rename</span></button>
- </div>
- <button class="fileOprBtn" title="Upload" onclick="upload(); "><img class="opricon" src="img/opr/upload.svg"><p class="oprtxt wideScreenOnly" locale="fileopr/Upload">Upload</p></button>
- <button class="fileOprBtn" title="Download" onclick="downloadFile(); "><img class="opricon" src="img/opr/download.svg"><p class="oprtxt wideScreenOnly" locale="fileopr/Download">Download</p></button>
- <div class="fileoprGroupDivider" style="display: inline-block; vertical-align: top;"></div>
-
- <div class="fileoprGroupDivider" style="display: inline-block; vertical-align: top;">
- <button class="fileoprSmallBtn" title="New File" onclick="newFile();"><i style="color: #c7c7c7 !important;" class="file outline icon"></i> <span locale="fileopr/New File">New File</span></button><br>
- <button class="fileoprSmallBtn" title="New Folder" onclick="newFolder();"><i style="color: #ffe79e !important;" class="yellow folder icon"></i> <span locale="fileopr/New Folder">New Folder</span></button><br>
- <button class="fileoprSmallBtn" title="Delete" onclick="deleteFile();"><i class="red times icon"></i> <span locale="fileopr/Delete">Delete</span></button><br>
- </div>
- <button class="fileOprBtn" title="Download" onclick="shareFile(); "><img class="opricon" src="img/opr/share.svg"><p class="oprtxt wideScreenOnly" locale="fileopr/Share">Share</p></button>
- <br>
- <!-- Directoy navigations -->
- <div class="addressBar">
- <button id="prevDir" class="navibarBtn" onclick="prevDir();" title="Back"><i class="left arrow icon"></i></button>
- <button id="ppbtn" class="navibarBtn" onclick="parentDir();" title="Parent Folder"><i class="up arrow icon"></i></button>
- <div id="pathDisplayField" class="ui breadcrumb addressText pathDisplay desktopOnly" >
- <div class="section selectable"><i class="folder icon"></i> user</div>
- <div class="divider">:/</div>
- <div class="section selectable">Desktop</div>
- </div>
- <button id="togglePropertiesViewBtn" style="margin-left: 0.4em;" class="ui icon tiny button videmode propbar" title="Show Properties" onclick="togglePropertiesView(this);"><i class="columns icon"></i></button>
- </div>
-
- <div class="msgbox" style="z-index:999; display:none; padding-bottom: 1em;">
- <i class="checkmark icon showicon"></i> <span style="word-break: break-all;">No Message</span>
- <div class="closeMsgButton" onclick="$(this).parent().stop().slideUp('fast');"><i class="caret down icon"></i></div>
- </div>
- </div>
-
- <div id="mainWindow">
- <div id="folderView" style="height: 100%;">
- <div id="folderList" class="fileviewList">
-
- </div>
- <div id="fileList" class="fileviewList">
- </div>
- <br>
- </div>
- <div id="propertiesView" class="small">
- <div class="preview" style="margin-top: 0.4em;" align="center">
- <img class="ui image" style="max-height: 300px;">
- <audio src="" controls style="display:none; width: 100%;"></audio>
- </div>
- <h3 class="ui header" style="margin-top: 0.4em;">
- <span class="filename" style="word-break: break-all;" locale="sidebar/default/nofileselected">No File Selected</span>
- <div id="fileVpath" class="sub header vpath" style="word-break: break-all;" locale="sidebar/default/instruction">Select a file to view file properties</div>
- </h3>
- <table class="ui very basic table">
- <tbody class="propertiesTable">
-
- </tbody>
- </table>
- <button id="loadPreviewButton" class="ui small fluid basic disabled button" onclick="loadPreview();">Load Preview</button>
- <button id="removeShareButton" class="ui small fluid basic disabled button" style="margin-top: 0.4em;" onclick="removeShare();"><i class="ui red remove icon"></i> Remove Share</button>
- </div>
- <div id="uploadProgressBar">
- <div class="ui small indicating progress" style="margin-bottom: 0px !important; border-radius: 0 !important;">
- <div class="bar" style="background-color: #92cfe7 !important; min-width: 0; border-radius: 0 !important;">
- <div class="progress"></div>
- </div>
- <div class="label">Uploading Files</div>
- </div>
- </div>
- </div>
- <script>
- let currentPath = "/";
- let listDirInitTime = 0;
- let propertiesView = true;
- //Uploads
- let uploadPendingFiles = [];
- let currentlyUploading = false;
- //File operations
- let cutMode = false;
- let cutPendingFilepath = [];
- let copyBuffering = false;
- let copySrcFilename = "";
- let copyPendingFile = undefined;
- //History
- let dirHistory = [];
- $(window).on("resize", function(){
- updateElementSize();
- })
- $(document).ready(function(){
- if (window.location.hash.length > 1){
- let previousPath = window.location.hash.substr(1);
- listDir(previousPath);
- }else{
- listDir("/");
- }
-
- //Add drop events to file view
- var folderView = document.getElementById('folderView');
- // Add event listeners for dragover and drop events
- folderView.addEventListener('dragover', handleDragOver, false);
- folderView.addEventListener('drop', handleFileDrop, false);
- });
- // Event handler for dragover event
- function handleDragOver(event) {
- event.preventDefault();
- event.stopPropagation();
- }
- function handleFileDrop(event) {
- event.preventDefault();
- event.stopPropagation();
- // Get the File object from the dataTransfer object
- var files = event.dataTransfer.files;
- //Push the files into queue
- for (var i = 0; i < files.length; i++) {
- if (files[i].name.indexOf(".") < 0){
- msgbox("Folder upload is not supported", false);
- if (files.length == 1){
- return;
- }
- continue;
- }
- let pathToUpload = currentPath;
- uploadPendingFiles.push({
- "file": files[i],
- "dir": pathToUpload
- });
- }
-
- //Upload the first file
- msgbox("File Upload Started")
- if (!currentlyUploading){
- uploadFileQueue();
- }
-
- }
- function uploadFileQueue(){
- if (uploadPendingFiles.length > 0){
- currentlyUploading = true;
- let nextFileToUpload = uploadPendingFiles.pop();
- handleFile(nextFileToUpload.file, nextFileToUpload.dir, function(){
- msgbox(nextFileToUpload.file.name + " uploaded");
- setTimeout(function(){
- uploadFileQueue();
- }, 300);
- });
- }else{
- msgbox("Upload Queue Completed");
- currentlyUploading = false;
- }
- }
- function deleteFile(){
- if ($(".fileObject.selected").length == 0){
- return;
- }
- if (confirm("Confirm removing " + $(".fileObject.selected").length + " files?")){
- let counter = $(".fileObject.selected").length;
- $(".fileObject.selected").each(function(){
- let thisFilepath = $(this).attr("filepath");
- $.ajax({
- url: "/api/fs/del?target=" + thisFilepath,
- method: "POST",
- success: function(data){
- if (data.error != undefined){
- msgbox(data.error, false);
- }else{
- counter--;
- if (counter == 0){
- //All removed
- msgbox("File(s) Removed");
- refresh();
- }
- }
- }
- });
- });
- }
-
- }
-
- //Check if a extension is code file, remember to trim the first dot
- function isCodeFiles(ext){
- if (ext == "html" || ext == "htm"|| ext == "css"|| ext == "js"|| ext == "json"){
- return true;
- }
- return false;
- }
- function openViaButton(evt){
- if ($(".fileObject.selected").length == 0){
- return;
- }
- let editableCodeFiles = [];
- $(".fileObject.selected").each(function(){
- let ftype = $(this).attr('type');
- let filepath = $(this).attr("filepath");
- let filename = $(this).attr("filename");
- if (ftype != "folder"){
- let ext = filepath.split(".").pop();
- if (isCodeFiles(ext)){
- editableCodeFiles.push({
- "filename": filename,
- "filepath": filepath
- });
- }else{
- openthis($(this), evt);
- }
- }
- });
- if (editableCodeFiles.length > 0){
- let hash = encodeURIComponent(JSON.stringify(editableCodeFiles))
- window.open("notepad/index.html#" + hash);
- }
- }
- function refresh(){
- listDir(currentPath);
- }
- function updatePathDisplay(){
- $("#pathDisplayField").empty();
- $("#pathDisplayField").append(`<div class="section selectable" onclick="jumpToDir('/');"><i class="microchip icon"></i> ESP8266</div><div class="divider">:/</div>`);
- let pathSegments = currentPath;
- if (pathSegments.startsWith("/")){
- pathSegments = pathSegments.substr(1);
- }
- if (pathSegments.endsWith("/")){
- pathSegments = pathSegments.substr(0, pathSegments.length - 1);
- }
- pathSegments = pathSegments.split("/");
- let htmlSegments = [];
- let accumulativeDir = "/";
- pathSegments.forEach(function(segment){
- accumulativeDir = accumulativeDir + segment + "/"
- htmlSegments.push(`<div class="section selectable" onclick="jumpToDir('${accumulativeDir}');">${segment}</div>`);
- });
- $("#pathDisplayField").append(htmlSegments.join(`<div class="divider">/</div>`));
- }
- function cut(){
- if ($(".fileObject.selected").length == 0){
- msgbox("No file selected", false);
- return;
- }
- cutMode = true;
- cutPendingFilepath = [];
- $(".fileObject.selected").each(function(){
- cutPendingFilepath.push({
- filename: $(this).attr("filename"),
- filepath: $(this).attr("filepath")
- });
- });
- msgbox("File Ready to Paste");
- }
- function downloadFile(){
- let selectedFiles = [];
- let filenames = [];
- let containsDir = false;
- $(".fileObject.selected").each(function(){
- if ($(this).attr("type") == "folder"){
- containsDir = true;
- }
- });
- if (containsDir){
- msgbox("Folder download is not supported", false);
- return;
- };
- if ($(".fileObject.selected").length > 0){
- $(".fileObject.selected").each(function(){
- selectedFiles.push($(this).attr("filepath"));
- filenames.push($(this).attr("filename"))
- });
- }
-
- if (selectedFiles.length == 1){
- //Only one file
- //window.open("/api/fs/download?file=" + selectedFiles[0]);
- var file_path = "/api/fs/download?file=" + selectedFiles[0];
- var a = document.createElement('A');
- a.href = file_path;
- a.download = file_path.substr(file_path.lastIndexOf('/') + 1);
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- }else{
- let urls = [];
- selectedFiles.forEach(function(thisFilepath){
- urls.push("/api/fs/download?file=" + thisFilepath);
- });
- msgbox("Zipping might take a few minutes...");
- compressFileToZip(urls, filenames);
- }
-
- }
- function compressFileToZip(urls, filenames) {
- var zip = new JSZip();
- // Create a function to fetch each image and add it to the zip
- var addFileToZip = function (url, filename) {
- return new Promise(function (resolve, reject) {
- var xhr = new XMLHttpRequest();
- xhr.open('GET', url);
- xhr.responseType = 'blob';
- xhr.onload = function () {
- if (xhr.status === 200) {
- zip.file(filename, xhr.response);
- resolve();
- } else {
- reject(Error('Failed to fetch file: ' + url));
- }
- };
- xhr.onerror = function () {
- reject(Error('Error fetching file: ' + url));
- };
- xhr.send();
- });
- };
- // Iterate over each image URL and add it to the zip
- var promises = urls.map(function (url, index) {
- var filename = filenames[index];
- return addFileToZip(url, filename);
- });
- // When all promises are resolved, generate the zip file
- Promise.all(promises).then(function () {
- zip.generateAsync({ type: 'blob' }).then(function (content) {
- // Save the zip file or do something with it
- msgbox("Download Zip Created");
- saveAs(content, 'dl_' + Math.floor(Date.now() / 1000) +'.zip');
- });
- }).catch(function (error) {
- console.error(error);
- });
- }
- function saveAs(blob, filename) {
- if (navigator.msSaveBlob) {
- // For IE and Edge browsers
- navigator.msSaveBlob(blob, filename);
- } else {
- // For other browsers
- var link = document.createElement('a');
- link.href = URL.createObjectURL(blob);
- link.download = filename;
- link.style.display = 'none';
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- }
- }
- //Jump to new directory via path input field
- function jumpToDir(newDir){
- if (newDir == currentPath){
- return;
- }
- listDir(newDir);
- }
- function prevDir(){
- if (dirHistory.length > 0){
- let pathToGo = dirHistory.pop();
- if (pathToGo == currentPath && dirHistory.length > 0){
- //pop again
- pathToGo = dirHistory.pop();
- listDir(pathToGo);
- return;
- }
- listDir(pathToGo, false);
- }
- }
- function listDir(path, recordHistory = true){
- if (path.length > 0 && path.substr(0, 1) != "/"){
- path = "/" + path;
- }
- if (!path.endsWith("/")){
- path = path + "/";
- }
- //Update the current path
- currentPath = path;
- if (recordHistory && currentPath != dirHistory[dirHistory.length - 1]){
- dirHistory.push(currentPath);
- }
- window.location.hash = currentPath;
- updatePathDisplay();
- listDirInitTime = Date.now();
- let validationTimestamp = listDirInitTime;
- $("#folderList").html(`<div class="fileObject item" style="pointer-events: none;">
- <span style="display:inline-block !important;word-break: break-all; width:100%;" class="normal object">
- <i class="loading spinner icon" style="margin-right:12px; color:#grey;"></i> <span class="filename">Loading</span>
- </span>
- </div>`);
- $("#fileList").html("");
- $.ajax({
- url: "/api/fs/list?dir=" + path,
- success: function(data){
- if (validationTimestamp != listDirInitTime){
- //Another refresh is in progress. Skip render.
- return;
- }
- $("#folderList").html("");
- if (data.error != undefined){
- $("#folderList").append(`<div class="ui segment">
- <div class="ui header themed">
- <i class="remove icon" style="display: inline-block;"></i> <span>Error Opening Folder</span>
- <div class="sub header" style="margin-top:12px;">Server return the following error message: <br><code>${data.error.toUpperCase()}</code><br>
- ${new Date().toLocaleString()}</div>
- </div>
- </div>`);
- msgbox(data.error, false);
- }else{
- data.forEach(function(filedata){
- let isDir = filedata.IsDir;
- let filename = filedata.Filename;
- let filesize = filedata.Filesize;
- let shareID = filedata.Share;
-
- if (isDir){
- $("#folderList").append(`<div class="fileObject item" draggable="true" filename="${filename}" filepath="${path + filename}" ondblclick="openthis(this,event);" type="folder">
- <span style="display:inline-block !important;word-break: break-all; width:100%;" class="normal object">
- <i class="folder icon" style="margin-right:12px; color:#eab54e;"></i> <span class="filename">${filename}</span>
- </span>
- </div>`);
- }else{
- let shareIcon = "";
- if (shareID != ""){
- shareIcon = ` <a href="/share?id=${shareID}" target="_blank"><i class="ui green share alternate icon"></i></a>`;
- }
- let extension = "." + filename.split(".").pop();
- let fileIcon = getFileIcon(extension);
- $("#fileList").append(`<div class="fileObject item" isShared="${(shareID!="")?"true":"false"}" draggable="true" filename="${filename}" filepath="${path + filename}" ondblclick="openthis(this,event);" type="file">
- <span style="display:inline-block !important;word-break: break-all; width:100%;" class="normal object">
- <i class="${fileIcon} icon" style="margin-right:12px; color:grey;"></i> <span class="filename">${filename} (${humanFileSize(filesize)}) ${shareIcon}</span>
- </span>
- </div>`);
- }
- });
- }
- $(".fileObject").off("click").on("click", function(e){
- if (!e.ctrlKey) {
- $(".fileObject.selected").removeClass("selected");
- getFileProperties( $(this).attr("filepath"));
- let fileType = $(this).attr('type');
- if (fileType == "folder"){
- $("#propertiesView").find(".preview").find("img").attr("src", "img/folder.svg");
- }else{
- $("#propertiesView").find(".preview").find("img").attr("src", "img/file.svg");
- }
- }
- $(this).addClass("selected");
- });
- sortFileList();
- }
- });
- }
- function openthis(target, event){
- let isDir = ($(target).attr("type") == "folder");
- if (isDir){
- let targetPath = $(target).attr("filepath");
- listDir(targetPath);
- }else{
- let ext = $(target).attr("filepath").split(".").pop();
- if (ext == "txt" || ext == "md"){
- //Open with markdown editor
- let hash = encodeURIComponent(JSON.stringify({
- "filename": $(target).attr("filename"),
- "filepath": $(target).attr("filepath")
- }))
- window.open("mde/index.html#" + hash);
- }else if (ext == "jpg" || ext == "jpeg" || ext == "png" || ext == "gif" || ext == "webp"){
- //Open with photo viewer
- let hash = encodeURIComponent(JSON.stringify({
- "filename": $(target).attr("filename"),
- "filepath": $(target).attr("filepath")
- }))
- window.open("photo.html#" + hash);
- }else if (ext == "mp3" || ext == "aac" || ext == "ogg"){
- //Open with music player
- let hash = encodeURIComponent(JSON.stringify({
- "filename": $(target).attr("filename"),
- "filepath": $(target).attr("filepath")
- }))
- window.open("music.html#" + hash);
- }else if (ext == "mp4" || ext == "webm"){
- //Open with video player
- let hash = encodeURIComponent(JSON.stringify({
- "filename": $(target).attr("filename"),
- "filepath": $(target).attr("filepath")
- }))
- window.open("video.html#" + hash);
- }else if (isCodeFiles(ext)){
- //Open with notepad
- //**Notes the array wrapper in JSON object**
- let hash = encodeURIComponent(JSON.stringify([{
- "filename": $(target).attr("filename"),
- "filepath": $(target).attr("filepath")
- }]))
- window.open("notepad/index.html#" + hash);
- }else{
- window.open("/api/fs/download?file=" + $(target).attr("filepath") + "&preview=true");
- }
-
- }
- }
- function isFilenameValid(filename) {
- // Split the filename into the name and extension parts
- var name = filename.slice(0, filename.lastIndexOf('.'));
- var extension = filename.slice(filename.lastIndexOf('.') + 1);
- // Check if the name and extension lengths are within the limits
- if (name.length <= 8 && extension.length <= 3) {
- return true;
- } else {
- return false;
- }
- }
- function newFile(){
- var fileName = window.prompt("Name for new file: ", "file.txt");
- if (fileName.indexOf("/") >= 0){
- //Contains /. Reject
- msgbox("File name cannot contain path seperator", false);
- return;
- }
- if (fileName.indexOf(".") == -1){
- msgbox("Missing file extension")
- return
- }
- let filenameOnly = fileName.split(".");
- let ext = filenameOnly.pop();
- filenameOnly = filenameOnly.join(".");
- /*
- //Skip checking as modern FAT32 file systems can store as LONG FILENAME
- if (filenameOnly.length > 8){
- msgbox("File name too long (8 char max)", false);
- return;
- }
- if (ext.length > 3){
- msgbox("File extension too long (3 char max)", false);
- return
- }
- */
- //OK! Create the file
- const blob = new Blob(["\n"], { type: 'text/plain' });
- const file = new File([blob], fileName);
- handleFile(file, currentPath, function(){
- msgbox("New File Created");
- });
- }
- function newFolder(){
- var folderName = window.prompt("Name for new folder (8 char max): ", "Folder");
- if (folderName.indexOf("/") >= 0){
- //Contains /. Reject
- msgbox("Folder name cannot contain path seperator", false);
- return;
- }
- if (folderName.length > 8){
- msgbox("Folder name too long (8 char max)", false);
- return;
- }
- $.post("/api/fs/newFolder?path=" + currentPath + folderName, function(data){
- if (data.error != undefined){
- msgbox(data.error, false);
- }else{
- msgbox("Folder Created");
- refresh();
- }
- });
- }
- function rename(){
- if ($(".fileObject.selected").length > 1){
- //Too many objects
- }else if ($(".fileObject.selected").length == 1){
- var oldName = $(".fileObject.selected").attr("filename");
- var oldPath = $(".fileObject.selected").attr("filepath");
- var newName = window.prompt("Rename " + oldName + " to: ", oldName);
- if (newName.indexOf("/") >= 0){
- //Contains /. Reject
- msgbox("File name cannot contain path seperator", false);
- return;
- }
- /*
- //FAT32 allows filename longer than 8.3
- if (!isFilenameValid(newName)){
- msgbox("File name too long (8 char max)", false);
- return;
- }
- */
-
- if (newName && newName != oldName) {
- // User entered a new name, perform renaming logic here
- console.log(oldPath, currentPath + newName);
- $.ajax({
- url: "/api/fs/move?src=" + oldPath + "&dest=" + currentPath + newName,
- method: "POST",
- success: function(data){
- if (data.error != undefined){
- msgbox(data.error, false);
- }else{
- msgbox("File renamed");
- refresh();
- }
- }
- })
- }
- }
- }
- function getFileIcon(extension) {
- const textExtensions = [".md", ".txt"];
- const codeExtensions = [".js", ".json", ".css", ".html", ".htm"];
- const musicExtensions = [".mp3", ".aac", ".ogg", ".wav"];
- const videoExtensions = [".mp4", ".m4v", ".webm"];
- const photoExtensions = [".png", ".gif", ".jpg", ".ico", ".svg"];
-
- if (textExtensions.includes(extension)) {
- return "file alternate outline";
- } else if (codeExtensions.includes(extension)) {
- return "black file code outline";
- } else if (musicExtensions.includes(extension)) {
- return "blue music";
- } else if (videoExtensions.includes(extension)) {
- return "red video";
- } else if (photoExtensions.includes(extension)) {
- return "green image outline";
- } else {
- return "file outline";
- }
- }
- function isPreviewable(ext){
- let previeableFiles = [".png", ".gif", ".jpg", ".ico", ".svg"];
- return previeableFiles.includes(ext);
- }
- function getFileProperties(filepath){
- $.get("/api/fs/properties?file=" + filepath, function(data){
- if (data.error != undefined){
- msgbox(data.error, false);
- return;
- }
- $("#propertiesView").find(".filename").text(data.filename);
- $("#propertiesView").find(".vpath").text(data.filepath);
- let propTable = $("#propertiesView").find(".propertiesTable");
- let styleOverwrite = `min-width: 4em;`;
- $(propTable).html("");
- $(propTable).append(`<tr>
- <td style="${styleOverwrite}">
- File Size
- </td>
- <td>
- ${bytesToSize(data.filesize)}
- </td>
- </tr><tr>
- <td style="${styleOverwrite}">
- Disk Path
- </td>
- <td style="word-break: break-all;">
- /www${data.filepath}
- </td>
- </tr>
- <tr>
- <td style="${styleOverwrite}">
- Share ID
- </td>
- <td style="word-break: break-all;">
- ${(data.shareid=="" || data.shareid == undefined)?`File Not Shared`:`<a href="/share?id=${data.shareid}" target="_blank">${data.shareid}</a>`}
- </td>
- </tr>
- <tr>
- <td style="${styleOverwrite}">
- Folder
- </td>
- <td style="word-break: break-all;">
- ${data.isDir?`<i class="ui green check icon"></i>`:`<i class="ui red times icon"></i>`}
- </td>
- </tr>`);
- if (data.isDir){
- $(propTable).append(`<tr>
- <td style="${styleOverwrite}">
- Files #
- </td>
- <td>
- ${data.fileCounts}
- </td>
- </tr><tr>
- <td style="${styleOverwrite}">
- Folders #
- </td>
- <td style="word-break: break-all;">
- ${data.folderCounts}
- </td>
- </tr>`);
- //Folder is not previewable
- $("#propertiesView").find(".preview").find("img").attr("xsrc", "");
- $("#loadPreviewButton").addClass("disabled");
- }else{
- let ext = data.filepath.split(".").pop();
- if (isPreviewable("." + ext)){
- $("#propertiesView").find(".preview").find("img").attr("xsrc", "/api/fs/download?preview=true&file=" + data.filepath);
- $("#loadPreviewButton").removeClass("disabled");
- }else{
- $("#propertiesView").find(".preview").find("img").attr("xsrc", "");
- $("#loadPreviewButton").addClass("disabled");
- }
- if (data.shareid!=""){
- $("#removeShareButton").removeClass('disabled');
- }else{
- $("#removeShareButton").addClass('disabled');
- }
-
- }
- })
- }
- function loadPreview(){
- let readyToLoadSrc = $("#propertiesView").find(".preview").find("img").attr("xsrc");
- if (readyToLoadSrc == undefined || readyToLoadSrc == "" ){
- }else{
- let ext = readyToLoadSrc.split(".").pop();
- $("#propertiesView").find(".preview").find("img").show();
- $("#propertiesView").find(".preview").find("audio").hide();
- $("#propertiesView").find(".preview").find("img").attr("src", readyToLoadSrc);
- }
- }
- function bytesToSize(bytes) {
- var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
- if (bytes == 0) return '0 Byte';
- var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
- return Math.round(bytes / Math.pow(1024, i) * 100, 2) / 100 + ' ' + sizes[i];
- }
- function getParentDirectory(path) {
- // Remove any trailing slashes
- if (path.endsWith("/")){
- path = path.substr(0, path.length - 1);
- }
- // Find the last index of the slash character
- var lastIndex = path.lastIndexOf('/');
- // Extract the parent directory substring
- var parentDir = path.substring(0, lastIndex);
- return parentDir;
- }
- function parentDir(){
- if (currentPath.indexOf("/") >= 0 && currentPath != "/"){
- let parentPath = getParentDirectory(currentPath);
- listDir(parentPath);
- }else{
- //already top
- }
- }
- function humanFileSize(bytes, si=false, dp=1) {
- const thresh = si ? 1000 : 1024;
- if (Math.abs(bytes) < thresh) {
- return bytes + ' B';
- }
- const units = si
- ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
- : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
- let u = -1;
- const r = 10**dp;
- do {
- bytes /= thresh;
- ++u;
- } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
- return bytes.toFixed(dp) + ' ' + units[u];
- }
- function updateElementSize(){
- $("#mainWindow").css("height", window.innerHeight - $("#navibar").height() + "px");
- }
- updateElementSize();
- function upload() {
- // Create a file input element
- var fileInput = $('<input>').attr('type', 'file');
- // Trigger the file selector dialog
- fileInput.trigger('click');
- // Handle the selected file using a callback event handler
- fileInput.change(function(e) {
- var file = e.target.files[0];
- if (currentlyUploading){
- //Already tasks uploading in the background. Add it to queue
- uploadPendingFiles.push({
- "file": file,
- "dir": currentPath
- });
- msgbox("File Added to Upload Queue");
- return;
- }
- // Pass the file object to the callback event handler
- msgbox("File Upload Started")
- handleFile(file, currentPath, function(){
- msgbox("Upload Completed");
- });
- });
- }
- function handleFile(file, dir=currentPath, callback=undefined) {
- // Perform actions with the selected file
- $("#pasteButton").addClass("disabled");
- console.log('Selected file:', file);
- var formdata = new FormData();
- formdata.append("file1", file);
- var ajax = new XMLHttpRequest();
- ajax.upload.addEventListener("progress", progressHandler, false);
- ajax.addEventListener("load", function(event){
- let responseText = event.target.responseText;
- try{
- responseText = JSON.parse(responseText);
- if (responseText.error != undefined){
- alert(responseText.error);
- }
- }catch(ex){
- }
- completeHandler(event, dir==currentPath);
- $("#pasteButton").removeClass("disabled");
- if (callback != undefined){
- callback();
- }
- }, false); // doesnt appear to ever get called even upon success
- ajax.addEventListener("error", errorHandler, false);
- ajax.addEventListener("abort", abortHandler, false);
- ajax.open("POST", "/upload?dir=" + dir);
- ajax.send(formdata);
- }
- function progressHandler(event) {
- //_("loaded_n_total").innerHTML = "Uploaded " + event.loaded + " bytes of " + event.total; // event.total doesnt show accurate total file size
-
- var percent = (event.loaded / event.total) * 100;
- $("#uploadProgressBar").find(".bar").css("width", Math.round(percent) + "%");
- console.log("Uploaded " + event.loaded + " bytes => " + percent +"%");
- if (percent >= 100) {
- $("#uploadProgressBar").find(".bar").css("width", "100%");
- //_("status").innerHTML = "Please wait, writing file to filesystem";
- }
- }
- function completeHandler(event, requireRefresh=true) {
- $("#uploadProgressBar").find(".bar").css("width", "0%");
- if(requireRefresh){
- refresh();
- }
- }
- function errorHandler(event) {
- msgbox("Upload Failed", false);
- $("#pasteButton").removeClass("disabled");
- }
- function abortHandler(event) {
- msgbox("Upload Aborted", false);
- $("#pasteButton").removeClass("disabled");
- }
- function msgbox(message, succ=true){
- function capitalizeFirstLetter(string) {
- return string.charAt(0).toUpperCase() + string.slice(1);
- }
- message = capitalizeFirstLetter(message);
- if (succ){
- $(".msgbox").find(".showicon").attr("class", "green circle check icon showicon");
- }else{
- $(".msgbox").find(".showicon").attr("class", "red circle times icon showicon");
- }
- $(".msgbox").find("span").text(message);
- $(".msgbox").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
- }
- //Copy file
- function copy(){
- if ($(".fileObject.selected").length == 0){
- //No file selected
- msgbox("No file selected", false);
- return;
- }
- if ($(".fileObject.selected").length > 1){
- msgbox("WebStick can only support 1 file copy at a time", false);
- return;
- }
- if ($(".fileObject.selected").attr('type') == "folder"){
- msgbox("Folder copy is not supported", false);
- return;
- }
- let url = "/api/fs/download?file=" + $(".fileObject.selected").attr("filepath");
- let filename = $(".fileObject.selected").attr("filename");
-
- cutMode = false;
- copyBuffering = true;
- copySrcFilename = filename;
- console.log("Buffering " + filename + " from " + url);
- msgbox("Allocating memory for copy...")
- $("#pasteButton").addClass("disabled");
- $("#copyButton").addClass("disabled");
- // Fetch the content from the URL
- fetch(url).then(response => response.blob()).then(blob => {
- // Create a new File object from the Blob
-
- copyPendingFile = (blob);
- msgbox("File Ready to Paste");
- $("#pasteButton").removeClass("disabled");
- $("#copyButton").removeClass("disabled");
- copyBuffering = false;
- }).catch(error => {
- msgbox("Copy Allocation Failed", false);
- $("#pasteButton").removeClass("disabled");
- $("#copyButton").removeClass("disabled");
- copyBuffering = false;
- });
- }
- function fileExistsInThisFolder(filename){
- let exists = false;
- $(".fileObject").each(function(){
- if ($(this).attr("filename") == filename){
- exists = true;
- }
- });
- return exists;
- }
- function paste(){
- if (cutMode){
- let remainingFilesCounter = cutPendingFilepath.length;
- console.log("Moving " , cutPendingFilepath);
- cutPendingFilepath.forEach(fileToPaste => {
- let filename = fileToPaste.filename;
- let filepath = fileToPaste.filepath;
- $.ajax({
- url: "/api/fs/move?src=" + filepath + "&dest=" + currentPath + filename,
- method: "POST",
- success: function(data){
- if (data.error != undefined){
- msgbox(data.error)
- }else{
- remainingFilesCounter--;
- if (remainingFilesCounter == 0){
- msgbox("File Move Completed");
- refresh();
- }
- }
- }
- })
- });
- }else{
- //Copy and Paste
- if (copyBuffering){
- msgbox("Copy buffer allocation in progress", false);
- return;
- }
-
- let pasteFilename = copySrcFilename;
- let counter = 1;
- while(fileExistsInThisFolder(pasteFilename)){
- let nameOnly = copySrcFilename.split(".");
- let ext = nameOnly.pop();
- nameOnly = nameOnly.join(".");
- pasteFilename = nameOnly + "-" + counter + "." + ext;
- counter++;
- }
-
- //Change the name if there is name clash
- const file = new File([copyPendingFile], pasteFilename);
- //Upload the file
- msgbox("Writing file to SD card...");
- handleFile(file, currentPath, function(){
- msgbox("File Pasted");
- });
- }
- }
- function sortFileList(){
- sortFileObjects("folderList");
- sortFileObjects("fileList");
- }
- function sortFileObjects(listSelector) {
- const fileObjects = document.querySelectorAll(`#${listSelector} .fileObject`)
- // Convert the NodeList to an array for sorting
- const fileObjectsArray = Array.from(fileObjects);
- // Sort the elements based on their text content
- fileObjectsArray.sort((a, b) => {
- const textA = a.querySelector('.filename').textContent.toLowerCase();
- const textB = b.querySelector('.filename').textContent.toLowerCase();
- return textA.localeCompare(textB);
- });
- // Reorder the elements in the DOM
- const fileList = document.getElementById(listSelector);
- fileObjectsArray.forEach((fileObject) => {
- fileList.appendChild(fileObject);
- });
- }
- function togglePropertiesView(object){
- propertiesView = !propertiesView;
- if (propertiesView){
- $("#propertiesView").show();
- $(object).addClass('active');
- localStorage.setItem("file_explorer/viewProperties", "true");
- if ($(".fileObject.selected").length >= 1){
- //Load the file properties
- let targetFile = getFileObjectFromFID(lastClickedFileID);
- if (targetFile == null){
- targetFile = $(".fileObject.selected")[0];
- }
- let filepath = $(targetFile).attr("filepath");
- loadFileProperties(filepath);
- }
- }else{
- $("#propertiesView").hide();
- $(object).removeClass('active');
- localStorage.setItem("file_explorer/viewProperties", "false");
- }
- }
- // Bind the onDeleteKeyPress() function to the document's keydown event
- $(document).on("keydown", function(evt){
- if (event.ctrlKey) {
- // Check for Ctrl key combinations
- if (event.keyCode == 67) {
- // Ctrl + C
- evt.preventDefault();
- copy();
- } else if (event.keyCode == 86) {
- // Ctrl + V
- evt.preventDefault();
- paste();
- } else if (event.keyCode == 88) {
- // Ctrl + X
- evt.preventDefault();
- cut();
- }
- } else {
- if (event.keyCode == 46) {
- //Delete
- evt.preventDefault();
- deleteFile();
- }else if (event.keyCode == 13){
- //Enter
- evt.preventDefault();
- $(".fileObject.selected").each(function(e){
- openthis($(this), e);
- });
-
- }
- }
- });
- //Share file API
- function shareFile(){
- if ($(".fileObject.selected").length > 1){
- alert("File Share only support single file per share");
- }else if ($(".fileObject.selected").length == 1){
- var fileType = $(".fileObject.selected").attr("type");
- if (fileType == "folder"){
- alert("Folder sharing is not supported");
- return;
- }
- //Create an share entry for this file.
- var filename = $(".fileObject.selected").attr("filename");
- var filepath = $(".fileObject.selected").attr("filepath");
- $.post("/api/share/new?filename=" + filepath, function(data){
- if (data.error != undefined){
- alert(data.error);
- }else{
- refresh();
- getFileProperties(filepath);
- msgbox("File Shared");
- }
- });
- }
- }
- function removeShare(){
- let fileVpath = $("#fileVpath").text();
- $.post("/api/share/del?filename=" + fileVpath, function(data){
- if (data.error != undefined){
- alert(data.error);
- }else{
- refresh();
- getFileProperties(fileVpath);
- msgbox("Share Removed");
- }
- });
- }
- </script>
- </body>
- </html>
|