import { getFileIcon, getFileEmoji, getExt, isImage, formatSize, formatDate } from '../utils/formatters.js';
export class UIRenderer {
constructor(stateManager, hfService) {
this.state = stateManager;
this.hf = hfService;
this.containers = {
folders: document.getElementById('foldersContainer'),
files: document.getElementById('filesContainer'),
breadcrumbs: document.getElementById('breadcrumbs'),
toast: document.getElementById('toastContainer'),
progress: document.getElementById('uploadProgress'),
progressText: document.getElementById('progressText')
};
}
showToast(msg, type = 'info') {
const icons = {
success: 'ph-fill ph-check-circle',
error: 'ph-fill ph-warning-circle',
info: 'ph-fill ph-info',
warning: 'ph-fill ph-warning'
};
const t = document.createElement('div');
t.className = `toast toast-${type}`;
t.innerHTML = `${msg}`;
this.containers.toast.appendChild(t);
requestAnimationFrame(() => t.classList.add('show'));
setTimeout(() => {
t.classList.remove('show');
setTimeout(() => t.remove(), 400);
}, 3500);
}
showProgress(msg = 'Working...') {
this.containers.progressText.textContent = msg;
this.containers.progress.classList.add('active');
}
hideProgress() {
this.containers.progress.classList.remove('active');
}
renderBreadcrumbs(onCrumbClick) {
this.containers.breadcrumbs.innerHTML = '';
const root = document.createElement('span');
root.className = 'breadcrumb-item' + (this.state.currentPath.length === 0 ? ' active' : '');
root.textContent = 'My Files';
root.onclick = () => onCrumbClick([]);
this.containers.breadcrumbs.appendChild(root);
this.state.currentPath.forEach((seg, idx) => {
const sep = document.createElement('span');
sep.className = 'breadcrumb-separator';
sep.innerHTML = '';
this.containers.breadcrumbs.appendChild(sep);
const crumb = document.createElement('span');
crumb.className = 'breadcrumb-item' + (idx === this.state.currentPath.length - 1 ? ' active' : '');
crumb.textContent = seg;
crumb.onclick = () => onCrumbClick(this.state.currentPath.slice(0, idx + 1));
this.containers.breadcrumbs.appendChild(crumb);
});
}
renderFolders(folders, onFolderClick, onRename, onDelete) {
this.containers.folders.innerHTML = '';
if (!folders.length) return;
folders.forEach(folder => {
const card = document.createElement('div');
card.className = 'folder-card';
card.innerHTML = `
${folder.name}
Folder
`;
card.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
if (e.target.closest('.card-menu')) return;
onFolderClick(folder.name);
};
card.querySelector('[data-action="rename"]').onclick = (e) => {
e.stopPropagation();
if (onRename) onRename(folder.path, folder.name);
};
card.querySelector('[data-action="delete"]').onclick = (e) => {
e.stopPropagation();
if (onDelete) onDelete(folder.path, folder.name);
};
const menuBtn = card.querySelector('.card-menu-btn');
const menu = card.querySelector('.dropdown-menu');
menuBtn.onclick = (e) => {
e.stopPropagation();
menu.classList.toggle('open');
};
this.containers.folders.appendChild(card);
});
}
renderFiles(files, actions) {
this.containers.files.innerHTML = '';
const mode = this.state.viewMode;
this.containers.files.className = mode === 'grid' ? 'grid-container' : 'list-container';
if (!files.length) {
this.showEmpty();
return;
}
files.forEach(file => {
const card = document.createElement('div');
card.className = mode === 'grid' ? 'file-card' : 'file-list-item';
const { icon, color } = getFileIcon(file.name);
const ext = getExt(file.name).toUpperCase();
const size = formatSize(file.size);
const starred = this.state.starred.includes(file.path);
const url = actions.getUrl(file.path);
if (mode === 'grid') {
const isImg = isImage(file.name);
const previewHTML = isImg
? `
`
: `${getFileEmoji(ext)}`;
card.innerHTML = `
${previewHTML}
${ext}
${file.name}
${size} • ${file.lastModified ? formatDate(file.lastModified) : 'Recently'}
â‹®
`;
} else {
card.innerHTML = `
${file.name}
${size} • ${ext}
`;
}
card.onclick = (e) => {
if (e.target.closest('.file-actions, .quick-actions, .list-actions')) return;
actions.onPreview(file);
};
// Action Handlers
card.addEventListener('click', (e) => {
const btn = e.target.closest('[data-action]');
if (!btn) return;
const action = btn.dataset.action;
if (action === 'preview') actions.onPreview(file);
else if (action === 'download') actions.onDownload(url, file.name);
else if (action === 'rename') actions.onRename(file.path, file.name);
else if (action === 'star') actions.onStar(file.path);
else if (action === 'delete') actions.onDelete(file.path, file.name);
else if (action === 'history') actions.onHistory(file.path, file.name);
});
this.containers.files.appendChild(card);
});
}
showEmpty() {
this.containers.files.innerHTML = `
Nothing here yet
Upload files or create folders to get started.
`;
}
showError(message = 'Unable to load files right now.') {
this.containers.folders.innerHTML = '';
this.containers.files.innerHTML = `
Connection Problem
${message}
`;
}
showSkeletons(foldersCount = 3, filesCount = 6) {
this.containers.folders.innerHTML = '';
this.containers.files.innerHTML = '';
for (let i = 0; i < foldersCount; i++) {
const el = document.createElement('div');
el.className = 'skeleton skeleton-card';
this.containers.folders.appendChild(el);
}
for (let i = 0; i < filesCount; i++) {
const el = document.createElement('div');
el.className = 'skeleton skeleton-card';
this.containers.files.appendChild(el);
}
}
showHistoryModal(fileName) {
const modal = document.getElementById('historyModal');
const nameEl = document.getElementById('historyFileName');
const list = document.getElementById('historyList');
nameEl.textContent = fileName;
list.innerHTML = '';
modal.classList.add('active');
}
renderHistory(history, onRestore) {
const list = document.getElementById('historyList');
if (!history.length) {
list.innerHTML = 'No version history found.
';
return;
}
list.innerHTML = '';
history.forEach((commit, idx) => {
const item = document.createElement('div');
item.className = 'history-item';
const date = new Date(commit.timestamp).toLocaleString();
const shortId = commit.id.substring(0, 7);
item.innerHTML = `
${commit.message}
${date} • ${shortId} by ${commit.author}
${idx === 0 ? 'Current' : `
`}
`;
item.querySelectorAll('.restore-btn').forEach(btn => {
btn.onclick = () => {
const asCopy = btn.dataset.action === 'copy';
onRestore(commit.id, asCopy);
};
});
list.appendChild(item);
});
}
}