// launcher.js - Funcionalidad del Launcher GUI class LauncherManager { constructor() { this.currentGroup = null; this.groups = []; this.scripts = []; this.favorites = new Set(); this.history = []; this.categories = {}; this.currentFilter = 'all'; this.currentEditingGroup = null; this.pythonEnvs = []; this.markdownFiles = []; } async init() { console.log('Inicializando Launcher GUI...'); await this.loadCategories(); await this.loadPythonEnvironments(); await this.loadGroups(); await this.loadFavorites(); await this.loadHistory(); this.setupEventListeners(); this.renderInterface(); } async loadCategories() { try { const response = await fetch('/api/launcher-categories'); this.categories = await response.json(); } catch (error) { console.error('Error loading launcher categories:', error); } } async loadPythonEnvironments() { try { const response = await fetch('/api/python-environments'); this.pythonEnvs = await response.json(); this.renderPythonEnvSelector(); } catch (error) { console.error('Error loading Python environments:', error); } } renderPythonEnvSelector() { const selector = document.getElementById('group-python-env'); if (!selector) return; selector.innerHTML = ''; this.pythonEnvs.forEach(env => { const option = document.createElement('option'); option.value = env.name; option.textContent = env.display_name; selector.appendChild(option); }); } async loadGroups() { try { const response = await fetch('/api/launcher-groups'); this.groups = await response.json(); this.renderGroupSelector(); } catch (error) { console.error('Error loading launcher groups:', error); } } async loadFavorites() { try { const response = await fetch('/api/launcher-favorites'); const data = await response.json(); this.favorites = new Set(data.favorites.map(f => `${f.group_id}_${f.script_name}`)); await this.renderFavorites(data.favorites); } catch (error) { console.error('Error loading favorites:', error); } } async loadHistory() { try { const response = await fetch('/api/launcher-history'); const data = await response.json(); this.history = data.history || []; this.renderHistory(); } catch (error) { console.error('Error loading history:', error); } } setupEventListeners() { // Event listener para el formulario de grupos const groupForm = document.getElementById('group-form'); if (groupForm) { groupForm.addEventListener('submit', (e) => { e.preventDefault(); this.saveGroup(); }); } // Event listener para el formulario de metadatos de scripts const scriptMetadataForm = document.getElementById('script-metadata-form'); if (scriptMetadataForm) { scriptMetadataForm.addEventListener('submit', (e) => { e.preventDefault(); this.saveScriptMetadata(); }); } } renderInterface() { this.renderGroupSelector(); this.renderCategoryFilter(); this.updateFavoritesCount(); } renderGroupSelector() { const selector = document.getElementById('launcher-group-select'); if (!selector) return; // Guardar la selección actual const currentSelection = this.getCurrentGroupSelection(); selector.innerHTML = ''; // Ordenar grupos alfabéticamente por nombre const sortedGroups = [...this.groups].sort((a, b) => a.name.localeCompare(b.name)); sortedGroups.forEach(group => { const option = document.createElement('option'); option.value = group.id; option.textContent = group.name; option.dataset.category = group.category; option.dataset.description = group.description; selector.appendChild(option); }); // Restaurar la selección guardada this.restoreGroupSelection(currentSelection); } // Nuevo método para obtener la selección actual del localStorage getCurrentGroupSelection() { const currentValue = document.getElementById('launcher-group-select')?.value; return localStorage.getItem('launcher-selected-group') || currentValue || ''; } // Nuevo método para restaurar la selección desde localStorage restoreGroupSelection(groupId) { const selector = document.getElementById('launcher-group-select'); if (!selector || !groupId) return; // Verificar que el grupo aún existe const groupExists = this.groups.some(g => g.id === groupId); if (groupExists) { selector.value = groupId; // Cargar scripts del grupo restaurado automáticamente this.loadLauncherScripts(); } else { // Si el grupo ya no existe, limpiar localStorage localStorage.removeItem('launcher-selected-group'); } } renderCategoryFilter() { const filterContainer = document.querySelector('.category-filter .flex'); if (!filterContainer) return; // Limpiar botones existentes excepto "Todas" const buttons = filterContainer.querySelectorAll('.category-btn:not([data-category="all"])'); buttons.forEach(btn => btn.remove()); // Agregar botones por categoría Object.keys(this.categories).forEach(category => { const categoryData = this.categories[category]; const button = document.createElement('button'); button.className = 'category-btn px-3 py-1 rounded-full text-sm border'; button.dataset.category = category; button.innerHTML = `${categoryData.icon} ${category}`; button.onclick = () => this.filterByCategory(category); filterContainer.appendChild(button); }); } async loadLauncherScripts() { const groupId = document.getElementById('launcher-group-select').value; // Guardar la selección en localStorage if (groupId) { localStorage.setItem('launcher-selected-group', groupId); } else { localStorage.removeItem('launcher-selected-group'); } if (!groupId) { this.scripts = []; this.markdownFiles = []; this.renderScripts(); this.renderMarkdownFiles(); this.updateManageScriptsButton(false); this.updateEditorButtons(false); return; } // Cargar scripts (parte crítica) try { const response = await fetch(`/api/launcher-scripts/${groupId}`); if (response.ok) { this.scripts = await response.json(); } else { console.error('Error loading scripts:', response.status, response.statusText); this.scripts = []; } } catch (error) { console.error('Error loading launcher scripts:', error); this.scripts = []; } // Cargar archivos markdown (parte opcional) try { const markdownResponse = await fetch(`/api/launcher-markdown/${groupId}`); if (markdownResponse.ok) { const markdownData = await markdownResponse.json(); this.markdownFiles = markdownData.files || []; } else { console.warn('No markdown files available for group:', groupId); this.markdownFiles = []; } } catch (error) { console.warn('Error loading markdown files (non-critical):', error); this.markdownFiles = []; } // Actualizar interfaz this.currentGroup = this.groups.find(g => g.id === groupId); this.updateGroupIcon(); this.renderScripts(); this.renderMarkdownFiles(); this.updateManageScriptsButton(this.scripts.length > 0); this.updateEditorButtons(true); // Usar la nueva función } updateManageScriptsButton(show) { const button = document.getElementById('manage-scripts-btn'); if (button) { button.style.display = show ? 'block' : 'none'; } } // Función actualizada para mostrar/ocultar todos los botones updateEditorButtons(show) { const vscodeButton = document.getElementById('vscode-launcher-btn'); const cursorButton = document.getElementById('cursor-launcher-btn'); const folderButton = document.getElementById('folder-launcher-btn'); const copyPathButton = document.getElementById('copy-path-launcher-btn'); if (vscodeButton) { vscodeButton.style.display = show ? 'block' : 'none'; } if (cursorButton) { cursorButton.style.display = show ? 'block' : 'none'; } if (folderButton) { folderButton.style.display = show ? 'block' : 'none'; } if (copyPathButton) { copyPathButton.style.display = show ? 'block' : 'none'; } } updateGroupIcon() { const iconElement = document.getElementById('selected-group-icon'); if (!iconElement || !this.currentGroup) return; // Intentar cargar icono personalizado const img = document.createElement('img'); img.src = `/api/group-icon/launcher/${this.currentGroup.id}`; img.className = 'w-6 h-6 rounded'; img.onerror = () => { // Fallback a icono por defecto iconElement.innerHTML = this.getDefaultIconForCategory(this.currentGroup.category); }; img.onload = () => { iconElement.innerHTML = ''; iconElement.appendChild(img); }; } getDefaultIconForCategory(category) { const icons = { 'Herramientas': '🔧', 'Análisis': '📊', 'Utilidades': '⚙️', 'Desarrollo': '💻', 'Visualización': '📈', 'Otros': '📁' }; return icons[category] || '📁'; } renderScripts() { const grid = document.getElementById('launcher-scripts-grid'); if (!grid) return; if (this.scripts.length === 0) { grid.innerHTML = `

