Code from github to self hosted with git tea in AWS
This commit is contained in:
226
mini-dropbox/CODE/app/static/css/style.css
Normal file
226
mini-dropbox/CODE/app/static/css/style.css
Normal file
@@ -0,0 +1,226 @@
|
||||
/* === FILE: app/static/css/style.css === */
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #0f1117;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(180deg, #6366f1, #8b5cf6);
|
||||
border-radius: 10px;
|
||||
border: 2px solid #0f1117;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(180deg, #4f46e5, #7c3aed);
|
||||
}
|
||||
|
||||
/* Drop zone states */
|
||||
#drop-zone {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
#drop-zone.drag-over {
|
||||
border-color: #6366f1 !important;
|
||||
background-color: rgba(99, 102, 241, 0.1) !important;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
/* File card hover effect */
|
||||
.file-card {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.file-card:hover {
|
||||
transform: translateY(-4px) scale(1.02);
|
||||
}
|
||||
|
||||
/* Progress bar animation */
|
||||
#progress-bar {
|
||||
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#progress-bar::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.3),
|
||||
transparent
|
||||
);
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Button loading spinner */
|
||||
.btn-loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.btn-loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -10px;
|
||||
margin-top: -10px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: #ffffff;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Modal animations */
|
||||
.modal-enter {
|
||||
animation: modalFadeIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
@keyframes modalFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-exit {
|
||||
animation: modalFadeOut 0.2s cubic-bezier(0.4, 0, 1, 1);
|
||||
}
|
||||
|
||||
@keyframes modalFadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(20px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Smooth transitions */
|
||||
button, a, .action-btn {
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Action button styles */
|
||||
.action-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Flash message animation */
|
||||
.flash-message {
|
||||
animation: slideInRight 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pulse animation for empty state */
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-pulse {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
/* Gradient text animation */
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.bg-clip-text {
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* Glassmorphism effect */
|
||||
.glass {
|
||||
background: rgba(26, 29, 39, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Hover glow effect */
|
||||
.hover-glow {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hover-glow::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 2px;
|
||||
background: linear-gradient(45deg, #6366f1, #8b5cf6, #ec4899);
|
||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.hover-glow:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
398
mini-dropbox/CODE/app/static/js/dashboard.js
Normal file
398
mini-dropbox/CODE/app/static/js/dashboard.js
Normal file
@@ -0,0 +1,398 @@
|
||||
// === FILE: app/static/js/dashboard.js ===
|
||||
|
||||
// Global variables
|
||||
let selectedFile = null;
|
||||
let deleteFileId = null;
|
||||
|
||||
// ========================================
|
||||
// UPLOAD FUNCTIONALITY
|
||||
// ========================================
|
||||
|
||||
// Open upload modal
|
||||
document.getElementById('open-upload-modal').addEventListener('click', function() {
|
||||
const modal = document.getElementById('upload-modal');
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex', 'modal-enter');
|
||||
resetUploadModal();
|
||||
});
|
||||
|
||||
// Close upload modal
|
||||
document.getElementById('cancel-upload').addEventListener('click', function() {
|
||||
closeUploadModal();
|
||||
});
|
||||
|
||||
// Close upload modal with X button
|
||||
document.getElementById('close-upload-modal').addEventListener('click', function() {
|
||||
closeUploadModal();
|
||||
});
|
||||
|
||||
function closeUploadModal() {
|
||||
const modal = document.getElementById('upload-modal');
|
||||
modal.classList.add('modal-exit');
|
||||
setTimeout(() => {
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex', 'modal-enter', 'modal-exit');
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// Reset upload modal
|
||||
function resetUploadModal() {
|
||||
selectedFile = null;
|
||||
document.getElementById('file-input').value = '';
|
||||
document.getElementById('selected-file').classList.add('hidden');
|
||||
document.getElementById('progress-container').classList.add('hidden');
|
||||
document.getElementById('upload-error').classList.add('hidden');
|
||||
document.getElementById('progress-bar').style.width = '0%';
|
||||
document.getElementById('upload-btn').disabled = true;
|
||||
}
|
||||
|
||||
// Drop zone click
|
||||
document.getElementById('drop-zone').addEventListener('click', function() {
|
||||
document.getElementById('file-input').click();
|
||||
});
|
||||
|
||||
// File input change
|
||||
document.getElementById('file-input').addEventListener('change', function(e) {
|
||||
if (e.target.files.length > 0) {
|
||||
handleFileSelect(e.target.files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
// Drag and drop handlers
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
|
||||
dropZone.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dropZone.classList.add('drag-over');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dropZone.classList.remove('drag-over');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dropZone.classList.remove('drag-over');
|
||||
|
||||
if (e.dataTransfer.files.length > 0) {
|
||||
handleFileSelect(e.dataTransfer.files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle file selection
|
||||
function handleFileSelect(file) {
|
||||
// Check file size (100MB max)
|
||||
const maxSize = 100 * 1024 * 1024; // 100MB
|
||||
if (file.size > maxSize) {
|
||||
showUploadError('File size exceeds 100MB limit');
|
||||
return;
|
||||
}
|
||||
|
||||
selectedFile = file;
|
||||
document.getElementById('selected-file-name').textContent = file.name;
|
||||
document.getElementById('selected-file').classList.remove('hidden');
|
||||
document.getElementById('upload-btn').disabled = false;
|
||||
document.getElementById('upload-error').classList.add('hidden');
|
||||
}
|
||||
|
||||
// Show upload error
|
||||
function showUploadError(message) {
|
||||
const errorDiv = document.getElementById('upload-error');
|
||||
const errorText = document.getElementById('upload-error-text');
|
||||
if (errorText) {
|
||||
errorText.textContent = message;
|
||||
} else {
|
||||
errorDiv.textContent = message;
|
||||
}
|
||||
errorDiv.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Upload button click
|
||||
document.getElementById('upload-btn').addEventListener('click', function() {
|
||||
if (!selectedFile) {
|
||||
showUploadError('Please select a file');
|
||||
return;
|
||||
}
|
||||
|
||||
uploadFile(selectedFile);
|
||||
});
|
||||
|
||||
// Upload file function
|
||||
function uploadFile(file) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
// Show progress bar
|
||||
document.getElementById('progress-container').classList.remove('hidden');
|
||||
document.getElementById('upload-btn').disabled = true;
|
||||
document.getElementById('cancel-upload').disabled = true;
|
||||
|
||||
// Use XMLHttpRequest for progress tracking
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
// Progress handler
|
||||
xhr.upload.addEventListener('progress', function(e) {
|
||||
if (e.lengthComputable) {
|
||||
const percentage = (e.loaded / e.total) * 100;
|
||||
const roundedPercent = Math.round(percentage);
|
||||
document.getElementById('progress-bar').style.width = percentage + '%';
|
||||
document.getElementById('progress-text').textContent = `Uploading...`;
|
||||
const progressPercent = document.getElementById('progress-percent');
|
||||
if (progressPercent) {
|
||||
progressPercent.textContent = roundedPercent + '%';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Load handler (upload complete)
|
||||
xhr.addEventListener('load', function() {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
if (response.success) {
|
||||
// Success - reload page to show new file
|
||||
window.location.reload();
|
||||
} else {
|
||||
showUploadError(response.message || 'Upload failed');
|
||||
document.getElementById('upload-btn').disabled = false;
|
||||
document.getElementById('cancel-upload').disabled = false;
|
||||
}
|
||||
} catch (e) {
|
||||
showUploadError('Upload failed');
|
||||
document.getElementById('upload-btn').disabled = false;
|
||||
document.getElementById('cancel-upload').disabled = false;
|
||||
}
|
||||
} else {
|
||||
showUploadError('Upload failed. Please try again.');
|
||||
document.getElementById('upload-btn').disabled = false;
|
||||
document.getElementById('cancel-upload').disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Error handler
|
||||
xhr.addEventListener('error', function() {
|
||||
showUploadError('Network error. Please try again.');
|
||||
document.getElementById('upload-btn').disabled = false;
|
||||
document.getElementById('cancel-upload').disabled = false;
|
||||
});
|
||||
|
||||
// Send request
|
||||
xhr.open('POST', '/files/upload');
|
||||
xhr.send(formData);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// DELETE FUNCTIONALITY
|
||||
// ========================================
|
||||
|
||||
function confirmDelete(fileId, fileName) {
|
||||
deleteFileId = fileId;
|
||||
document.getElementById('delete-file-name').textContent = fileName;
|
||||
|
||||
const modal = document.getElementById('delete-modal');
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex', 'modal-enter');
|
||||
}
|
||||
|
||||
// Cancel delete
|
||||
document.getElementById('cancel-delete').addEventListener('click', function() {
|
||||
closeDeleteModal();
|
||||
});
|
||||
|
||||
function closeDeleteModal() {
|
||||
const modal = document.getElementById('delete-modal');
|
||||
modal.classList.add('modal-exit');
|
||||
setTimeout(() => {
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex', 'modal-enter', 'modal-exit');
|
||||
deleteFileId = null;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// Confirm delete
|
||||
document.getElementById('confirm-delete-btn').addEventListener('click', function() {
|
||||
if (!deleteFileId) return;
|
||||
|
||||
const btn = this;
|
||||
btn.classList.add('btn-loading');
|
||||
btn.disabled = true;
|
||||
|
||||
fetch(`/files/delete/${deleteFileId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Remove file card from DOM
|
||||
const fileCard = document.querySelector(`.file-card[data-file-id="${deleteFileId}"]`);
|
||||
if (fileCard) {
|
||||
fileCard.style.opacity = '0';
|
||||
fileCard.style.transform = 'scale(0.9)';
|
||||
setTimeout(() => {
|
||||
fileCard.remove();
|
||||
|
||||
// Check if no files left
|
||||
const filesGrid = document.getElementById('files-grid');
|
||||
if (filesGrid && filesGrid.children.length === 0) {
|
||||
window.location.reload();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
closeDeleteModal();
|
||||
showFlashMessage('File deleted successfully', 'success');
|
||||
} else {
|
||||
showFlashMessage(data.message || 'Failed to delete file', 'error');
|
||||
btn.classList.remove('btn-loading');
|
||||
btn.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showFlashMessage('Failed to delete file', 'error');
|
||||
btn.classList.remove('btn-loading');
|
||||
btn.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// SHARE FUNCTIONALITY
|
||||
// ========================================
|
||||
|
||||
function shareFile(fileId) {
|
||||
fetch(`/files/share/${fileId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
document.getElementById('share-url').value = data.share_url;
|
||||
|
||||
const modal = document.getElementById('share-modal');
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex', 'modal-enter');
|
||||
} else {
|
||||
showFlashMessage(data.message || 'Failed to generate share link', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showFlashMessage('Failed to generate share link', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// Close share modal
|
||||
document.getElementById('close-share-modal').addEventListener('click', function() {
|
||||
closeShareModal();
|
||||
});
|
||||
|
||||
function closeShareModal() {
|
||||
const modal = document.getElementById('share-modal');
|
||||
modal.classList.add('modal-exit');
|
||||
setTimeout(() => {
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex', 'modal-enter', 'modal-exit');
|
||||
document.getElementById('copy-feedback').classList.add('hidden');
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// Copy link button
|
||||
document.getElementById('copy-link-btn').addEventListener('click', function() {
|
||||
const shareUrl = document.getElementById('share-url');
|
||||
shareUrl.select();
|
||||
shareUrl.setSelectionRange(0, 99999); // For mobile devices
|
||||
|
||||
// Copy to clipboard
|
||||
navigator.clipboard.writeText(shareUrl.value)
|
||||
.then(() => {
|
||||
const feedback = document.getElementById('copy-feedback');
|
||||
feedback.classList.remove('hidden');
|
||||
|
||||
// Hide after 2 seconds
|
||||
setTimeout(() => {
|
||||
feedback.classList.add('hidden');
|
||||
}, 2000);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Failed to copy:', err);
|
||||
showFlashMessage('Failed to copy link', 'error');
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// PREVIEW FUNCTIONALITY
|
||||
// ========================================
|
||||
|
||||
function previewFile(fileId) {
|
||||
fetch(`/files/preview/${fileId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success && data.preview_url) {
|
||||
window.open(data.preview_url, '_blank');
|
||||
} else {
|
||||
showFlashMessage('Preview not available', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showFlashMessage('Failed to generate preview', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// HELPER FUNCTIONS
|
||||
// ========================================
|
||||
|
||||
function showFlashMessage(message, category) {
|
||||
const flashContainer = document.getElementById('flash-messages') || createFlashContainer();
|
||||
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `flash-message px-6 py-3 rounded-lg shadow-lg text-white`;
|
||||
|
||||
if (category === 'success') {
|
||||
messageDiv.classList.add('bg-[#22c55e]');
|
||||
} else if (category === 'error') {
|
||||
messageDiv.classList.add('bg-[#ef4444]');
|
||||
} else {
|
||||
messageDiv.classList.add('bg-[#6366f1]');
|
||||
}
|
||||
|
||||
messageDiv.textContent = message;
|
||||
flashContainer.appendChild(messageDiv);
|
||||
|
||||
// Auto-dismiss after 4 seconds
|
||||
setTimeout(() => {
|
||||
messageDiv.style.opacity = '0';
|
||||
messageDiv.style.transform = 'translateX(100%)';
|
||||
messageDiv.style.transition = 'all 0.3s ease-out';
|
||||
setTimeout(() => {
|
||||
messageDiv.remove();
|
||||
}, 300);
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
function createFlashContainer() {
|
||||
const container = document.createElement('div');
|
||||
container.id = 'flash-messages';
|
||||
container.className = 'fixed top-4 right-4 z-50 space-y-2';
|
||||
document.body.appendChild(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// INITIALIZATION
|
||||
// ========================================
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('Dashboard loaded');
|
||||
});
|
||||
Reference in New Issue
Block a user