const FilesUI = {
currentPath: '',
async load() {
await FilesUI.navigate('');
},
async navigate(path) {
try {
const data = await API.files.list(path);
FilesUI.currentPath = data.path || '';
FilesUI.render(data);
} catch (err) {
Toast.error(`Erro ao carregar arquivos: ${err.message}`);
}
},
render(data) {
const container = document.getElementById('files-container');
if (!container) return;
const breadcrumb = FilesUI._renderBreadcrumb(data.path);
const entries = data.entries || [];
if (entries.length === 0) {
container.innerHTML = `
${breadcrumb}
Nenhum arquivo encontrado neste diretório
`;
Utils.refreshIcons(container);
return;
}
const rows = entries.map(entry => FilesUI._renderRow(entry, data.path)).join('');
container.innerHTML = `
${breadcrumb}
${entries.length} ${entries.length === 1 ? 'item' : 'itens'}
| Nome |
Tamanho |
Modificado |
|
${rows}
`;
Utils.refreshIcons(container);
},
_renderBreadcrumb(currentPath) {
const parts = currentPath ? currentPath.split('/').filter(Boolean) : [];
let html = `';
return html;
},
_renderRow(entry, currentPath) {
const fullPath = currentPath ? `${currentPath}/${entry.name}` : entry.name;
const icon = entry.type === 'directory' ? 'folder' : FilesUI._fileIcon(entry.extension);
const iconColor = entry.type === 'directory' ? 'var(--warning)' : 'var(--text-muted)';
const size = entry.type === 'directory' ? '—' : FilesUI._formatSize(entry.size);
const date = FilesUI._formatDate(entry.modified);
const nameCell = entry.type === 'directory'
? ` ${Utils.escapeHtml(entry.name)}`
: ` ${Utils.escapeHtml(entry.name)}`;
const downloadBtn = entry.type === 'directory'
? ``
: ``;
const isRootDir = entry.type === 'directory' && !currentPath;
const publishBtn = isRootDir
? ``
: '';
const deleteBtn = ``;
const actions = `${downloadBtn}${publishBtn}${deleteBtn}`;
return `
| ${nameCell} |
${size} |
${date} |
${actions} |
`;
},
_fileIcon(ext) {
const map = {
js: 'file-code-2', ts: 'file-code-2', jsx: 'file-code-2', tsx: 'file-code-2',
py: 'file-code-2', rb: 'file-code-2', go: 'file-code-2', rs: 'file-code-2',
java: 'file-code-2', c: 'file-code-2', cpp: 'file-code-2', h: 'file-code-2',
html: 'file-code-2', css: 'file-code-2', scss: 'file-code-2', vue: 'file-code-2',
json: 'file-json', xml: 'file-json', yaml: 'file-json', yml: 'file-json',
md: 'file-text', txt: 'file-text', log: 'file-text', csv: 'file-text',
pdf: 'file-text',
png: 'file-image', jpg: 'file-image', jpeg: 'file-image', gif: 'file-image',
svg: 'file-image', webp: 'file-image', ico: 'file-image',
zip: 'file-archive', tar: 'file-archive', gz: 'file-archive', rar: 'file-archive',
sh: 'file-terminal', bash: 'file-terminal',
sql: 'database',
env: 'file-lock',
};
return map[ext] || 'file';
},
_formatSize(bytes) {
if (bytes == null) return '—';
if (bytes === 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return (bytes / Math.pow(1024, i)).toFixed(i > 0 ? 1 : 0) + ' ' + units[i];
},
_formatDate(isoString) {
if (!isoString) return '—';
const d = new Date(isoString);
return d.toLocaleDateString('pt-BR') + ' ' + d.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
},
downloadFile(path) {
const a = document.createElement('a');
a.href = `/api/files/download?path=${encodeURIComponent(path)}`;
a.download = '';
a.click();
},
downloadFolder(path) {
const a = document.createElement('a');
a.href = `/api/files/download-folder?path=${encodeURIComponent(path)}`;
a.download = '';
a.click();
},
async publishProject(path) {
const name = path.split('/').pop();
const confirmed = await Modal.confirm(
'Publicar projeto',
`Isso irá criar o repositório "${name}" no Gitea, fazer push dos arquivos e publicar em ${name}.nitro-cloud.duckdns.org. Continuar?`
);
if (!confirmed) return;
try {
Toast.info('Publicando projeto... isso pode levar alguns segundos');
const result = await API.files.publish(path);
Toast.success(`Projeto publicado com sucesso!`);
const modal = document.getElementById('execution-detail-modal-overlay');
const title = document.getElementById('execution-detail-title');
const content = document.getElementById('execution-detail-content');
if (modal && title && content) {
title.textContent = 'Projeto Publicado';
content.innerHTML = `
Status: ${Utils.escapeHtml(result.status)}
${result.message ? `
${Utils.escapeHtml(result.message)}
` : ''}
`;
Modal.open('execution-detail-modal-overlay');
}
await FilesUI.navigate(FilesUI.currentPath);
} catch (err) {
Toast.error(`Erro ao publicar: ${err.message}`);
}
},
async deleteEntry(path, entryType) {
const label = entryType === 'directory' ? 'pasta' : 'arquivo';
const name = path.split('/').pop();
const confirmed = await Modal.confirm(
`Excluir ${label}`,
`Tem certeza que deseja excluir "${name}"? Esta ação não pode ser desfeita.`
);
if (!confirmed) return;
try {
await API.files.delete(path);
Toast.success(`${label.charAt(0).toUpperCase() + label.slice(1)} excluído`);
await FilesUI.navigate(FilesUI.currentPath);
} catch (err) {
Toast.error(`Erro ao excluir: ${err.message}`);
}
},
};
window.FilesUI = FilesUI;