diff --git a/public/s/js/upload.js b/public/s/js/upload.js index 587c2eb..b991781 100644 --- a/public/s/js/upload.js +++ b/public/s/js/upload.js @@ -5,6 +5,7 @@ const fileInput = document.getElementById('file-input'); const dropZone = document.getElementById('drop-zone'); const filePreview = document.getElementById('file-preview'); + // Note: prompt is now a label, but accessible via class const dropZonePrompt = dropZone.querySelector('.drop-zone-prompt'); const fileName = document.getElementById('file-name'); const fileSize = document.getElementById('file-size'); @@ -23,6 +24,43 @@ let tags = []; let selectedFile = null; + // Flash Message Logic + const showFlash = (msg, type = 'success') => { + const existing = document.querySelector('.flash-message'); + if (existing) existing.remove(); + + const flash = document.createElement('div'); + flash.className = `flash-message ${type}`; + flash.textContent = msg; + + Object.assign(flash.style, { + position: 'fixed', + top: '20px', + left: '50%', + transform: 'translateX(-50%)', + padding: '15px 30px', + borderRadius: '5px', + color: '#fff', + fontWeight: '600', + zIndex: '9999', + boxShadow: '0 4px 12px rgba(0,0,0,0.3)', + background: type === 'success' ? '#51cf66' : '#ff6b6b', + opacity: '0', + transition: 'opacity 0.3s' + }); + + document.body.appendChild(flash); + + // Fade in + requestAnimationFrame(() => flash.style.opacity = '1'); + + // Remove after 5s + setTimeout(() => { + flash.style.opacity = '0'; + setTimeout(() => flash.remove(), 300); + }, 5000); + }; + const formatSize = (bytes) => { const units = ['B', 'KB', 'MB', 'GB']; let i = 0; @@ -59,7 +97,11 @@ if (!file) return; const validTypes = ['video/mp4', 'video/webm']; - if (!validTypes.includes(file.type)) { + // Check extensions as fallback + const ext = file.name.split('.').pop().toLowerCase(); + const validExts = ['mp4', 'webm']; + + if (!validTypes.includes(file.type) && !validExts.includes(ext)) { statusDiv.textContent = 'Only mp4 and webm files are allowed'; statusDiv.className = 'upload-status error'; return; @@ -72,9 +114,56 @@ filePreview.style.display = 'flex'; statusDiv.textContent = ''; statusDiv.className = 'upload-status'; + + // Video Preview + const itemPreview = filePreview.querySelector('.item-preview') || document.createElement('div'); + itemPreview.className = 'item-preview'; + itemPreview.style.marginRight = '15px'; + + // Clear previous + const existingVid = filePreview.querySelector('video'); + if (existingVid) existingVid.remove(); + + const vid = document.createElement('video'); + vid.src = URL.createObjectURL(file); + vid.controls = false; + vid.autoplay = true; + vid.muted = true; + vid.loop = true; + vid.style.maxHeight = '100px'; + vid.style.maxWidth = '150px'; + vid.style.borderRadius = '4px'; + + filePreview.prepend(vid); + updateSubmitButton(); }; + const preventDefaults = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; + + // Attach drag events only to dropZone now (Input is hidden) + ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { + dropZone.addEventListener(eventName, preventDefaults, false); + }); + + ['dragenter', 'dragover'].forEach(eventName => { + dropZone.addEventListener(eventName, () => dropZone.classList.add('dragover'), false); + }); + + ['dragleave', 'drop'].forEach(eventName => { + dropZone.addEventListener(eventName, () => dropZone.classList.remove('dragover'), false); + }); + + dropZone.addEventListener('drop', (e) => { + const dt = e.dataTransfer; + const files = dt.files; + handleFile(files[0]); + }); + + // Native change listener on hidden input fileInput.addEventListener('change', (e) => handleFile(e.target.files[0])); removeFile.addEventListener('click', (e) => { @@ -84,24 +173,13 @@ fileInput.value = ''; dropZonePrompt.style.display = 'block'; filePreview.style.display = 'none'; + // Clear preview video + const vid = filePreview.querySelector('video'); + if (vid) vid.remove(); + updateSubmitButton(); }); - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('dragover'); - }); - - dropZone.addEventListener('dragleave', () => { - dropZone.classList.remove('dragover'); - }); - - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('dragover'); - handleFile(e.dataTransfer.files[0]); - }); - const addTag = (tagName) => { tagName = tagName.trim().toLowerCase(); if (!tagName || tags.includes(tagName)) return; @@ -215,18 +293,24 @@ if (res.success) { statusDiv.innerHTML = '✓ ' + res.msg; statusDiv.className = 'upload-status success'; + // Flash Message + showFlash(res.msg, 'success'); + form.reset(); tags = []; tagsList.innerHTML = ''; selectedFile = null; - dropZonePrompt.style.display = 'block'; + dropZonePrompt.style.display = 'block'; // label is actually flex/block via CSS filePreview.style.display = 'none'; + const vid = filePreview.querySelector('video'); + if (vid) vid.remove(); } else { statusDiv.textContent = '✕ ' + res.msg; statusDiv.className = 'upload-status error'; if (res.repost) { statusDiv.innerHTML += ' View existing'; } + showFlash('Upload failed: ' + res.msg, 'error'); } submitBtn.querySelector('.btn-text').style.display = 'inline'; @@ -239,6 +323,7 @@ xhr.onerror = () => { statusDiv.textContent = '✕ Upload failed. Please try again.'; statusDiv.className = 'upload-status error'; + showFlash('Upload failed network error', 'error'); submitBtn.querySelector('.btn-text').style.display = 'inline'; submitBtn.querySelector('.btn-loading').style.display = 'none'; progressContainer.style.display = 'none'; @@ -252,6 +337,7 @@ console.error(err); statusDiv.textContent = '✕ Upload failed: ' + err.message; statusDiv.className = 'upload-status error'; + showFlash('Upload failed: ' + err.message, 'error'); submitBtn.querySelector('.btn-text').style.display = 'inline'; submitBtn.querySelector('.btn-loading').style.display = 'none'; updateSubmitButton(); diff --git a/views/upload.html b/views/upload.html index 2a82f6c..6d9213c 100644 --- a/views/upload.html +++ b/views/upload.html @@ -36,15 +36,16 @@