an attempt at making an efficient file manager
This commit is contained in:
186
res/include/script/file_manager/FileManager.js
Normal file
186
res/include/script/file_manager/FileManager.js
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
function FileManager(windowElement) {
|
||||||
|
this.window = windowElement;
|
||||||
|
this.navBar = this.window.querySelector("#nav_bar");
|
||||||
|
this.btnMenu = this.navBar.querySelector("#btn_menu");
|
||||||
|
this.btnBack = this.navBar.querySelector("#btn_back");
|
||||||
|
this.btnUp = this.navBar.querySelector("#btn_up");
|
||||||
|
this.btnForward = this.navBar.querySelector("#btn_forward");
|
||||||
|
this.btnHome = this.navBar.querySelector("#btn_home");
|
||||||
|
this.breadcrumbs = this.navBar.querySelector("#breadcrumbs");
|
||||||
|
this.btnReload = this.navBar.querySelector("#btn_reload");
|
||||||
|
this.inputSearch = this.navBar.querySelector("#input_search");
|
||||||
|
this.directoryArea = this.window.querySelector("#directory_area");
|
||||||
|
this.directoryFooter = this.window.querySelector("#directory_footer");
|
||||||
|
|
||||||
|
this.dirContainer = document.createElement("div");
|
||||||
|
this.directoryArea.appendChild(this.dirContainer);
|
||||||
|
|
||||||
|
this.inputSearch.addEventListener("keyup", (e) => {
|
||||||
|
this.search(this.inputSearch.value);
|
||||||
|
})
|
||||||
|
this.directoryArea.addEventListener("scroll", (e) => {
|
||||||
|
this.renderVisibleFiles(this.visibleFiles, false);
|
||||||
|
})
|
||||||
|
|
||||||
|
this.allFiles = [];
|
||||||
|
this.visibleFiles = [];
|
||||||
|
|
||||||
|
this.lastScrollTop = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileManager.prototype.search = function(term) {
|
||||||
|
if (term === "") {
|
||||||
|
this.visibleFiles = this.allFiles;
|
||||||
|
this.renderVisibleFiles(this.visibleFiles, true);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.visibleFiles = [];
|
||||||
|
|
||||||
|
term = term.toLowerCase();
|
||||||
|
for (let i in this.allFiles) {
|
||||||
|
if (this.allFiles[i].name.toLowerCase().includes(term)) {
|
||||||
|
this.visibleFiles.push(this.allFiles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.renderVisibleFiles(this.visibleFiles, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileManager.prototype.getDirectory = function(path) {
|
||||||
|
console.log("ayy!");
|
||||||
|
}
|
||||||
|
|
||||||
|
FileManager.prototype.getUserFiles = function() {
|
||||||
|
let getAll = (page) => {
|
||||||
|
fetch(apiEndpoint+"/user/files?page="+page+"&limit=10000").then(resp => {
|
||||||
|
if (!resp.ok) {Promise.reject("yo");}
|
||||||
|
return resp.json();
|
||||||
|
}).then(resp => {
|
||||||
|
if (page === 0) {
|
||||||
|
this.allFiles = resp.files;
|
||||||
|
} else {
|
||||||
|
this.allFiles = this.allFiles.concat(resp.files);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.allFiles.sort((a, b) => {
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
});
|
||||||
|
this.visibleFiles = this.allFiles;
|
||||||
|
|
||||||
|
this.renderVisibleFiles(this.visibleFiles, true);
|
||||||
|
|
||||||
|
if (resp.files.length === 10000) {
|
||||||
|
getAll(page+1);
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log("Req failed:" + err);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getAll(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileManager.prototype.renderVisibleFiles = function(files, freshStart) {
|
||||||
|
if (freshStart) {
|
||||||
|
this.dirContainer.innerHTML = "";
|
||||||
|
|
||||||
|
let totalSize = 0;
|
||||||
|
for (let i in files) {
|
||||||
|
totalSize += files[i].size;
|
||||||
|
}
|
||||||
|
this.directoryFooter.innerText = files.length+
|
||||||
|
" items (0 directories and "+
|
||||||
|
files.length+
|
||||||
|
" files). Total size: "
|
||||||
|
+formatDataVolume(totalSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
let scrollDown = this.lastScrollTop <= this.directoryArea.scrollTop;
|
||||||
|
this.lastScrollTop = this.directoryArea.scrollTop;
|
||||||
|
|
||||||
|
let fileHeight = 28;
|
||||||
|
let totalHeight = (files.length * fileHeight)+16;
|
||||||
|
let viewportHeight = this.directoryArea.clientHeight+100;
|
||||||
|
let paddingTop = this.directoryArea.scrollTop-100;
|
||||||
|
if (paddingTop < 0) { paddingTop = 0;}
|
||||||
|
let paddingBottom = totalHeight - paddingTop - viewportHeight;
|
||||||
|
if (paddingBottom < 0) {paddingBottom = 0;}
|
||||||
|
|
||||||
|
// Pad the items out which we're not going to show
|
||||||
|
this.dirContainer.style.marginTop = paddingTop+"px";
|
||||||
|
this.dirContainer.style.marginBottom = paddingBottom+"px";
|
||||||
|
|
||||||
|
let start = Math.floor(paddingTop/fileHeight);
|
||||||
|
let end = Math.ceil((paddingTop+viewportHeight)/fileHeight);
|
||||||
|
if (end > files.length) { end = files.length-1; }
|
||||||
|
|
||||||
|
// First remove the elements which are out of bounds
|
||||||
|
let firstEl;
|
||||||
|
let firstIdx = -1;
|
||||||
|
let lastEl;
|
||||||
|
let lastIdx = -1;
|
||||||
|
while (true && !freshStart) {
|
||||||
|
firstEl = this.dirContainer.firstElementChild;
|
||||||
|
if (firstEl === null) {break;}
|
||||||
|
firstIdx = Number.parseInt(firstEl.getAttribute("fileindex"));
|
||||||
|
lastEl = this.dirContainer.lastElementChild;
|
||||||
|
lastIdx = Number.parseInt(lastEl.getAttribute("fileindex"));
|
||||||
|
|
||||||
|
this.dirContainer.insert
|
||||||
|
|
||||||
|
if (firstIdx < start) {
|
||||||
|
this.dirContainer.removeChild(firstEl);
|
||||||
|
console.debug("Remove start "+firstIdx);
|
||||||
|
} else if (lastIdx > end) {
|
||||||
|
this.dirContainer.removeChild(lastEl);
|
||||||
|
console.debug("Remove end "+lastIdx);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug("Start "+start+" end "+end+" first el "+firstIdx+" last el "+lastIdx);
|
||||||
|
|
||||||
|
|
||||||
|
let makeButton = (i, file) => {
|
||||||
|
let el = document.createElement("a");
|
||||||
|
el.classList = "node";
|
||||||
|
el.href = "/u/"+file.id;
|
||||||
|
el.target = "_blank";
|
||||||
|
el.title = file.name;
|
||||||
|
el.setAttribute("fileindex", i);
|
||||||
|
|
||||||
|
let thumb = document.createElement("img");
|
||||||
|
thumb.src = apiEndpoint+"/file/"+file.id+"/thumbnail?width=32&height=32";
|
||||||
|
|
||||||
|
let label = document.createElement("span");
|
||||||
|
label.innerText = file.name;
|
||||||
|
|
||||||
|
el.appendChild(thumb);
|
||||||
|
el.appendChild(label);
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then add the elements which have become visible. When the user scrolls
|
||||||
|
// down we can append the items in chronologic order, but when the user
|
||||||
|
// scrolls up we have to prepend the items in reverse order to avoid them
|
||||||
|
// appearing from high to low.
|
||||||
|
if (scrollDown) {
|
||||||
|
for (let i = start; i <= end && i < files.length; i++) {
|
||||||
|
if (firstIdx !== -1 && lastIdx !== -1 && i >= firstIdx && i <= lastIdx) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dirContainer.append(makeButton(i, files[i]));
|
||||||
|
console.debug("Append "+i+" "+files[i].name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = end; i >= start && i < files.length; i--) {
|
||||||
|
if (firstIdx !== -1 && lastIdx !== -1 && i >= firstIdx && i <= lastIdx) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.dirContainer.prepend(makeButton(i, files[i]));
|
||||||
|
console.debug("Prepend "+i+" "+files[i].name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -20,7 +20,7 @@ TextViewer.prototype.getText = function() {
|
|||||||
this.pre.innerText = "Loading...";
|
this.pre.innerText = "Loading...";
|
||||||
this.container.appendChild(this.pre);
|
this.container.appendChild(this.pre);
|
||||||
|
|
||||||
if (this.file.size > 1<<22) { // File larger than 4 MiB
|
if (this.file.size > 1<<20) { // File larger than 1 MiB
|
||||||
this.pre.innerText = "File is too large to view online.\nPlease download and view it locally.";
|
this.pre.innerText = "File is too large to view online.\nPlease download and view it locally.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -53,14 +53,14 @@ UploadProgressBar.prototype.onFinished = function(id){
|
|||||||
this.uploadDiv.appendChild(document.createElement("br"))
|
this.uploadDiv.appendChild(document.createElement("br"))
|
||||||
this.uploadDiv.appendChild(linkSpan)
|
this.uploadDiv.appendChild(linkSpan)
|
||||||
}
|
}
|
||||||
UploadProgressBar.prototype.onFailure = function(error) {
|
UploadProgressBar.prototype.onFailure = function(val, msg) {
|
||||||
this.uploadDiv.innerHTML = "" // Remove uploading progress
|
this.uploadDiv.innerHTML = "" // Remove uploading progress
|
||||||
this.uploadDiv.style.background = 'var(--danger_color)'
|
this.uploadDiv.style.background = 'var(--danger_color)'
|
||||||
this.uploadDiv.appendChild(document.createTextNode(this.file.name))
|
this.uploadDiv.appendChild(document.createTextNode(this.file.name))
|
||||||
this.uploadDiv.appendChild(document.createElement("br"))
|
this.uploadDiv.appendChild(document.createElement("br"))
|
||||||
this.uploadDiv.appendChild(document.createTextNode("Upload failed after three tries:"))
|
this.uploadDiv.appendChild(document.createTextNode("Upload failed after three tries:"))
|
||||||
this.uploadDiv.appendChild(document.createElement("br"))
|
this.uploadDiv.appendChild(document.createElement("br"))
|
||||||
this.uploadDiv.appendChild(document.createTextNode(error))
|
this.uploadDiv.appendChild(document.createTextNode(val))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
<head>
|
<head>
|
||||||
{{template "meta_tags" "File Manager"}}
|
{{template "meta_tags" "File Manager"}}
|
||||||
{{template "user_style" .}}
|
{{template "user_style" .}}
|
||||||
<script>var apiEndpoint = '{{.APIEndpoint}}';</script>
|
|
||||||
<style>
|
<style>
|
||||||
#page_body {
|
#page_body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -15,84 +14,114 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: var(--layer_1_color);
|
background-color: var(--layer_1_color);
|
||||||
box-shadow: #000000 8px 8px 50px 5px;
|
box-shadow: #000000 8px 8px 50px 5px;
|
||||||
left:50px;
|
left:2%;
|
||||||
top:50px;
|
top:2%;
|
||||||
right: 50px;
|
right:2%;
|
||||||
bottom: 50px;
|
bottom:2%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
.file_manager > .nav_bar {
|
.file_manager > .nav_bar {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.file_manager > .nav_bar > .spacer {width: 8px;}
|
||||||
|
.file_manager > .nav_bar > .breadcrumbs {
|
||||||
|
flex-grow: .8;
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
.file_manager > .nav_bar > .input_search {
|
||||||
|
flex-grow: .2;
|
||||||
|
flex-shrink: 1;
|
||||||
}
|
}
|
||||||
.file_manager > .nav_bar :first-child {margin-left: 5px;}
|
|
||||||
.file_manager > .nav_bar :last-child {margin-right: 5px;}
|
|
||||||
.file_manager > .nav_bar > .breadcrumbs {flex: 1; margin: 1px 10px; min-width: 100px;}
|
|
||||||
.file_manager > .directory_area {
|
.file_manager > .directory_area {
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
|
flex-grow: 1;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 5px;
|
/* padding: 0; */
|
||||||
|
padding: 8px;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
box-sizing: border-box;
|
||||||
.file_manager > .directory_area > .file_button {
|
|
||||||
margin: 5px;
|
|
||||||
}
|
}
|
||||||
.file_manager > .status_bar {
|
.file_manager > .status_bar {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file_button {
|
.node {
|
||||||
height: 20px;
|
position: relative;
|
||||||
box-shadow: none;
|
height: 24px;
|
||||||
background: none;
|
overflow: hidden;
|
||||||
margin: 2px;
|
margin-top: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
color: var(--text_color);
|
||||||
|
}
|
||||||
|
.node:hover {background-color: var(--input_color_dark); text-decoration: none;}
|
||||||
|
.node > span {vertical-align: middle;}
|
||||||
|
.node > img {
|
||||||
|
max-height: 100%;
|
||||||
|
margin-right: 5px;
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
}
|
}
|
||||||
.file_button > img {height: 100% !important;}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<template id="tpl_spinner"><div id="spinner" class="spinner">{{template `spinner.svg` .}}</div></template>
|
||||||
|
|
||||||
{{template "page_menu" .}}
|
{{template "page_menu" .}}
|
||||||
|
|
||||||
<div id="page_body" class="page_body">
|
<div id="page_body" class="page_body">
|
||||||
<div class="file_manager">
|
<div id="file_manager" class="file_manager">
|
||||||
<div class="nav_bar highlight_light">
|
<div id="nav_bar" class="nav_bar highlight_light">
|
||||||
<button>⇐</button>
|
<button id="btn_menu">☰</button>
|
||||||
<button>⇑</button>
|
<div class="spacer"></div>
|
||||||
<button style="margin-right: 16px;">⇒</button>
|
<button id="btn_back" >⇐</button id="btn_forward">
|
||||||
<button>🏠</button>
|
<button id="btn_up" >⇑</button id="btn_forward">
|
||||||
|
<button id="btn_forward">⇒</button>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<button id="btn_home">🏠</button>
|
||||||
|
<div class="spacer"></div>
|
||||||
<input class="breadcrumbs" type="text" value="/{{.Username}}/Documents"/>
|
<input class="breadcrumbs" type="text" value="/{{.Username}}/Documents"/>
|
||||||
<button>↻</button>
|
<div class="spacer"></div>
|
||||||
<button>🔎</button>
|
<input id="input_search" class="input_search" type="text" placeholder="Search..."/>
|
||||||
<button style="margin-left: 16px;">_</button>
|
<div class="spacer"></div>
|
||||||
<button>☐</button>
|
<button id="btn_reload">↻</button>
|
||||||
<button class="button_red">✕</button>
|
|
||||||
</div>
|
|
||||||
<div class="directory_area">
|
|
||||||
{{$size := 0}}
|
|
||||||
{{$count := 0}}
|
|
||||||
|
|
||||||
{{$files := .PixelAPI.UserFiles 0 10000}}
|
|
||||||
{{range $files.Files}}
|
|
||||||
{{$size = add $size .Size}}
|
|
||||||
{{$count = add $count 1}}
|
|
||||||
<a class="file_button" href="/u/{{.ID}}" target="_blank">
|
|
||||||
<img src="{{$.APIEndpoint}}/file/{{.ID}}/thumbnail?width=16&height=16" alt="{{.Name}}" />
|
|
||||||
{{.Name}}
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
<div class="status_bar highlight_light">
|
|
||||||
{{$count}} items (0 directories, {{$count}} files). Total size: {{formatData $size}}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="directory_area" class="directory_area"></div>
|
||||||
|
<div id="directory_footer" class="status_bar highlight_light"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
let apiEndpoint = '{{.APIEndpoint}}';
|
||||||
|
{{template `util.js`}}
|
||||||
|
{{template `FileManager.js`}}
|
||||||
|
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
let fm = new FileManager(document.getElementById("file_manager"));
|
||||||
|
fm.getUserFiles();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{{template "analytics"}}
|
{{template "analytics"}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Reference in New Issue
Block a user