121 lines
5.4 KiB
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 %}
|