0.5.0: A functional shit frontend made by chatgpt because I hate frontend so much

This commit is contained in:
Fijxu 2024-08-04 03:32:30 -04:00
parent e096ed5215
commit b2395b3dea
Signed by: Fijxu
GPG key ID: 32C1DDF333EDA6A4
11 changed files with 518 additions and 99 deletions

View file

@ -5,4 +5,6 @@ I'm making this to replace my current File uploader hosted on https://ayaya.beau
## TODO ## TODO
- Add file size limit - ~~Add file size limit~~ ADDED
- Fix error when accessing `http://127.0.0.1:8080` with an empty DB.
- Better frontend...

View file

@ -1,2 +0,0 @@
blocked_extensions:
- "exe"

BIN
public/chatterino.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

140
public/script.js Normal file
View file

@ -0,0 +1,140 @@
document.addEventListener("DOMContentLoaded", () => {
const dropArea = document.getElementById("drop-area");
const fileInput = document.getElementById("fileElem");
const uploadStatus = document.getElementById("upload-status");
// Prevent default drag behaviors
["dragenter", "dragover", "dragleave", "drop"].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
// Highlight drop area when item is dragged over
["dragenter", "dragover"].forEach(eventName => {
dropArea.addEventListener(eventName, highlight, false);
});
["dragleave", "drop"].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false);
});
// Handle dropped files
dropArea.addEventListener("drop", handleDrop, false);
dropArea.addEventListener("click", () => fileInput.click());
// Handle file selection
fileInput.addEventListener("change", () => {
const files = fileInput.files;
handleFiles(files);
}, false);
// Handle pasted files
document.addEventListener("paste", handlePaste, false);
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
function highlight() {
dropArea.classList.add("highlight");
}
function unhighlight() {
dropArea.classList.remove("highlight");
}
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles(files);
}
function handlePaste(e) {
const items = e.clipboardData.items;
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.kind === "file") {
const file = item.getAsFile();
handleFiles([file]);
}
}
}
function handleFiles(files) {
if (files.length > 0) {
for (const file of files) {
uploadFile(file);
}
}
}
function uploadFile(file) {
const url = "upload"; // Replace with your upload URL
const xhr = new XMLHttpRequest();
// Create a new upload status container and link elements
const uploadContainer = document.createElement("div");
const statusLink = document.createElement("div");
const uploadText = document.createElement("span");
const copyButton = document.createElement("button");
uploadContainer.className = "upload-status"; // Use the existing CSS class for styling
uploadContainer.appendChild(uploadText);
uploadContainer.appendChild(statusLink);
uploadContainer.appendChild(copyButton);
uploadStatus.appendChild(uploadContainer); // Append to the main upload status container
// Update upload text
uploadText.innerHTML = "0%";
uploadText.className = "percent"
copyButton.className = "copy-button"; // Add class for styling
copyButton.innerHTML = "Copy Link"; // Set button text
copyButton.style.display = "none"; // Hide initially
// Update progress text
xhr.upload.addEventListener("progress", (e) => {
if (e.lengthComputable) {
const percentComplete = Math.round((e.loaded / e.total) * 100);
uploadText.innerHTML = `${percentComplete}%`; // Update the text with the percentage
}
});
// Handle response
xhr.onload = () => {
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText);
const fileLink = response.link; // Assuming the response contains a key 'link'
statusLink.innerHTML = `<a href="${fileLink}" target="_blank">${fileLink}</a>`;
copyButton.style.display = "inline"; // Show the copy button
copyButton.onclick = () => copyToClipboard(fileLink); // Set the copy action
} catch (error) {
statusLink.textContent = "File uploaded but failed to parse response.";
}
} else {
statusLink.textContent = "File upload failed.";
}
};
// Handle errors
xhr.onerror = () => {
statusLink.textContent = "An error occurred during the file upload.";
};
// Send file
const formData = new FormData();
formData.append("file", file);
xhr.open("POST", url, true);
xhr.send(formData);
}
// Function to copy the link to the clipboard
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
// alert("Link copied to clipboard!"); // Notify the user
}).catch(err => {
console.error("Failed to copy: ", err);
});
}
});