No hay scripts disponibles

Selecciona un grupo o verifica que el directorio contenga archivos .py

`; return; } let filteredScripts = this.scripts; if (this.currentFilter !== 'all' && this.currentGroup) { // Filtrar solo si la categoría del grupo no coincide con el filtro const groupCategory = this.currentGroup.category; if (groupCategory !== this.currentFilter) { filteredScripts = []; } } grid.innerHTML = ''; filteredScripts.forEach(script => { const favoriteId = `${this.currentGroup.id}_${script.name}`; const isFavorite = this.favorites.has(favoriteId); const card = document.createElement('div'); card.className = `script-card ${isFavorite ? 'favorited' : ''}`; card.innerHTML = `

${script.display_name}

${script.description || 'Script: ' + script.name}

${this.currentGroup.category}
${script.long_description && script.long_description.trim() ? ` ` : ''}
`; grid.appendChild(card); }); } renderMarkdownFiles() { const container = document.getElementById('markdown-files-section'); if (!container) return; if (!this.currentGroup || this.markdownFiles.length === 0) { container.style.display = 'none'; return; } container.style.display = 'block'; const grid = document.getElementById('markdown-files-grid'); if (!grid) return; grid.innerHTML = ''; this.markdownFiles.forEach(file => { const card = document.createElement('div'); card.className = 'markdown-file-card bg-white border rounded-lg p-3 hover:shadow-md transition-shadow cursor-pointer'; card.innerHTML = `

