From 9e9907bc004af98380297ffc3a63104023570c4d Mon Sep 17 00:00:00 2001 From: retoor Date: Sun, 9 Nov 2025 00:52:26 +0100 Subject: [PATCH] Update. --- retoors/static/js/components/upload.js | 116 +++++++++++++++++++++++++ retoors/views/upload.py | 50 +++++++++++ 2 files changed, 166 insertions(+) create mode 100644 retoors/static/js/components/upload.js create mode 100644 retoors/views/upload.py diff --git a/retoors/static/js/components/upload.js b/retoors/static/js/components/upload.js new file mode 100644 index 0000000..b81f027 --- /dev/null +++ b/retoors/static/js/components/upload.js @@ -0,0 +1,116 @@ +export function showUploadModal() { + document.getElementById('upload-modal').style.display = 'block'; + // Clear previous selections and progress + document.getElementById('selected-files-preview').innerHTML = ''; + document.getElementById('upload-progress-container').innerHTML = ''; + document.getElementById('file-input-multiple').value = ''; // Clear selected files + document.getElementById('start-upload-btn').disabled = true; +} + +document.addEventListener('DOMContentLoaded', () => { + const fileInput = document.getElementById('file-input-multiple'); + const selectedFilesPreview = document.getElementById('selected-files-preview'); + const startUploadBtn = document.getElementById('start-upload-btn'); + const uploadProgressContainer = document.getElementById('upload-progress-container'); + + let filesToUpload = []; + + fileInput.addEventListener('change', (event) => { + filesToUpload = Array.from(event.target.files); + selectedFilesPreview.innerHTML = ''; // Clear previous previews + uploadProgressContainer.innerHTML = ''; // Clear previous progress bars + + if (filesToUpload.length > 0) { + startUploadBtn.disabled = false; + filesToUpload.forEach(file => { + const fileEntry = document.createElement('div'); + fileEntry.className = 'file-entry'; + fileEntry.innerHTML = ` + ${file.name} + (${(file.size / 1024 / 1024).toFixed(2)} MB) +
+ `; + selectedFilesPreview.appendChild(fileEntry); + + // Display thumbnail for image files + if (file.type.startsWith('image/')) { + const reader = new FileReader(); + reader.onload = (e) => { + const img = document.createElement('img'); + img.src = e.target.result; + fileEntry.querySelector('.thumbnail-preview').appendChild(img); + }; + reader.readAsDataURL(file); + } + }); + } else { + startUploadBtn.disabled = true; + } + }); + + startUploadBtn.addEventListener('click', () => { + if (filesToUpload.length > 0) { + uploadFiles(filesToUpload); + } + }); + + async function uploadFiles(files) { + startUploadBtn.disabled = true; // Disable button during upload + uploadProgressContainer.innerHTML = ''; // Clear previous progress + + const currentPath = new URLSearchParams(window.location.search).get('path') || ''; + + for (const file of files) { + const formData = new FormData(); + formData.append('file', file); + + const progressBarContainer = document.createElement('div'); + progressBarContainer.className = 'progress-bar-container'; + progressBarContainer.innerHTML = ` +
${file.name}
+
+
+
+
0%
+ `; + uploadProgressContainer.appendChild(progressBarContainer); + + const xhr = new XMLHttpRequest(); + xhr.open('POST', `/files/upload?current_path=${encodeURIComponent(currentPath)}`, true); + + xhr.upload.addEventListener('progress', (event) => { + if (event.lengthComputable) { + const percent = (event.loaded / event.total) * 100; + document.getElementById(`progress-${file.name.replace(/\./g, '-')}`).style.width = `${percent}%`; + document.getElementById(`progress-text-${file.name.replace(/\./g, '-')}`).textContent = `${Math.round(percent)}%`; + } + }); + + xhr.addEventListener('load', () => { + if (xhr.status === 200) { + console.log(`File ${file.name} uploaded successfully.`); + // Update progress to 100% on completion + document.getElementById(`progress-${file.name.replace(/\./g, '-')}`).style.width = `100%`; + document.getElementById(`progress-text-${file.name.replace(/\./g, '-')}`).textContent = `100% (Done)`; + } else { + console.error(`Error uploading ${file.name}: ${xhr.statusText}`); + document.getElementById(`progress-text-${file.name.replace(/\./g, '-')}`).textContent = `Failed (${xhr.status})`; + document.getElementById(`progress-${file.name.replace(/\./g, '-')}`).style.backgroundColor = `red`; + } + }); + + xhr.addEventListener('error', () => { + console.error(`Network error uploading ${file.name}.`); + document.getElementById(`progress-text-${file.name.replace(/\./g, '-')}`).textContent = `Network Error`; + document.getElementById(`progress-${file.name.replace(/\./g, '-')}`).style.backgroundColor = `red`; + }); + + xhr.send(formData); + } + // After all files are sent, refresh the page to show new files + // A small delay to allow server to process and update file list + setTimeout(() => { + window.location.reload(); + }, 1000); + } +}); diff --git a/retoors/views/upload.py b/retoors/views/upload.py new file mode 100644 index 0000000..c86440c --- /dev/null +++ b/retoors/views/upload.py @@ -0,0 +1,50 @@ +from aiohttp import web +import aiohttp_jinja2 +from aiohttp.web_response import json_response + +from ..helpers.auth import login_required + +class UploadView(web.View): + @login_required + async def post(self): + user_email = self.request["user"]["email"] + file_service = self.request.app["file_service"] + # Get current path from query parameter or form data + current_path = self.request.query.get("current_path", "") + + try: + reader = await self.request.multipart() + files_uploaded = [] + errors = [] + + while True: + field = await reader.next() + if field is None: + break + + # Check if the field is a file input + if field.name == "file": # Assuming the input field name is 'file' + filename = field.filename + if not filename: + errors.append("Filename is required for one of the files.") + continue + + content = await field.read() + # Construct the full file path relative to the user's base directory + full_file_path_for_service = f"{current_path}/{filename}" if current_path else filename + + success = await file_service.upload_file(user_email, full_file_path_for_service, content) + if success: + files_uploaded.append(filename) + else: + errors.append(f"Failed to upload file '{filename}'") + + if errors: + return json_response({"status": "error", "message": "Some files failed to upload", "details": errors}, status=500) + elif files_uploaded: + return json_response({"status": "success", "message": f"Successfully uploaded {len(files_uploaded)} files", "files": files_uploaded}) + else: + return json_response({"status": "error", "message": "No files were uploaded"}, status=400) + + except Exception as e: + return json_response({"status": "error", "message": f"Upload error: {str(e)}"}, status=500)