11
public/sharex.sxcu Normal file
View file

@ -0,0 +1,11 @@
{
"Version": "14.0.1",
"DestinationType": "ImageUploader, FileUploader",
"RequestMethod": "POST",
"RequestURL": "https://ayaya.beauty/upload",
"Body": "MultipartFormData",
"FileFormName": "file",
"URL": "{json:link}",
"DeletionURL": "{json:deleteLink}",
"ErrorMessage": "{json:error}"
}

135
public/styles.css Normal file
View file

@ -0,0 +1,135 @@
body {
font-family: Arial, sans-serif;
background-color: #111;
margin: 0;
padding: 20px;
}
p, h1, h2, h3, h4, h5 {
color: aliceblue
}
.percent {
color: aliceblue
}
.container {
max-width: 700px;
margin: auto;
/* background: white; */
padding: 20px;
border-radius: 0px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
#drop-area {
/*! border: 2px solid #00ff00; */
/*! border-radius: 6px; */
/*! padding-left: 10px; */
/*! padding-right: 10px; */
text-align: center;
position: relative;
width: fit-content;
margin: 0 auto; /* Center the element */
display: block; /* Ensure it behaves as a block-level element */
background: rgba(202,230,190,.75);
border: 1px solid #b7d1a0;
border-radius: 4px;
color: #468847;
cursor: pointer;
/*! display: inline-block; */
font-size: 24px;
padding: 28px 48px;
text-shadow: 0 1px hsla(0,0%,100%,.5);
transition: background-color .25s,width .5s,height .5s;
}
.button {
display: inline-block;
padding: 10px 20px;
/* background: #; */
color: white;
border-radius: 5px;
cursor: pointer;
/* margin-top: 10px; */
}
.upload-status {
margin-top: 10px;
}
.copy-button {
margin-top: 5px;
padding: 5px 10px;
background: #28a745;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
display: none; /* Hidden initially */
}
nav a, nav > ul
{
list-style: none;
margin: 0;
padding: 0;
text-align: center;
}
#upload-status {
margin: 20px; /* Adjust as needed */
}
.upload-status {
display: flex;
align-items: center;
justify-content: space-between;
border: 2px solid #f40101; /* Optional styling for the status box */
padding: 10px; /* Optional padding */
border-radius: 6px; /* Optional rounded corners */
/*! background-color: #f9f9f9; */ /* Optional background color */
}
.link-container {
display: flex;
align-items: center;
margin-left: auto; /* Pushes the link and button to the right */
}
.link {
color: #ffb6c1;
text-decoration: none; /* Remove underline from link */
margin-right: 5px; /* Space between link and button */
}
.link:hover {
text-decoration: underline; /* Optional: underline on hover */
}
.copy-button {
display: inline;
background-color: #5e5e5e; /* Button background color */
color: white; /* Button text color */
border: none; /* Remove border */
border-radius: 3px; /* Rounded corners for the button */
padding: 5px 10px; /* Button padding */
cursor: pointer; /* Pointer cursor on hover */
}
.copy-button:hover {
background-color: #404040; /* Darker shade on hover */
}
a:link {
color: #ffb6c1
}
a:visited {
color: #ffb6c1
}
a:hover {
color: #ffb6c1
}

165
public/upload.js Normal file
View file

