fs.html 47 KB

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