forked from spitkov/sxbin
641 lines
21 KiB
HTML
641 lines
21 KiB
HTML
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>aCloud - File Sharing Service</title>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
line-height: 1.6;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 100vh;
|
|
background-color: #1a1a1a;
|
|
color: #f0f0f0;
|
|
}
|
|
.container {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 20px;
|
|
}
|
|
nav {
|
|
margin-bottom: 20px;
|
|
}
|
|
nav a {
|
|
color: #4CAF50;
|
|
text-decoration: none;
|
|
margin: 0 10px;
|
|
}
|
|
.upload-options {
|
|
margin-bottom: 20px;
|
|
}
|
|
.upload-options button {
|
|
background-color: #4CAF50;
|
|
border: none;
|
|
color: white;
|
|
padding: 10px 20px;
|
|
text-align: center;
|
|
text-decoration: none;
|
|
display: inline-block;
|
|
font-size: 16px;
|
|
margin: 4px 2px;
|
|
cursor: pointer;
|
|
border-radius: 4px;
|
|
transition: all 0.3s ease;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
.upload-options button:hover {
|
|
transform: scale(1.05);
|
|
box-shadow: 0 0 10px rgba(76, 175, 80, 0.5);
|
|
}
|
|
.upload-options button:active {
|
|
transform: scale(0.95);
|
|
}
|
|
.upload-options button::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
width: 5px;
|
|
height: 5px;
|
|
background: rgba(255, 255, 255, .5);
|
|
opacity: 0;
|
|
border-radius: 100%;
|
|
transform: scale(1, 1) translate(-50%);
|
|
transform-origin: 50% 50%;
|
|
}
|
|
@keyframes ripple {
|
|
0% {
|
|
transform: scale(0, 0);
|
|
opacity: 1;
|
|
}
|
|
20% {
|
|
transform: scale(25, 25);
|
|
opacity: 1;
|
|
}
|
|
100% {
|
|
opacity: 0;
|
|
transform: scale(40, 40);
|
|
}
|
|
}
|
|
.upload-options button:focus:not(:active)::after {
|
|
animation: ripple 1s ease-out;
|
|
}
|
|
@keyframes pulse {
|
|
0% { transform: scale(1); }
|
|
50% { transform: scale(1.05); }
|
|
100% { transform: scale(1); }
|
|
}
|
|
.pulse {
|
|
animation: pulse 0.5s;
|
|
}
|
|
.footer {
|
|
text-align: center;
|
|
padding: 10px;
|
|
background-color: #2a2a2a;
|
|
color: #f0f0f0;
|
|
}
|
|
.typewriter-container {
|
|
font-family: monospace;
|
|
white-space: pre-wrap;
|
|
overflow: hidden;
|
|
font-size: 1.2em;
|
|
margin-bottom: 20px;
|
|
text-align: center;
|
|
}
|
|
.cursor {
|
|
display: inline-block;
|
|
width: 10px;
|
|
height: 20px;
|
|
background-color: #4CAF50;
|
|
animation: blink 0.7s infinite;
|
|
}
|
|
@keyframes blink {
|
|
0% { opacity: 0; }
|
|
50% { opacity: 1; }
|
|
100% { opacity: 0; }
|
|
}
|
|
.modal {
|
|
display: none;
|
|
position: fixed;
|
|
z-index: 1;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: auto;
|
|
background-color: rgba(0,0,0,0.4);
|
|
}
|
|
.modal-content {
|
|
background-color: #2a2a2a;
|
|
margin: 15% auto;
|
|
padding: 20px 30px;
|
|
border: 1px solid #4CAF50;
|
|
width: 50%;
|
|
max-width: 500px;
|
|
border-radius: 8px;
|
|
}
|
|
.close {
|
|
color: #aaa;
|
|
float: right;
|
|
font-size: 28px;
|
|
font-weight: bold;
|
|
}
|
|
.close:hover,
|
|
.close:focus {
|
|
color: #4CAF50;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
}
|
|
.upload-area {
|
|
border: 2px dashed #4CAF50;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
margin-bottom: 20px;
|
|
width: calc(100% - 44px);
|
|
}
|
|
.upload-area:hover {
|
|
background-color: #2d2d2d;
|
|
}
|
|
#fileInput {
|
|
display: none;
|
|
}
|
|
.progress-bar {
|
|
width: 100%;
|
|
background-color: #333;
|
|
border-radius: 4px;
|
|
margin-top: 10px;
|
|
}
|
|
.progress {
|
|
width: 0%;
|
|
height: 20px;
|
|
background-color: #4CAF50;
|
|
border-radius: 4px;
|
|
text-align: center;
|
|
line-height: 20px;
|
|
color: white;
|
|
}
|
|
textarea, input[type="text"], input[type="file"], input[type="password"] {
|
|
width: calc(100% - 20px);
|
|
padding: 10px;
|
|
margin: 10px 0;
|
|
background-color: #333;
|
|
border: 1px solid #4CAF50;
|
|
color: #f0f0f0;
|
|
border-radius: 4px;
|
|
}
|
|
button {
|
|
background-color: #4CAF50;
|
|
color: white;
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
button:hover {
|
|
background-color: #45a049;
|
|
}
|
|
|
|
.file-group {
|
|
border: 1px solid #4CAF50;
|
|
border-radius: 5px;
|
|
margin-bottom: 10px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.file-group-header {
|
|
background-color: #4CAF50;
|
|
color: white;
|
|
padding: 10px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.file-group-header span:first-child {
|
|
margin-right: 10px;
|
|
}
|
|
|
|
.file-group-header span:nth-child(2) {
|
|
flex-grow: 1;
|
|
text-align: left;
|
|
}
|
|
|
|
.file-group-content {
|
|
padding: 10px;
|
|
display: none;
|
|
}
|
|
|
|
.file-group-content.show {
|
|
display: block;
|
|
}
|
|
|
|
.other-links-btn {
|
|
background-color: #45a049;
|
|
color: white;
|
|
border: none;
|
|
padding: 5px 10px;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.other-links {
|
|
margin-top: 10px;
|
|
display: none;
|
|
}
|
|
|
|
.other-links.show {
|
|
display: block;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<nav>
|
|
{% if user %}
|
|
<a href="{{ url_for('user_files', username=user.username) }}">View Dashboard</a>
|
|
<a href="{{ url_for('logout') }}">Logout</a>
|
|
{% else %}
|
|
<a href="{{ url_for('login') }}">Login</a>
|
|
<a href="{{ url_for('register') }}">Register</a>
|
|
{% endif %}
|
|
</nav>
|
|
|
|
<div class="typewriter-container">
|
|
<span id="typewriter-text"></span><span id="cursor" class="cursor"></span>
|
|
</div>
|
|
|
|
<div class="upload-options">
|
|
<button onclick="openModal('textModal')">Upload Text</button>
|
|
<button onclick="openModal('fileModal')">Upload File</button>
|
|
<button onclick="openModal('urlModal')">Shorten URL</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="textModal" class="modal">
|
|
<div class="modal-content">
|
|
<span class="close" onclick="closeModal('textModal')">×</span>
|
|
<h2>Upload Text</h2>
|
|
<textarea id="textContent" rows="4" placeholder="Enter text here..."></textarea>
|
|
<div>
|
|
<input type="checkbox" id="isPrivate" name="isPrivate">
|
|
<label for="isPrivate">Add password protection</label>
|
|
</div>
|
|
<div id="passwordField" style="display: none;">
|
|
<input type="password" id="textPassword" placeholder="Enter password">
|
|
</div>
|
|
<button onclick="uploadText()">Upload Text</button>
|
|
<div id="textResult"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="fileModal" class="modal">
|
|
<div class="modal-content">
|
|
<span class="close" onclick="closeModal('fileModal')">×</span>
|
|
<h2>Upload File</h2>
|
|
<div id="uploadArea" class="upload-area">
|
|
<p>Drag and drop files here or click to select files</p>
|
|
<input type="file" id="fileInput" multiple>
|
|
</div>
|
|
<div id="selectedFiles"></div>
|
|
<div>
|
|
<input type="checkbox" id="fileIsPrivate" name="fileIsPrivate">
|
|
<label for="fileIsPrivate">Add password protection</label>
|
|
</div>
|
|
<div id="filePasswordField" style="display: none;">
|
|
<input type="password" id="filePassword" placeholder="Enter password">
|
|
</div>
|
|
<button onclick="uploadSelectedFiles()">Upload</button>
|
|
<div class="progress-bar">
|
|
<div id="progressBar" class="progress"></div>
|
|
</div>
|
|
<div id="fileResult"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="urlModal" class="modal">
|
|
<div class="modal-content">
|
|
<span class="close" onclick="closeModal('urlModal')">×</span>
|
|
<h2>Shorten URL</h2>
|
|
<input type="text" id="urlInput" placeholder="Enter URL here...">
|
|
<button onclick="shortenUrl()">Shorten URL</button>
|
|
<div id="urlResult"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<footer class="footer">
|
|
<p>Source code available on:
|
|
<a href="https://github.com/realcgcristi/order" target="_blank">GitHub (not up-to-date)</a> |
|
|
<a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git (main)</a>
|
|
</p>
|
|
</footer>
|
|
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
const message1 = "Welcome to aCloud.";
|
|
const message2 = "\nA simple toolbox for file uploading,\nURL shortening and pastebin.";
|
|
const typewriterTextElement = document.getElementById('typewriter-text');
|
|
const cursorElement = document.getElementById('cursor');
|
|
const typingSpeed = 70;
|
|
|
|
function typeMessage(message, callback) {
|
|
let index = 0;
|
|
|
|
function typeCharacter() {
|
|
if (index < message.length) {
|
|
if (message[index] === '\n') {
|
|
typewriterTextElement.innerHTML += '<br>';
|
|
} else {
|
|
typewriterTextElement.innerHTML += message[index];
|
|
}
|
|
index++;
|
|
setTimeout(typeCharacter, typingSpeed);
|
|
} else if (callback) {
|
|
setTimeout(callback, typingSpeed);
|
|
}
|
|
}
|
|
|
|
typeCharacter();
|
|
}
|
|
|
|
typeMessage(message1, function() {
|
|
typeMessage(message2);
|
|
});
|
|
});
|
|
|
|
function openModal(modalId) {
|
|
const modal = document.getElementById(modalId);
|
|
const button = document.querySelector(`button[onclick="openModal('${modalId}')"]`);
|
|
|
|
modal.style.display = "block";
|
|
button.classList.add('pulse');
|
|
|
|
// Add ripple effect
|
|
button.classList.add('ripple');
|
|
setTimeout(() => {
|
|
button.classList.remove('ripple');
|
|
}, 1000);
|
|
|
|
// Add fade-in animation to modal
|
|
modal.style.opacity = 0;
|
|
let opacity = 0;
|
|
const fadeIn = setInterval(() => {
|
|
if (opacity < 1) {
|
|
opacity += 0.1;
|
|
modal.style.opacity = opacity;
|
|
} else {
|
|
clearInterval(fadeIn);
|
|
}
|
|
}, 30);
|
|
}
|
|
|
|
function closeModal(modalId) {
|
|
const modal = document.getElementById(modalId);
|
|
const button = document.querySelector(`button[onclick="openModal('${modalId}')"]`);
|
|
|
|
button.classList.remove('pulse');
|
|
|
|
// Add fade-out animation to modal
|
|
let opacity = 1;
|
|
const fadeOut = setInterval(() => {
|
|
if (opacity > 0) {
|
|
opacity -= 0.1;
|
|
modal.style.opacity = opacity;
|
|
} else {
|
|
clearInterval(fadeOut);
|
|
modal.style.display = "none";
|
|
}
|
|
}, 30);
|
|
}
|
|
|
|
window.onclick = function(event) {
|
|
if (event.target.className === "modal") {
|
|
event.target.style.display = "none";
|
|
}
|
|
}
|
|
|
|
document.getElementById('isPrivate').addEventListener('change', function() {
|
|
document.getElementById('passwordField').style.display = this.checked ? 'block' : 'none';
|
|
});
|
|
|
|
document.getElementById('fileIsPrivate').addEventListener('change', function() {
|
|
document.getElementById('filePasswordField').style.display = this.checked ? 'block' : 'none';
|
|
});
|
|
|
|
function uploadText() {
|
|
const content = document.getElementById('textContent').value;
|
|
const isPrivate = document.getElementById('isPrivate').checked;
|
|
const password = isPrivate ? document.getElementById('textPassword').value : null;
|
|
|
|
const data = {
|
|
content: content,
|
|
password: password
|
|
};
|
|
|
|
fetch('/upload/pastebin', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(data),
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('Network response was not ok');
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data.success) {
|
|
const simpleUrl = `${window.location.origin}/${data.vanity}`;
|
|
document.getElementById('textResult').innerHTML = `Pastebin created. Access it <a href="${simpleUrl}" target="_blank">${simpleUrl}</a>`;
|
|
} else {
|
|
document.getElementById('textResult').innerHTML = `Error: ${data.error}`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
document.getElementById('textResult').innerHTML = `An error occurred: ${error.message}`;
|
|
});
|
|
}
|
|
|
|
const uploadArea = document.getElementById('uploadArea');
|
|
const fileInput = document.getElementById('fileInput');
|
|
const progressBar = document.getElementById('progressBar');
|
|
const selectedFiles = document.getElementById('selectedFiles');
|
|
let filesToUpload = [];
|
|
|
|
uploadArea.addEventListener('click', () => fileInput.click());
|
|
uploadArea.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
uploadArea.style.backgroundColor = '#2d2d2d';
|
|
});
|
|
uploadArea.addEventListener('dragleave', () => {
|
|
uploadArea.style.backgroundColor = '';
|
|
});
|
|
uploadArea.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
uploadArea.style.backgroundColor = '';
|
|
handleFiles(e.dataTransfer.files);
|
|
});
|
|
fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
|
|
|
|
function handleFiles(files) {
|
|
filesToUpload = Array.from(files);
|
|
updateSelectedFilesList();
|
|
}
|
|
|
|
function updateSelectedFilesList() {
|
|
selectedFiles.innerHTML = filesToUpload.map(file => `<div>${file.name}</div>`).join('');
|
|
}
|
|
|
|
function uploadSelectedFiles() {
|
|
const isPrivate = document.getElementById('fileIsPrivate').checked;
|
|
const password = isPrivate ? document.getElementById('filePassword').value : null;
|
|
|
|
filesToUpload.forEach(file => uploadFile(file, isPrivate, password));
|
|
}
|
|
|
|
function uploadFile(file, isPrivate, password) {
|
|
if (isPrivate && (password === 'info' || password === 'download')) {
|
|
document.getElementById('fileResult').innerHTML += `Error: Password cannot be 'info' or 'download'<br>`;
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
if (isPrivate && password) {
|
|
formData.append('password', password);
|
|
}
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open('POST', '/upload/file', true);
|
|
|
|
xhr.upload.onprogress = (e) => {
|
|
if (e.lengthComputable) {
|
|
const percentComplete = (e.loaded / e.total) * 100;
|
|
progressBar.style.width = percentComplete + '%';
|
|
progressBar.textContent = percentComplete.toFixed(2) + '%';
|
|
}
|
|
};
|
|
|
|
xhr.onload = function() {
|
|
if (xhr.status === 200) {
|
|
const response = JSON.parse(xhr.responseText);
|
|
if (response.success) {
|
|
let resultHtml = `
|
|
<div class="file-group">
|
|
<div class="file-group-header" onclick="toggleFileGroup(this)">
|
|
<span>▼</span>
|
|
<span>${file.name}</span>
|
|
<button class="other-links-btn" onclick="toggleOtherLinks(event, this)">Other Links</button>
|
|
</div>
|
|
<div class="file-group-content show">
|
|
<p>File uploaded: <a href="${response.url}" target="_blank">${response.url}</a></p>
|
|
<div class="other-links">
|
|
`;
|
|
|
|
if (isPrivate) {
|
|
resultHtml += `
|
|
<p>Password-protected link: <a href="${response.url}/${password}" target="_blank">${response.url}/${password}</a></p>
|
|
<p>Direct download link: <a href="${response.download_url}" target="_blank">${response.download_url}</a></p>
|
|
<p>Password-protected direct download link: <a href="${response.download_url}/${password}" target="_blank">${response.download_url}/${password}</a></p>
|
|
`;
|
|
} else {
|
|
resultHtml += `
|
|
<p>Direct download link: <a href="${response.download_url}" target="_blank">${response.download_url}</a></p>
|
|
`;
|
|
}
|
|
|
|
resultHtml += `
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('fileResult').innerHTML += resultHtml;
|
|
} else {
|
|
document.getElementById('fileResult').innerHTML += `Error: ${response.error}<br>`;
|
|
}
|
|
} else {
|
|
document.getElementById('fileResult').innerHTML += `Error uploading file: ${xhr.statusText}<br>`;
|
|
}
|
|
progressBar.style.width = '0%';
|
|
progressBar.textContent = '';
|
|
};
|
|
|
|
xhr.onerror = function() {
|
|
console.error('Error:', xhr.statusText);
|
|
document.getElementById('fileResult').innerHTML += `Error uploading file: ${xhr.statusText}<br>`;
|
|
progressBar.style.width = '0%';
|
|
progressBar.textContent = '';
|
|
};
|
|
|
|
xhr.send(formData);
|
|
}
|
|
|
|
function toggleFileGroup(header) {
|
|
const content = header.nextElementSibling;
|
|
const arrow = header.querySelector('span:first-child');
|
|
const filename = header.querySelector('span:nth-child(2)');
|
|
content.classList.toggle('show');
|
|
if (content.classList.contains('show')) {
|
|
arrow.textContent = '▼';
|
|
filename.style.display = 'inline';
|
|
} else {
|
|
arrow.textContent = '▶';
|
|
filename.style.display = 'inline';
|
|
}
|
|
}
|
|
|
|
function toggleOtherLinks(event, button) {
|
|
event.stopPropagation();
|
|
const otherLinks = button.closest('.file-group-header').nextElementSibling.querySelector('.other-links');
|
|
otherLinks.classList.toggle('show');
|
|
}
|
|
|
|
function shortenUrl() {
|
|
const url = document.getElementById('urlInput').value;
|
|
|
|
fetch('/shorten', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ url: url }),
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('Network response was not ok');
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data.success) {
|
|
const shortUrl = `${window.location.origin}/${data.vanity}`;
|
|
document.getElementById('urlResult').innerHTML = `URL shortened. Access it <a href="${shortUrl}" target="_blank">${shortUrl}</a>`;
|
|
} else {
|
|
document.getElementById('urlResult').innerHTML = `Error: ${data.error}`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
document.getElementById('urlResult').innerHTML = `An error occurred: ${error.message}`;
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|