@ -0,0 +1,165 @@
// document.addEventListener("DOMContentLoaded", () => {
// const dropArea = document.getElementById("drop-area");
// const fileInput = document.getElementById("fileElem");
// const progressContainer = document.getElementById("progress-container");
// const progressBar = document.getElementById("progress-bar");
// const status = document.getElementById("status");
// // Prevent default drag behaviors
// ["dragenter", "dragover", "dragleave", "drop"].forEach(eventName => {
// dropArea.addEventListener(eventName, preventDefaults, false);
// document.body.addEventListener(eventName, preventDefaults, false);
// });
// // Highlight drop area when item is dragged over
// ["dragenter", "dragover"].forEach(eventName => {
// dropArea.addEventListener(eventName, highlight, false);
// });
// ["dragleave", "drop"].forEach(eventName => {
// dropArea.addEventListener(eventName, unhighlight, false);
// });
// // Handle dropped files
// dropArea.addEventListener("drop", handleDrop, false);
// dropArea.addEventListener("click", () => fileInput.click());
// // Handle file selection
// fileInput.addEventListener("change", () => {
// const files = fileInput.files;
// handleFiles(files);
// }, false);
// // Handle pasted files
// document.addEventListener("paste", handlePaste, false);
// function preventDefaults(e) {
// e.preventDefault();
// e.stopPropagation();
// }
// function highlight() {
// dropArea.classList.add("highlight");
// }
// function unhighlight() {
// dropArea.classList.remove("highlight");
// }
// function handleDrop(e) {
// const dt = e.dataTransfer;
// const files = dt.files;
// handleFiles(files);
// }
// function handlePaste(e) {
// const items = e.clipboardData.items;
// for (let i = 0; i < items.length; i++) {
// const item = items[i];
// if (item.kind === "file") {
// const file = item.getAsFile();
// handleFiles([file]);
// }
// }
// }
// function handleFiles(files) {
// if (files.length > 0) {
// uploadFile(files[0]);
// }
// }
// function uploadFile(file) {
// const url = "upload"; // Replace with your upload URL
// const xhr = new XMLHttpRequest();
// // Update progress bar
// xhr.upload.addEventListener("progress", (e) => {
// if (e.lengthComputable) {
// const percentComplete = (e.loaded / e.total) * 100;
// progressBar.style.width = percentComplete + "%"; // Set the width of the progress bar
// progressContainer.style.display = "block"; // Show progress container
// }
// });
// // Handle response
// xhr.onload = () => {
// if (xhr.status === 200) {
// try {
// const response = JSON.parse(xhr.responseText);
// const fileLink = response.link; // Assuming the response contains a key 'link'
// status.innerHTML = `<a href="${fileLink}" target="_blank">File uploaded successfully! Click here to view the file</a>`;
// } catch (error) {
// status.textContent = "File uploaded but failed to parse response.";
// }
// } else {
// status.textContent = "File upload failed.";
// }
// progressBar.style.width = "0"; // Reset progress bar
// progressContainer.style.display = "none"; // Hide progress container
// };
// // Handle errors
// xhr.onerror = () => {
// status.textContent = "An error occurred during the file upload.";
// progressBar.style.width = "0"; // Reset progress bar
// progressContainer.style.display = "none"; // Hide progress container
// };
// // Send file
// const formData = new FormData();
// formData.append("file", file);
// xhr.open("POST", url, true);
// xhr.send(formData);
// }
// });
function handleFiles(input) {
const files = input.files;
Array.from(files).forEach(file => {
// Display download link initially
document.querySelector(`#link-${file.name}`).textContent = "Uploading...";
// Create a new FormData instance
let formData = new FormData();
formData.append('file', file);
// Simulate a request to the server
fetch('/upload', { method: 'POST', body: formData })
.then(response => response.json())
.then(data => {
// Update the progress bar
document.querySelector(`#progress-${file.name}`).style.width = `${data.progress}%`;
// Display the download link
document.querySelector(`#link-${file.name}`).textContent = data.link;
})
.catch(error => console.error('Error:', error));
});
}
// Handle drag & drop
document.addEventListener('dragover', function(event) {
event.preventDefault();
event.stopPropagation();
});
document.addEventListener('drop', function(event) {
event.preventDefault();
event.stopPropagation();
const files = event.dataTransfer.files;
handleFiles(files);
}, false);
// Handle clipboard paste
document.addEventListener('paste', function(event) {
event.preventDefault();
event.stopPropagation();
const items = event.clipboardData.items;
if (items.length > 0 && items[0].type.indexOf("text") !== -1) {
const file = items[0].getAsFile();
handleFiles([file]);
}
}, false);

