mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-03 06:07:20 +02:00
1065 lines
46 KiB
HTML
1065 lines
46 KiB
HTML
<html>
|
|
<head>
|
|
<title>File Manager</title>
|
|
<meta charset="UTF-8">
|
|
<meta name="zoraxy.csrf.Token" content="{{.csrfToken}}">
|
|
<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 src="../script/utils.js"></script>
|
|
<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">
|
|
<link rel="stylesheet" href="../darktheme.css">
|
|
<script src="../script/darktheme.js"></script>
|
|
<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>
|
|
|
|
<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>
|
|
<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" style="height: 100%;">
|
|
<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 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>
|
|
</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 copyPendingFiles = [];
|
|
|
|
//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");
|
|
$.cjax({
|
|
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();
|
|
openthis($(this), evt);
|
|
}
|
|
});
|
|
}
|
|
|
|
function refresh(){
|
|
listDir(currentPath);
|
|
}
|
|
|
|
function updatePathDisplay(){
|
|
$("#pathDisplayField").empty();
|
|
$("#pathDisplayField").append(`<div class="section selectable" onclick="jumpToDir('/');"><i class="folder icon"></i> Zoraxy</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.size;
|
|
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 isDarkTheme = $("body").hasClass("darkTheme");
|
|
let extension = "." + filename.split(".").pop();
|
|
let fileIcon = getFileIcon(extension);
|
|
$("#fileList").append(`<div class="fileObject item" 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:${isDarkTheme?'white':'grey'} !important;"></i> <span class="filename">${filename} (${humanFileSize(filesize)})</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();
|
|
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(".");
|
|
|
|
|
|
//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: ", "Folder");
|
|
if (folderName.indexOf("/") >= 0){
|
|
//Contains /. Reject
|
|
msgbox("Folder name cannot contain path seperator", false);
|
|
return;
|
|
}
|
|
|
|
$.cjax({
|
|
url: "/api/fs/newFolder",
|
|
method: "POST",
|
|
data: {
|
|
"path": currentPath + folderName,
|
|
},
|
|
success: 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;
|
|
}
|
|
|
|
if (newName && newName != oldName) {
|
|
// User entered a new name, perform renaming logic here
|
|
console.log(oldPath, currentPath + newName);
|
|
$.cjax({
|
|
url: "/api/fs/move",
|
|
data: {
|
|
"srcpath": oldPath,
|
|
"destpath": 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.size)}
|
|
</td>
|
|
</tr><tr>
|
|
<td style="${styleOverwrite}">
|
|
Disk Path
|
|
</td>
|
|
<td style="word-break: break-all;">
|
|
/www${data.filepath}
|
|
</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>`);
|
|
|
|
}
|
|
})
|
|
}
|
|
|
|
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=true, 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("file", 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", "/api/fs/upload?dir=" + dir);
|
|
ajax.setRequestHeader("X-CSRF-Token", document.getElementsByTagName("meta")["zoraxy.csrf.Token"].getAttribute("content"));
|
|
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;
|
|
}
|
|
|
|
let selectedFiles = [];
|
|
$(".fileObject.selected").each(function(){
|
|
let filepath = $(this).attr("filepath");
|
|
selectedFiles.push(filepath);
|
|
console.log(filepath);
|
|
});
|
|
|
|
copyPendingFiles = selectedFiles;
|
|
cutMode = false;
|
|
|
|
msgbox(`${selectedFiles.length} files ready to paste`, true)
|
|
}
|
|
|
|
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;
|
|
|
|
$.cjax({
|
|
url: "/api/fs/move",
|
|
data:{
|
|
"srcpath": filepath,
|
|
"destpath": 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
|
|
copyFirstItemInQueueUntilAllCopied();
|
|
}
|
|
}
|
|
|
|
function copyFirstItemInQueueUntilAllCopied(){
|
|
let file = copyPendingFiles.shift();
|
|
let startingDir = currentPath;
|
|
$.cjax({
|
|
url: "/api/fs/copy",
|
|
method: "POST",
|
|
data: {
|
|
"srcpath": file,
|
|
"destpath": currentPath
|
|
},
|
|
success: function(data){
|
|
if (data.error != undefined){
|
|
msgbox(data.error, false);
|
|
}else{
|
|
if (copyPendingFiles.length > 0){
|
|
//Contine to copy and paste the files
|
|
copyFirstItemInQueueUntilAllCopied();
|
|
if (startingDir == currentPath){
|
|
refresh();
|
|
}
|
|
}else{
|
|
//All copy operation done
|
|
msgbox("Files copied");
|
|
if (startingDir == currentPath){
|
|
refresh();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
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);
|
|
});
|
|
|
|
}
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
</script>
|
|
</body>
|
|
</html> |