rfiles-online/portal/templates/portal/upload.html

121 lines
5.4 KiB
HTML

{% extends "portal/base.html" %}
{% block title %}Upload Files{% endblock %}
{% block extra_css %}
<style>
.upload-zone { border: 2px dashed var(--border); border-radius: 12px; padding: 4rem 2rem; text-align: center; transition: all 0.3s; cursor: pointer; margin-bottom: 2rem; }
.upload-zone:hover, .upload-zone.dragover { border-color: var(--primary); background: rgba(59, 130, 246, 0.05); }
.upload-zone.uploading { pointer-events: none; opacity: 0.7; }
.upload-icon { font-size: 3rem; margin-bottom: 1rem; }
.upload-zone h2 { font-size: 1.25rem; margin-bottom: 0.5rem; }
.upload-zone p { color: var(--text-muted); font-size: 0.875rem; }
.upload-zone input[type="file"] { display: none; }
.results { display: grid; gap: 1rem; }
.result-item { display: flex; justify-content: space-between; align-items: center; padding: 1rem; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; }
.result-item.success { border-color: var(--success); }
.result-item.error { border-color: var(--error); }
.result-info { flex: 1; }
.result-info h3 { font-size: 1rem; margin-bottom: 0.25rem; }
.result-info .meta { font-size: 0.75rem; color: var(--text-muted); }
.share-link { display: flex; align-items: center; gap: 0.5rem; background: var(--bg); padding: 0.5rem 1rem; border-radius: 6px; font-family: monospace; font-size: 0.875rem; }
.share-link input { background: transparent; border: none; color: var(--text); width: 300px; outline: none; }
.copy-btn { background: var(--primary); color: white; border: none; padding: 0.25rem 0.75rem; border-radius: 4px; cursor: pointer; font-size: 0.75rem; }
.copy-btn:hover { background: var(--primary-hover); }
.progress-bar { height: 4px; background: var(--border); border-radius: 2px; overflow: hidden; margin-top: 0.5rem; }
.progress-bar .progress { height: 100%; background: var(--primary); transition: width 0.3s; }
</style>
{% endblock %}
{% block content %}
<div class="upload-zone" id="uploadZone">
<div class="upload-icon">+</div>
<h2>Drop files here to upload</h2>
<p>or click to select files</p>
<p class="mt-1 text-muted">No file size limit</p>
<input type="file" id="fileInput" multiple>
</div>
<div class="results" id="results"></div>
{% endblock %}
{% block extra_js %}
<script>
const uploadZone = document.getElementById('uploadZone');
const fileInput = document.getElementById('fileInput');
const results = document.getElementById('results');
uploadZone.addEventListener('click', () => fileInput.click());
uploadZone.addEventListener('dragover', (e) => { e.preventDefault(); uploadZone.classList.add('dragover'); });
uploadZone.addEventListener('dragleave', () => { uploadZone.classList.remove('dragover'); });
uploadZone.addEventListener('drop', (e) => { e.preventDefault(); uploadZone.classList.remove('dragover'); handleFiles(e.dataTransfer.files); });
fileInput.addEventListener('change', () => { handleFiles(fileInput.files); fileInput.value = ''; });
function handleFiles(files) { Array.from(files).forEach(uploadFile); }
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function uploadFile(file) {
const item = document.createElement('div');
item.className = 'result-item';
item.innerHTML = `
<div class="result-info">
<h3>${file.name}</h3>
<div class="meta">${formatBytes(file.size)} - Uploading...</div>
<div class="progress-bar"><div class="progress" style="width: 0%"></div></div>
</div>
`;
results.insertBefore(item, results.firstChild);
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
item.querySelector('.progress').style.width = (e.loaded / e.total * 100) + '%';
}
});
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
item.classList.add('success');
item.innerHTML = `
<div class="result-info">
<h3>${data.file.title}</h3>
<div class="meta">${formatBytes(data.file.size)} - Uploaded successfully</div>
</div>
<div class="share-link">
<input type="text" value="${data.share.url}" readonly onclick="this.select()">
<button class="copy-btn" onclick="copyLink(this, '${data.share.url}')">Copy</button>
</div>
`;
} else {
item.classList.add('error');
item.querySelector('.meta').textContent = 'Upload failed';
}
});
xhr.addEventListener('error', () => {
item.classList.add('error');
item.querySelector('.meta').textContent = 'Upload failed';
});
xhr.open('POST', '{% url "portal:api_upload" %}');
xhr.send(formData);
}
function copyLink(btn, url) {
navigator.clipboard.writeText(url).then(() => {
const originalText = btn.textContent;
btn.textContent = 'Copied!';
setTimeout(() => btn.textContent = originalText, 2000);
});
}
</script>
{% endblock %}