269 lines
9.0 KiB
JavaScript
Raw Normal View History

2025-11-10 15:46:40 +01:00
export class BaseFileList extends HTMLElement {
constructor() {
super();
this.files = [];
this.folders = [];
this.selectedFiles = new Set();
this.selectedFolders = new Set();
this.boundHandleClick = this.handleClick.bind(this);
this.boundHandleDblClick = this.handleDblClick.bind(this);
this.boundHandleChange = this.handleChange.bind(this);
}
connectedCallback() {
this.addEventListener('click', this.boundHandleClick);
this.addEventListener('dblclick', this.boundHandleDblClick);
this.addEventListener('change', this.boundHandleChange);
}
disconnectedCallback() {
this.removeEventListener('click', this.boundHandleClick);
this.removeEventListener('dblclick', this.boundHandleDblClick);
this.removeEventListener('change', this.boundHandleChange);
}
isEditableFile(filename, mimeType) {
if (mimeType && mimeType.startsWith('text/')) return true;
const editableExtensions = [
'txt', 'md', 'log', 'json', 'js', 'py', 'html', 'css',
'xml', 'yaml', 'yml', 'sh', 'bat', 'ini', 'conf', 'cfg'
];
const extension = filename.split('.').pop().toLowerCase();
return editableExtensions.includes(extension);
}
getFileIcon(mimeType) {
if (mimeType.startsWith('image/')) return '📷';
if (mimeType.startsWith('video/')) return '🎥';
if (mimeType.startsWith('audio/')) return '🎵';
if (mimeType.includes('pdf')) return '📄';
if (mimeType.includes('text')) return '📄';
return '📄';
}
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';
}
renderFolder(folder) {
const isSelected = this.selectedFolders.has(folder.id);
const starIcon = folder.is_starred ? '&#9733;' : '&#9734;';
const starAction = folder.is_starred ? 'unstar-folder' : 'star-folder';
const actions = this.getFolderActions(folder);
return `
<div class="file-item folder-item ${isSelected ? 'selected' : ''}" data-folder-id="${folder.id}">
<input type="checkbox" class="select-item" data-type="folder" data-id="${folder.id}" ${isSelected ? 'checked' : ''}>
<div class="file-icon">&#128193;</div>
<div class="file-name">${folder.name}</div>
<div class="file-actions-menu">
${actions}
</div>
</div>
`;
}
renderFile(file) {
const isSelected = this.selectedFiles.has(file.id);
const icon = this.getFileIcon(file.mime_type);
const size = this.formatFileSize(file.size);
const starIcon = file.is_starred ? '&#9733;' : '&#9734;';
const starAction = file.is_starred ? 'unstar-file' : 'star-file';
const actions = this.getFileActions(file);
return `
<div class="file-item ${isSelected ? 'selected' : ''}" data-file-id="${file.id}">
<input type="checkbox" class="select-item" data-type="file" data-id="${file.id}" ${isSelected ? 'checked' : ''}>
<div class="file-icon">${icon}</div>
<div class="file-name">${file.name}</div>
<div class="file-size">${size}</div>
<div class="file-actions-menu">
${actions}
</div>
</div>
`;
}
getFolderActions(folder) {
return '';
}
getFileActions(file) {
return '';
}
handleClick(e) {
const target = e.target;
if (target.id === 'clear-selection-btn') {
this.clearSelection();
return;
}
if (target.classList.contains('action-btn')) {
e.stopPropagation();
const action = target.dataset.action;
const id = parseInt(target.dataset.id);
this.handleAction(action, id);
return;
}
if (target.classList.contains('select-item')) {
e.stopPropagation();
return;
}
const fileItem = target.closest('.file-item:not(.folder-item)');
if (fileItem) {
const fileId = parseInt(fileItem.dataset.fileId);
const file = this.files.find(f => f.id === fileId);
if (this.isEditableFile(file.name, file.mime_type)) {
this.dispatchEvent(new CustomEvent('edit-file', {
detail: { file: file },
bubbles: true
}));
} else {
this.dispatchEvent(new CustomEvent('photo-click', {
detail: { photo: file },
bubbles: true
}));
}
}
}
handleDblClick(e) {
const folderItem = e.target.closest('.folder-item');
if (folderItem) {
const folderId = parseInt(folderItem.dataset.folderId);
this.dispatchEvent(new CustomEvent('folder-open', { detail: { folderId } }));
}
}
handleChange(e) {
const target = e.target;
if (target.id === 'select-all') {
this.toggleSelectAll(target.checked);
return;
}
if (target.classList.contains('select-item')) {
const type = target.dataset.type;
const id = parseInt(target.dataset.id);
this.toggleSelectItem(type, id, target.checked);
}
}
toggleSelectItem(type, id, checked) {
if (type === 'file') {
if (checked) {
this.selectedFiles.add(id);
} else {
this.selectedFiles.delete(id);
}
} else if (type === 'folder') {
if (checked) {
this.selectedFolders.add(id);
} else {
this.selectedFolders.delete(id);
}
}
const item = this.querySelector(`[data-${type}-id="${id}"]`);
if (item) {
if (checked) {
item.classList.add('selected');
} else {
item.classList.remove('selected');
}
}
this.updateSelectionUI();
}
toggleSelectAll(checked) {
this.selectedFiles.clear();
this.selectedFolders.clear();
if (checked) {
this.files.forEach(file => this.selectedFiles.add(file.id));
this.folders.forEach(folder => this.selectedFolders.add(folder.id));
}
this.querySelectorAll('.select-item').forEach(checkbox => {
checkbox.checked = checked;
});
this.querySelectorAll('.file-item').forEach(item => {
if (checked) {
item.classList.add('selected');
} else {
item.classList.remove('selected');
}
});
this.updateSelectionUI();
}
clearSelection() {
this.selectedFiles.clear();
this.selectedFolders.clear();
this.querySelectorAll('.select-item').forEach(checkbox => {
checkbox.checked = false;
});
this.querySelectorAll('.file-item').forEach(item => {
item.classList.remove('selected');
});
this.updateSelectionUI();
}
updateSelectionUI() {
const hasSelected = this.selectedFiles.size > 0 || this.selectedFolders.size > 0;
const totalItems = this.files.length + this.folders.length;
const totalSelected = this.selectedFiles.size + this.selectedFolders.size;
const allSelected = totalItems > 0 && totalSelected === totalItems;
const selectAllCheckbox = this.querySelector('#select-all');
const selectAllLabel = this.querySelector('label[for="select-all"]');
const batchActionsDiv = this.querySelector('.batch-actions');
if (selectAllCheckbox) {
selectAllCheckbox.checked = allSelected;
this.updateIndeterminateState();
}
if (selectAllLabel) {
selectAllLabel.textContent = hasSelected ? `${totalSelected} selected` : 'Select all';
}
if (hasSelected && !batchActionsDiv) {
this.createBatchActionsBar();
} else if (!hasSelected && batchActionsDiv) {
batchActionsDiv.remove();
}
}
createBatchActionsBar() {
}
updateIndeterminateState() {
const selectAllCheckbox = this.querySelector('#select-all');
if (selectAllCheckbox) {
const totalItems = this.files.length + this.folders.length;
const totalSelected = this.selectedFiles.size + this.selectedFolders.size;
const hasSelected = totalSelected > 0;
const allSelected = totalItems > 0 && totalSelected === totalItems;
selectAllCheckbox.indeterminate = hasSelected && !allSelected;
}
}
async handleAction(action, id) {
}
}