View file

@ -4,6 +4,7 @@ class Config
include YAML::Serializable include YAML::Serializable
property files : String = "./files" property files : String = "./files"
property secure : Bool = false
property db : String = "./db.sqlite3" property db : String = "./db.sqlite3"
property filename_lenght : Int8 = 3 property filename_lenght : Int8 = 3
# In MiB # In MiB
@ -16,6 +17,8 @@ class Config
property delete_key_lenght : Int8 = 8 property delete_key_lenght : Int8 = 8
# Blocked extensions that are not allowed to be uploaded to the server # Blocked extensions that are not allowed to be uploaded to the server
property blocked_extensions : Array(String) = [] of String property blocked_extensions : Array(String) = [] of String
property siteInfo : String = "xd"
property siteWarning : String? = ""
def self.load def self.load
config_file = "config/config.yml" config_file = "config/config.yml"

View file

@ -14,10 +14,16 @@ CONFIG = Config.load
Kemal.config.port = CONFIG.port Kemal.config.port = CONFIG.port
SQL = DB.open("sqlite3://#{CONFIG.db}") SQL = DB.open("sqlite3://#{CONFIG.db}")
# https://github.com/iv-org/invidious/blob/90e94d4e6cc126a8b7a091d12d7a5556bfe369d5/src/invidious.cr#L78
CURRENT_BRANCH = {{ "#{`git branch | sed -n '/* /s///p'`.strip}" }}
CURRENT_COMMIT = {{ "#{`git rev-list HEAD --max-count=1 --abbrev-commit`.strip}" }}
CURRENT_VERSION = {{ "#{`git log -1 --format=%ci | awk '{print $1}' | sed s/-/./g`.strip}" }}
Utils.create_db Utils.create_db
Utils.create_files_dir Utils.create_files_dir
get "/" do |env| get "/" do |env|
host = env.request.headers["Host"]
render "src/views/index.ecr" render "src/views/index.ecr"
end end
@ -47,3 +53,7 @@ end
CHECK_OLD_FILES.enqueue CHECK_OLD_FILES.enqueue
Kemal.run Kemal.run
{% if flag?(:release) || flag?(:production) %}
Kemal.config.env = "production" if !ENV.has_key?("KEMAL_ENV")
{% end %}

View file

@ -56,7 +56,8 @@ end
if !filename.empty? if !filename.empty?
JSON.build do |j| JSON.build do |j|
j.object do j.object do
j.field "link", "https://#{env.request.headers["Host"]}/#{filename + extension}" CONFIG.secure ? j.field "link", "https://#{env.request.headers["Host"]}/#{filename}" : j.field "link", "http://#{env.request.headers["Host"]}/#{filename}"
j.field "linkExt", "https://#{env.request.headers["Host"]}/#{filename}#{extension}"
j.field "id", filename j.field "id", filename
j.field "ext", extension j.field "ext", extension
j.field "name", original_filename j.field "name", original_filename
@ -71,6 +72,7 @@ end
end end
def retrieve_file(env) def retrieve_file(env)
puts env.params.url
filename = SQL.query_one "SELECT filename FROM files WHERE filename = ?", env.params.url["filename"].to_s.split(".").first, as: String filename = SQL.query_one "SELECT filename FROM files WHERE filename = ?", env.params.url["filename"].to_s.split(".").first, as: String
extension = SQL.query_one "SELECT extension FROM files WHERE filename = ?", filename, as: String extension = SQL.query_one "SELECT extension FROM files WHERE filename = ?", filename, as: String
send_file env, "#{CONFIG.files}/#{filename}#{extension}" send_file env, "#{CONFIG.files}/#{filename}#{extension}"
@ -102,7 +104,7 @@ end
file_extension = SQL.query_one "SELECT extension FROM files WHERE delete_key = ?", env.params.query["key"], as: String file_extension = SQL.query_one "SELECT extension FROM files WHERE delete_key = ?", env.params.query["key"], as: String
File.delete("#{CONFIG.files}/#{file_to_delete}#{file_extension}") File.delete("#{CONFIG.files}/#{file_to_delete}#{file_extension}")
SQL.exec "DELETE FROM files WHERE delete_key = ?", env.params.query["key"] SQL.exec "DELETE FROM files WHERE delete_key = ?", env.params.query["key"]
msg("File deleted successfully") msg("File '#{file_to_delete}' deleted successfully")
rescue ex rescue ex
error403("Unknown error: #{ex.message}") error403("Unknown error: #{ex.message}")
end end

