Files

243 lines
8.8 KiB
JavaScript
Raw Permalink Normal View History

2026-04-02 16:05:40 +03:00
'use strict';
// ── Helpers ────────────────────────────────────────────────────────────────
function formatBytes(n) {
if (n < 1024) return n + ' B';
if (n < 1024 ** 2) return (n / 1024).toFixed(1) + ' KB';
if (n < 1024 ** 3) return (n / 1024 ** 2).toFixed(1) + ' MB';
return (n / 1024 ** 3).toFixed(2) + ' GB';
}
function copyToClipboard(text, btn) {
navigator.clipboard.writeText(text).then(() => {
const orig = btn.textContent;
btn.textContent = 'Copied!';
setTimeout(() => (btn.textContent = orig), 1500);
});
}
// ── DOM refs ───────────────────────────────────────────────────────────────
const tabBtns = document.querySelectorAll('.tab-btn');
const tabContents = document.querySelectorAll('.tab-content');
const formView = document.getElementById('formView');
const resultView = document.getElementById('resultView');
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const dzIcon = document.getElementById('dzIcon');
const dzMain = document.getElementById('dzMain');
const dzHint = document.getElementById('dzHint');
const dzName = document.getElementById('dzName');
const dzSize = document.getElementById('dzSize');
const textArea = document.getElementById('textArea');
const ttlSelect = document.getElementById('ttlSelect');
const passwordIn = document.getElementById('passwordInput');
const burnToggle = document.getElementById('burnToggle');
const uploadBtn = document.getElementById('uploadBtn');
const progressWrap = document.getElementById('progressWrap');
const progressFill = document.getElementById('progressFill');
const progressLbl = document.getElementById('progressLbl');
const alertBox = document.getElementById('alertBox');
const resultUrl = document.getElementById('resultUrl');
const copyUrlBtn = document.getElementById('copyUrlBtn');
const resultPwWrap = document.getElementById('resultPwWrap');
const resultPw = document.getElementById('resultPw');
const copyPwBtn = document.getElementById('copyPwBtn');
const shareAgainBtn = document.getElementById('shareAgainBtn');
let selectedFile = null;
let activeTab = 'file';
// ── Tabs ───────────────────────────────────────────────────────────────────
tabBtns.forEach(btn => {
btn.addEventListener('click', () => {
activeTab = btn.dataset.tab;
tabBtns.forEach(b => b.classList.toggle('active', b === btn));
tabContents.forEach(c => c.classList.toggle('active', c.id === 'tab-' + activeTab));
clearAlert();
});
});
// ── Drop zone ──────────────────────────────────────────────────────────────
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');
if (e.dataTransfer.files.length) setFile(e.dataTransfer.files[0]);
});
fileInput.addEventListener('change', () => {
if (fileInput.files.length) setFile(fileInput.files[0]);
});
function setFile(file) {
selectedFile = file;
dropZone.classList.add('has-file');
dzIcon.textContent = '📄';
dzMain.textContent = '';
dzHint.textContent = '';
dzName.textContent = file.name;
dzSize.textContent = formatBytes(file.size);
}
// ── Alerts ─────────────────────────────────────────────────────────────────
function showAlert(msg, type = 'error') {
alertBox.className = `alert alert-${type}`;
alertBox.textContent = msg;
alertBox.classList.remove('hidden');
}
function clearAlert() { alertBox.classList.add('hidden'); }
// ── Upload ─────────────────────────────────────────────────────────────────
uploadBtn.addEventListener('click', handleUpload);
async function handleUpload() {
clearAlert();
const isFile = activeTab === 'file';
const password = passwordIn.value.trim();
if (isFile && !selectedFile) { showAlert('Please select a file first.'); return; }
if (!isFile && !textArea.value.trim()) { showAlert('Please enter some text first.'); return; }
uploadBtn.disabled = true;
progressWrap.classList.remove('hidden');
setProgress(0, 'Preparing…');
try {
const form = new FormData();
if (isFile) {
form.append('file', selectedFile, selectedFile.name);
form.append('share_type', 'file');
form.append('original_filename', selectedFile.name);
form.append('mimetype', selectedFile.type || 'application/octet-stream');
} else {
const blob = new Blob([textArea.value], { type: 'text/plain' });
form.append('file', blob, 'text.txt');
form.append('share_type', 'text');
form.append('mimetype', 'text/plain');
}
form.append('ttl_hours', ttlSelect.value);
form.append('burn_after_read', burnToggle.checked);
if (password) form.append('password', password);
setProgress(10, 'Uploading…');
const data = await xhrUpload('/api/upload', form, pct => {
setProgress(10 + Math.round(pct * 0.88), `Uploading… ${Math.round(pct)}%`);
});
setProgress(100, 'Done');
const shareUrl = `${location.origin}/s/${data.share_id}`;
switchToResult(shareUrl, password || null);
} catch (err) {
showAlert(err.message || 'Upload failed. Please try again.');
uploadBtn.disabled = false;
progressWrap.classList.add('hidden');
}
}
function setProgress(pct, label) {
progressFill.style.width = pct + '%';
progressLbl.textContent = label;
}
function xhrUpload(url, formData, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', url);
xhr.upload.onprogress = e => {
if (e.lengthComputable) onProgress((e.loaded / e.total) * 100);
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText));
} else {
let msg = 'Upload failed';
try { msg = JSON.parse(xhr.responseText).detail || msg; } catch {}
reject(new Error(msg));
}
};
xhr.onerror = () => reject(new Error('Network error'));
xhr.send(formData);
});
}
// ── View crossfade ─────────────────────────────────────────────────────────
function switchToResult(url, pw) {
resultUrl.value = url;
copyUrlBtn.onclick = () => copyToClipboard(url, copyUrlBtn);
if (pw) {
resultPwWrap.classList.remove('hidden');
resultPw.value = pw;
copyPwBtn.onclick = () => copyToClipboard(pw, copyPwBtn);
} else {
resultPwWrap.classList.add('hidden');
}
// Re-trigger SVG check animation
['.check-circle', '.check-mark'].forEach(sel => {
const el = resultView.querySelector(sel);
el.style.animation = 'none';
el.getBoundingClientRect();
el.style.animation = '';
});
formView.classList.add('view-exiting');
setTimeout(() => {
formView.classList.add('hidden');
formView.classList.remove('view-exiting');
progressWrap.classList.add('hidden');
resultView.classList.remove('hidden');
resultView.classList.add('view-entering');
setTimeout(() => resultView.classList.remove('view-entering'), 350);
}, 260);
}
function switchToForm() {
resultView.classList.add('view-exiting');
setTimeout(() => {
resultView.classList.add('hidden');
resultView.classList.remove('view-exiting');
resetForm();
formView.classList.remove('hidden');
formView.classList.add('view-entering');
setTimeout(() => formView.classList.remove('view-entering'), 350);
}, 260);
}
function resetForm() {
selectedFile = null;
fileInput.value = '';
dropZone.classList.remove('has-file');
dzIcon.textContent = '📂';
dzMain.textContent = 'Drop a file or click to browse';
dzHint.textContent = '1 GB recommended · up to 6 GB accepted';
dzName.textContent = '';
dzSize.textContent = '';
textArea.value = '';
passwordIn.value = '';
burnToggle.checked = false;
ttlSelect.value = '24';
clearAlert();
uploadBtn.disabled = false;
}
shareAgainBtn.addEventListener('click', switchToForm);