${file.display_name}

📄 ${(file.size / 1024).toFixed(1)} KB ${file.level === 1 ? ' • Subdirectorio' : ''}

${this.getTimeAgo(file.modified)}
`; card.onclick = () => this.openMarkdownViewer(file.relative_path, file.display_name); grid.appendChild(card); }); } async openMarkdownViewer(relativePath, displayName) { if (!this.currentGroup) return; try { const response = await fetch(`/api/launcher-markdown-content/${this.currentGroup.id}/${encodeURIComponent(relativePath)}`); const result = await response.json(); if (result.status === 'success') { const modal = document.getElementById('markdown-viewer-modal'); const titleElement = document.getElementById('markdown-viewer-title'); const pathElement = document.getElementById('markdown-viewer-path'); const contentElement = document.getElementById('markdown-viewer-content'); if (modal && titleElement && contentElement) { titleElement.textContent = displayName; pathElement.textContent = `Archivo: ${relativePath}`; // Renderizar markdown const md = window.markdownit(); contentElement.innerHTML = md.render(result.content); modal.classList.remove('hidden'); } } else { alert(`Error: ${result.message}`); } } catch (error) { console.error('Error loading markdown file:', error); alert('Error cargando archivo Markdown'); } } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } async showDescriptionModal(scriptName, displayName) { if (!this.currentGroup) return; try { // Cargar metadatos del script para obtener la descripción larga const response = await fetch(`/api/launcher-script-metadata/${this.currentGroup.id}/${scriptName}`); const metadata = await response.json(); const modal = document.getElementById('script-description-modal'); const scriptNameElement = document.getElementById('desc-modal-script-name'); const scriptFileElement = document.getElementById('desc-modal-script-file'); const contentElement = document.getElementById('script-description-content'); if (modal && scriptNameElement && contentElement) { scriptNameElement.textContent = displayName; scriptFileElement.textContent = `Archivo: ${scriptName}`; // Renderizar markdown if (metadata.long_description && metadata.long_description.trim()) { const md = window.markdownit(); contentElement.innerHTML = md.render(metadata.long_description); } else { contentElement.innerHTML = '

No hay descripción disponible para este script.

'; } modal.classList.remove('hidden'); } } catch (error) { console.error('Error loading script description:', error); alert('Error cargando la descripción del script'); } } // === GESTIÓN DE SCRIPTS INDIVIDUALES === async openScriptManager() { if (!this.currentGroup) { alert('Selecciona un grupo primero'); return; } const modal = document.getElementById('script-manager-modal'); const groupInfo = document.getElementById('script-manager-group-info'); if (modal && groupInfo) { groupInfo.textContent = `Grupo: ${this.currentGroup.name} (${this.currentGroup.category})`; await this.loadAllScriptsForManagement(); modal.classList.remove('hidden'); } } closeScriptManager() { const modal = document.getElementById('script-manager-modal'); if (modal) { modal.classList.add('hidden'); } } async loadAllScriptsForManagement() { if (!this.currentGroup) return; try { const response = await fetch(`/api/launcher-scripts-all/${this.currentGroup.id}`); const allScripts = await response.json(); this.renderScriptManagerList(allScripts); } catch (error) { console.error('Error loading all scripts for management:', error); } } renderScriptManagerList(scripts) { const list = document.getElementById('script-manager-list'); if (!list) return; if (scripts.length === 0) { list.innerHTML = `

No hay scripts Python en este directorio

`; return; } list.innerHTML = ''; scripts.forEach(script => { const item = document.createElement('div'); item.className = `border rounded-lg p-4 ${script.hidden ? 'bg-gray-50 border-gray-300' : 'bg-white border-gray-200'}`; item.innerHTML = `

${script.display_name}

${script.hidden ? 'Oculto' : ''}

${script.description || 'Sin descripción'}

Archivo: ${script.name}

