F diff --git a/css/style.css b/css/style.css --- a/css/style.css +++ b/css/style.cssbody {background: #f0f0f0;color: black;- font-family: sans-serif;+ font-family: Roboto, sans-serif;overflow: hidden;}}form {- background: white;+ background: #fbfbfb;margin: 4.5rem;padding-top: 0;box-shadow: 0 0.8rem 1.3rem rgba(0,0,0,0.2);border-radius: 0.5rem;- border-radius: 0.5rem;border: 1px solid #b9b9b9;}min-width: 300px;}- input:not([type=file]) {+ button,+ input:not([type=file])+ {border: 1px solid #bbb;- padding: 0.8rem 2rem 0.5rem 2rem;+ padding: 0.5rem 0.8rem;font-size: inherit;font-family: inherit;border-radius: 0.3rem;- background: #f6f6f6;+ background: #fcfcfc;}input[type=button], button, input[type=submit] {cursor: pointer;}- input[type=button]:hover, button:hover, input[type=submit]:hover {- background: white;- }input[type=submit] {margin-top: 2rem;outline: none;}- input:focus,- input:hover {- background: white;++ input:hover,+ button:hover+ {+ background: white;}input:focus {padding: 0;box-shadow: 0 0.8rem 1.3rem rgba(0,0,0,0.2);border-radius: 0.5rem;- border-radius: 0.5rem;border: 1px solid #b9b9b9;display: block;padding: 1rem;display: grid;- grid-gap: 20px;+ grid-gap: 0rem;grid-auto-rows: 10rem;grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));}border-color: #ddd;}+ .file > img {+ flex: 1 0 0;+ }+.file:hover > img {- filter: brightness(150%);+ filter: brightness(120%);+ }++ .filesystem > h2 {+ display: flex;+ align-items: stretch;+ font-weight: normal;+ padding: 0rem;+ border-bottom: 1px solid #bbb;++ }++ .filesystem > h2 button {+ border: none;+ padding: 0.3rem 1.1rem;+ background: inherit;+ border-radius: 0;+ }++ .filesystem > h2 button:not(.pathentry):hover {+ background: white;+ }++ .filesystem > h2 > .separator {+ flex: 0 0 1px;+ align-self: stretch;+ background: #bbb;+ }++ .filesystem > h2 > *:first-child {+ border-top-left-radius: 0.5rem;}.path {- font-size: 1.5rem;+ display: flex;+ align-items: stretch;+ font-size: 0.8em;}- .path > a {- color: #333;- text-decoration: none;+ .path > .separator {+ user-select: none;+ -webkit-user-select: none;+ -ms-user-select: none;+ padding: 0 0.2rem;+ align-self: center;}- .path > a:hover {+ .pathentry:hover {text-decoration: underline;}- .filesystem > h2 {- display: flex;- align-items: baseline;- font-weight: normal;+ .context {+ background: white;+ position: absolute;+ z-index: 1000;+ list-style-type: none;+ margin: 0;+ padding: 0;+ top: 0;+ left: 0;++ border: 1px solid #ccc;+ box-shadow: 0 0.3rem 0.5rem rgba(0,0,0,0.2);+ }++ .context > li {+ padding: 0.4rem 1.5rem;+ margin: 0;++ user-select: none;+ -webkit-user-select: none;+ -ms-user-select: none;+ }++ .context > li:hover {+ background: #2e91db;+ color: white;}F diff --git a/index.php b/index.php --- a/index.php +++ b/index.php<html><head><meta charset="utf-8">+ <link rel="preconnect" href="https://fonts.gstatic.com">+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet"><link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"><title>shady file upload</title> <link rel="stylesheet" type="text/css" href="css/style.css"></head>F diff --git a/loggedin.js b/loggedin.js --- a/loggedin.js +++ b/loggedin.js- var FORM_ASYNC = false;+ var FORM_ASYNC = true;const upload_form = document.getElementById("upload_form");const the_file = document.getElementById("the_file");const filename_input = document.getElementById("filename");const upload_btn = document.getElementById("upload_btn");+ const the_path = document.getElementById("the_path");const current_directory = document.getElementById("current_directory");the_file.onchange = on_file_added;- var files = [];+ var pwd = [];+ const pending_uploads = [];- const pwd = "/";+ var context_menu = null;- const pending_uploads = [];+ class FileView {+ constructor(filename, visuals, mimetype, is_directory) {+ this.filename = filename;+ this.visuals = visuals;+ this.mimetype = mimetype;+ this.is_directory = is_directory;+ }+ }++ class PendingUpload {+ constructor(fileview) {+ this.fileview = fileview;+ }+ }++ var files = [];function on_file_added(_e) {if (the_file.files.length >= 1) {return;}+ var fileview = add_file_visuals(filename_input.value, false, "pending");+// Send the form asynchronously through the fetch apifetch(upload_form.action, {method: upload_form.method,body: new FormData(upload_form)}).then((resp) => {if (resp.status == 200) {- add_file_visuals(filename_input.value, true);- }- else {+ done_upload(fileview);+ } else {alert("Upload failed");}}, () => {alert("Upload failed")});--++ pending_uploads.push(fileview);}else {alert("No files selected");}+ }++ function done_upload(fileview) {+ var index = pending_uploads.indexOf(fileview);+ if (index >= 0)+ pending_uploads.splice(index, 1);+ load_dir();}- function load_dir(pwd) {+ function load_dir() {++ while (the_path.children.length > 1)+ the_path.removeChild(the_path.lastChild);++ for (let i = 0; i < pwd.length; i++) {+ var d = pwd[i];++ var separator_div = document.createElement('div');+ separator_div.classList.add('separator');+ the_path.appendChild(separator_div);+ separator_div.innerText = "ยป";++ var entry = document.createElement('button');+ entry.classList.add('pathentry');+ entry.innerText = d;+ the_path.appendChild(entry);++ entry.onclick = () => {+ pwd.length = i + 1;+ load_dir();+ }+ }+var data = new FormData();- data.append('path', '/');+ data.append('path', get_path());var xhr = new XMLHttpRequest();xhr.open('POST', '/php/readdir.php', true);xhr.onload = function () {for (const f of files)- f[1].remove();+ f.visuals.remove();files = [];var json = JSON.parse(this.responseText);for (const f of json) {- add_file_visuals(f.name, f.mimetype);+ add_file_visuals(f.name, f.is_directory, f.mimetype);}};xhr.send(data);}- function add_file_visuals(name, mimetype) {+ function delete_file(filename) {+ var file_full_path = get_path() + filename;++ var data = new FormData();+ data.append('path', file_full_path);++ var xhr = new XMLHttpRequest();+ xhr.open('POST', '/php/delete.php', true);+ xhr.onload = function () {+ load_dir();+ };+ xhr.send(data);+ }++ function rename_file(filename) {+ var file_full_path = get_path() + filename;++ var new_name = prompt(`Rename ${filename} to`, filename);+ if (!new_name)+ return;++ var data = new FormData();+ data.append('path', file_full_path);+ data.append('new_name', new_name);++ var xhr = new XMLHttpRequest();+ xhr.open('POST', '/php/rename.php', true);+ xhr.onload = function () {+ load_dir();+ };+ xhr.send(data);+ }++ function add_file_visuals(name, is_directory, mimetype) {var fileDiv = document.createElement('div');var img = document.createElement('img');var filename = document.createElement('div');- img.src="/mimeicons/application-pdf.png";+ if (is_directory) {+ img.src="/mimeicons/directory.png";+ fileDiv.onclick = () => {+ pwd.push(name);+ load_dir();+ }+ } else {+ img.src=`/mimeicons/${mimetype.replace("/", "-")}.png`;+ }++ fileDiv.oncontextmenu = (e) => {+ context(e, [+ ['Open', () => {+ if (is_directory) {+ pwd.push(name);+ load_dir();+ } else {+ alert('not implemented');+ }+ }],+ ['Rename', () => { rename_file(name); }],+ ['Share', () => {alert('not implemented')}],+ ['Delete', () => { delete_file(name); }],+ ]);+ e.preventDefault();+ }+fileDiv.classList.add('file');filename.classList.add('filename');filename.innerText = name;+ if (mimetype == "pending")+ fileDiv.classList.add('pending');+fileDiv.appendChild(img);fileDiv.appendChild(filename);current_directory.appendChild(fileDiv);- files.push([name, fileDiv]);-- return fileDiv;+ var file = new FileView(name, fileDiv, mimetype, is_directory);+ files.push(file);+ return file;}function begin_upload() {the_file.click();}- load_dir("/");+ function context(e, entries) {+ if (context_menu)+ context_menu.remove();++ context_menu = document.createElement('ul');+ context_menu.classList.add('context');++ context_menu.style.left = e.clientX + "px";+ context_menu.style.top = e.clientY + "px";+++ for (const e of entries) {+ const li = document.createElement('li');+ li.innerText = e[0];+ li.onclick = e[1];+ context_menu.appendChild(li);+ }++ document.body.appendChild(context_menu);+ }++ function get_path() {+ var path = "/";+ for (const d of pwd)+ path += d + "/";+ return path;+ }++ document.body.onclick = () => {+ if (context_menu)+ context_menu.remove();+ }++ load_dir();F diff --git a/loggedin.php b/loggedin.php --- a/loggedin.php +++ b/loggedin.php<div><div class="filesystem">- <h2 style="display: flex; gap: 1rem;">- <div class="path">- <a class="pathentry" href="#"> <?php echo $_SESSION['username'] ?>'s files/</a><a class="pathentry" href="#">foo/</a><a class="pathentry" href="#">bar</a></div>- <input id="upload_btn" type="button" value="Upload" onclick="begin_upload()">+ <h2 style="display: flex; gap: 0rem;">+ <button id="upload_btn" onclick="begin_upload()">Upload</button>+ <div class="separator"></div>+ <button id="upload_btn" onclick="begin_upload()">New Folder</button>+ <div class="separator"></div>+ <div class="path" id="the_path">+ <!-- <a class="pathentry" href="#"> /</a><a class="pathentry" href="#">foo/</a><a class="pathentry" href="#">bar</a> -->+ <button class="pathentry" onclick="pwd.length = 0; load_dir();"><?php echo $_SESSION['username'] ?>'s files</button>+ </div></h2><div class="files" id="current_directory"><input type="file" name="the_file" id="the_file"></form>+<script src="loggedin.js"></script>F diff --git a/mimeicons/directory.png b/mimeicons/directory.png new file mode 100644B Binary files /dev/null and b/mimeicons/directory.png differF diff --git a/mimeicons/pending.png b/mimeicons/pending.png new file mode 100644B Binary files /dev/null and b/mimeicons/pending.png differF diff --git a/mimeicons/text-plain.png b/mimeicons/text-plain.png new file mode 100644B Binary files /dev/null and b/mimeicons/text-plain.png differ