fs.html 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057
  1. <html>
  2. <head>
  3. <title>File Manager</title>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
  6. <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
  7. <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" />
  8. <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.js"></script>
  9. <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.6.0/jszip.min.js"></script>
  10. <link rel="stylesheet" href="fs.css">
  11. <script>
  12. </script>
  13. <style>
  14. body{
  15. background-color: white;
  16. }
  17. #uploadProgressBar{
  18. position: fixed;
  19. bottom: 0;
  20. left: 0;
  21. width: 100%;
  22. }
  23. .fileOprBtn.disabled{
  24. opacity: 0.5;
  25. user-select: none;
  26. pointer-events: none;
  27. }
  28. </style>
  29. </head>
  30. <body class="whiteTheme">
  31. <div id="navibar" class="navibar">
  32. <!-- File Opr Group-->
  33. <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>
  34. <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>
  35. <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>
  36. <div class="fileoprGroupDivider" style="display: inline-block; vertical-align: top;">
  37. <button class="fileoprSmallBtn" title="Refresh" onclick="refresh();"><i class="green refresh icon"></i> <span locale="fileopr/Refresh">Refresh</span></button><br>
  38. <button class="fileoprSmallBtn" title="Cut" onclick="cut();"><i class="blue cut icon"></i> <span locale="fileopr/Cut">Cut</span></button><br>
  39. <button class="fileoprSmallBtn" title="Rename" onclick="rename();"><i class="teal i cursor icon"></i> <span locale="fileopr/Rename">Rename</span></button>
  40. </div>
  41. <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>
  42. <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>
  43. <div class="fileoprGroupDivider" style="display: inline-block; vertical-align: top;"></div>
  44. <div class="fileoprGroupDivider" style="display: inline-block; vertical-align: top;">
  45. <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>
  46. <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>
  47. <button class="fileoprSmallBtn" title="Delete" onclick="deleteFile();"><i class="red times icon"></i> <span locale="fileopr/Delete">Delete</span></button><br>
  48. </div>
  49. <br>
  50. <!-- Directoy navigations -->
  51. <div class="addressBar">
  52. <button id="prevDir" class="navibarBtn" onclick="prevDir();" title="Back"><i class="left arrow icon"></i></button>
  53. <button id="ppbtn" class="navibarBtn" onclick="parentDir();" title="Parent Folder"><i class="up arrow icon"></i></button>
  54. <div id="pathDisplayField" class="ui breadcrumb addressText pathDisplay desktopOnly" >
  55. </div>
  56. <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>
  57. </div>
  58. <div class="msgbox" style="z-index:999; display:none; padding-bottom: 1em;">
  59. <i class="checkmark icon showicon"></i> <span style="word-break: break-all;">No Message</span>
  60. <div class="closeMsgButton" onclick="$(this).parent().stop().slideUp('fast');"><i class="caret down icon"></i></div>
  61. </div>
  62. </div>
  63. <div id="mainWindow">
  64. <div id="folderView" style="height: 100%;">
  65. <div id="folderList" class="fileviewList">
  66. </div>
  67. <div id="fileList" class="fileviewList">
  68. </div>
  69. <br>
  70. </div>
  71. <div id="propertiesView" class="small">
  72. <h3 class="ui header" style="margin-top: 0.4em;">
  73. <span class="filename" style="word-break: break-all;" locale="sidebar/default/nofileselected">No File Selected</span>
  74. <div class="sub header vpath" style="word-break: break-all;" locale="sidebar/default/instruction">Select a file to view file properties</div>
  75. </h3>
  76. <table class="ui very basic table">
  77. <tbody class="propertiesTable">
  78. </tbody>
  79. </table>
  80. </div>
  81. <div id="uploadProgressBar">
  82. <div class="ui small indicating progress" style="margin-bottom: 0px !important; border-radius: 0 !important;">
  83. <div class="bar" style="background-color: #92cfe7 !important; min-width: 0; border-radius: 0 !important;">
  84. <div class="progress"></div>
  85. </div>
  86. <div class="label">Uploading Files</div>
  87. </div>
  88. </div>
  89. </div>
  90. <script>
  91. let currentPath = "/";
  92. let listDirInitTime = 0;
  93. let propertiesView = true;
  94. //Uploads
  95. let uploadPendingFiles = [];
  96. let currentlyUploading = false;
  97. //File operations
  98. let cutMode = false;
  99. let cutPendingFilepath = [];
  100. let copyPendingFiles = [];
  101. //History
  102. let dirHistory = [];
  103. $(window).on("resize", function(){
  104. updateElementSize();
  105. })
  106. $(document).ready(function(){
  107. if (window.location.hash.length > 1){
  108. let previousPath = window.location.hash.substr(1);
  109. listDir(previousPath);
  110. }else{
  111. listDir("/");
  112. }
  113. //Add drop events to file view
  114. var folderView = document.getElementById('folderView');
  115. // Add event listeners for dragover and drop events
  116. folderView.addEventListener('dragover', handleDragOver, false);
  117. folderView.addEventListener('drop', handleFileDrop, false);
  118. });
  119. // Event handler for dragover event
  120. function handleDragOver(event) {
  121. event.preventDefault();
  122. event.stopPropagation();
  123. }
  124. function handleFileDrop(event) {
  125. event.preventDefault();
  126. event.stopPropagation();
  127. // Get the File object from the dataTransfer object
  128. var files = event.dataTransfer.files;
  129. //Push the files into queue
  130. for (var i = 0; i < files.length; i++) {
  131. if (files[i].name.indexOf(".") < 0){
  132. msgbox("Folder upload is not supported", false);
  133. if (files.length == 1){
  134. return;
  135. }
  136. continue;
  137. }
  138. let pathToUpload = currentPath;
  139. uploadPendingFiles.push({
  140. "file": files[i],
  141. "dir": pathToUpload
  142. });
  143. }
  144. //Upload the first file
  145. msgbox("File Upload Started")
  146. if (!currentlyUploading){
  147. uploadFileQueue();
  148. }
  149. }
  150. function uploadFileQueue(){
  151. if (uploadPendingFiles.length > 0){
  152. currentlyUploading = true;
  153. let nextFileToUpload = uploadPendingFiles.pop();
  154. handleFile(nextFileToUpload.file, nextFileToUpload.dir, function(){
  155. msgbox(nextFileToUpload.file.name + " uploaded");
  156. setTimeout(function(){
  157. uploadFileQueue();
  158. }, 300);
  159. });
  160. }else{
  161. msgbox("Upload Queue Completed");
  162. currentlyUploading = false;
  163. }
  164. }
  165. function deleteFile(){
  166. if ($(".fileObject.selected").length == 0){
  167. return;
  168. }
  169. if (confirm("Confirm removing " + $(".fileObject.selected").length + " files?")){
  170. let counter = $(".fileObject.selected").length;
  171. $(".fileObject.selected").each(function(){
  172. let thisFilepath = $(this).attr("filepath");
  173. $.ajax({
  174. url: "/api/fs/del?target=" + thisFilepath,
  175. method: "POST",
  176. success: function(data){
  177. if (data.error != undefined){
  178. msgbox(data.error, false);
  179. }else{
  180. counter--;
  181. if (counter == 0){
  182. //All removed
  183. msgbox("File(s) Removed");
  184. refresh();
  185. }
  186. }
  187. }
  188. })
  189. });
  190. }
  191. }
  192. //Check if a extension is code file, remember to trim the first dot
  193. function isCodeFiles(ext){
  194. if (ext == "html" || ext == "htm"|| ext == "css"|| ext == "js"|| ext == "json"){
  195. return true;
  196. }
  197. return false;
  198. }
  199. function openViaButton(evt){
  200. if ($(".fileObject.selected").length == 0){
  201. return;
  202. }
  203. let editableCodeFiles = [];
  204. $(".fileObject.selected").each(function(){
  205. let ftype = $(this).attr('type');
  206. let filepath = $(this).attr("filepath");
  207. let filename = $(this).attr("filename");
  208. if (ftype != "folder"){
  209. let ext = filepath.split(".").pop();
  210. if (isCodeFiles(ext)){
  211. editableCodeFiles.push({
  212. "filename": filename,
  213. "filepath": filepath
  214. });
  215. }else{
  216. openthis($(this), evt);
  217. }
  218. }
  219. });
  220. if (editableCodeFiles.length > 0){
  221. let hash = encodeURIComponent(JSON.stringify(editableCodeFiles))
  222. window.open("notepad/index.html#" + hash);
  223. }
  224. }
  225. function refresh(){
  226. listDir(currentPath);
  227. }
  228. function updatePathDisplay(){
  229. $("#pathDisplayField").empty();
  230. $("#pathDisplayField").append(`<div class="section selectable" onclick="jumpToDir('/');"><i class="folder icon"></i> Zoraxy</div><div class="divider">:/</div>`);
  231. let pathSegments = currentPath;
  232. if (pathSegments.startsWith("/")){
  233. pathSegments = pathSegments.substr(1);
  234. }
  235. if (pathSegments.endsWith("/")){
  236. pathSegments = pathSegments.substr(0, pathSegments.length - 1);
  237. }
  238. pathSegments = pathSegments.split("/");
  239. let htmlSegments = [];
  240. let accumulativeDir = "/";
  241. pathSegments.forEach(function(segment){
  242. accumulativeDir = accumulativeDir + segment + "/"
  243. htmlSegments.push(`<div class="section selectable" onclick="jumpToDir('${accumulativeDir}');">${segment}</div>`);
  244. });
  245. $("#pathDisplayField").append(htmlSegments.join(`<div class="divider">/</div>`));
  246. }
  247. function cut(){
  248. if ($(".fileObject.selected").length == 0){
  249. msgbox("No file selected", false);
  250. return;
  251. }
  252. cutMode = true;
  253. cutPendingFilepath = [];
  254. $(".fileObject.selected").each(function(){
  255. cutPendingFilepath.push({
  256. filename: $(this).attr("filename"),
  257. filepath: $(this).attr("filepath")
  258. });
  259. });
  260. msgbox("File Ready to Paste");
  261. }
  262. function downloadFile(){
  263. let selectedFiles = [];
  264. let filenames = [];
  265. let containsDir = false;
  266. $(".fileObject.selected").each(function(){
  267. if ($(this).attr("type") == "folder"){
  268. containsDir = true;
  269. }
  270. });
  271. if (containsDir){
  272. msgbox("Folder download is not supported", false);
  273. return;
  274. };
  275. if ($(".fileObject.selected").length > 0){
  276. $(".fileObject.selected").each(function(){
  277. selectedFiles.push($(this).attr("filepath"));
  278. filenames.push($(this).attr("filename"))
  279. });
  280. }
  281. if (selectedFiles.length == 1){
  282. //Only one file
  283. //window.open("/api/fs/download?file=" + selectedFiles[0]);
  284. var file_path = "/api/fs/download?file=" + selectedFiles[0];
  285. var a = document.createElement('A');
  286. a.href = file_path;
  287. a.download = file_path.substr(file_path.lastIndexOf('/') + 1);
  288. document.body.appendChild(a);
  289. a.click();
  290. document.body.removeChild(a);
  291. }else{
  292. let urls = [];
  293. selectedFiles.forEach(function(thisFilepath){
  294. urls.push("/api/fs/download?file=" + thisFilepath);
  295. });
  296. msgbox("Zipping might take a few minutes...");
  297. compressFileToZip(urls, filenames);
  298. }
  299. }
  300. function compressFileToZip(urls, filenames) {
  301. var zip = new JSZip();
  302. // Create a function to fetch each image and add it to the zip
  303. var addFileToZip = function (url, filename) {
  304. return new Promise(function (resolve, reject) {
  305. var xhr = new XMLHttpRequest();
  306. xhr.open('GET', url);
  307. xhr.responseType = 'blob';
  308. xhr.onload = function () {
  309. if (xhr.status === 200) {
  310. zip.file(filename, xhr.response);
  311. resolve();
  312. } else {
  313. reject(Error('Failed to fetch file: ' + url));
  314. }
  315. };
  316. xhr.onerror = function () {
  317. reject(Error('Error fetching file: ' + url));
  318. };
  319. xhr.send();
  320. });
  321. };
  322. // Iterate over each image URL and add it to the zip
  323. var promises = urls.map(function (url, index) {
  324. var filename = filenames[index];
  325. return addFileToZip(url, filename);
  326. });
  327. // When all promises are resolved, generate the zip file
  328. Promise.all(promises).then(function () {
  329. zip.generateAsync({ type: 'blob' }).then(function (content) {
  330. // Save the zip file or do something with it
  331. msgbox("Download Zip Created");
  332. saveAs(content, 'dl_' + Math.floor(Date.now() / 1000) +'.zip');
  333. });
  334. }).catch(function (error) {
  335. console.error(error);
  336. });
  337. }
  338. function saveAs(blob, filename) {
  339. if (navigator.msSaveBlob) {
  340. // For IE and Edge browsers
  341. navigator.msSaveBlob(blob, filename);
  342. } else {
  343. // For other browsers
  344. var link = document.createElement('a');
  345. link.href = URL.createObjectURL(blob);
  346. link.download = filename;
  347. link.style.display = 'none';
  348. document.body.appendChild(link);
  349. link.click();
  350. document.body.removeChild(link);
  351. }
  352. }
  353. //Jump to new directory via path input field
  354. function jumpToDir(newDir){
  355. if (newDir == currentPath){
  356. return;
  357. }
  358. listDir(newDir);
  359. }
  360. function prevDir(){
  361. if (dirHistory.length > 0){
  362. let pathToGo = dirHistory.pop();
  363. if (pathToGo == currentPath && dirHistory.length > 0){
  364. //pop again
  365. pathToGo = dirHistory.pop();
  366. listDir(pathToGo);
  367. return;
  368. }
  369. listDir(pathToGo, false);
  370. }
  371. }
  372. function listDir(path, recordHistory = true){
  373. if (path.length > 0 && path.substr(0, 1) != "/"){
  374. path = "/" + path;
  375. }
  376. if (!path.endsWith("/")){
  377. path = path + "/";
  378. }
  379. //Update the current path
  380. currentPath = path;
  381. if (recordHistory && currentPath != dirHistory[dirHistory.length - 1]){
  382. dirHistory.push(currentPath);
  383. }
  384. window.location.hash = currentPath;
  385. updatePathDisplay();
  386. listDirInitTime = Date.now();
  387. let validationTimestamp = listDirInitTime;
  388. $("#folderList").html(`<div class="fileObject item" style="pointer-events: none;">
  389. <span style="display:inline-block !important;word-break: break-all; width:100%;" class="normal object">
  390. <i class="loading spinner icon" style="margin-right:12px; color:#grey;"></i> <span class="filename">Loading</span>
  391. </span>
  392. </div>`);
  393. $("#fileList").html("");
  394. $.ajax({
  395. url: "/api/fs/list?dir=" + path,
  396. success: function(data){
  397. if (validationTimestamp != listDirInitTime){
  398. //Another refresh is in progress. Skip render.
  399. return;
  400. }
  401. $("#folderList").html("");
  402. if (data.error != undefined){
  403. $("#folderList").append(`<div class="ui segment">
  404. <div class="ui header themed">
  405. <i class="remove icon" style="display: inline-block;"></i> <span>Error Opening Folder</span>
  406. <div class="sub header" style="margin-top:12px;">Server return the following error message: <br><code>${data.error.toUpperCase()}</code><br>
  407. ${new Date().toLocaleString()}</div>
  408. </div>
  409. </div>`);
  410. msgbox(data.error, false);
  411. }else{
  412. data.forEach(function(filedata){
  413. let isDir = filedata.isDir;
  414. let filename = filedata.filename;
  415. let filesize = filedata.size;
  416. if (isDir){
  417. $("#folderList").append(`<div class="fileObject item" draggable="true" filename="${filename}" filepath="${path + filename}" ondblclick="openthis(this,event);" type="folder">
  418. <span style="display:inline-block !important;word-break: break-all; width:100%;" class="normal object">
  419. <i class="folder icon" style="margin-right:12px; color:#eab54e;"></i> <span class="filename">${filename}</span>
  420. </span>
  421. </div>`);
  422. }else{
  423. let extension = "." + filename.split(".").pop();
  424. let fileIcon = getFileIcon(extension);
  425. $("#fileList").append(`<div class="fileObject item" draggable="true" filename="${filename}" filepath="${path + filename}" ondblclick="openthis(this,event);" type="file">
  426. <span style="display:inline-block !important;word-break: break-all; width:100%;" class="normal object">
  427. <i class="${fileIcon} icon" style="margin-right:12px; color:grey;"></i> <span class="filename">${filename} (${humanFileSize(filesize)})</span>
  428. </span>
  429. </div>`);
  430. }
  431. });
  432. }
  433. $(".fileObject").off("click").on("click", function(e){
  434. if (!e.ctrlKey) {
  435. $(".fileObject.selected").removeClass("selected");
  436. getFileProperties( $(this).attr("filepath"));
  437. let fileType = $(this).attr('type');
  438. if (fileType == "folder"){
  439. $("#propertiesView").find(".preview").find("img").attr("src", "img/folder.svg");
  440. }else{
  441. $("#propertiesView").find(".preview").find("img").attr("src", "img/file.svg");
  442. }
  443. }
  444. $(this).addClass("selected");
  445. });
  446. sortFileList();
  447. }
  448. });
  449. }
  450. function openthis(target, event){
  451. let isDir = ($(target).attr("type") == "folder");
  452. if (isDir){
  453. let targetPath = $(target).attr("filepath");
  454. listDir(targetPath);
  455. }else{
  456. let ext = $(target).attr("filepath").split(".").pop();
  457. window.open("/api/fs/download?file=" + $(target).attr("filepath") + "&preview=true");
  458. }
  459. }
  460. function isFilenameValid(filename) {
  461. // Split the filename into the name and extension parts
  462. var name = filename.slice(0, filename.lastIndexOf('.'));
  463. var extension = filename.slice(filename.lastIndexOf('.') + 1);
  464. // Check if the name and extension lengths are within the limits
  465. if (name.length <= 8 && extension.length <= 3) {
  466. return true;
  467. } else {
  468. return false;
  469. }
  470. }
  471. function newFile(){
  472. var fileName = window.prompt("Name for new file: ", "file.txt");
  473. if (fileName.indexOf("/") >= 0){
  474. //Contains /. Reject
  475. msgbox("File name cannot contain path seperator", false);
  476. return;
  477. }
  478. if (fileName.indexOf(".") == -1){
  479. msgbox("Missing file extension")
  480. return
  481. }
  482. let filenameOnly = fileName.split(".");
  483. let ext = filenameOnly.pop();
  484. filenameOnly = filenameOnly.join(".");
  485. //OK! Create the file
  486. const blob = new Blob(["\n"], { type: 'text/plain' });
  487. const file = new File([blob], fileName);
  488. handleFile(file, currentPath, function(){
  489. msgbox("New File Created");
  490. });
  491. }
  492. function newFolder(){
  493. var folderName = window.prompt("Name for new folder: ", "Folder");
  494. if (folderName.indexOf("/") >= 0){
  495. //Contains /. Reject
  496. msgbox("Folder name cannot contain path seperator", false);
  497. return;
  498. }
  499. $.post("/api/fs/newFolder?path=" + currentPath + folderName, function(data){
  500. if (data.error != undefined){
  501. msgbox(data.error, false);
  502. }else{
  503. msgbox("Folder Created");
  504. refresh();
  505. }
  506. });
  507. }
  508. function rename(){
  509. if ($(".fileObject.selected").length > 1){
  510. //Too many objects
  511. }else if ($(".fileObject.selected").length == 1){
  512. var oldName = $(".fileObject.selected").attr("filename");
  513. var oldPath = $(".fileObject.selected").attr("filepath");
  514. var newName = window.prompt("Rename " + oldName + " to: ", oldName);
  515. if (newName.indexOf("/") >= 0){
  516. //Contains /. Reject
  517. msgbox("File name cannot contain path seperator", false);
  518. return;
  519. }
  520. if (newName && newName != oldName) {
  521. // User entered a new name, perform renaming logic here
  522. console.log(oldPath, currentPath + newName);
  523. $.ajax({
  524. url: "/api/fs/move?srcpath=" + oldPath + "&destpath=" + currentPath + newName,
  525. method: "POST",
  526. success: function(data){
  527. if (data.error != undefined){
  528. msgbox(data.error, false);
  529. }else{
  530. msgbox("File renamed");
  531. refresh();
  532. }
  533. }
  534. })
  535. }
  536. }
  537. }
  538. function getFileIcon(extension) {
  539. const textExtensions = [".md", ".txt"];
  540. const codeExtensions = [".js", ".json", ".css", ".html", ".htm"];
  541. const musicExtensions = [".mp3", ".aac", ".ogg", ".wav"];
  542. const videoExtensions = [".mp4", ".m4v", ".webm"];
  543. const photoExtensions = [".png", ".gif", ".jpg", ".ico", ".svg"];
  544. if (textExtensions.includes(extension)) {
  545. return "file alternate outline";
  546. } else if (codeExtensions.includes(extension)) {
  547. return "black file code outline";
  548. } else if (musicExtensions.includes(extension)) {
  549. return "blue music";
  550. } else if (videoExtensions.includes(extension)) {
  551. return "red video";
  552. } else if (photoExtensions.includes(extension)) {
  553. return "green image outline";
  554. } else {
  555. return "file outline";
  556. }
  557. }
  558. function isPreviewable(ext){
  559. let previeableFiles = [".png", ".gif", ".jpg", ".ico", ".svg"];
  560. return previeableFiles.includes(ext);
  561. }
  562. function getFileProperties(filepath){
  563. $.get("/api/fs/properties?file=" + filepath, function(data){
  564. if (data.error != undefined){
  565. msgbox(data.error, false);
  566. return;
  567. }
  568. $("#propertiesView").find(".filename").text(data.filename);
  569. $("#propertiesView").find(".vpath").text(data.filepath);
  570. let propTable = $("#propertiesView").find(".propertiesTable");
  571. let styleOverwrite = `min-width: 4em;`;
  572. $(propTable).html("");
  573. $(propTable).append(`<tr>
  574. <td style="${styleOverwrite}">
  575. File Size
  576. </td>
  577. <td>
  578. ${bytesToSize(data.size)}
  579. </td>
  580. </tr><tr>
  581. <td style="${styleOverwrite}">
  582. Disk Path
  583. </td>
  584. <td style="word-break: break-all;">
  585. /www${data.filepath}
  586. </td>
  587. </tr><tr>
  588. <td style="${styleOverwrite}">
  589. Folder
  590. </td>
  591. <td style="word-break: break-all;">
  592. ${data.isDir?`<i class="ui green check icon"></i>`:`<i class="ui red times icon"></i>`}
  593. </td>
  594. </tr>`);
  595. if (data.isDir){
  596. $(propTable).append(`<tr>
  597. <td style="${styleOverwrite}">
  598. Files #
  599. </td>
  600. <td>
  601. ${data.fileCounts}
  602. </td>
  603. </tr><tr>
  604. <td style="${styleOverwrite}">
  605. Folders #
  606. </td>
  607. <td style="word-break: break-all;">
  608. ${data.folderCounts}
  609. </td>
  610. </tr>`);
  611. }
  612. })
  613. }
  614. function loadPreview(){
  615. let readyToLoadSrc = $("#propertiesView").find(".preview").find("img").attr("xsrc");
  616. if (readyToLoadSrc == undefined || readyToLoadSrc == "" ){
  617. }else{
  618. let ext = readyToLoadSrc.split(".").pop();
  619. $("#propertiesView").find(".preview").find("img").show();
  620. $("#propertiesView").find(".preview").find("audio").hide();
  621. $("#propertiesView").find(".preview").find("img").attr("src", readyToLoadSrc);
  622. }
  623. }
  624. function bytesToSize(bytes) {
  625. var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
  626. if (bytes == 0) return '0 Byte';
  627. var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
  628. return Math.round(bytes / Math.pow(1024, i) * 100, 2) / 100 + ' ' + sizes[i];
  629. }
  630. function getParentDirectory(path) {
  631. // Remove any trailing slashes
  632. if (path.endsWith("/")){
  633. path = path.substr(0, path.length - 1);
  634. }
  635. // Find the last index of the slash character
  636. var lastIndex = path.lastIndexOf('/');
  637. // Extract the parent directory substring
  638. var parentDir = path.substring(0, lastIndex);
  639. return parentDir;
  640. }
  641. function parentDir(){
  642. if (currentPath.indexOf("/") >= 0 && currentPath != "/"){
  643. let parentPath = getParentDirectory(currentPath);
  644. listDir(parentPath);
  645. }else{
  646. //already top
  647. }
  648. }
  649. function humanFileSize(bytes, si=true, dp=1) {
  650. const thresh = si ? 1000 : 1024;
  651. if (Math.abs(bytes) < thresh) {
  652. return bytes + ' B';
  653. }
  654. const units = si
  655. ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
  656. : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
  657. let u = -1;
  658. const r = 10**dp;
  659. do {
  660. bytes /= thresh;
  661. ++u;
  662. } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
  663. return bytes.toFixed(dp) + ' ' + units[u];
  664. }
  665. function updateElementSize(){
  666. $("#mainWindow").css("height", window.innerHeight - $("#navibar").height() + "px");
  667. }
  668. updateElementSize();
  669. function upload() {
  670. // Create a file input element
  671. var fileInput = $('<input>').attr('type', 'file');
  672. // Trigger the file selector dialog
  673. fileInput.trigger('click');
  674. // Handle the selected file using a callback event handler
  675. fileInput.change(function(e) {
  676. var file = e.target.files[0];
  677. if (currentlyUploading){
  678. //Already tasks uploading in the background. Add it to queue
  679. uploadPendingFiles.push({
  680. "file": file,
  681. "dir": currentPath
  682. });
  683. msgbox("File Added to Upload Queue");
  684. return;
  685. }
  686. // Pass the file object to the callback event handler
  687. msgbox("File Upload Started")
  688. handleFile(file, currentPath, function(){
  689. msgbox("Upload Completed");
  690. });
  691. });
  692. }
  693. function handleFile(file, dir=currentPath, callback=undefined) {
  694. // Perform actions with the selected file
  695. $("#pasteButton").addClass("disabled");
  696. console.log('Selected file:', file);
  697. var formdata = new FormData();
  698. formdata.append("file", file);
  699. var ajax = new XMLHttpRequest();
  700. ajax.upload.addEventListener("progress", progressHandler, false);
  701. ajax.addEventListener("load", function(event){
  702. let responseText = event.target.responseText;
  703. try{
  704. responseText = JSON.parse(responseText);
  705. if (responseText.error != undefined){
  706. alert(responseText.error);
  707. }
  708. }catch(ex){
  709. }
  710. completeHandler(event, dir==currentPath);
  711. $("#pasteButton").removeClass("disabled");
  712. if (callback != undefined){
  713. callback();
  714. }
  715. }, false); // doesnt appear to ever get called even upon success
  716. ajax.addEventListener("error", errorHandler, false);
  717. ajax.addEventListener("abort", abortHandler, false);
  718. ajax.open("POST", "/api/fs/upload?dir=" + dir);
  719. ajax.send(formdata);
  720. }
  721. function progressHandler(event) {
  722. //_("loaded_n_total").innerHTML = "Uploaded " + event.loaded + " bytes of " + event.total; // event.total doesnt show accurate total file size
  723. var percent = (event.loaded / event.total) * 100;
  724. $("#uploadProgressBar").find(".bar").css("width", Math.round(percent) + "%");
  725. console.log("Uploaded " + event.loaded + " bytes => " + percent +"%");
  726. if (percent >= 100) {
  727. $("#uploadProgressBar").find(".bar").css("width", "100%");
  728. //_("status").innerHTML = "Please wait, writing file to filesystem";
  729. }
  730. }
  731. function completeHandler(event, requireRefresh=true) {
  732. $("#uploadProgressBar").find(".bar").css("width", "0%");
  733. if(requireRefresh){
  734. refresh();
  735. }
  736. }
  737. function errorHandler(event) {
  738. msgbox("Upload Failed", false);
  739. $("#pasteButton").removeClass("disabled");
  740. }
  741. function abortHandler(event) {
  742. msgbox("Upload Aborted", false);
  743. $("#pasteButton").removeClass("disabled");
  744. }
  745. function msgbox(message, succ=true){
  746. function capitalizeFirstLetter(string) {
  747. return string.charAt(0).toUpperCase() + string.slice(1);
  748. }
  749. message = capitalizeFirstLetter(message);
  750. if (succ){
  751. $(".msgbox").find(".showicon").attr("class", "green circle check icon showicon");
  752. }else{
  753. $(".msgbox").find(".showicon").attr("class", "red circle times icon showicon");
  754. }
  755. $(".msgbox").find("span").text(message);
  756. $(".msgbox").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
  757. }
  758. //Copy file
  759. function copy(){
  760. if ($(".fileObject.selected").length == 0){
  761. //No file selected
  762. msgbox("No file selected", false);
  763. return;
  764. }
  765. let selectedFiles = [];
  766. $(".fileObject.selected").each(function(){
  767. let filepath = $(this).attr("filepath");
  768. selectedFiles.push(filepath);
  769. console.log(filepath);
  770. });
  771. copyPendingFiles = selectedFiles;
  772. cutMode = false;
  773. msgbox(`${selectedFiles.length} files ready to paste`, true)
  774. }
  775. function fileExistsInThisFolder(filename){
  776. let exists = false;
  777. $(".fileObject").each(function(){
  778. if ($(this).attr("filename") == filename){
  779. exists = true;
  780. }
  781. });
  782. return exists;
  783. }
  784. function paste(){
  785. if (cutMode){
  786. let remainingFilesCounter = cutPendingFilepath.length;
  787. console.log("Moving " , cutPendingFilepath);
  788. cutPendingFilepath.forEach(fileToPaste => {
  789. let filename = fileToPaste.filename;
  790. let filepath = fileToPaste.filepath;
  791. $.ajax({
  792. url: "/api/fs/move?srcpath=" + filepath + "&destpath=" + currentPath + filename,
  793. method: "POST",
  794. success: function(data){
  795. if (data.error != undefined){
  796. msgbox(data.error)
  797. }else{
  798. remainingFilesCounter--;
  799. if (remainingFilesCounter == 0){
  800. msgbox("File Move Completed");
  801. refresh();
  802. }
  803. }
  804. }
  805. })
  806. });
  807. }else{
  808. //Copy and Paste
  809. copyFirstItemInQueueUntilAllCopied();
  810. }
  811. }
  812. function copyFirstItemInQueueUntilAllCopied(){
  813. let file = copyPendingFiles.shift();
  814. let startingDir = currentPath;
  815. $.ajax({
  816. url: "/api/fs/copy",
  817. method: "POST",
  818. data: {
  819. "srcpath": file,
  820. "destpath": currentPath
  821. },
  822. success: function(data){
  823. if (data.error != undefined){
  824. msgbox(data.error, false);
  825. }else{
  826. if (copyPendingFiles.length > 0){
  827. //Contine to copy and paste the files
  828. copyFirstItemInQueueUntilAllCopied();
  829. if (startingDir == currentPath){
  830. refresh();
  831. }
  832. }else{
  833. //All copy operation done
  834. msgbox("Files copied");
  835. if (startingDir == currentPath){
  836. refresh();
  837. }
  838. }
  839. }
  840. }
  841. })
  842. }
  843. function sortFileList(){
  844. sortFileObjects("folderList");
  845. sortFileObjects("fileList");
  846. }
  847. function sortFileObjects(listSelector) {
  848. const fileObjects = document.querySelectorAll(`#${listSelector} .fileObject`)
  849. // Convert the NodeList to an array for sorting
  850. const fileObjectsArray = Array.from(fileObjects);
  851. // Sort the elements based on their text content
  852. fileObjectsArray.sort((a, b) => {
  853. const textA = a.querySelector('.filename').textContent.toLowerCase();
  854. const textB = b.querySelector('.filename').textContent.toLowerCase();
  855. return textA.localeCompare(textB);
  856. });
  857. // Reorder the elements in the DOM
  858. const fileList = document.getElementById(listSelector);
  859. fileObjectsArray.forEach((fileObject) => {
  860. fileList.appendChild(fileObject);
  861. });
  862. }
  863. function togglePropertiesView(object){
  864. propertiesView = !propertiesView;
  865. if (propertiesView){
  866. $("#propertiesView").show();
  867. $(object).addClass('active');
  868. localStorage.setItem("file_explorer/viewProperties", "true");
  869. if ($(".fileObject.selected").length >= 1){
  870. //Load the file properties
  871. let targetFile = getFileObjectFromFID(lastClickedFileID);
  872. if (targetFile == null){
  873. targetFile = $(".fileObject.selected")[0];
  874. }
  875. let filepath = $(targetFile).attr("filepath");
  876. loadFileProperties(filepath);
  877. }
  878. }else{
  879. $("#propertiesView").hide();
  880. $(object).removeClass('active');
  881. localStorage.setItem("file_explorer/viewProperties", "false");
  882. }
  883. }
  884. // Bind the onDeleteKeyPress() function to the document's keydown event
  885. $(document).on("keydown", function(evt){
  886. if (event.ctrlKey) {
  887. // Check for Ctrl key combinations
  888. if (event.keyCode == 67) {
  889. // Ctrl + C
  890. evt.preventDefault();
  891. copy();
  892. } else if (event.keyCode == 86) {
  893. // Ctrl + V
  894. evt.preventDefault();
  895. paste();
  896. } else if (event.keyCode == 88) {
  897. // Ctrl + X
  898. evt.preventDefault();
  899. cut();
  900. }
  901. } else {
  902. if (event.keyCode == 46) {
  903. //Delete
  904. evt.preventDefault();
  905. deleteFile();
  906. }else if (event.keyCode == 13){
  907. //Enter
  908. evt.preventDefault();
  909. $(".fileObject.selected").each(function(e){
  910. openthis($(this), e);
  911. });
  912. }
  913. }
  914. });
  915. </script>
  916. </body>
  917. </html>