// /js/shared.js (function () { 'use strict'; /* ======================= * CONFIG * ======================= */ const SMALL_FILE_LIMIT = 256 * 1024 * 1024; // 256MB const TUS_ENDPOINT = '/storage/tus'; /* ======================= * STATE * ======================= */ let currentUploadType = ''; /* ======================= * UTILS * ======================= */ function formatBytes(bytes) { if (!bytes) return '0 Bytes'; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i]; } function getErrorMessage(code) { const errors = { no_file: 'No file selected', upload_failed: 'Upload failed', storage_limit: 'Storage limit exceeded', invalid_folder: 'Invalid folder selected' }; return errors[code] || 'Unknown error'; } function getSuccessMessage(code) { const success = { file_uploaded: 'File uploaded successfully', folder_created: 'Folder created successfully', files_deleted: 'Files deleted successfully' }; return success[code] || 'Success'; } /* ======================= * NOTIFICATIONS * ======================= */ window.showNotification = function (message, type = 'info') { const body = document.body; const notification = document.createElement('div'); const oldNotifications = document.querySelectorAll('.global-notification'); oldNotifications.forEach(n => n.remove()); notification.className = 'global-notification'; notification.textContent = message; const bgColor = type === 'error' ? '#e53e3e' : type === 'success' ? '#38a169' : type === 'warning' ? '#d69e2e' : '#3182ce'; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 12px 16px; background: ${bgColor}; color: #fff; border-radius: 8px; z-index: 10000; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); animation: slideIn 0.3s ease-out; `; body.appendChild(notification); setTimeout(() => { notification.style.animation = 'slideOut 0.3s ease-in'; setTimeout(() => notification.remove(), 300); }, 3000); if (!document.querySelector('#notification-styles')) { const style = document.createElement('style'); style.id = 'notification-styles'; style.textContent = ` @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } `; document.head.appendChild(style); } }; /* ======================= * MODAL MANAGEMENT * ======================= */ window.modalManager = { currentModal: null, open(modal) { this.closeAll(); modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; this.currentModal = modal; const escHandler = (e) => { if (e.key === 'Escape') this.close(); }; modal._escHandler = escHandler; document.addEventListener('keydown', escHandler); }, close() { if (this.currentModal) { document.removeEventListener('keydown', this.currentModal._escHandler); this.currentModal.style.display = 'none'; document.body.style.overflow = ''; this.currentModal = null; } }, closeAll() { document.querySelectorAll('.modal-overlay').forEach(modal => { modal.style.display = 'none'; if (modal._escHandler) { document.removeEventListener('keydown', modal._escHandler); } }); document.body.style.overflow = ''; this.currentModal = null; } }; /* ======================= * FILE UPLOAD * ======================= */ class FileUploader { constructor(options = {}) { this.options = { formSelector: '', fileInputSelector: '', folderSelectSelector: null, progressFillSelector: '.progress-fill', progressTextSelector: '.progress-text', uploadProgressSelector: '.upload-progress', submitBtnSelector: '', cancelBtnSelector: '', onSuccess: null, onError: null, ...options }; this.form = document.querySelector(this.options.formSelector); this.fileInput = document.querySelector(this.options.fileInputSelector); this.folderSelect = this.options.folderSelectSelector ? document.querySelector(this.options.folderSelectSelector) : null; this.progressFill = document.querySelector(this.options.progressFillSelector); this.progressText = document.querySelector(this.options.progressTextSelector); this.uploadProgress = document.querySelector(this.options.uploadProgressSelector); this.submitBtn = document.querySelector(this.options.submitBtnSelector); this.cancelBtn = document.querySelector(this.options.cancelBtnSelector); this.bindEvents(); } bindEvents() { if (this.form) { this.form.addEventListener('submit', (e) => this.handleSubmit(e)); } if (this.fileInput) { this.fileInput.addEventListener('change', () => this.handleFileSelect()); } if (this.cancelBtn) { this.cancelBtn.addEventListener('click', () => this.reset()); } } handleFileSelect() { const file = this.fileInput.files[0]; if (!file) return; console.log('File selected:', file.name, formatBytes(file.size)); } async handleSubmit(e) { e.preventDefault(); const file = this.fileInput.files[0]; const folder = this.folderSelect ? this.folderSelect.value : this.form.querySelector('input[name="folder"]')?.value; if (!file) { showNotification('Please select a file', 'error'); return; } if (this.folderSelect && !folder) { showNotification('Please select a folder', 'error'); return; } this.setUploadingState(true); if (file.size <= SMALL_FILE_LIMIT) { await this.uploadSmallFile(file, folder); } else { this.uploadLargeFile(file, folder); } } async uploadSmallFile(file, folder) { try { this.updateProgress(0, 'Starting upload...'); const formData = new FormData(); formData.append('file', file); if (folder) formData.append('folder', folder); formData.append('_csrf', this.form.querySelector('input[name="_csrf"]')?.value || ''); let progress = 0; const progressInterval = setInterval(() => { if (progress < 90) { progress += 10; this.updateProgress(progress); } }, 300); const response = await fetch('/storage/files', { method: 'POST', body: formData }); clearInterval(progressInterval); const result = await response.json(); if (!result.success) { throw new Error(result.error || 'Upload failed'); } this.updateProgress(100, 'Upload complete!'); this.onUploadSuccess(); } catch (error) { this.onUploadError(error.message); } } uploadLargeFile(file, folder) { this.updateProgress(0, 'Starting large file upload...'); const upload = new tus.Upload(file, { endpoint: TUS_ENDPOINT, chunkSize: 5 * 1024 * 1024, retryDelays: [0, 1000, 3000, 5000], metadata: { folder: folder || '', filename: file.name }, withCredentials: true, onProgress: (uploaded, total) => { const percent = Math.round((uploaded / total) * 100); this.updateProgress(percent, `${percent}%`); }, onSuccess: () => { this.updateProgress(100, 'Upload complete!'); setTimeout(() => this.onUploadSuccess(), 500); }, onError: (error) => { this.onUploadError(error.toString()); } }); upload.start(); } updateProgress(percent, text = '') { if (this.progressFill) { this.progressFill.style.width = percent + '%'; } if (this.progressText) { this.progressText.textContent = text || percent + '%'; } } onUploadSuccess() { showNotification('File uploaded successfully', 'success'); this.setUploadingState(false); modalManager.close(); this.reset(); if (this.options.onSuccess) { this.options.onSuccess(); } else { setTimeout(() => location.reload(), 1000); } } onUploadError(error) { showNotification(error, 'error'); this.setUploadingState(false); this.reset(); if (this.options.onError) { this.options.onError(error); } } setUploadingState(uploading) { if (this.submitBtn) this.submitBtn.disabled = uploading; if (this.cancelBtn) this.cancelBtn.disabled = uploading; if (this.uploadProgress) { this.uploadProgress.style.display = uploading ? 'block' : 'none'; } } reset() { if (this.form) this.form.reset(); this.updateProgress(0); this.setUploadingState(false); } } /* ======================= * INIT URL PARAMS * ======================= */ function initUrlParams() { const params = new URLSearchParams(window.location.search); if (params.get('error')) { showNotification(getErrorMessage(params.get('error')), 'error'); } if (params.get('success')) { showNotification(getSuccessMessage(params.get('success')), 'success'); } } /* ======================= * EXPORTS * ======================= */ window.sharedUtils = { formatBytes, getErrorMessage, getSuccessMessage, showNotification, modalManager, FileUploader, initUrlParams, SMALL_FILE_LIMIT, TUS_ENDPOINT }; document.addEventListener('DOMContentLoaded', initUrlParams); })();