|
import { api } from '../api.js';
|
|
|
|
export class FileUploadView extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.folderId = null;
|
|
this.handleEscape = this.handleEscape.bind(this);
|
|
this.uploadItems = new Map();
|
|
}
|
|
|
|
connectedCallback() {
|
|
document.addEventListener('keydown', this.handleEscape);
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
document.removeEventListener('keydown', this.handleEscape);
|
|
}
|
|
|
|
setFolder(folderId) {
|
|
this.folderId = folderId;
|
|
this.render();
|
|
this.attachListeners();
|
|
this.openFileSelector();
|
|
}
|
|
|
|
render() {
|
|
const folderInfo = this.folderId ? `(Folder ID: ${this.folderId})` : '(Root)';
|
|
this.innerHTML = `
|
|
<div class="file-upload-view" style="display: none;">
|
|
<div class="file-upload-header">
|
|
<div class="header-left">
|
|
<button class="button" id="upload-back-btn">Back</button>
|
|
<h2>Uploading Files ${folderInfo}</h2>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="file-upload-body">
|
|
<div class="upload-list" id="upload-list"></div>
|
|
</div>
|
|
<input type="file" id="file-input" multiple style="display: none;">
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
openFileSelector() {
|
|
const fileInput = this.querySelector('#file-input');
|
|
if (fileInput) {
|
|
fileInput.click();
|
|
}
|
|
}
|
|
|
|
attachListeners() {
|
|
const fileInput = this.querySelector('#file-input');
|
|
const backBtn = this.querySelector('#upload-back-btn');
|
|
|
|
if (backBtn) {
|
|
backBtn.addEventListener('click', () => this.close());
|
|
}
|
|
|
|
if (fileInput) {
|
|
fileInput.addEventListener('change', (e) => {
|
|
if (e.target.files.length > 0) {
|
|
const view = this.querySelector('.file-upload-view');
|
|
if (view) {
|
|
view.style.display = 'flex';
|
|
}
|
|
this.handleFiles(e.target.files);
|
|
} else {
|
|
this.close();
|
|
}
|
|
});
|
|
|
|
fileInput.addEventListener('cancel', () => {
|
|
this.close();
|
|
});
|
|
}
|
|
}
|
|
|
|
handleEscape(e) {
|
|
if (e.key === 'Escape') {
|
|
this.close();
|
|
}
|
|
}
|
|
|
|
close() {
|
|
window.history.back();
|
|
}
|
|
|
|
hide() {
|
|
document.removeEventListener('keydown', this.handleEscape);
|
|
this.remove();
|
|
}
|
|
|
|
async handleFiles(files) {
|
|
const uploadList = this.querySelector('#upload-list');
|
|
if (!uploadList) return;
|
|
|
|
const uploadPromises = [];
|
|
|
|
for (const file of files) {
|
|
const itemId = `upload-${Date.now()}-${Math.random()}`;
|
|
const item = document.createElement('div');
|
|
item.className = 'upload-item';
|
|
item.id = itemId;
|
|
item.innerHTML = `
|
|
<div class="upload-item-info">
|
|
<div class="upload-item-name">${file.name}</div>
|
|
<div class="upload-item-size">${this.formatFileSize(file.size)}</div>
|
|
</div>
|
|
<div class="upload-item-progress">
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" style="width: 0%"></div>
|
|
</div>
|
|
<div class="upload-item-status">Uploading...</div>
|
|
</div>
|
|
`;
|
|
uploadList.appendChild(item);
|
|
|
|
this.uploadItems.set(itemId, {
|
|
element: item,
|
|
progress: 0
|
|
});
|
|
|
|
const promise = this.uploadFile(file, itemId)
|
|
.then(() => {
|
|
setTimeout(() => {
|
|
this.uploadItems.delete(itemId);
|
|
item.remove();
|
|
}, 500);
|
|
})
|
|
.catch(error => {
|
|
const statusEl = item.querySelector('.upload-item-status');
|
|
if (statusEl) {
|
|
statusEl.textContent = 'Failed: ' + error.message;
|
|
statusEl.classList.add('error');
|
|
}
|
|
});
|
|
|
|
uploadPromises.push(promise);
|
|
}
|
|
|
|
await Promise.all(uploadPromises);
|
|
|
|
this.dispatchEvent(new CustomEvent('upload-complete', { bubbles: true }));
|
|
|
|
setTimeout(() => {
|
|
this.close();
|
|
}, 1500);
|
|
}
|
|
|
|
sortUploadList() {
|
|
const uploadList = this.querySelector('#upload-list');
|
|
if (!uploadList) return;
|
|
|
|
const items = Array.from(this.uploadItems.entries())
|
|
.sort((a, b) => b[1].progress - a[1].progress);
|
|
|
|
uploadList.innerHTML = '';
|
|
items.forEach(([itemId, data]) => {
|
|
uploadList.appendChild(data.element);
|
|
});
|
|
}
|
|
|
|
async uploadFile(file, itemId) {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
if (this.folderId !== null && this.folderId !== undefined) {
|
|
formData.append('folder_id', String(this.folderId));
|
|
}
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
xhr.upload.addEventListener('progress', (e) => {
|
|
if (e.lengthComputable) {
|
|
const percentComplete = (e.loaded / e.total) * 100;
|
|
const itemData = this.uploadItems.get(itemId);
|
|
if (itemData) {
|
|
itemData.progress = percentComplete;
|
|
const progressFill = itemData.element.querySelector('.progress-fill');
|
|
if (progressFill) {
|
|
progressFill.style.width = percentComplete + '%';
|
|
}
|
|
this.sortUploadList();
|
|
}
|
|
}
|
|
});
|
|
|
|
xhr.addEventListener('load', () => {
|
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
const itemData = this.uploadItems.get(itemId);
|
|
if (itemData) {
|
|
itemData.progress = 100;
|
|
const progressFill = itemData.element.querySelector('.progress-fill');
|
|
const statusEl = itemData.element.querySelector('.upload-item-status');
|
|
if (progressFill) {
|
|
progressFill.style.width = '100%';
|
|
}
|
|
if (statusEl) {
|
|
statusEl.textContent = 'Complete';
|
|
statusEl.classList.add('success');
|
|
}
|
|
this.sortUploadList(); // Sort after completion to move completed items to top
|
|
}
|
|
resolve(JSON.parse(xhr.responseText));
|
|
} else {
|
|
reject(new Error(xhr.statusText));
|
|
}
|
|
});
|
|
|
|
xhr.addEventListener('error', () => reject(new Error('Upload failed')));
|
|
xhr.addEventListener('abort', () => reject(new Error('Upload aborted')));
|
|
|
|
xhr.open('POST', '/files/upload');
|
|
xhr.setRequestHeader('Authorization', `Bearer ${api.getToken()}`);
|
|
xhr.send(formData);
|
|
});
|
|
}
|
|
|
|
formatFileSize(bytes) {
|
|
if (bytes < 1024) return bytes + ' B';
|
|
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
|
|
if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + ' MB';
|
|
return (bytes / 1073741824).toFixed(1) + ' GB';
|
|
}
|
|
}
|
|
|
|
customElements.define('file-upload-view', FileUploadView);
|