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'}
${rows}
Nome Tamanho Modificado
`; 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 commitPushBtn = isRootDir ? `` : ''; const publishBtn = isRootDir ? `` : ''; const deleteBtn = ``; const actions = `${downloadBtn}${commitPushBtn}${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 = `
Repositório: ${Utils.escapeHtml(result.repoUrl)}
Site: ${Utils.escapeHtml(result.siteUrl)}
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 commitPush(path) { const name = path.split('/').pop(); const message = await Modal.prompt( 'Commit & Push', `Mensagem do commit para ${name}:`, `Atualização - ${new Date().toLocaleDateString('pt-BR')} ${new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })}` ); if (message === null) return; try { Toast.info('Realizando commit e push...'); const result = await API.files.commitPush(path, message || undefined); if (result.status === 'clean') { Toast.info(result.message); } else { Toast.success(`${result.changes} arquivo(s) enviados ao Gitea`); } } catch (err) { Toast.error(`Erro no commit/push: ${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;