diff --git a/res/include/script/file_manager/DirectoryElement.js b/res/include/script/file_manager/DirectoryElement.js deleted file mode 100644 index 3d9080d..0000000 --- a/res/include/script/file_manager/DirectoryElement.js +++ /dev/null @@ -1,325 +0,0 @@ -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.minWidth = width - this.sortButtons[field].addEventListener("click", () => { - this.sortBy(field) - }) - this.directorySorters.appendChild(this.sortButtons[field]) - } - - // These widths are used for the sorters and the file nodes itself - this.fieldDateWidth = "160px" - this.fieldSizeWidth = "90px" - 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, selected} - 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, - selected: false, - }) -} - -DirectoryElement.prototype.renderFiles = function () { - this.search(this.lastSearchTerm) -} - -// 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) { - term = term.toLowerCase() - this.lastSearchTerm = term - this.visibleFiles = [] - - if (term === "") { - for (let i in this.allFiles) { - this.visibleFiles.push(i) - } - this.sortBy("") - this.renderVisibleFiles(true) - return - } - - let fileName = "" - for (let i in this.allFiles) { - fileName = this.allFiles[i].name.toLowerCase() - - // If there's an exact match we'll show it as the only result - if (fileName === term) { - this.visibleFiles = [i] - break - } - - // If a file name contains the search term we include it in the results - if (fileName.includes(term)) { - this.visibleFiles.push(i) - } - } - - this.sortBy("") - this.renderVisibleFiles(true) -} - -// searchSubmit opens the first file in the search results -DirectoryElement.prototype.searchSubmit = function () { - if (this.visibleFiles.length === 0) { - return // There are no files visible - } - - window.location = this.getVisibleFile(0).href -} - -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 we 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") - label.innerText = printDate(new Date(file.dateCreated), true, true, false) - 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" - 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 deleted file mode 100644 index 24695c5..0000000 --- a/res/include/script/file_manager/FileManager.js +++ /dev/null @@ -1,127 +0,0 @@ -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") - - // Register keyboard shortcuts - document.addEventListener("keydown", e => { this.keyboardEvent(e) }) - - this.inputSearch.addEventListener("keyup", e => { - if (e.keyCode === 27) { // Escape - e.preventDefault() - this.inputSearch.blur() - return - } else if (e.keyCode === 13) { // Enter - e.preventDefault() - this.directoryElement.searchSubmit() - return - } - requestAnimationFrame(() => { - this.directoryElement.search(this.inputSearch.value) - }) - }) - - 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)) -} -FileManager.prototype.delSpinner = function () { - for (let i in this.window.children) { - if ( - typeof (this.window.children[i].classList) === "object" && - this.window.children[i].classList.contains("spinner") - ) { - this.window.children[i].remove() - } - } -} - -FileManager.prototype.getUserFiles = function () { - this.setSpinner() - - let getAll = (page) => { - let numFiles = 1000 - 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.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.directoryElement.renderFiles() - - if (resp.files.length === numFiles) { - getAll(page + 1) - } else { - // Less than the maximum number of results means we're done - // loading, we can remove the loading spinner - this.delSpinner() - } - }).catch((err) => { - this.delSpinner() - throw (err) - }) - } - - this.directoryElement.reset() - getAll(0) -} - -FileManager.prototype.getUserLists = function () { - this.setSpinner() - this.directoryElement.reset() - - fetch(apiEndpoint + "/user/lists").then(resp => { - if (!resp.ok) { Promise.reject("yo") } - return resp.json() - }).then(resp => { - for (let i in resp.lists) { - 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.directoryElement.renderFiles() - this.delSpinner() - }).catch((err) => { - this.delSpinner() - throw (err) - }) -} - -FileManager.prototype.keyboardEvent = function (e) { - console.log("Pressed: " + e.keyCode) - - // 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/template/account/file_manager.html b/res/template/account/file_manager.html index 260dcb0..3567a23 100644 --- a/res/template/account/file_manager.html +++ b/res/template/account/file_manager.html @@ -3,64 +3,17 @@
{{template "meta_tags" "File Manager"}} {{template "user_style" .}} - + + + + -