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);