diff --git a/res/include/script/dependencies/util.js b/res/include/script/dependencies/util.js index c463b76..f0694c7 100644 --- a/res/include/script/dependencies/util.js +++ b/res/include/script/dependencies/util.js @@ -1,48 +1,3 @@ -function renderFileButton(apiURL, id, title, subtitle) { - let btn = document.createElement("a"); - btn.classList = "file_button"; - btn.href = "/u/"+id; - btn.target = "_blank"; - let thumbnail = document.createElement("img"); - thumbnail.src = apiURL+"/file/"+id+"/thumbnail?width=80&height=80"; - thumbnail.alt = title; - let titleSpan = document.createElement("span"); - titleSpan.classList = "file_button_title"; - titleSpan.innerText = title; - let br = document.createElement("br"); - let subtitleSpan = document.createElement("span"); - subtitleSpan.classList = "file_button_subtitle"; - subtitleSpan.innerText = subtitle; - - btn.appendChild(thumbnail); - btn.appendChild(titleSpan); - btn.appendChild(br); - btn.appendChild(subtitleSpan); - return btn; -} -function renderListButton(apiURL, id, title, subtitle) { - let btn = document.createElement("a"); - btn.classList = "file_button"; - btn.href = "/l/"+id; - btn.target = "_blank"; - let thumbnail = document.createElement("img"); - thumbnail.src = apiURL+"/list/"+id+"/thumbnail?width=80&height=80"; - thumbnail.alt = title; - let titleSpan = document.createElement("span"); - titleSpan.classList = "file_button_title"; - titleSpan.innerText = title; - let br = document.createElement("br"); - let subtitleSpan = document.createElement("span"); - subtitleSpan.classList = "file_button_subtitle"; - subtitleSpan.innerText = subtitle; - - btn.appendChild(thumbnail); - btn.appendChild(titleSpan); - btn.appendChild(br); - btn.appendChild(subtitleSpan); - return btn; -} - function addUploadHistory(fileID) { // Make sure the user is not logged in, for privacy. This keeps the // files uploaded while logged in and anonymously uploaded files diff --git a/res/include/script/file_manager/DirectoryElement.js b/res/include/script/file_manager/DirectoryElement.js new file mode 100644 index 0000000..b9f544a --- /dev/null +++ b/res/include/script/file_manager/DirectoryElement.js @@ -0,0 +1,314 @@ +function DirectoryElement(directoryArea, footer) { + // Main elements + this.directoryArea = directoryArea + this.footer = footer + this.directorySorters = this.directoryArea.querySelector("#directory_sorters") + + // Create sort buttons + + // Sorting internal state. By default we sort by dateCreated in descending + // order (new to old) + this.currentSortField = "dateCreated" + this.currentSortAscending = false + this.sortButtons = [] + + // Field is the name of the field in the file structure to sort on. Label is + // the text that appears in the sorting button + let makeSortButton = (field, label, width) => { + this.sortButtons[field] = document.createElement("div") + this.sortButtons[field].innerText = label + this.sortButtons[field].style.width = width + this.sortButtons[field].addEventListener("click", () => { + this.sortBy(field) + }) + this.directorySorters.appendChild(this.sortButtons[field]) + } + + this.fieldDateWidth = "160px" + this.fieldSizeWidth = "100px" + this.fieldTypeWidth = "200px" + makeSortButton("name", "Name", "") + makeSortButton("dateCreated", "Creation Date", this.fieldDateWidth) + makeSortButton("size", "Size", this.fieldSizeWidth) + makeSortButton("type", "Type", this.fieldTypeWidth) + + + // Scroll event for rendering new file nodes when they become visible + this.frameRequested = false; + this.directoryArea.addEventListener("scroll", (e) => { + if (this.frameRequested) { return } + this.frameRequested = true + requestAnimationFrame(() => { + this.renderVisibleFiles(false) + this.frameRequested = false + }) + }) + + // The directory container itself. This is where the files are rendered + this.dirContainer = document.createElement("div") + this.dirContainer.classList = "directory_node_container" + this.directoryArea.appendChild(this.dirContainer) + + // Internal state, contains a list of all files in the directory, visible + // files in the directory and the last scroll position. These are used for + // rendering the file list correctly + + // type: {icon, name, href, type, size, sizeLabel, dateCreated} + this.allFiles = [] + + // This array contains indexes referring to places in the allFiles array + this.visibleFiles = [] + + this.lastSearchTerm = "" + this.lastScrollTop = 0 +} + +DirectoryElement.prototype.reset = function() { + this.allFiles = [] + this.visibleFiles = [] +} + +DirectoryElement.prototype.addFile = function(icon, name, href, type, size, sizeLabel, dateCreated) { + this.allFiles.push({ + icon: icon, + name: name, + href: href, + type: type, + size: size, + sizeLabel: sizeLabel, + dateCreated: dateCreated, + }) +} + +DirectoryElement.prototype.renderFiles = function() { + this.visibleFiles = [] + for (let i in this.allFiles) { + this.visibleFiles.push(i) + } + this.sortBy("") +} + +// search filters the allFiles array on a search term. All files which match the +// search term will be put into visibleFiles. The visibleFiles array will then +// be rendered by renderVisibleFiles +DirectoryElement.prototype.search = function(term) { + this.lastSearchTerm = term + this.visibleFiles = [] + + if (term === "") { + for (let i in this.allFiles) { + this.visibleFiles.push(i) + } + this.renderVisibleFiles(true) + return + } + + for (let i in this.allFiles) { + if (this.allFiles[i].name.toLowerCase().includes(term.toLowerCase())) { + this.visibleFiles.push(i) + } + } + + // We have to resort because we modified the visibleFiles array + this.sortBy("") + this.renderVisibleFiles(true) +} + +DirectoryElement.prototype.sortBy = function(field) { + if (field === "") { + // If no sort field is provided we use the last used sort field + field = this.currentSortField + } else { + // If a sort field is provided we check in which direction we have to + // sort + if (this.currentSortField !== field) { + // If this field is a different field than before we sort it in + // ascending order + this.currentSortAscending = true + this.currentSortField = field + } else if (this.currentSortField === field) { + // If it is the same field as before re reverse the sort order + this.currentSortAscending = !this.currentSortAscending + } + } + + // Add the arrow to the sort label. First remove the arrow from all sort + // labels + for (let el in this.sortButtons) { + this.sortButtons[el].innerText = this.sortButtons[el].innerText.replace("▲ ", "").replace("▼ ", "") + } + + // Then prepend the arrow to the current sort label + if (this.currentSortAscending) { + this.sortButtons[field].innerText = "▼ "+this.sortButtons[field].innerText + } else { + this.sortButtons[field].innerText = "▲ "+this.sortButtons[field].innerText + } + + let fieldA, fieldB + this.visibleFiles.sort((a, b) => { + fieldA = this.allFiles[a][this.currentSortField] + fieldB = this.allFiles[b][this.currentSortField] + + if (typeof(fieldA) === "number") { + if (this.currentSortAscending) { + return fieldA - fieldB + } else { + return fieldB - fieldA + } + } else { + if (this.currentSortAscending) { + return fieldA.localeCompare(fieldB) + } else { + return fieldB.localeCompare(fieldA) + } + } + }) + this.renderVisibleFiles(true) +} + +DirectoryElement.prototype.createFileButton = function(file, index) { + let el = document.createElement("a") + el.classList = "node" + el.href = file.href + el.target = "_blank" + el.title = file.name + el.setAttribute("fileindex", index) + + { + let cell = document.createElement("div") + let thumb = document.createElement("img") + thumb.src = file.icon + cell.appendChild(thumb) + let label = document.createElement("span") + label.innerText = file.name + cell.appendChild(label) + cell.appendChild(label) + el.appendChild(cell) + } + { + let cell = document.createElement("div") + cell.style.width = this.fieldDateWidth + let label = document.createElement("span") + let date = new Date(file.dateCreated) + label.innerText = date.getFullYear() + +"-"+("00"+(date.getMonth()+1)).slice(-2) + +"-"+("00"+date.getDate()).slice(-2) + +" "+("00"+date.getHours()).slice(-2) + +":"+("00"+date.getMinutes()).slice(-2) + cell.appendChild(label) + el.appendChild(cell) + } + { + let cell = document.createElement("div") + cell.style.width = this.fieldSizeWidth + let label = document.createElement("span") + label.innerText = file.sizeLabel + cell.appendChild(label) + el.appendChild(cell) + } + { + let cell = document.createElement("div") + cell.style.width = this.fieldTypeWidth + let label = document.createElement("span") + label.innerText = file.type + cell.appendChild(label) + el.appendChild(cell) + } + + return el +} + +// This function dereferences an index in the visibleFiles array to a real file +// in the allFiles array. The notation is a bit confusing so the separate +// function is just for clarity +DirectoryElement.prototype.getVisibleFile = function(index) { + return this.allFiles[this.visibleFiles[index]] +} + +DirectoryElement.prototype.renderVisibleFiles = function(freshStart) { + let scrollDown = this.lastScrollTop <= this.directoryArea.scrollTop + this.lastScrollTop = this.directoryArea.scrollTop + + let fileHeight = 40 + let totalHeight = (this.visibleFiles.length * fileHeight) + let viewportHeight = this.directoryArea.clientHeight + + if (freshStart) { + this.dirContainer.innerHTML = "" + this.dirContainer.style.height = totalHeight+"px" + this.dirContainer.scrollTop = 0 + this.lastScrollTop = 0 + scrollDown = true + + let totalSize = 0 + for (let i in this.visibleFiles) { + totalSize += this.getVisibleFile(i).size + } + this.footer.innerText = this.visibleFiles.length+" items. Total size: "+formatDataVolume(totalSize, 4) + } + + let paddingTop = this.lastScrollTop - this.lastScrollTop%fileHeight + let start = Math.floor(paddingTop/fileHeight) - 5 + if (start < 0) { start = 0 } + + let end = Math.ceil((paddingTop+viewportHeight)/fileHeight) + 5 + if (end > this.visibleFiles.length) { end = this.visibleFiles.length-1 } + + this.dirContainer.style.paddingTop = (start*fileHeight)+"px" + + // Remove the elements which are out of bounds + let firstEl + let firstIdx = -1 + let lastEl + let lastIdx = -1 + while (!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")) + + 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+ + " firstIdx "+firstIdx+ + " lastIdx "+lastIdx+ + " freshStart "+freshStart+ + " scrollDown "+scrollDown+ + " children "+this.dirContainer.childElementCount + ) + + // 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 < this.visibleFiles.length; i++) { + if (lastIdx !== -1 && i <= lastIdx) { + continue + } + this.dirContainer.append(this.createFileButton(this.getVisibleFile(i), i)) + console.debug("Append "+i); + } + } else { + for (let i = end; i >= start; i--) { + if (firstIdx !== -1 && i >= firstIdx) { + continue + } + this.dirContainer.prepend(this.createFileButton(this.getVisibleFile(i), i)) + console.debug("Prepend "+i); + } + } +} diff --git a/res/include/script/file_manager/FileManager.js b/res/include/script/file_manager/FileManager.js index 9e733d1..ce25076 100644 --- a/res/include/script/file_manager/FileManager.js +++ b/res/include/script/file_manager/FileManager.js @@ -1,59 +1,40 @@ 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.directorySorters = this.window.querySelector("#directory_sorters"); - this.directoryArea = this.window.querySelector("#directory_area"); - this.directoryFooter = this.window.querySelector("#directory_footer"); - - // Sorters - this.currentSortField = ""; - this.currentSortAscending = true; - - this.btnSortName = document.createElement("div"); - this.btnSortName.innerText = "Name"; - this.btnSortName.addEventListener("click", () => { this.sortBy("name"); }); - this.directorySorters.appendChild(this.btnSortName); - - this.btnSortType = document.createElement("div"); - this.btnSortType.innerText = "Type"; - this.btnSortType.addEventListener("click", () => { this.sortBy("type"); }); - this.directorySorters.appendChild(this.btnSortType); - - this.btnSortSize = document.createElement("div"); - this.btnSortSize.innerText = "Size"; - this.btnSortSize.addEventListener("click", () => { this.sortBy("size"); }); - this.directorySorters.appendChild(this.btnSortSize); + 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") // Buttons - this.btnReload.addEventListener("click", () => { this.getUserFiles(); }) + this.btnReload.addEventListener("click", () => { this.getUserFiles() }) - this.dirContainer = document.createElement("div"); - this.directoryArea.appendChild(this.dirContainer); + // Register keyboard shortcuts + document.addEventListener("keydown", e => { this.keyboardEvent(e) }) - this.inputSearch.addEventListener("keyup", (e) => { - this.search(this.inputSearch.value); - }) - this.directoryArea.addEventListener("scroll", (e) => { - this.renderVisibleFiles(this.visibleFiles, false); + this.inputSearch.addEventListener("keyup", e => { + if (e.keyCode === 27) { + e.preventDefault() + this.inputSearch.blur() + return + } + requestAnimationFrame(() => { + this.directoryElement.search(this.inputSearch.value) + }) }) - // type: {icon, name, href, type, size} - this.allFiles = []; - this.visibleFiles = []; - - this.lastScrollTop = 0; + this.directoryElement = new DirectoryElement( + this.window.querySelector("#directory_area"), + this.window.querySelector("#directory_footer"), + ) } FileManager.prototype.setSpinner = function() { - this.window.appendChild(document.getElementById("tpl_spinner").content.cloneNode(true)); + this.window.appendChild(document.getElementById("tpl_spinner").content.cloneNode(true)) } FileManager.prototype.delSpinner = function() { for (let i in this.window.children) { @@ -61,227 +42,101 @@ FileManager.prototype.delSpinner = function() { typeof(this.window.children[i].classList) === "object" && this.window.children[i].classList.contains("spinner") ) { - this.window.children[i].remove(); + this.window.children[i].remove() } } } -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!"); + console.log("ayy!") } FileManager.prototype.getUserFiles = function() { - this.setSpinner(); + this.setSpinner() let getAll = (page) => { - fetch(apiEndpoint+"/user/files?page="+page+"&limit=10000").then(resp => { - if (!resp.ok) {Promise.reject("yo");} - return resp.json(); + let numFiles = 10000 + fetch(apiEndpoint+"/user/files?page="+page+"&limit="+numFiles).then(resp => { + if (!resp.ok) { Promise.reject("yo") } + return resp.json() }).then(resp => { for (let i in resp.files) { - this.allFiles.push({ - icon: apiEndpoint+"/file/"+resp.files[i].id+"/thumbnail?width=32&height=32", - name: resp.files[i].name, - href: "/u/"+resp.files[i].id, - type: resp.files[i].mime_type, - size: resp.files[i].size - }) + this.directoryElement.addFile( + apiEndpoint+"/file/"+resp.files[i].id+"/thumbnail?width=32&height=32", + resp.files[i].name, + "/u/"+resp.files[i].id, + resp.files[i].mime_type, + resp.files[i].size, + formatDataVolume(resp.files[i].size, 4), + resp.files[i].date_upload, + ) } - this.visibleFiles = this.allFiles; - this.currentSortField = ""; - this.sortBy("name"); + this.directoryElement.renderFiles() - if (resp.files.length === 10000) { - getAll(page+1); + if (resp.files.length === numFiles) { + getAll(page+1) } else { - // Less than 10000 results means we're done loading, we can - // remove the loading spinner - this.delSpinner(); + // Less than the maximum number of results means we're done + // loading, we can remove the loading spinner + this.delSpinner() } }).catch((err) => { - this.delSpinner(); - console.log("Req failed:" + err); + this.delSpinner() + throw(err) }) } - getAll(0); + this.directoryElement.reset() + getAll(0) } FileManager.prototype.getUserLists = function() { - this.setSpinner(); + this.setSpinner() let getAll = (page) => { - fetch(apiEndpoint+"/user/lists?page="+page+"&limit=10000").then(resp => { - if (!resp.ok) {Promise.reject("yo");} - return resp.json(); + let numFiles = 10000 + fetch(apiEndpoint+"/user/lists?page="+page+"&limit="+numFiles).then(resp => { + if (!resp.ok) { Promise.reject("yo") } + return resp.json() }).then(resp => { for (let i in resp.lists) { - this.allFiles.push({ - icon: apiEndpoint+"/list/"+resp.lists[i].id+"/thumbnail?width=32&height=32", - name: resp.lists[i].title, - href: "/l/"+resp.lists[i].id, - type: "list", - size: 0 - }) + this.directoryElement.addFile( + apiEndpoint+"/list/"+resp.lists[i].id+"/thumbnail?width=32&height=32", + resp.lists[i].title, + "/l/"+resp.lists[i].id, + "list", + resp.lists[i].file_count, + resp.lists[i].file_count+" files", + resp.lists[i].date_created, + ) } - this.visibleFiles = this.allFiles; - this.currentSortField = ""; - this.sortBy("name"); + this.directoryElement.renderFiles() - if (resp.lists.length === 10000) { - getAll(page+1); + if (resp.lists.length === numFiles) { + getAll(page+1) } else { - // Less than 10000 results means we're done loading, we can - // remove the loading spinner - this.delSpinner(); + // Less than the maximum number of results means we're done + // loading, we can remove the loading spinner + this.delSpinner() } }).catch((err) => { - this.delSpinner(); - console.log("Req failed:" + err); + this.delSpinner() + throw(err) }) } - getAll(0); + this.directoryElement.reset() + getAll(0) } -FileManager.prototype.sortBy = function(field) { - if (this.currentSortField !== field) { - this.currentSortAscending = true; - this.currentSortField = field; - } else if (this.currentSortField === field) { - this.currentSortAscending = !this.currentSortAscending; - } +FileManager.prototype.keyboardEvent = function(e) { + console.log("Pressed: "+e.keyCode) - this.visibleFiles.sort((a, b) => { - if (this.currentSortAscending) { - return a[field].localeCompare(b[field]); - } else { - return b[field].localeCompare(a[field]); - } - }); - this.renderVisibleFiles(this.visibleFiles, true); -} - -FileManager.prototype.renderVisibleFiles = function(files, freshStart) { - let scrollDown = this.lastScrollTop <= this.directoryArea.scrollTop; - this.lastScrollTop = this.directoryArea.scrollTop; - - let fileMargin = 4; - let fileHeight = 32 + fileMargin; - let totalHeight = (files.length * fileHeight); - let viewportHeight = this.directoryArea.clientHeight; - - if (freshStart) { - this.dirContainer.innerHTML = ""; - this.dirContainer.style.height = totalHeight+"px"; - this.dirContainer.scrollTop = 0; - this.lastScrollTop = 0; - scrollDown = true; - - 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, 4); - } - - let paddingTop = this.lastScrollTop - this.lastScrollTop%fileHeight; - let start = Math.floor(paddingTop/fileHeight) - 5; - if (start < 0) { start = 0; } - - let end = Math.ceil((paddingTop+viewportHeight)/fileHeight) + 5; - if (end > files.length) { end = files.length-1; } - - this.dirContainer.style.paddingTop = (start*fileHeight)+"px"; - - // 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")); - - 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 = file.href; - el.target = "_blank"; - el.title = file.name; - el.setAttribute("fileindex", i); - - let thumb = document.createElement("img"); - thumb.src = file.icon; - - 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); - } + // CTRL + F or "/" opens the search bar + if (e.ctrlKey && e.keyCode === 70 || !e.ctrlKey && e.keyCode === 191) { + e.preventDefault() + this.inputSearch.focus() } } diff --git a/res/include/script/history.js b/res/include/script/history.js index 6fce945..5656eab 100644 --- a/res/include/script/history.js +++ b/res/include/script/history.js @@ -1,3 +1,26 @@ +function renderFileButton(apiURL, id, title, subtitle) { + let btn = document.createElement("a"); + btn.classList = "file_button"; + btn.href = "/u/"+id; + btn.target = "_blank"; + let thumbnail = document.createElement("img"); + thumbnail.src = apiURL+"/file/"+id+"/thumbnail?width=80&height=80"; + thumbnail.alt = title; + let titleSpan = document.createElement("span"); + titleSpan.classList = "file_button_title"; + titleSpan.innerText = title; + let br = document.createElement("br"); + let subtitleSpan = document.createElement("span"); + subtitleSpan.classList = "file_button_subtitle"; + subtitleSpan.innerText = subtitle; + + btn.appendChild(thumbnail); + btn.appendChild(titleSpan); + btn.appendChild(br); + btn.appendChild(subtitleSpan); + return btn; +} + function getCookie(name) { var result = new RegExp('(?:^|; )' + encodeURIComponent(name) + '=([^;]*)').exec(document.cookie); return result ? result[1] : null; diff --git a/res/include/style/file_manager.css b/res/include/style/file_manager.css new file mode 100644 index 0000000..fea4098 --- /dev/null +++ b/res/include/style/file_manager.css @@ -0,0 +1,167 @@ +#page_body { + height: 100%; + padding: 0; +} + +#button_toggle_navigation { + display: none; +} + +.file_manager { + position: absolute; + padding: 0; + background-color: var(--layer_1_color); + box-shadow: #000000 8px 8px 50px 5px; + left: 24px; + top: 24px; + right: 24px; + bottom: 24px; + display: flex; + flex-direction: column; +} +@media (max-width: 1000px) { + .file_manager { + left: 0; + top: 0; + right: 0; + bottom: 0; + } +} + +.file_manager > .nav_bar { + flex-shrink: 0; + display: flex; + flex-direction: row; +} +.file_manager > .nav_bar > button { + flex-shrink: 0; +} +.file_manager > .nav_bar > .spacer {width: 8px;} +.file_manager > .nav_bar > .breadcrumbs { + flex-grow: .7; + flex-shrink: 1; + min-width: 0; +} +.file_manager > .nav_bar > .input_search { + flex-grow: .3; + flex-shrink: 1; + min-width: 100px; +} + +.file_manager > .directory_area > .directory_sorters { + display: flex; + flex-direction: row; + position: sticky; + overflow: hidden; + top: 0; + z-index: 1; + background-color: var(--layer_1_color); +} +.file_manager > .directory_area > .directory_sorters > div { + flex-shrink: 0; + flex-grow: 1; + display: inline-block; + margin: 4px 10px; + border-bottom: 1px solid var(--input_color); + cursor: pointer; +} + +.file_manager > .directory_area > .directory_sorters > :first-child, +.node > :first-child { + flex-shrink: 1; + flex-grow: 1; +} +.file_manager > .directory_area > .directory_sorters > :not(:first-child), +.node > :not(:first-child) { + flex-shrink: 0; + flex-grow: 0; +} +@media (max-width: 1000px) { + .file_manager > .directory_area > .directory_sorters > :not(:first-child), + .node > :not(:first-child) { + display: none; + } +} + +.file_manager > .directory_area { + flex-shrink: 1; + flex-grow: 1; + margin: 0; + padding: 0; + overflow-x: auto; + text-align: left; + box-sizing: border-box; +} +.file_manager > .directory_area > .directory_node_container { + /* Required because we use padding for moving the nodes down when items + above are out of view*/ + box-sizing: border-box; + display: block; + overflow: hidden; +} + +.file_manager > .status_bar { + flex-shrink: 0; + text-align: left; +} + +.node { + display: flex; + flex-direction: row; + + position: relative; + height: 40px; + overflow: hidden; + + /* I use padding instead of margin here because it goves me more precise + control over the size. + Check out https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Mastering_margin_collapsing*/ + margin: 0; + padding: 4px; + box-sizing: border-box; + color: var(--text_color); + + /* padding-top: 6px; */ +} +.node:hover, .node_selected { + background-color: var(--input_color_dark); + color: var(--input_text_color); + text-decoration: none; +} +.node > div { + height: 100%; + overflow: hidden; + margin: auto 10px; + display: inline-block; + text-overflow: ellipsis; + white-space: nowrap; +} +.node > div > span { + margin: auto; + margin-top: 4px; /* I couldn't find another way to vertically align the text*/ + box-sizing: border-box; + display: block; + text-overflow: ellipsis; + white-space: nowrap; +} +.node > div > img { + max-height: 100%; + margin-right: 6px; + width: auto; + min-width: auto; + float: left; + display: block; +} + +.spinner { + position: absolute; + display: block; + margin: auto; + max-width: 100%; + max-height: 100%; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 100px; + height: 100px; +} diff --git a/res/include/style/viewer.css b/res/include/style/viewer.css index 8c3b44f..3d6816d 100644 --- a/res/include/style/viewer.css +++ b/res/include/style/viewer.css @@ -41,8 +41,8 @@ display: flex; flex-direction: column; overflow: hidden; - white-space: nowrap; line-height: 2em; + white-space: nowrap; text-overflow: ellipsis; } .file_viewer > .file_viewer_headerbar > .button_home::after { diff --git a/res/template/account/file_manager.html b/res/template/account/file_manager.html index 1d4a7ad..09b865b 100644 --- a/res/template/account/file_manager.html +++ b/res/template/account/file_manager.html @@ -3,118 +3,7 @@ {{template "meta_tags" "File Manager"}} {{template "user_style" .}} - + @@ -125,22 +14,24 @@
-
-
+ +
+
+
@@ -149,13 +40,25 @@ 'use strict'; let apiEndpoint = '{{.APIEndpoint}}'; {{template `util.js`}} + {{template `DirectoryElement.js`}} {{template `FileManager.js`}} let fm = null; window.addEventListener("load", () => { - fm = new FileManager(document.getElementById("file_manager")); - fm.getUserFiles(); - }); + fm = new FileManager(document.getElementById("file_manager")) + + let breadcrumbs = document.querySelector("#nav_bar > .breadcrumbs") + + if (window.location.href.endsWith("?files")) { + breadcrumbs.value += "/Files" + fm.getUserFiles() + } else if (window.location.href.endsWith("?lists")) { + breadcrumbs.value += "/Lists" + fm.getUserLists() + } else { + alert("invalid file manager type") + } + }) {{template "analytics"}} diff --git a/res/template/fragments/page_wrap.html b/res/template/fragments/page_wrap.html index e92b048..372db80 100644 --- a/res/template/fragments/page_wrap.html +++ b/res/template/fragments/page_wrap.html @@ -4,8 +4,8 @@ Home
{{if .Authenticated}}{{.Username}} - My Files - My Lists + My Files + My Lists Log out {{else}} Login