View file

@ -1,100 +1,53 @@
<!-- <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Upload with Progress Bar</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>File Upload</h1>
<div id="drop-area">
<p>Drag & Drop your file here or click to upload</p>
<input type="file" id="fileElem" accept="*/*" style="display: none;">
<label for="fileElem" class="button">Select File</label>
<div id="progress-container" style="display: none;">
<div id="progress-bar"></div>
</div>
</div>
<div id="status"></div>
</div>
<script src="script.js"></script>
</body>
</html> -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Upload</title> <title> <%= host %> </title>
<style> <link rel="stylesheet" href="styles.css">
body {
background-color: #111;
color: aliceblue
}
.container {
margin: 0 auto;
max-width: 700px;
}
.jumbotron {
margin: 60px 0;
text-align: center;
transition: width .5s, height .5s, margin .5s, padding .5s;
}
#progressContainer {
margin-top: 10px;
width: 100%;
background-color: #343434;
border: 1px solid #ccc;
}
#progressBar {
height: 20px;
width: 0;
background-color: #6d95bb;
}
</style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<div class="jumbotron"> <h1 style="font-size: 72px; text-align: center; margin: 20px;"> <%= host %> </h1>
<div id="dropZone" class="drop-zone"> <p style="text-align: center; font-size: 22px;"> <%= CONFIG.siteInfo %> </p>
<p>Drag and drop a file here or click to select</p> <div id="drop-area">
<input type="file" id="fileInput" name="file" style="display: none;"> <p style='padding: 0;margin: 0; color: #123718bf;'>Drop or Choose file(s)</p>
</div> <input type="file" id="fileElem" accept="*/*" style="display: none;">
<label for="fileInput"></label> <!-- <label for="fileElem" class="button">Select File</label> -->
<input type="file" id="fileInput"> </div>
<button id="uploadButton">Upload File</button> <div id="upload-status"></div>
<div id="progressContainer"> </div>
<div id="progressBar"></div> <div>
</div> <div style="text-align:center;">
<div id="linkContainer"></div> <p> <a href='./chatterino.png'>Chatterino Config</a> </p>
</div> <p> <a href='./sharex.sxcu'>ShareX Config</a> </p>
</div> <p> <a href='https://codeberg.org/Fijxu/file-uploader-crystal'>file-uploader-crystal (BETA <%= CURRENT_VERSION %>-<%= CURRENT_COMMIT %> @ <%= CURRENT_BRANCH %>)</a> </p>
</div>
<script> <script src="script.js"></script>
document.getElementById('uploadButton').addEventListener('click', () => {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
xhr.open('POST', 'upload', true);
// Track upload progress
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
document.getElementById('progressBar').style.width = percentComplete + '%';
document.getElementById('progressBar').textContent = Math.round(percentComplete) + '%';
}
});
// Handle upload completion
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
const linkContainer = document.getElementById('linkContainer');
linkContainer.innerHTML = `<a href="${response.link}" target="_blank">View Uploaded File</a>`;
} else if (xhr.status === 403) {
const response = JSON.parse(xhr.responseText);
const linkContainer = document.getElementById('linkContainer');
linkContainer.innerHTML = `<a target="_blank">ERROR: ${response.error}</a>`;
}
});
// Handle upload error
xhr.addEventListener('error', () => {
console.error('Upload error');
});
xhr.send(formData);
});
</script>
</body> </body>
</html>
</html>