const dropzone = document.getElementById('dropzone'); const fileInput = document.getElementById('fileInput'); const fileList = document.getElementById('fileList'); const setupBtn = document.getElementById('setupFfmpegBtn'); const TYPE_ICONS = { image: 'πŸ–ΌοΈ', video: '🎬', audio: '🎡', archive: 'πŸ“¦', unknown: 'πŸ“„', }; // drag & drop dropzone.addEventListener('dragover', e => { e.preventDefault(); dropzone.classList.add('drag-over'); }); dropzone.addEventListener('dragleave', e => { if (!dropzone.contains(e.relatedTarget)) { dropzone.classList.remove('drag-over'); } }); dropzone.addEventListener('drop', e => { e.preventDefault(); dropzone.classList.remove('drag-over'); handleFiles(Array.from(e.dataTransfer.files)); }); // ΠΊΠ»ΠΈΠΊ Π½Π° Π·ΠΎΠ½Ρƒ β€” ΠΎΡ‚ΠΊΡ€Ρ‹Π²Π°Π΅ΠΌ Ρ„Π°ΠΉΠ»ΠΎΠ²Ρ‹ΠΉ Π΄ΠΈΠ°Π»ΠΎΠ³ dropzone.addEventListener('click', e => { if (e.target.tagName === 'LABEL' || e.target.closest('label')) return; fileInput.click(); }); fileInput.addEventListener('change', () => { handleFiles(Array.from(fileInput.files)); fileInput.value = ''; }); // ctrl+v β€” вставка ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΊΠΈ ΠΈΠ· Π±ΡƒΡ„Π΅Ρ€Π° (ΡΠΊΡ€ΠΈΠ½ΡˆΠΎΡ‚Ρ‹ ΠΈ Ρ‚.Π΄.) document.addEventListener('paste', e => { const items = Array.from(e.clipboardData?.items || []); const files = items .filter(i => i.kind === 'file') .map(i => i.getAsFile()) .filter(Boolean); if (files.length > 0) handleFiles(files); }); function handleFiles(files) { if (files.length === 0) return; fileList.classList.remove('hidden'); files.forEach(f => uploadFile(f)); } async function uploadFile(file) { const card = makeCard(file.name, 'unknown'); fileList.prepend(card); const status = card.querySelector('.file-status'); const fd = new FormData(); fd.append('file', file); let data; try { const r = await fetch('/api/upload', { method: 'POST', body: fd }); data = await r.json(); if (!r.ok) throw new Error(data.error || r.statusText); } catch (err) { setStatus(status, 'ошибка: ' + err.message, 'error'); return; } // обновляСм ΠΈΠΊΠΎΠ½ΠΊΡƒ ΠΈ ΠΌΠ΅Ρ‚Π°-ΠΈΠ½Ρ„ΠΎ const icon = card.querySelector('.file-icon'); icon.textContent = TYPE_ICONS[data.type] || 'πŸ“„'; icon.className = `file-icon ${data.type}`; card.querySelector('.file-meta').textContent = data.type + ' Β· ' + fmtSize(file.size); if (data.targets.length === 0) { setStatus(status, 'Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ Π½Π΅ поддСрТиваСтся', 'error'); return; } // строим select + ΠΊΠ½ΠΎΠΏΠΊΡƒ ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ const actions = card.querySelector('.file-actions'); actions.innerHTML = ''; const sel = document.createElement('select'); sel.className = 'format-select'; data.targets.forEach(t => { const opt = document.createElement('option'); opt.value = t; opt.textContent = t; sel.appendChild(opt); }); const btn = document.createElement('button'); btn.className = 'btn btn-primary'; btn.textContent = 'ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ'; btn.onclick = () => doConvert(data, sel.value, card); setStatus(status, 'Π³ΠΎΡ‚ΠΎΠ²'); actions.appendChild(sel); actions.appendChild(btn); } async function doConvert(uploadData, target, card) { const btn = card.querySelector('.btn-primary'); const status = card.querySelector('.file-status'); btn.disabled = true; status.innerHTML = 'ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚ΠΈΡ€ΡƒΡŽ...'; status.className = 'file-status'; // Π±Π΅Ρ€Ρ‘ΠΌ Π°ΠΊΡ‚ΡƒΠ°Π»ΡŒΠ½ΠΎΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ select Π½Π° ΠΌΠΎΠΌΠ΅Π½Ρ‚ наТатия const sel = card.querySelector('.format-select'); const actualTarget = sel ? sel.value : target; let result; try { const r = await fetch('/api/convert', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: uploadData.id, saved: uploadData.saved, target: actualTarget, filename: uploadData.filename, }), }); result = await r.json(); if (!r.ok) throw new Error(result.error || r.statusText); } catch (err) { const msg = err.message.slice(0, 220); setStatus(status, 'ошибка: ' + msg, 'error'); btn.disabled = false; return; } setStatus(status, 'Π³ΠΎΡ‚ΠΎΠ²ΠΎ', 'success'); btn.disabled = false; // ΠΊΠ½ΠΎΠΏΠΊΠ° ΡΠΊΠ°Ρ‡Π°Ρ‚ΡŒ β€” замСняСм Ссли ΡƒΠΆΠ΅ Π΅ΡΡ‚ΡŒ const actions = card.querySelector('.file-actions'); actions.querySelector('.btn-download')?.remove(); const dl = document.createElement('a'); dl.href = '/api/download/' + encodeURIComponent(result.result); dl.download = result.filename; dl.className = 'btn btn-download'; dl.textContent = 'ΡΠΊΠ°Ρ‡Π°Ρ‚ΡŒ'; actions.appendChild(dl); } function makeCard(filename, type) { const card = document.createElement('div'); card.className = 'file-card'; card.innerHTML = `
${TYPE_ICONS[type] || 'πŸ“„'}
${escHtml(filename)}
Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°...
β€”
`; return card; } function setStatus(el, text, cls = '') { el.textContent = text; el.className = 'file-status' + (cls ? ' ' + cls : ''); } function fmtSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; if (bytes < 1024 ** 3) return (bytes / 1024 / 1024).toFixed(1) + ' MB'; return (bytes / 1024 ** 3).toFixed(2) + ' GB'; } function escHtml(s) { return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } // ΠΊΠ½ΠΎΠΏΠΊΠ° setup ffmpeg (Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π½Π° windows, показываСтся Π² шапкС) if (setupBtn) { setupBtn.addEventListener('click', async () => { setupBtn.textContent = 'ΠΊΠ°Ρ‡Π°ΡŽ...'; setupBtn.disabled = true; try { const r = await fetch('/api/setup-ffmpeg', { method: 'POST' }); const d = await r.json(); if (d.ffmpeg) { setupBtn.closest('.ffmpeg-warn').textContent = 'βœ“ ffmpeg Π³ΠΎΡ‚ΠΎΠ² β€” ΠΏΠ΅Ρ€Π΅Π·Π°Π³Ρ€ΡƒΠ·ΠΈ страницу'; } else { setupBtn.textContent = d.msg || 'ошибка'; setupBtn.disabled = false; } } catch (e) { setupBtn.textContent = 'ошибка сСти'; setupBtn.disabled = false; } }); }