F diff --git a/css/style.css b/css/style.css
--- a/css/style.css
+++ b/css/style.css
body {
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 api
fetch(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 100644
B Binary files /dev/null and b/mimeicons/directory.png differ
F diff --git a/mimeicons/pending.png b/mimeicons/pending.png
new file mode 100644
B Binary files /dev/null and b/mimeicons/pending.png differ
F diff --git a/mimeicons/text-plain.png b/mimeicons/text-plain.png
new file mode 100644
B Binary files /dev/null and b/mimeicons/text-plain.png differ