mirror of
https://github.com/Dangoware/confetti-box.git
synced 2025-04-19 23:32:58 -05:00
Fixed relative paths. Fixed multi-file uploading. Fixed config application.
This commit is contained in:
parent
e356e9377d
commit
eb9db0f546
6 changed files with 142 additions and 91 deletions
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::HashMap, fs::{self, File}, path::{Path, PathBuf}, sync::{Arc, RwLock}, time::Duration};
|
use std::{collections::HashMap, fs::{self, File}, path::{Path, PathBuf}, sync::{Arc, RwLock}};
|
||||||
|
|
||||||
use bincode::{config::Configuration, decode_from_std_read, encode_into_std_write, Decode, Encode};
|
use bincode::{config::Configuration, decode_from_std_read, encode_into_std_write, Decode, Encode};
|
||||||
use chrono::{DateTime, TimeDelta, Utc};
|
use chrono::{DateTime, TimeDelta, Utc};
|
||||||
|
@ -121,6 +121,10 @@ impl MochiFile {
|
||||||
let datetime = Utc::now();
|
let datetime = Utc::now();
|
||||||
datetime > self.expiry_datetime
|
datetime > self.expiry_datetime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hash(&self) -> &Hash {
|
||||||
|
&self.hash
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
@ -175,9 +179,9 @@ fn clean_database(db: &Arc<RwLock<Database>>) {
|
||||||
pub async fn clean_loop(
|
pub async fn clean_loop(
|
||||||
db: Arc<RwLock<Database>>,
|
db: Arc<RwLock<Database>>,
|
||||||
mut shutdown_signal: Receiver<()>,
|
mut shutdown_signal: Receiver<()>,
|
||||||
interval: Duration,
|
interval: TimeDelta,
|
||||||
) {
|
) {
|
||||||
let mut interval = time::interval(interval);
|
let mut interval = time::interval(interval.to_std().unwrap());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
select! {
|
select! {
|
||||||
|
|
65
src/main.rs
65
src/main.rs
|
@ -2,13 +2,13 @@ mod database;
|
||||||
mod strings;
|
mod strings;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
|
||||||
use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}, time::Duration};
|
use std::{path::Path, sync::{Arc, RwLock}};
|
||||||
use blake3::Hash;
|
use blake3::Hash;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, TimeDelta, Utc};
|
||||||
use database::{clean_loop, Database, MochiFile};
|
use database::{clean_loop, Database, MochiFile};
|
||||||
use log::info;
|
use log::info;
|
||||||
use rocket::{
|
use rocket::{
|
||||||
data::{Limits, ToByteUnit}, form::Form, fs::{FileServer, Options, TempFile}, get, post, response::content::{RawCss, RawJavaScript}, routes, serde::{json::Json, Serialize}, tokio::{self, fs::File, io::AsyncReadExt}, Config, FromForm, State, http::ContentType,
|
data::{Limits, ToByteUnit}, form::Form, fs::{FileServer, Options, TempFile}, get, http::{ContentType, RawStr}, post, response::{content::{RawCss, RawJavaScript}, status::NotFound, Redirect}, routes, serde::{json::Json, Serialize}, tokio::{self, fs::File, io::AsyncReadExt}, Config, FromForm, State
|
||||||
};
|
};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use strings::{parse_time_string, to_pretty_time};
|
use strings::{parse_time_string, to_pretty_time};
|
||||||
|
@ -22,9 +22,9 @@ fn head(page_title: &str) -> Markup {
|
||||||
meta name="viewport" content="width=device-width, initial-scale=1";
|
meta name="viewport" content="width=device-width, initial-scale=1";
|
||||||
title { (page_title) }
|
title { (page_title) }
|
||||||
// Javascript stuff for client side handling
|
// Javascript stuff for client side handling
|
||||||
script src="request.js" { }
|
script src="./request.js" { }
|
||||||
link rel="icon" type="image/svg+xml" href="favicon.svg";
|
link rel="icon" type="image/svg+xml" href="favicon.svg";
|
||||||
link rel="stylesheet" href="main.css";
|
link rel="stylesheet" href="./main.css";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,6 @@ fn favicon() -> (ContentType, &'static str) {
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn home(settings: &State<Settings>) -> Markup {
|
fn home(settings: &State<Settings>) -> Markup {
|
||||||
dbg!(settings.duration.default);
|
|
||||||
html! {
|
html! {
|
||||||
(head("Confetti-Box"))
|
(head("Confetti-Box"))
|
||||||
|
|
||||||
|
@ -55,6 +54,10 @@ fn home(settings: &State<Settings>) -> Markup {
|
||||||
h1 { "Confetti-Box 🎉" }
|
h1 { "Confetti-Box 🎉" }
|
||||||
h2 { "Files up to " (settings.max_filesize.bytes()) " in size are allowed!" }
|
h2 { "Files up to " (settings.max_filesize.bytes()) " in size are allowed!" }
|
||||||
hr;
|
hr;
|
||||||
|
button.main_file_upload onclick="document.getElementById('fileInput').click()" {
|
||||||
|
h4 { "Upload File" }
|
||||||
|
p { "Click or Drag and Drop" }
|
||||||
|
}
|
||||||
h3 { "Expire after:" }
|
h3 { "Expire after:" }
|
||||||
div id="durationBox" {
|
div id="durationBox" {
|
||||||
@for d in &settings.duration.allowed {
|
@for d in &settings.duration.allowed {
|
||||||
|
@ -67,15 +70,11 @@ fn home(settings: &State<Settings>) -> Markup {
|
||||||
}
|
}
|
||||||
form #uploadForm {
|
form #uploadForm {
|
||||||
// It's stupid how these can't be styled so they're just hidden here...
|
// It's stupid how these can't be styled so they're just hidden here...
|
||||||
input id="fileInput" type="file" name="fileUpload"
|
input id="fileInput" type="file" name="fileUpload" multiple
|
||||||
onchange="formSubmit(this.parentNode)" data-max-filesize=(settings.max_filesize) style="display:none;";
|
onchange="formSubmit(this.parentNode)" data-max-filesize=(settings.max_filesize) style="display:none;";
|
||||||
input id="fileDuration" type="text" name="duration" minlength="2"
|
input id="fileDuration" type="text" name="duration" minlength="2"
|
||||||
maxlength="7" value=(settings.duration.default.num_seconds().to_string() + "s") style="display:none;";
|
maxlength="7" value=(settings.duration.default.num_seconds().to_string() + "s") style="display:none;";
|
||||||
}
|
}
|
||||||
button.main_file_upload onclick="document.getElementById('fileInput').click()" {
|
|
||||||
h4 { "Upload File" }
|
|
||||||
p { "Click or Drag and Drop" }
|
|
||||||
}
|
|
||||||
hr;
|
hr;
|
||||||
|
|
||||||
h3 { "Uploaded Files" }
|
h3 { "Uploaded Files" }
|
||||||
|
@ -97,11 +96,11 @@ fn home(settings: &State<Settings>) -> Markup {
|
||||||
|
|
||||||
#[derive(Debug, FromForm)]
|
#[derive(Debug, FromForm)]
|
||||||
struct Upload<'r> {
|
struct Upload<'r> {
|
||||||
#[field(name = "fileUpload")]
|
|
||||||
file: TempFile<'r>,
|
|
||||||
|
|
||||||
#[field(name = "duration")]
|
#[field(name = "duration")]
|
||||||
expire_time: String,
|
expire_time: String,
|
||||||
|
|
||||||
|
#[field(name = "fileUpload")]
|
||||||
|
file: TempFile<'r>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a file upload and store it
|
/// Handle a file upload and store it
|
||||||
|
@ -111,8 +110,8 @@ async fn handle_upload(
|
||||||
db: &State<Arc<RwLock<Database>>>,
|
db: &State<Arc<RwLock<Database>>>,
|
||||||
settings: &State<Settings>,
|
settings: &State<Settings>,
|
||||||
) -> Result<Json<ClientResponse>, std::io::Error> {
|
) -> Result<Json<ClientResponse>, std::io::Error> {
|
||||||
let mut out_path = PathBuf::from("files/");
|
|
||||||
let mut temp_dir = settings.temp_dir.clone();
|
let mut temp_dir = settings.temp_dir.clone();
|
||||||
|
let mut out_path = settings.file_dir.clone();
|
||||||
|
|
||||||
let expire_time = if let Ok(t) = parse_time_string(&file_data.expire_time) {
|
let expire_time = if let Ok(t) = parse_time_string(&file_data.expire_time) {
|
||||||
if t > settings.duration.maximum {
|
if t > settings.duration.maximum {
|
||||||
|
@ -149,7 +148,7 @@ async fn handle_upload(
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
// Get temp path and hash it
|
// Get temp path and hash it
|
||||||
temp_dir.push(&Uuid::new_v4().to_string());
|
temp_dir.push(Uuid::new_v4().to_string());
|
||||||
let temp_filename = temp_dir;
|
let temp_filename = temp_dir;
|
||||||
file_data.file.persist_to(&temp_filename).await?;
|
file_data.file.persist_to(&temp_filename).await?;
|
||||||
let hash = hash_file(&temp_filename).await?;
|
let hash = hash_file(&temp_filename).await?;
|
||||||
|
@ -182,6 +181,7 @@ async fn handle_upload(
|
||||||
status: true,
|
status: true,
|
||||||
name: constructed_file.name().clone(),
|
name: constructed_file.name().clone(),
|
||||||
url: "files/".to_string() + &filename,
|
url: "files/".to_string() + &filename,
|
||||||
|
hash: hash.0.to_hex()[0..10].to_string(),
|
||||||
expires: Some(constructed_file.get_expiry()),
|
expires: Some(constructed_file.get_expiry()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}))
|
}))
|
||||||
|
@ -200,6 +200,8 @@ struct ClientResponse {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(skip_serializing_if = "String::is_empty")]
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
pub url: String,
|
pub url: String,
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
|
pub hash: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub expires: Option<DateTime<Utc>>,
|
pub expires: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
@ -247,6 +249,27 @@ struct ServerInfo {
|
||||||
allowed_durations: Vec<u32>,
|
allowed_durations: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Look up the hash of a file to find it. This only returns the first
|
||||||
|
/// hit for a hash, so different filenames may not be found.
|
||||||
|
#[get("/f/<id>")]
|
||||||
|
fn lookup(
|
||||||
|
db: &State<Arc<RwLock<Database>>>,
|
||||||
|
id: &str
|
||||||
|
) -> Result<Redirect, NotFound<()>> {
|
||||||
|
for file in db.read().unwrap().files.values() {
|
||||||
|
if file.hash().to_hex()[0..10].to_string() == id {
|
||||||
|
let filename = get_id(
|
||||||
|
file.name(),
|
||||||
|
*file.hash()
|
||||||
|
);
|
||||||
|
let filename = RawStr::new(&filename).percent_encode().to_string();
|
||||||
|
return Ok(Redirect::to(format!("/files/{}", filename)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(NotFound(()))
|
||||||
|
}
|
||||||
|
|
||||||
#[rocket::main]
|
#[rocket::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Get or create config file
|
// Get or create config file
|
||||||
|
@ -264,24 +287,24 @@ async fn main() {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let database = Arc::new(RwLock::new(Database::open(&"database.mochi")));
|
let database = Arc::new(RwLock::new(Database::open(&config.database_path)));
|
||||||
let local_db = database.clone();
|
let local_db = database.clone();
|
||||||
|
|
||||||
// Start monitoring thread
|
// Start monitoring thread, cleaning the database every 2 minutes
|
||||||
let (shutdown, rx) = tokio::sync::mpsc::channel(1);
|
let (shutdown, rx) = tokio::sync::mpsc::channel(1);
|
||||||
tokio::spawn({
|
tokio::spawn({
|
||||||
let cleaner_db = database.clone();
|
let cleaner_db = database.clone();
|
||||||
async move { clean_loop(cleaner_db, rx, Duration::from_secs(120)).await }
|
async move { clean_loop(cleaner_db, rx, TimeDelta::minutes(2)).await }
|
||||||
});
|
});
|
||||||
|
|
||||||
let rocket = rocket::build()
|
let rocket = rocket::build()
|
||||||
.mount(
|
.mount(
|
||||||
config.server.root_path.clone() + "/",
|
config.server.root_path.clone() + "/",
|
||||||
routes![home, handle_upload, form_handler_js, stylesheet, server_info, favicon]
|
routes![home, handle_upload, form_handler_js, stylesheet, server_info, favicon, lookup]
|
||||||
)
|
)
|
||||||
.mount(
|
.mount(
|
||||||
config.server.root_path.clone() + "/files",
|
config.server.root_path.clone() + "/files",
|
||||||
FileServer::new("files/", Options::Missing | Options::NormalizeDirs)
|
FileServer::new(config.file_dir.clone(), Options::Missing | Options::NormalizeDirs)
|
||||||
)
|
)
|
||||||
.manage(database)
|
.manage(database)
|
||||||
.manage(config)
|
.manage(config)
|
||||||
|
|
|
@ -29,6 +29,10 @@ pub struct Settings {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub temp_dir: PathBuf,
|
pub temp_dir: PathBuf,
|
||||||
|
|
||||||
|
/// Directory in which to store hosted files
|
||||||
|
#[serde(default)]
|
||||||
|
pub file_dir: PathBuf,
|
||||||
|
|
||||||
/// Settings pertaining to the server configuration
|
/// Settings pertaining to the server configuration
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub server: ServerSettings,
|
pub server: ServerSettings,
|
||||||
|
@ -46,7 +50,8 @@ impl Default for Settings {
|
||||||
server: ServerSettings::default(),
|
server: ServerSettings::default(),
|
||||||
path: "./settings.toml".into(),
|
path: "./settings.toml".into(),
|
||||||
database_path: "./database.mochi".into(),
|
database_path: "./database.mochi".into(),
|
||||||
temp_dir: std::env::temp_dir()
|
temp_dir: std::env::temp_dir(),
|
||||||
|
file_dir: "./files/".into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ button.main_file_upload {
|
||||||
height: 75px;
|
height: 75px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #84E5FF;
|
background-color: #84E5FF;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -110,10 +111,27 @@ button.main_file_upload {
|
||||||
min-height: 2em;
|
min-height: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#uploadedFilesDisplay p.file_name {
|
||||||
|
width: 50%;
|
||||||
|
overflow: clip;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#uploadedFilesDisplay p.status {
|
||||||
|
overflow: clip;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: right;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
#uploadedFilesDisplay div {
|
#uploadedFilesDisplay div {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#uploadedFilesDisplay > div > progress {
|
#uploadedFilesDisplay > div > progress {
|
||||||
|
@ -123,8 +141,9 @@ button.main_file_upload {
|
||||||
}
|
}
|
||||||
|
|
||||||
#uploadedFilesDisplay button {
|
#uploadedFilesDisplay button {
|
||||||
height: 50px;
|
height: fit-content;
|
||||||
width: 50px;
|
margin: auto;
|
||||||
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
progress[value] {
|
progress[value] {
|
||||||
|
@ -134,6 +153,7 @@ progress[value] {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
|
border-radius: 5px;
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,76 +2,89 @@ let statusNotifier;
|
||||||
let uploadedFilesDisplay;
|
let uploadedFilesDisplay;
|
||||||
let durationBox;
|
let durationBox;
|
||||||
|
|
||||||
let uploadInProgress = false;
|
const TOO_LARGE_TEXT = "Too large!";
|
||||||
|
const ERROR_TEXT = "Error!";
|
||||||
const TOO_LARGE_TEXT = "File is too large!";
|
|
||||||
const ERROR_TEXT = "An error occured!";
|
|
||||||
|
|
||||||
async function formSubmit(form) {
|
async function formSubmit(form) {
|
||||||
if (uploadInProgress) {
|
|
||||||
return; // TODO: REMOVE THIS ONCE MULTIPLE CAN WORK!
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get file size and don't upload if it's too large
|
// Get file size and don't upload if it's too large
|
||||||
let file_upload = document.getElementById("fileInput");
|
let file_upload = document.getElementById("fileInput");
|
||||||
let file = file_upload.files[0];
|
|
||||||
if (file.size > file_upload.dataset.maxFilesize) {
|
|
||||||
progressValue.textContent = TOO_LARGE_TEXT;
|
|
||||||
console.error(
|
|
||||||
"Provided file is too large", file.size, "bytes; max",
|
|
||||||
CAPABILITIES.max_filesize, "bytes"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let [progressBar, progressText] = addNewToList(file.name);
|
for (const file of file_upload.files) {
|
||||||
|
let [linkRow, progressBar, progressText] = addNewToList(file.name);
|
||||||
|
if (file.size > file_upload.dataset.maxFilesize) {
|
||||||
|
makeErrored(progressBar, progressText, linkRow, TOO_LARGE_TEXT);
|
||||||
|
console.error(
|
||||||
|
"Provided file is too large", file.size, "bytes; max",
|
||||||
|
file_upload.dataset.maxFilesize, "bytes"
|
||||||
|
);
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
let url = "/upload";
|
let request = new XMLHttpRequest();
|
||||||
let request = new XMLHttpRequest();
|
request.open('POST', "./upload", true);
|
||||||
request.open('POST', url, true);
|
|
||||||
|
|
||||||
// Set up event listeners
|
// Set up event listeners
|
||||||
request.upload.addEventListener('progress', (p) => {uploadProgress(p, progressBar, progressText)}, false);
|
request.upload.addEventListener('progress', (p) => {uploadProgress(p, progressBar, progressText, linkRow)}, false);
|
||||||
request.addEventListener('load', (c) => {uploadComplete(c, progressBar, progressText)}, false);
|
request.addEventListener('load', (c) => {uploadComplete(c, progressBar, progressText, linkRow)}, false);
|
||||||
request.addEventListener('error', networkErrorHandler, false);
|
request.addEventListener('error', (e) => {networkErrorHandler(e, progressBar, progressText, linkRow)}, false);
|
||||||
|
|
||||||
uploadInProgress = true;
|
// Create and send FormData
|
||||||
// Create and send FormData
|
try {
|
||||||
try {
|
request.send(new FormData(form));
|
||||||
request.send(new FormData(form));
|
} catch (e) {
|
||||||
} catch (e) {
|
makeErrored(progressBar, progressText, linkRow, ERROR_TEXT);
|
||||||
console.error("An error occured while uploading", e);
|
console.error("An error occured while uploading", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the form file data since we've successfully submitted it
|
// Reset the form file data since we've successfully submitted it
|
||||||
form.elements["fileUpload"].value = "";
|
form.elements["fileUpload"].value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function networkErrorHandler(_err) {
|
function makeErrored(progressBar, progressText, linkRow, errorMessage) {
|
||||||
uploadInProgress = false;
|
progressText.textContent = errorMessage;
|
||||||
console.error("A network error occured while uploading");
|
progressBar.style.display = "none";
|
||||||
progressValue.textContent = "A network error occured!";
|
linkRow.style.background = "#ffb2ae";
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadComplete(response, _progressBar, progressText) {
|
function makeFinished(progressBar, progressText, linkRow, linkAddress, hash) {
|
||||||
|
progressText.textContent = "";
|
||||||
|
const link = progressText.appendChild(document.createElement("a"));
|
||||||
|
link.textContent = hash;
|
||||||
|
link.href = linkAddress;
|
||||||
|
|
||||||
|
let button = linkRow.appendChild(document.createElement("button"));
|
||||||
|
button.textContent = "📝";
|
||||||
|
button.addEventListener('click', function(e) {
|
||||||
|
navigator.clipboard.writeText("https://" + window.location.host + "/" + linkAddress)
|
||||||
|
})
|
||||||
|
|
||||||
|
progressBar.style.display = "none";
|
||||||
|
linkRow.style.background = "#a4ffbb";
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkErrorHandler(err, progressBar, progressText, linkRow) {
|
||||||
|
makeErrored(progressBar, progressText, linkRow, "A network error occured");
|
||||||
|
console.error("A network error occured while uploading", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadComplete(response, progressBar, progressText, linkRow) {
|
||||||
let target = response.target;
|
let target = response.target;
|
||||||
|
|
||||||
if (target.status === 200) {
|
if (target.status === 200) {
|
||||||
const response = JSON.parse(target.responseText);
|
const response = JSON.parse(target.responseText);
|
||||||
|
|
||||||
if (response.status) {
|
if (response.status) {
|
||||||
progressText.textContent = "Success";
|
makeFinished(progressBar, progressText, linkRow, response.url, response.hash);
|
||||||
} else {
|
} else {
|
||||||
console.error("Error uploading", response)
|
console.error("Error uploading", response)
|
||||||
progressText.textContent = response.response;
|
makeErrored(progressBar, progressText, linkRow, response.response);
|
||||||
}
|
}
|
||||||
} else if (target.status === 413) {
|
} else if (target.status === 413) {
|
||||||
progressText.textContent = TOO_LARGE_TEXT;
|
makeErrored(progressBar, progressText, linkRow, TOO_LARGE_TEXT);
|
||||||
} else {
|
} else {
|
||||||
progressText.textContent = ERROR_TEXT;
|
makeErrored(progressBar, progressText, linkRow, ERROR_TEXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadInProgress = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNewToList(origFileName) {
|
function addNewToList(origFileName) {
|
||||||
|
@ -81,18 +94,18 @@ function addNewToList(origFileName) {
|
||||||
const progressTxt = linkRow.appendChild(document.createElement("p"));
|
const progressTxt = linkRow.appendChild(document.createElement("p"));
|
||||||
|
|
||||||
fileName.textContent = origFileName;
|
fileName.textContent = origFileName;
|
||||||
|
fileName.classList.add("file_name");
|
||||||
|
progressTxt.classList.add("status");
|
||||||
progressBar.max="100";
|
progressBar.max="100";
|
||||||
progressBar.value="0";
|
progressBar.value="0";
|
||||||
|
|
||||||
return [progressBar, progressTxt];
|
return [linkRow, progressBar, progressTxt];
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadProgress(progress, progressBar, progressText) {
|
function uploadProgress(progress, progressBar, progressText, linkRow) {
|
||||||
console.log(progress);
|
|
||||||
if (progress.lengthComputable) {
|
if (progress.lengthComputable) {
|
||||||
const progressPercent = Math.floor((progress.loaded / progress.total) * 100);
|
const progressPercent = Math.floor((progress.loaded / progress.total) * 100);
|
||||||
progressBar.value = progressPercent;
|
progressBar.value = progressPercent;
|
||||||
console.log(progressBar.value);
|
|
||||||
progressText.textContent = progressPercent + "%";
|
progressText.textContent = progressPercent + "%";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,24 +117,10 @@ document.addEventListener("DOMContentLoaded", function(_event){
|
||||||
uploadedFilesDisplay = document.getElementById("uploadedFilesDisplay");
|
uploadedFilesDisplay = document.getElementById("uploadedFilesDisplay");
|
||||||
durationBox = document.getElementById("durationBox");
|
durationBox = document.getElementById("durationBox");
|
||||||
|
|
||||||
getServerCapabilities();
|
initEverything();
|
||||||
});
|
});
|
||||||
|
|
||||||
function toPrettyTime(seconds) {
|
async function initEverything() {
|
||||||
var days = Math.floor(seconds / 86400);
|
|
||||||
var hour = Math.floor((seconds - (days * 86400)) / 3600);
|
|
||||||
var mins = Math.floor((seconds - (hour * 3600) - (days * 86400)) / 60);
|
|
||||||
var secs = seconds - (hour * 3600) - (mins * 60) - (days * 86400);
|
|
||||||
|
|
||||||
if(days == 0) {days = "";} else if(days == 1) {days += "<br>day"} else {days += "<br>days"}
|
|
||||||
if(hour == 0) {hour = "";} else if(hour == 1) {hour += "<br>hour"} else {hour += "<br>hours"}
|
|
||||||
if(mins == 0) {mins = "";} else if(mins == 1) {mins += "<br>minute"} else {mins += "<br>minutes"}
|
|
||||||
if(secs == 0) {secs = "";} else if(secs == 1) {secs += "<br>second"} else {secs += "<br>seconds"}
|
|
||||||
|
|
||||||
return (days + " " + hour + " " + mins + " " + secs).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getServerCapabilities() {
|
|
||||||
const durationButtons = durationBox.getElementsByTagName("button");
|
const durationButtons = durationBox.getElementsByTagName("button");
|
||||||
for (const b of durationButtons) {
|
for (const b of durationButtons) {
|
||||||
b.addEventListener("click", function (_e) {
|
b.addEventListener("click", function (_e) {
|
||||||
|
|
|
@ -37,9 +37,9 @@ pub fn parse_time_string(string: &str) -> Result<TimeDelta, Box<dyn Error>> {
|
||||||
|
|
||||||
pub fn to_pretty_time(seconds: u32) -> String {
|
pub fn to_pretty_time(seconds: u32) -> String {
|
||||||
let days = (seconds as f32 / 86400.0).floor();
|
let days = (seconds as f32 / 86400.0).floor();
|
||||||
let hour = ((seconds as f32 - (days as f32 * 86400.0)) / 3600.0).floor();
|
let hour = ((seconds as f32 - (days * 86400.0)) / 3600.0).floor();
|
||||||
let mins = ((seconds as f32 - (hour * 3600.0) - (days * 86400.0)) / 60.0).floor();
|
let mins = ((seconds as f32 - (hour * 3600.0) - (days * 86400.0)) / 60.0).floor();
|
||||||
let secs = seconds as f32 - (hour as f32 * 3600.0) - (mins as f32 * 60.0) - (days as f32 * 86400.0);
|
let secs = seconds as f32 - (hour * 3600.0) - (mins * 60.0) - (days * 86400.0);
|
||||||
|
|
||||||
let days = if days == 0.0 {"".to_string()} else if days == 1.0 {days.to_string() + "<br>day"} else {days.to_string() + "<br>days"};
|
let days = if days == 0.0 {"".to_string()} else if days == 1.0 {days.to_string() + "<br>day"} else {days.to_string() + "<br>days"};
|
||||||
let hour = if hour == 0.0 {"".to_string()} else if hour == 1.0 {hour.to_string() + "<br>hour"} else {hour.to_string() + "<br>hours"};
|
let hour = if hour == 0.0 {"".to_string()} else if hour == 1.0 {hour.to_string() + "<br>hour"} else {hour.to_string() + "<br>hours"};
|
||||||
|
|
Loading…
Reference in a new issue