`; list.appendChild(item); }); } async editScriptMetadata(scriptName) { if (!this.currentGroup) return; try { const response = await fetch(`/api/launcher-script-metadata/${this.currentGroup.id}/${scriptName}`); const metadata = await response.json(); // Poblar el formulario document.getElementById('edit-meta-group-id').value = this.currentGroup.id; document.getElementById('edit-meta-script-name').value = scriptName; document.getElementById('edit-meta-filename-display').textContent = scriptName; document.getElementById('edit-meta-display-name').value = metadata.display_name || scriptName.replace('.py', ''); document.getElementById('edit-meta-description').value = metadata.description || ''; document.getElementById('edit-meta-long-description').value = metadata.long_description || ''; document.getElementById('edit-meta-hidden').checked = metadata.hidden || false; // Mostrar modal document.getElementById('script-metadata-editor-modal').classList.remove('hidden'); } catch (error) { console.error('Error loading script metadata:', error); alert('Error cargando metadatos del script'); } } closeScriptMetadataEditor() { document.getElementById('script-metadata-editor-modal').classList.add('hidden'); } async saveScriptMetadata() { const groupId = document.getElementById('edit-meta-group-id').value; const scriptName = document.getElementById('edit-meta-script-name').value; const metadata = { display_name: document.getElementById('edit-meta-display-name').value, description: document.getElementById('edit-meta-description').value, long_description: document.getElementById('edit-meta-long-description').value, hidden: document.getElementById('edit-meta-hidden').checked }; try { const response = await fetch(`/api/launcher-script-metadata/${groupId}/${scriptName}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(metadata) }); const result = await response.json(); if (result.status === 'success') { this.closeScriptMetadataEditor(); // Recargar datos await this.loadAllScriptsForManagement(); await this.loadLauncherScripts(); // Actualizar la vista principal también } else { alert(`Error: ${result.message}`); } } catch (error) { console.error('Error saving script metadata:', error); alert('Error guardando metadatos del script'); } } // === GESTIÓN DE GRUPOS (actualizada) === populateGroupForm(group) { document.getElementById('group-id').value = group.id; document.getElementById('group-name').value = group.name; document.getElementById('group-description').value = group.description || ''; document.getElementById('group-category').value = group.category; document.getElementById('group-version').value = group.version || '1.0'; document.getElementById('group-python-env').value = group.python_env || 'base'; document.getElementById('group-directory').value = group.directory; } clearGroupForm() { document.getElementById('group-id').value = ''; document.getElementById('group-name').value = ''; document.getElementById('group-description').value = ''; document.getElementById('group-category').value = 'Otros'; document.getElementById('group-version').value = '1.0'; document.getElementById('group-python-env').value = 'base'; document.getElementById('group-directory').value = ''; document.getElementById('delete-group-btn').style.display = 'none'; } async saveGroup() { const formData = { id: document.getElementById('group-id').value, name: document.getElementById('group-name').value, description: document.getElementById('group-description').value, category: document.getElementById('group-category').value, version: document.getElementById('group-version').value, python_env: document.getElementById('group-python-env').value, directory: document.getElementById('group-directory').value }; try { let response; if (this.currentEditingGroup) { // Actualizar grupo existente response = await fetch(`/api/launcher-groups/${this.currentEditingGroup.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formData) }); } else { // Crear nuevo grupo response = await fetch('/api/launcher-groups', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formData) }); } const result = await response.json(); if (result.status === 'success') { await this.loadGroups(); this.closeGroupEditor(); this.renderInterface(); } else { alert(`Error: ${result.message}`); } } catch (error) { console.error('Error saving group:', error); alert('Error al guardar el grupo'); } } renderExistingGroups() { const container = document.getElementById('existing-groups-list'); if (!container) return; container.innerHTML = ''; this.groups.forEach(group => { const envInfo = this.pythonEnvs.find(env => env.name === group.python_env); const envDisplay = envInfo ? envInfo.display_name : group.python_env; const item = document.createElement('div'); item.className = 'group-list-item'; item.innerHTML = `
${this.getDefaultIconForCategory(group.category)}
${group.name}
${group.category} • ${envDisplay}
`; container.appendChild(item); }); } renderHistory() { const historyList = document.getElementById('history-list'); if (!historyList) return; if (this.history.length === 0) { historyList.innerHTML = `

No hay ejecuciones recientes

`; return; } historyList.innerHTML = ''; this.history.slice(0, 10).forEach(entry => { const group = this.groups.find(g => g.id === entry.group_id); const groupName = group ? group.name : 'Grupo desconocido'; const timeAgo = this.getTimeAgo(entry.executed_date); const statusClass = entry.status === 'success' ? 'success' : entry.status === 'error' ? 'error' : 'running'; // Iconos y mensajes más descriptivos por status let statusIcon, statusText; switch (entry.status) { case 'success': statusIcon = '✅'; statusText = 'Completado'; break; case 'error': statusIcon = '❌'; statusText = 'Error'; break; case 'running': statusIcon = '🔄'; statusText = 'En ejecución (GUI activa)'; break; default: statusIcon = '❓'; statusText = entry.status; } // Información del entorno Python y ejecutable const envInfo = entry.python_env ? ` • ${entry.python_env}` : ''; const executableType = entry.executable_type || 'python.exe'; const executableIcon = executableType.includes('pythonw') ? '🚀' : '🖥️'; // Botones para procesos en ejecución const processButtons = entry.status === 'running' && entry.pid ? `
` : ''; const item = document.createElement('div'); item.className = `history-item ${statusClass}`; item.innerHTML = `
${entry.script_name.replace('.py', '')} ${groupName}${envInfo}
${timeAgo}
${statusIcon} ${statusText} ${entry.execution_time ? ` • ${entry.execution_time.toFixed(1)}s` : ''} ${entry.arguments && entry.arguments.length > 0 ? ` • Con argumentos` : ''}
${executableIcon} ${executableType}${entry.working_directory ? ` • ${entry.working_directory}` : ''}
${processButtons} `; historyList.appendChild(item); }); } // ... métodos existentes sin cambios ... // El resto de métodos permanecen igual filterByCategory(category) { this.currentFilter = category; // Actualizar botones activos document.querySelectorAll('.category-btn').forEach(btn => { btn.classList.remove('active'); }); document.querySelector(`[data-category="${category}"]`).classList.add('active'); // Filtrar grupos en el selector const selector = document.getElementById('launcher-group-select'); Array.from(selector.options).forEach(option => { if (option.value === '') return; option.style.display = (category === 'all' || option.dataset.category === category) ? '' : 'none'; }); // Re-renderizar scripts si hay grupo seleccionado if (this.currentGroup) { this.renderScripts(); } } async toggleFavorite(groupId, scriptName) { try { const response = await fetch('/api/launcher-favorites', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ group_id: groupId, script_name: scriptName }) }); const result = await response.json(); if (result.status === 'success') { const favoriteId = `${groupId}_${scriptName}`; if (result.action === 'added') { this.favorites.add(favoriteId); } else { this.favorites.delete(favoriteId); } // Recargar datos y re-renderizar await this.loadFavorites(); this.renderScripts(); this.updateFavoritesCount(); } } catch (error) { console.error('Error toggling favorite:', error); } } showArgsModal(scriptName, displayName) { const modal = document.getElementById('script-args-modal'); const scriptDisplayElement = document.getElementById('script-display-name'); const argsInput = document.getElementById('script-args-input'); const workingDirInput = document.getElementById('script-working-dir'); if (modal && scriptDisplayElement && argsInput) { scriptDisplayElement.textContent = displayName; argsInput.value = ''; workingDirInput.value = ''; // Limpiar directorio de trabajo modal.classList.remove('hidden'); // Guardar datos para uso posterior modal.dataset.scriptName = scriptName; modal.dataset.groupId = this.currentGroup.id; } } async executeScript(scriptName, args = [], workingDir = null, usePythonw = false) { if (!this.currentGroup) return; try { const requestData = { group_id: this.currentGroup.id, script_name: scriptName, args: args, use_pythonw: usePythonw }; // Agregar directorio de trabajo si se especifica if (workingDir && workingDir.trim()) { requestData.working_dir = workingDir.trim(); } const response = await fetch('/api/execute-gui-script', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(requestData) }); const result = await response.json(); if (result.status === 'success') { // Recargar historial await this.loadHistory(); } } catch (error) { console.error('Error executing script:', error); } } // Función para buscar directorio de trabajo browseWorkingDirectory() { fetch('/api/browse-directories') .then(response => response.json()) .then(data => { if (data.status === 'success') { document.getElementById('script-working-dir').value = data.path; } }) .catch(error => { console.error('Error browsing directory:', error); }); } async renderFavorites(favorites) { const favoritesList = document.getElementById('favorites-list'); const favoritesPanel = document.getElementById('favorites-panel'); if (!favoritesList || !favoritesPanel) return; if (favorites.length === 0) { favoritesPanel.classList.add('empty'); return; } favoritesPanel.classList.remove('empty'); favoritesList.innerHTML = ''; // Cambiar a diseño de grid para cards más compactas favoritesList.className = 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3'; // Obtener metadatos de todos los scripts favoritos for (const fav of favorites.slice(0, 6)) { // Mostrar hasta 6 favoritos en grid const group = this.groups.find(g => g.id === fav.group_id); if (!group) continue; try { // Obtener metadatos del script para mostrar el display_name const response = await fetch(`/api/launcher-script-metadata/${fav.group_id}/${fav.script_name}`); const metadata = await response.json(); const displayName = metadata.display_name || fav.script_name.replace('.py', ''); const card = document.createElement('div'); card.className = 'bg-white border border-yellow-300 rounded-lg p-3 hover:shadow-md transition-all duration-200 hover:border-yellow-400'; // Crear contenedor para el icono que se actualizará dinámicamente const iconId = `fav-icon-${fav.group_id}`; card.innerHTML = `
${this.getDefaultIconForCategory(group.category)}
${displayName}
${group.name}
`; favoritesList.appendChild(card); // Intentar cargar el icono personalizado del grupo después de agregar la card this.loadGroupIconForFavorite(iconId, fav.group_id, group.category); } catch (error) { console.error('Error loading script metadata for favorite:', error); // Fallback al nombre del archivo const card = document.createElement('div'); card.className = 'bg-white border border-yellow-300 rounded-lg p-3 hover:shadow-md transition-all duration-200 hover:border-yellow-400'; const iconId = `fav-icon-${fav.group_id}`; card.innerHTML = `
${this.getDefaultIconForCategory(group.category)}
${fav.script_name.replace('.py', '')}
${group.name}
`; favoritesList.appendChild(card); // Intentar cargar el icono personalizado del grupo this.loadGroupIconForFavorite(iconId, fav.group_id, group.category); } } } // Nuevo método para cargar iconos específicos de grupo en favoritos loadGroupIconForFavorite(iconElementId, groupId, fallbackCategory) { const iconElement = document.getElementById(iconElementId); if (!iconElement) return; // Intentar cargar icono personalizado const img = document.createElement('img'); img.src = `/api/group-icon/launcher/${groupId}`; img.className = 'w-5 h-5 rounded object-cover'; img.onerror = () => { // Fallback a icono por defecto basado en categoría iconElement.innerHTML = this.getDefaultIconForCategory(fallbackCategory); iconElement.className = 'group-icon-small default mr-2 flex-shrink-0'; }; img.onload = () => { // Reemplazar con imagen personalizada iconElement.innerHTML = ''; iconElement.className = 'mr-2 flex-shrink-0 flex items-center justify-center'; iconElement.appendChild(img); }; } // Nuevo método para ejecutar favoritos en modo silencioso async executeFavoriteScriptSilent(groupId, scriptName) { // Cambiar al grupo correcto si no está seleccionado if (!this.currentGroup || this.currentGroup.id !== groupId) { document.getElementById('launcher-group-select').value = groupId; localStorage.setItem('launcher-selected-group', groupId); await this.loadLauncherScripts(); } this.executeScript(scriptName, [], null, true); // true para usar pythonw } async executeFavoriteScript(groupId, scriptName) { // Cambiar al grupo correcto si no está seleccionado if (!this.currentGroup || this.currentGroup.id !== groupId) { document.getElementById('launcher-group-select').value = groupId; localStorage.setItem('launcher-selected-group', groupId); await this.loadLauncherScripts(); } this.executeScript(scriptName); } getTimeAgo(dateString) { const date = new Date(dateString); const now = new Date(); const diffMs = now - date; const diffMinutes = Math.floor(diffMs / 60000); if (diffMinutes < 1) return 'ahora'; if (diffMinutes < 60) return `hace ${diffMinutes}m`; if (diffMinutes < 1440) return `hace ${Math.floor(diffMinutes / 60)}h`; return `hace ${Math.floor(diffMinutes / 1440)}d`; } updateFavoritesCount() { const countElement = document.getElementById('favorites-count'); if (countElement) { countElement.textContent = `${this.favorites.size} favoritos`; } } async clearLauncherHistory() { // Eliminada la confirmación - directamente procede a limpiar try { const response = await fetch('/api/launcher-history', { method: 'DELETE' }); const result = await response.json(); if (result.status === 'success') { this.history = []; this.renderHistory(); console.log('Historial del launcher limpiado'); } } catch (error) { console.error('Error clearing history:', error); } } // === GESTIÓN DE GRUPOS === openGroupEditor() { const modal = document.getElementById('group-editor-modal'); if (modal) { this.currentEditingGroup = null; this.clearGroupForm(); this.renderExistingGroups(); modal.classList.remove('hidden'); } } closeGroupEditor() { const modal = document.getElementById('group-editor-modal'); if (modal) { modal.classList.add('hidden'); this.currentEditingGroup = null; } } editGroup(groupId) { const group = this.groups.find(g => g.id === groupId); if (!group) return; this.currentEditingGroup = group; this.populateGroupForm(group); document.getElementById('delete-group-btn').style.display = 'block'; } async deleteGroup() { if (!this.currentEditingGroup) return; if (!confirm(`¿Estás seguro de que quieres eliminar el grupo "${this.currentEditingGroup.name}"?`)) return; try { const response = await fetch(`/api/launcher-groups/${this.currentEditingGroup.id}`, { method: 'DELETE' }); const result = await response.json(); if (result.status === 'success') { await this.loadGroups(); this.closeGroupEditor(); this.renderInterface(); // Limpiar selección si era el grupo actual if (this.currentGroup && this.currentGroup.id === this.currentEditingGroup.id) { document.getElementById('launcher-group-select').value = ''; this.currentGroup = null; this.scripts = []; this.renderScripts(); this.updateManageScriptsButton(false); this.updateEditorButtons(false); // Ocultar botones de editor } } else { alert(`Error: ${result.message}`); } } catch (error) { console.error('Error deleting group:', error); alert('Error al eliminar el grupo'); } } browseGroupDirectory() { // Similar a la función existente pero para el formulario de grupos fetch('/api/browse-directories') .then(response => response.json()) .then(data => { if (data.status === 'success') { document.getElementById('group-directory').value = data.path; } }) .catch(error => { console.error('Error browsing directory:', error); }); } // Nueva función genérica para abrir en editor async openGroupInEditor(editorCode, groupSystem, groupId) { if (!groupId) { alert('Selecciona un grupo primero'); return; } const editorName = editorCode.toUpperCase(); try { const response = await fetch(`/api/open-editor/${editorCode}/${groupSystem}/${groupId}`, { method: 'POST' }); if (!response.ok) { // If response is not OK, it might not be JSON (e.g., Flask error page) const errorText = await response.text(); console.error(`Error al abrir ${editorName}: HTTP ${response.status}`, errorText); alert(`Error al abrir ${editorName}: ${response.status} ${response.statusText}\n${errorText.substring(0, 200)}...`); // Show limited error text } else { const result = await response.json(); // Now it's safer to parse JSON if (result.status === 'success') { console.log(result.message); // Opcional: mostrar un toast/notificación de éxito // showToast(result.message, 'success'); } else { console.error(`Error al abrir ${editorName}:`, result.message); alert(`Error: ${result.message || 'Error desconocido.'}`); } } } catch (error) { console.error(`Error en la llamada fetch para open-editor (${editorName}):`, error); alert(`Error de red o del cliente al intentar abrir ${editorName}.`); } } async focusProcess(pid) { try { const response = await fetch(`/api/launcher-process-focus/${pid}`, { method: 'POST' }); const result = await response.json(); if (result.status === 'success') { console.log(result.message); } else { alert(`Error: ${result.message}`); } } catch (error) { console.error('Error focusing process:', error); alert('Error activando proceso'); } } async terminateProcess(pid) { // Eliminada la confirmación - directamente procede a cerrar try { const response = await fetch(`/api/launcher-process-terminate/${pid}`, { method: 'POST' }); const result = await response.json(); if (result.status === 'success') { console.log(result.message); // Recargar historial para actualizar estado await this.loadHistory(); } else { alert(`Error: ${result.message}`); } } catch (error) { console.error('Error terminating process:', error); alert('Error cerrando proceso'); } } // Nueva función para abrir carpeta del grupo launcher async openGroupFolder() { if (!this.currentGroup) { alert('Selecciona un grupo primero'); return; } try { const response = await fetch(`/api/open-group-folder/launcher/${this.currentGroup.id}`, { method: 'POST' }); const result = await response.json(); if (result.status === 'success') { console.log(`Carpeta abierta: ${result.path}`); } else { alert(`Error al abrir carpeta: ${result.message}`); } } catch (error) { console.error('Error opening folder:', error); alert('Error al comunicarse con el servidor'); } } // Nueva función para copiar path del grupo launcher async copyGroupPath() { if (!this.currentGroup) { alert('Selecciona un grupo primero'); return; } try { const response = await fetch(`/api/get-group-path/launcher/${this.currentGroup.id}`); const result = await response.json(); if (result.status === 'success') { // Copiar al portapapeles usando la API moderna if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(result.path); console.log('Path copiado al portapapeles'); } else { // Fallback para navegadores más antiguos const textArea = document.createElement('textarea'); textArea.value = result.path; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); console.log('Path copiado al portapapeles'); } catch (err) { console.error('Error copying to clipboard:', err); alert(`Error al copiar. Path: ${result.path}`); } document.body.removeChild(textArea); } } else { alert(`Error al obtener path: ${result.message}`); } } catch (error) { console.error('Error getting path:', error); alert('Error al comunicarse con el servidor'); } } } // === FUNCIONES GLOBALES === // Función para cambiar entre tabs function switchTab(tabName) { // Cambiar tabs activos document.querySelectorAll('.tab-button').forEach(btn => { btn.classList.remove('active'); }); document.getElementById(`${tabName}-tab`).classList.add('active'); // Cambiar contenido document.querySelectorAll('.tab-content').forEach(content => { content.classList.add('hidden'); }); document.getElementById(`${tabName}-content`).classList.remove('hidden'); // Inicializar launcher si es la primera vez if (tabName === 'launcher' && !window.launcherManager) { window.launcherManager = new LauncherManager(); window.launcherManager.init(); } // Inicializar C# launcher si es la primera vez if (tabName === 'csharp') { if (!window.csharpLauncherManager) { console.error('csharpLauncherManager not found! Make sure csharp_launcher.js is loaded.'); return; } if (!window.csharpLauncherManager.initialized) { window.csharpLauncherManager.init(); } } } // Funciones para modales function openGroupEditor() { if (window.launcherManager) { window.launcherManager.openGroupEditor(); } } function closeGroupEditor() { if (window.launcherManager) { window.launcherManager.closeGroupEditor(); } } function deleteGroup() { if (window.launcherManager) { window.launcherManager.deleteGroup(); } } function browseGroupDirectory() { if (window.launcherManager) { window.launcherManager.browseGroupDirectory(); } } function filterByCategory(category) { if (window.launcherManager) { window.launcherManager.filterByCategory(category); } } function loadLauncherScripts() { if (window.launcherManager) { window.launcherManager.loadLauncherScripts(); } } function clearLauncherHistory() { if (window.launcherManager) { window.launcherManager.clearLauncherHistory(); } } // Funciones para gestión de scripts function openScriptManager() { if (window.launcherManager) { window.launcherManager.openScriptManager(); } } function closeScriptManager() { if (window.launcherManager) { window.launcherManager.closeScriptManager(); } } function closeScriptMetadataEditor() { if (window.launcherManager) { window.launcherManager.closeScriptMetadataEditor(); } } // Funciones para modal de argumentos function closeArgsModal() { const modal = document.getElementById('script-args-modal'); if (modal) { modal.classList.add('hidden'); } } function executeWithArgs() { const modal = document.getElementById('script-args-modal'); const argsInput = document.getElementById('script-args-input'); const workingDirInput = document.getElementById('script-working-dir'); const executionTypeInputs = document.getElementsByName('execution-type'); if (modal && argsInput && window.launcherManager) { const scriptName = modal.dataset.scriptName; const args = argsInput.value.trim().split(/\s+/).filter(arg => arg.length > 0); const workingDir = workingDirInput.value.trim(); // Leer el tipo de ejecución seleccionado let usePythonw = false; for (const input of executionTypeInputs) { if (input.checked) { usePythonw = input.value === 'true'; break; } } window.launcherManager.executeScript(scriptName, args, workingDir, usePythonw); closeArgsModal(); } } function browseWorkingDirectory() { if (window.launcherManager) { window.launcherManager.browseWorkingDirectory(); } } // Funciones para modal de descripción function closeDescriptionModal() { const modal = document.getElementById('script-description-modal'); if (modal) { modal.classList.add('hidden'); } } function closeMarkdownViewer() { const modal = document.getElementById('markdown-viewer-modal'); if (modal) { modal.classList.add('hidden'); } } // Inicialización cuando se carga la página document.addEventListener('DOMContentLoaded', function () { console.log('Launcher JS loaded'); }); // Función para colapsar/expandir el historial function toggleHistoryPanel() { const historyList = document.getElementById('history-list'); const toggleIcon = document.getElementById('history-toggle-icon'); if (historyList && toggleIcon) { if (historyList.classList.contains('hidden')) { // Expandir historyList.classList.remove('hidden'); toggleIcon.textContent = '▼'; } else { // Colapsar historyList.classList.add('hidden'); toggleIcon.textContent = '▶'; } } }