ParamManagerScripts/static/js/csharp_launcher.js

1537 lines
61 KiB
JavaScript

// static/js/csharp_launcher.js - Gestor para Launcher C#
class CSharpLauncherManager {
constructor() {
this.currentProject = null;
this.projects = [];
this.executables = [];
this.favorites = new Set();
this.runningProcesses = [];
this.categories = [
'Aplicaciones', 'Herramientas', 'Análisis',
'Desarrollo', 'APIs', 'Otros'
];
this.currentCategory = 'all';
this.initialized = false;
this.processInterval = null;
}
async init() {
console.log('Initializing C# Launcher Manager...');
// Limpiar estado previo si existe
this.clearState();
await this.loadProjects();
await this.loadFavorites();
this.setupEventListeners();
this.renderInterface();
await this.refreshProcesses();
// Actualizar procesos cada 10 segundos
if (!this.processInterval) {
this.processInterval = setInterval(() => this.refreshProcesses(), 10000);
}
this.initialized = true;
console.log('C# Launcher Manager initialized successfully');
}
clearState() {
console.log('Clearing C# launcher state...');
this.currentProject = null;
this.projects = [];
this.executables = [];
this.favorites = new Set();
this.runningProcesses = [];
// Limpiar intervalos previos
if (this.processInterval) {
clearInterval(this.processInterval);
this.processInterval = null;
}
}
// Método de utilidad para resetear completamente el launcher
async reset() {
console.log('Resetting C# Launcher...');
localStorage.removeItem('csharp_last_selected_project');
this.clearState();
await this.init();
}
async loadProjects() {
try {
const response = await fetch('/api/csharp-projects');
if (response.ok) {
this.projects = await response.json();
this.renderProjectSelector();
} else {
console.error('Error loading C# projects:', await response.text());
}
} catch (error) {
console.error('Error loading C# projects:', error);
}
}
async loadFavorites() {
try {
const response = await fetch('/api/csharp-favorites');
if (response.ok) {
const data = await response.json();
this.favorites = new Set(data.favorites.map(fav => `${fav.project_id}_${fav.exe_name}`));
this.renderFavoritesPanel();
} else {
console.error('Error loading C# favorites:', await response.text());
}
} catch (error) {
console.error('Error loading C# favorites:', error);
}
}
setupEventListeners() {
// Event listeners específicos del launcher C#
const projectSelect = document.getElementById('csharp-project-select');
if (projectSelect) {
projectSelect.addEventListener('change', (e) => this.onProjectChange(e));
}
}
renderInterface() {
this.renderProjectSelector();
this.renderCategoryFilter();
this.renderFavoritesPanel();
}
renderProjectSelector() {
const select = document.getElementById('csharp-project-select');
if (!select) {
console.warn('C# project select element not found');
return;
}
select.innerHTML = '<option value="">-- Seleccionar Proyecto --</option>';
this.projects.forEach(project => {
const option = document.createElement('option');
option.value = project.id;
option.textContent = project.name;
option.setAttribute('data-description', project.description || '');
option.setAttribute('data-category', project.category || 'Otros');
select.appendChild(option);
});
// Restaurar última selección con delay para asegurar que el DOM esté listo
setTimeout(() => this.restoreLastSelectedProject(), 100);
}
restoreLastSelectedProject() {
try {
const lastSelectedProject = localStorage.getItem('csharp_last_selected_project');
console.log('Restoring last selected project:', lastSelectedProject);
if (!lastSelectedProject) {
console.log('No last selected project found');
return;
}
const project = this.projects.find(p => p.id === lastSelectedProject);
if (!project) {
console.log('Last selected project not found in current projects list, clearing localStorage');
localStorage.removeItem('csharp_last_selected_project');
return;
}
const select = document.getElementById('csharp-project-select');
if (!select) {
console.warn('Project select element not found during restore');
return;
}
console.log('Restoring project:', project.name);
select.value = lastSelectedProject;
// Disparar evento change para cargar el proyecto
const changeEvent = new Event('change', { bubbles: true });
select.dispatchEvent(changeEvent);
} catch (error) {
console.error('Error restoring last selected project:', error);
// Limpiar localStorage corrupto
localStorage.removeItem('csharp_last_selected_project');
}
}
async onProjectChange(e) {
const projectId = e.target.value;
console.log('Project change event:', projectId);
if (!projectId) {
console.log('No project selected, clearing state');
this.currentProject = null;
this.hideCSharpProjectButtons();
this.clearExecutables();
// Limpiar persistencia
localStorage.removeItem('csharp_last_selected_project');
return;
}
try {
this.currentProject = this.projects.find(p => p.id === projectId);
if (!this.currentProject) {
console.error('Project not found:', projectId);
return;
}
console.log('Loading project:', this.currentProject.name);
this.showCSharpProjectButtons();
await this.loadProjectExecutables(projectId);
// Guardar selección para persistencia
localStorage.setItem('csharp_last_selected_project', projectId);
console.log('Project loaded successfully');
} catch (error) {
console.error('Error changing project:', error);
this.showNotification('Error cargando el proyecto', 'error');
}
}
showCSharpProjectButtons() {
const buttons = ['cursor-csharp-btn', 'vs2022-csharp-btn', 'folder-csharp-btn', 'copy-path-csharp-btn', 'manage-csharp-executables-btn'];
buttons.forEach(id => {
const btn = document.getElementById(id);
if (btn) btn.style.display = 'block';
});
}
hideCSharpProjectButtons() {
const buttons = ['cursor-csharp-btn', 'vs2022-csharp-btn', 'folder-csharp-btn', 'copy-path-csharp-btn', 'manage-csharp-executables-btn'];
buttons.forEach(id => {
const btn = document.getElementById(id);
if (btn) btn.style.display = 'none';
});
}
async loadProjectExecutables(projectId) {
try {
const response = await fetch(`/api/csharp-executables/${projectId}`);
if (response.ok) {
this.executables = await response.json();
this.renderExecutables();
} else {
console.error('Error loading executables:', await response.text());
this.executables = [];
this.renderExecutables();
}
} catch (error) {
console.error('Error loading executables:', error);
this.executables = [];
this.renderExecutables();
}
}
renderExecutables() {
const grid = document.getElementById('csharp-executables-grid');
if (!grid) return;
// Filtrar por categoría si no es 'all'
let filteredExecutables = this.executables;
if (this.currentCategory !== 'all' && this.currentProject) {
filteredExecutables = this.executables.filter(exe =>
this.currentProject.category === this.currentCategory
);
}
if (filteredExecutables.length === 0) {
grid.innerHTML = `
<div class="col-span-full text-center py-8 text-gray-500">
<div class="text-4xl mb-2">🔍</div>
<div>No se encontraron ejecutables en este proyecto</div>
<div class="text-sm mt-1">Busque en: bin/Release y bin/Debug</div>
</div>
`;
return;
}
grid.innerHTML = filteredExecutables.map(exe => this.createExecutableCard(exe)).join('');
// Cargar argumentos para cada ejecutable después de renderizar
filteredExecutables.forEach(exe => {
this.loadExecutableArgumentsForCard(this.currentProject.id, exe.filename);
});
}
createExecutableCard(exe) {
const favoriteKey = `${this.currentProject.id}_${exe.filename}`;
const isFavorite = this.favorites.has(favoriteKey);
const buildTypeBadge = exe.build_type === 'Release' ?
'<span class="text-xs bg-green-100 text-green-800 px-2 py-1 rounded">Release</span>' :
'<span class="text-xs bg-yellow-100 text-yellow-800 px-2 py-1 rounded">Debug</span>';
// Los argumentos se cargan después de renderizar
return `
<div class="executable-card bg-white border rounded-lg p-4 hover:shadow-md transition-shadow">
<div class="flex justify-between items-start mb-2">
<h4 class="font-medium text-sm">${exe.display_name}</h4>
<button class="favorite-star text-gray-300 hover:text-yellow-500 ${isFavorite ? 'text-yellow-500' : ''}"
onclick="csharpLauncherManager.toggleFavorite('${this.currentProject.id}', '${exe.filename}')">⭐</button>
</div>
<p class="text-xs text-gray-600 mb-2">${exe.short_description}</p>
<div class="flex justify-between items-center mb-3">
${buildTypeBadge}
<span class="text-xs text-gray-500">${exe.filename}</span>
</div>
<!-- Combo de argumentos -->
<div class="mb-3">
<select id="args-combo-${exe.filename.replace('.exe', '').replace(/[^a-zA-Z0-9]/g, '_')}"
class="w-full text-xs border border-gray-300 rounded px-2 py-1 bg-white">
<option value="">Sin argumentos</option>
<!-- Las opciones se cargan dinámicamente -->
</select>
</div>
<div class="flex justify-between items-center">
<div class="space-x-1">
<button class="text-blue-500 hover:underline text-xs"
onclick="showCSharpExecutableArgs('${this.currentProject.id}', '${exe.filename}', '${exe.display_name}')">
Personalizar
</button>
</div>
<button class="bg-blue-500 text-white px-3 py-1 rounded text-xs hover:bg-blue-600"
onclick="csharpLauncherManager.executeWithSelectedArgs('${this.currentProject.id}', '${exe.filename}')">
Ejecutar
</button>
</div>
</div>
`;
}
async executeExecutable(projectId, exeName, args = [], workingDir = null) {
try {
const response = await fetch('/api/execute-csharp-executable', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
project_id: projectId,
exe_name: exeName,
args: args,
working_dir: workingDir
})
});
const result = await response.json();
if (result.status === 'success') {
// Actualizar procesos después de un breve delay
setTimeout(() => this.refreshProcesses(), 1000);
}
return result;
} catch (error) {
console.error('Error executing executable:', error);
return { status: 'error', message: error.message };
}
}
async toggleFavorite(projectId, exeName) {
try {
const response = await fetch('/api/csharp-favorites', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
project_id: projectId,
exe_name: exeName
})
});
if (response.ok) {
const result = await response.json();
const favoriteKey = `${projectId}_${exeName}`;
if (result.is_favorite) {
this.favorites.add(favoriteKey);
} else {
this.favorites.delete(favoriteKey);
}
this.renderExecutables();
this.renderFavoritesPanel();
}
} catch (error) {
console.error('Error toggling favorite:', error);
}
}
renderFavoritesPanel() {
const panel = document.getElementById('csharp-favorites-list');
const counter = document.getElementById('csharp-favorites-count');
if (!panel || !counter) return;
counter.textContent = `${this.favorites.size} favoritos`;
if (this.favorites.size === 0) {
panel.innerHTML = '<div class="text-center text-gray-500 py-2">No hay favoritos guardados</div>';
return;
}
// Crear lista de favoritos (simplificada - solo mostrar nombres)
const favoriteItems = Array.from(this.favorites).map(key => {
const [projectId, exeName] = key.split('_', 2);
const project = this.projects.find(p => p.id === projectId);
const projectName = project ? project.name : 'Proyecto desconocido';
return `
<div class="flex justify-between items-center bg-white p-2 rounded border">
<div>
<span class="font-medium text-sm">${exeName}</span>
<span class="text-xs text-gray-500 ml-2">${projectName}</span>
</div>
<button class="text-blue-500 hover:text-blue-700 text-xs"
onclick="csharpLauncherManager.executeFavorite('${projectId}', '${exeName}')">
Ejecutar
</button>
</div>
`;
}).join('');
panel.innerHTML = favoriteItems;
}
async executeFavorite(projectId, exeName) {
// Cambiar al proyecto si no está seleccionado
if (!this.currentProject || this.currentProject.id !== projectId) {
const select = document.getElementById('csharp-project-select');
if (select) {
select.value = projectId;
await this.onProjectChange({ target: { value: projectId } });
}
}
// Ejecutar el ejecutable
await this.executeExecutable(projectId, exeName);
}
renderCategoryFilter() {
// Las categorías ya están en el HTML, solo necesitamos la funcionalidad
console.log('Category filter rendered');
}
filterByCategory(category) {
this.currentCategory = category;
// Actualizar botones activos
document.querySelectorAll('.csharp-category-btn').forEach(btn => {
btn.classList.remove('active');
if (btn.getAttribute('data-category') === category) {
btn.classList.add('active');
}
});
this.renderExecutables();
}
async refreshProcesses() {
try {
const response = await fetch('/api/csharp-running-processes');
if (response.ok) {
const data = await response.json();
this.runningProcesses = data.processes || [];
this.renderRunningProcesses();
}
} catch (error) {
console.error('Error refreshing processes:', error);
}
}
renderRunningProcesses() {
const container = document.getElementById('csharp-running-processes');
if (!container) return;
if (this.runningProcesses.length === 0) {
container.innerHTML = '<div class="text-center text-gray-500 py-2">No hay procesos C# en ejecución</div>';
return;
}
const processItems = this.runningProcesses.map(process => {
const startTime = new Date(process.start_time).toLocaleTimeString();
return `
<div class="flex justify-between items-center bg-gray-50 p-3 rounded border">
<div>
<span class="font-medium">${process.display_name}</span>
<span class="text-sm text-gray-500 block">PID: ${process.pid} | Iniciado: ${startTime}</span>
</div>
<button class="text-red-500 hover:text-red-700 text-sm"
onclick="csharpLauncherManager.terminateProcess(${process.pid})">
Cerrar
</button>
</div>
`;
}).join('');
container.innerHTML = processItems;
}
async terminateProcess(pid) {
try {
const response = await fetch(`/api/csharp-process-terminate/${pid}`, {
method: 'POST'
});
if (response.ok) {
await this.refreshProcesses();
}
} catch (error) {
console.error('Error terminating process:', error);
}
}
async loadExecutableArgumentsForCard(projectId, exeName) {
try {
const response = await fetch(`/api/csharp-executable-arguments/${projectId}/${exeName}`);
if (response.ok) {
const data = await response.json();
const argsList = data.arguments || [];
this.populateArgumentsCombo(exeName, argsList);
}
} catch (error) {
console.error('Error loading arguments for card:', error);
}
}
populateArgumentsCombo(exeName, argsList) {
const comboId = `args-combo-${exeName.replace('.exe', '').replace(/[^a-zA-Z0-9]/g, '_')}`;
const combo = document.getElementById(comboId);
if (!combo) return;
// Limpiar opciones existentes excepto "Sin argumentos"
combo.innerHTML = '<option value="">Sin argumentos</option>';
// Agregar argumentos predefinidos
argsList.forEach((arg, index) => {
const option = document.createElement('option');
option.value = arg.arguments;
option.textContent = arg.description;
option.title = arg.arguments; // Tooltip con los argumentos
combo.appendChild(option);
});
}
async executeWithSelectedArgs(projectId, exeName) {
const comboId = `args-combo-${exeName.replace('.exe', '').replace(/[^a-zA-Z0-9]/g, '_')}`;
const combo = document.getElementById(comboId);
const selectedArgs = combo ? combo.value : '';
if (selectedArgs) {
const argsArray = selectedArgs.split(' ').filter(arg => arg.length > 0);
await this.executeExecutable(projectId, exeName, argsArray);
} else {
await this.executeExecutable(projectId, exeName);
}
}
clearExecutables() {
const grid = document.getElementById('csharp-executables-grid');
if (grid) {
grid.innerHTML = '<div class="col-span-full text-center py-8 text-gray-500">Selecciona un proyecto para ver los ejecutables</div>';
}
}
// === GESTIÓN DE PROYECTOS ===
openProjectEditor() {
// Cargar proyectos existentes y mostrar modal
this.renderExistingProjects();
this.clearProjectForm();
const modal = document.getElementById('csharp-project-editor-modal');
if (modal) {
modal.classList.remove('hidden');
}
}
closeProjectEditor() {
const modal = document.getElementById('csharp-project-editor-modal');
if (modal) {
modal.classList.add('hidden');
}
}
clearProjectForm() {
const form = document.getElementById('csharp-project-form');
if (form) {
form.reset();
document.getElementById('csharp-project-id').value = '';
}
// Ocultar botón de eliminar cuando se crea nuevo proyecto
const deleteBtn = document.getElementById('delete-csharp-project-btn');
if (deleteBtn) {
deleteBtn.style.display = 'none';
}
}
populateProjectForm(project) {
document.getElementById('csharp-project-id').value = project.id || '';
document.getElementById('csharp-project-name').value = project.name || '';
document.getElementById('csharp-project-description').value = project.description || '';
document.getElementById('csharp-project-directory').value = project.directory || '';
document.getElementById('csharp-project-category').value = project.category || 'Otros';
document.getElementById('csharp-project-version').value = project.version || '1.0';
document.getElementById('csharp-project-author').value = project.author || '';
document.getElementById('csharp-project-dotnet-version').value = project.dotnet_version || '';
// Tags (convertir array a string)
const tagsInput = document.getElementById('csharp-project-tags');
if (tagsInput && project.tags) {
tagsInput.value = Array.isArray(project.tags) ? project.tags.join(', ') : project.tags;
}
// Mostrar botón de eliminar cuando se edita un proyecto existente
const deleteBtn = document.getElementById('delete-csharp-project-btn');
if (deleteBtn) {
deleteBtn.style.display = 'block';
}
}
async saveProject() {
const projectData = {
name: document.getElementById('csharp-project-name').value.trim(),
description: document.getElementById('csharp-project-description').value.trim(),
directory: document.getElementById('csharp-project-directory').value.trim(),
category: document.getElementById('csharp-project-category').value || 'Otros',
version: document.getElementById('csharp-project-version').value.trim() || '1.0',
author: document.getElementById('csharp-project-author').value.trim(),
dotnet_version: document.getElementById('csharp-project-dotnet-version').value.trim(),
tags: document.getElementById('csharp-project-tags').value.split(',').map(tag => tag.trim()).filter(tag => tag)
};
// Validación básica
if (!projectData.name) {
this.showNotification('El nombre del proyecto es requerido', 'error');
document.getElementById('csharp-project-name').focus();
return;
}
if (!projectData.directory) {
this.showNotification('El directorio del proyecto es requerido', 'error');
document.getElementById('csharp-project-directory').focus();
return;
}
try {
const projectId = document.getElementById('csharp-project-id').value;
const isEdit = projectId && projectId.trim() !== '';
let response;
if (isEdit) {
// Actualizar proyecto existente
response = await fetch(`/api/csharp-projects/${projectId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(projectData)
});
} else {
// Crear nuevo proyecto
response = await fetch('/api/csharp-projects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(projectData)
});
}
const result = await response.json();
if (response.ok && result.status === 'success') {
// Recargar proyectos y cerrar modal
await this.loadProjects();
this.closeProjectEditor();
this.showNotification(`Proyecto ${isEdit ? 'actualizado' : 'creado'} exitosamente`, 'success');
} else {
this.showNotification(`Error: ${result.message || 'Error desconocido'}`, 'error');
}
} catch (error) {
console.error('Error saving project:', error);
this.showNotification('Error al comunicarse con el servidor', 'error');
}
}
editProject(projectId) {
const project = this.projects.find(p => p.id === projectId);
if (project) {
this.populateProjectForm(project);
// No necesitamos abrir el modal aquí, ya debería estar abierto
}
}
async deleteProject() {
const projectId = document.getElementById('csharp-project-id').value;
if (!projectId) {
this.showNotification('No hay proyecto seleccionado para eliminar', 'error');
return;
}
const project = this.projects.find(p => p.id === projectId);
const projectName = project ? project.name : 'este proyecto';
if (!confirm(`¿Estás seguro de que quieres eliminar ${projectName}?`)) {
return;
}
try {
const response = await fetch(`/api/csharp-projects/${projectId}`, {
method: 'DELETE'
});
const result = await response.json();
if (response.ok && result.status === 'success') {
// Recargar proyectos y limpiar formulario
await this.loadProjects();
this.clearProjectForm();
this.showNotification('Proyecto eliminado exitosamente', 'success');
} else {
this.showNotification(`Error: ${result.message || 'Error desconocido'}`, 'error');
}
} catch (error) {
console.error('Error deleting project:', error);
this.showNotification('Error al comunicarse con el servidor', 'error');
}
}
renderExistingProjects() {
const container = document.getElementById('csharp-existing-projects');
if (!container) return;
if (this.projects.length === 0) {
container.innerHTML = '<div class="text-center text-gray-500 py-4">No hay proyectos C# configurados</div>';
return;
}
const projectItems = this.projects.map(project => {
const categoryInfo = this.getCategoryInfo(project.category);
return `
<div class="project-item border rounded-lg p-4 hover:bg-gray-50">
<div class="flex justify-between items-start">
<div class="flex-1">
<div class="flex items-center gap-2 mb-2">
<span class="text-lg">${categoryInfo.icon}</span>
<h4 class="font-bold">${project.name}</h4>
<span class="text-xs px-2 py-1 rounded-full" style="background-color: ${categoryInfo.color}20; color: ${categoryInfo.color}">
${project.category}
</span>
</div>
<p class="text-sm text-gray-600 mb-1">${project.description || 'Sin descripción'}</p>
<p class="text-xs text-gray-500">${project.directory}</p>
<div class="flex items-center gap-4 mt-2 text-xs text-gray-500">
<span>v${project.version}</span>
${project.dotnet_version ? `<span>.NET ${project.dotnet_version}</span>` : ''}
${project.author ? `<span>por ${project.author}</span>` : ''}
</div>
</div>
<div class="flex gap-2 ml-4">
<button class="text-blue-500 hover:text-blue-700 text-sm"
onclick="csharpLauncherManager.editProject('${project.id}')">
Editar
</button>
</div>
</div>
</div>
`;
}).join('');
container.innerHTML = projectItems;
}
getCategoryInfo(category) {
const categories = {
'Aplicaciones': { icon: '🖥️', color: '#3B82F6' },
'Herramientas': { icon: '🔧', color: '#10B981' },
'Análisis': { icon: '📊', color: '#8B5CF6' },
'Desarrollo': { icon: '💻', color: '#F59E0B' },
'APIs': { icon: '🌐', color: '#EF4444' },
'Otros': { icon: '📁', color: '#6B7280' }
};
return categories[category] || categories['Otros'];
}
showNotification(message, type = 'info') {
// Usar la función showToast global si está disponible, sino crear una simple
if (typeof showToast === 'function') {
showToast(message, type);
} else {
// Fallback simple
const bgColor = type === 'success' ? 'bg-green-500' :
type === 'error' ? 'bg-red-500' : 'bg-blue-500';
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 ${bgColor} text-white px-4 py-2 rounded-lg shadow-lg z-50`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
}
async browseProjectDirectory() {
try {
const currentPath = document.getElementById('csharp-project-directory').value;
const response = await fetch('/api/browse-directories', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
if (response.ok) {
const result = await response.json();
if (result.status === 'success' && result.path) {
document.getElementById('csharp-project-directory').value = result.path;
}
} else {
// Fallback a prompt si falla el navegador
const newPath = prompt('Ingresa el directorio del proyecto C#:', currentPath);
if (newPath !== null) {
document.getElementById('csharp-project-directory').value = newPath.trim();
}
}
} catch (error) {
console.error('Error browsing directory:', error);
// Fallback a prompt si hay error
const currentPath = document.getElementById('csharp-project-directory').value;
const newPath = prompt('Ingresa el directorio del proyecto C#:', currentPath);
if (newPath !== null) {
document.getElementById('csharp-project-directory').value = newPath.trim();
}
}
}
}
// Funciones globales para el HTML
function loadCSharpExecutables() {
const select = document.getElementById('csharp-project-select');
if (select && window.csharpLauncherManager) {
window.csharpLauncherManager.onProjectChange({ target: select });
}
}
function filterCSharpByCategory(category) {
if (window.csharpLauncherManager) {
window.csharpLauncherManager.filterByCategory(category);
}
}
function refreshCSharpProcesses() {
if (window.csharpLauncherManager) {
window.csharpLauncherManager.refreshProcesses();
}
}
function openCSharpProjectEditor() {
if (window.csharpLauncherManager) {
window.csharpLauncherManager.openProjectEditor();
}
}
async function openCSharpProjectInEditor(editor) {
if (!window.csharpLauncherManager?.currentProject) {
alert('Selecciona un proyecto primero');
return;
}
const projectId = window.csharpLauncherManager.currentProject.id;
try {
const response = await fetch(`/api/open-editor/${editor}/csharp/${projectId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const result = await response.json();
if (response.ok && result.status === 'success') {
const editorName = editor === 'cursor' ? 'Cursor' : 'Visual Studio 2022';
if (window.csharpLauncherManager && window.csharpLauncherManager.showNotification) {
window.csharpLauncherManager.showNotification(`${editorName} abierto exitosamente`, 'success');
} else {
alert(`${editorName} abierto exitosamente`);
}
} else {
const errorMsg = `Error: ${result.message}`;
if (window.csharpLauncherManager && window.csharpLauncherManager.showNotification) {
window.csharpLauncherManager.showNotification(errorMsg, 'error');
} else {
alert(errorMsg);
}
}
} catch (error) {
console.error(`Error opening ${editor}:`, error);
const errorMsg = 'Error al comunicarse con el servidor';
if (window.csharpLauncherManager && window.csharpLauncherManager.showNotification) {
window.csharpLauncherManager.showNotification(errorMsg, 'error');
} else {
alert(errorMsg);
}
}
}
async function openCSharpProjectFolder() {
if (!window.csharpLauncherManager?.currentProject) {
alert('Selecciona un proyecto primero');
return;
}
const projectId = window.csharpLauncherManager.currentProject.id;
try {
const response = await fetch(`/api/open-group-folder/csharp/${projectId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const result = await response.json();
if (response.ok && result.status === 'success') {
if (window.csharpLauncherManager && window.csharpLauncherManager.showNotification) {
window.csharpLauncherManager.showNotification('Explorador abierto exitosamente', 'success');
} else {
alert('Explorador abierto exitosamente');
}
} else {
const errorMsg = `Error: ${result.message}`;
if (window.csharpLauncherManager && window.csharpLauncherManager.showNotification) {
window.csharpLauncherManager.showNotification(errorMsg, 'error');
} else {
alert(errorMsg);
}
}
} catch (error) {
console.error('Error opening folder:', error);
const errorMsg = 'Error al comunicarse con el servidor';
if (window.csharpLauncherManager && window.csharpLauncherManager.showNotification) {
window.csharpLauncherManager.showNotification(errorMsg, 'error');
} else {
alert(errorMsg);
}
}
}
async function copyCSharpProjectPath() {
if (!window.csharpLauncherManager?.currentProject) {
alert('Selecciona un proyecto primero');
return;
}
const projectPath = window.csharpLauncherManager.currentProject.directory;
try {
await navigator.clipboard.writeText(projectPath);
if (window.csharpLauncherManager && window.csharpLauncherManager.showNotification) {
window.csharpLauncherManager.showNotification('Path copiado al portapapeles', 'success');
} else {
alert('Path copiado al portapapeles');
}
} catch (error) {
console.error('Error copying path:', error);
// Fallback para navegadores que no soportan clipboard API
const textArea = document.createElement('textarea');
textArea.value = projectPath;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
if (window.csharpLauncherManager && window.csharpLauncherManager.showNotification) {
window.csharpLauncherManager.showNotification('Path copiado al portapapeles', 'success');
} else {
alert('Path copiado al portapapeles');
}
} catch (fallbackError) {
if (window.csharpLauncherManager && window.csharpLauncherManager.showNotification) {
window.csharpLauncherManager.showNotification('Error al copiar path', 'error');
} else {
alert('Error al copiar path');
}
}
document.body.removeChild(textArea);
}
}
function openCSharpExecutableManager() {
// TODO: Implementar gestor de ejecutables
alert('Gestor de ejecutables C# - Por implementar');
}
// === FUNCIONES GLOBALES PARA GESTIÓN DE PROYECTOS ===
function closeCSharpProjectEditor() {
if (window.csharpLauncherManager) {
window.csharpLauncherManager.closeProjectEditor();
}
}
function saveCSharpProject() {
if (window.csharpLauncherManager) {
window.csharpLauncherManager.saveProject();
}
}
function deleteCSharpProject() {
if (window.csharpLauncherManager) {
window.csharpLauncherManager.deleteProject();
}
}
function browseCSharpProjectDirectory() {
if (window.csharpLauncherManager) {
window.csharpLauncherManager.browseProjectDirectory();
}
}
function openCSharpExecutableManager() {
if (!csharpLauncherManager.currentProject) {
alert('Selecciona un proyecto primero');
return;
}
const modal = document.getElementById('csharp-executable-manager');
if (modal) {
modal.style.display = 'block';
loadCSharpExecutableManager();
}
}
async function loadCSharpExecutableManager() {
if (!csharpLauncherManager.currentProject) return;
try {
const response = await fetch(`/api/csharp-all-executables/${csharpLauncherManager.currentProject.id}`);
if (response.ok) {
const executables = await response.json();
renderCSharpExecutableManager(executables);
} else {
console.error('Error loading all executables:', await response.text());
}
} catch (error) {
console.error('Error loading all executables:', error);
}
}
function renderCSharpExecutableManager(executables) {
const container = document.getElementById('csharp-executable-list');
if (!container) return;
if (executables.length === 0) {
container.innerHTML = `
<div class="text-center text-gray-500 py-8">
<div class="text-4xl mb-2">📝</div>
<div>No se encontraron ejecutables en este proyecto</div>
</div>
`;
return;
}
container.innerHTML = executables.map(exe => `
<div class="executable-item bg-white border rounded-lg p-4 mb-3">
<div class="flex justify-between items-start">
<div class="flex-1">
<div class="flex items-center gap-2 mb-2">
<h4 class="font-medium">${exe.display_name}</h4>
<span class="text-xs px-2 py-1 rounded ${exe.build_type === 'Release' ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'}">${exe.build_type}</span>
<span class="text-xs px-2 py-1 rounded ${exe.hidden ? 'bg-red-100 text-red-800' : 'bg-green-100 text-green-800'}">${exe.hidden ? 'Oculto' : 'Visible'}</span>
</div>
<p class="text-sm text-gray-600 mb-1">${exe.short_description}</p>
<p class="text-xs text-gray-500">${exe.filename}</p>
</div>
<div class="flex gap-2">
<button class="text-blue-500 hover:underline text-sm"
onclick="editCSharpExecutableMetadata('${csharpLauncherManager.currentProject.id}', '${exe.filename}')">
Editar
</button>
<button class="text-green-500 hover:underline text-sm"
onclick="editCSharpExecutableArguments('${csharpLauncherManager.currentProject.id}', '${exe.filename}')">
Argumentos
</button>
</div>
</div>
</div>
`).join('');
}
async function editCSharpExecutableMetadata(projectId, exeName) {
try {
const response = await fetch(`/api/csharp-executable-metadata/${projectId}/${exeName}`);
const metadata = response.ok ? await response.json() : {};
showCSharpExecutableMetadataEditor(projectId, exeName, metadata);
} catch (error) {
console.error('Error loading executable metadata:', error);
}
}
function showCSharpExecutableMetadataEditor(projectId, exeName, metadata) {
const modalHtml = `
<div id="csharp-metadata-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white rounded-lg p-6 w-full max-w-2xl max-h-[90vh] overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Editar Ejecutable: ${exeName}</h3>
<button onclick="closeCSharpMetadataEditor()" class="text-gray-500 hover:text-gray-700">&times;</button>
</div>
<form id="csharp-metadata-form" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Nombre para mostrar</label>
<input type="text" id="metadata-display-name"
value="${metadata.display_name || exeName.replace('.exe', '')}"
class="w-full border border-gray-300 rounded-md px-3 py-2">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Descripción corta</label>
<input type="text" id="metadata-short-description"
value="${metadata.short_description || 'Aplicación C#'}"
class="w-full border border-gray-300 rounded-md px-3 py-2">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Descripción larga</label>
<textarea id="metadata-long-description" rows="3"
class="w-full border border-gray-300 rounded-md px-3 py-2">${metadata.long_description || ''}</textarea>
</div>
<div class="flex items-center">
<input type="checkbox" id="metadata-hidden" ${metadata.hidden ? 'checked' : ''}
class="h-4 w-4 text-blue-600 border-gray-300 rounded">
<label for="metadata-hidden" class="ml-2 text-sm text-gray-700">Ocultar ejecutable</label>
</div>
<div class="flex justify-end space-x-3 pt-4">
<button type="button" onclick="closeCSharpMetadataEditor()"
class="px-4 py-2 text-gray-600 border border-gray-300 rounded-md hover:bg-gray-50">
Cancelar
</button>
<button type="submit"
class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600">
Guardar
</button>
</div>
</form>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
document.getElementById('csharp-metadata-form').onsubmit = async (e) => {
e.preventDefault();
await saveCSharpExecutableMetadata(projectId, exeName);
};
}
async function saveCSharpExecutableMetadata(projectId, exeName) {
const metadata = {
display_name: document.getElementById('metadata-display-name').value,
short_description: document.getElementById('metadata-short-description').value,
long_description: document.getElementById('metadata-long-description').value,
hidden: document.getElementById('metadata-hidden').checked
};
try {
const response = await fetch(`/api/csharp-executable-metadata/${projectId}/${exeName}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(metadata)
});
const result = await response.json();
if (result.status === 'success') {
csharpLauncherManager.showNotification(result.message, 'success');
closeCSharpMetadataEditor();
await loadCSharpExecutableManager();
await csharpLauncherManager.loadProjectExecutables(projectId);
} else {
csharpLauncherManager.showNotification(result.message, 'error');
}
} catch (error) {
csharpLauncherManager.showNotification('Error guardando metadatos', 'error');
console.error('Error saving metadata:', error);
}
}
function closeCSharpMetadataEditor() {
const modal = document.getElementById('csharp-metadata-modal');
if (modal) modal.remove();
}
async function editCSharpExecutableArguments(projectId, exeName) {
try {
const response = await fetch(`/api/csharp-executable-arguments/${projectId}/${exeName}`);
const data = response.ok ? await response.json() : { arguments: [] };
showCSharpArgumentsEditor(projectId, exeName, data.arguments);
} catch (error) {
console.error('Error loading executable arguments:', error);
}
}
function showCSharpArgumentsEditor(projectId, exeName, argsList) {
const modalHtml = `
<div id="csharp-arguments-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white rounded-lg p-6 w-full max-w-4xl max-h-[90vh] overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Argumentos de: ${exeName}</h3>
<button onclick="closeCSharpArgumentsEditor()" class="text-gray-500 hover:text-gray-700">&times;</button>
</div>
<div class="mb-4">
<button onclick="addCSharpArgument()"
class="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600">
+ Agregar Argumento
</button>
</div>
<div id="csharp-arguments-list" class="space-y-3 mb-4">
<!-- Arguments will be rendered here -->
</div>
<div class="flex justify-end space-x-3 pt-4 border-t">
<button onclick="closeCSharpArgumentsEditor()"
class="px-4 py-2 text-gray-600 border border-gray-300 rounded-md hover:bg-gray-50">
Cancelar
</button>
<button onclick="saveCSharpExecutableArguments('${projectId}', '${exeName}')"
class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600">
Guardar
</button>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
renderCSharpArguments(argsList);
}
function renderCSharpArguments(argsList) {
const container = document.getElementById('csharp-arguments-list');
if (!container) return;
container.innerHTML = argsList.map((arg, index) => `
<div class="argument-item bg-gray-50 p-4 rounded-lg" data-index="${index}">
<div class="grid grid-cols-12 gap-3 items-center">
<div class="col-span-4">
<input type="text" placeholder="Descripción (ej: Modo debug)"
value="${arg.description || ''}"
class="w-full border border-gray-300 rounded-md px-3 py-2 text-sm arg-description">
</div>
<div class="col-span-7">
<input type="text" placeholder="Argumentos (ej: --debug --verbose)"
value="${arg.arguments || ''}"
class="w-full border border-gray-300 rounded-md px-3 py-2 text-sm arg-value">
</div>
<div class="col-span-1">
<button onclick="removeCSharpArgument(${index})"
class="text-red-500 hover:text-red-700 p-1">
🗑️
</button>
</div>
</div>
</div>
`).join('');
if (argsList.length === 0) {
container.innerHTML = `
<div class="text-center text-gray-500 py-8">
<div class="text-2xl mb-2">⚙️</div>
<div>No hay argumentos configurados</div>
<div class="text-sm">Usa el botón "Agregar Argumento" para crear opciones predefinidas</div>
</div>
`;
}
}
function addCSharpArgument() {
const container = document.getElementById('csharp-arguments-list');
if (!container) return;
const currentArguments = collectCSharpArguments();
currentArguments.push({ description: '', arguments: '' });
renderCSharpArguments(currentArguments);
}
function removeCSharpArgument(index) {
const currentArguments = collectCSharpArguments();
currentArguments.splice(index, 1);
renderCSharpArguments(currentArguments);
}
function collectCSharpArguments() {
const argumentItems = document.querySelectorAll('.argument-item');
const argsList = [];
argumentItems.forEach(item => {
const description = item.querySelector('.arg-description').value.trim();
const argumentValue = item.querySelector('.arg-value').value.trim();
if (description && argumentValue) {
argsList.push({ description, arguments: argumentValue });
}
});
return argsList;
}
async function saveCSharpExecutableArguments(projectId, exeName) {
const argsList = collectCSharpArguments();
try {
const response = await fetch(`/api/csharp-executable-arguments/${projectId}/${exeName}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ arguments: argsList })
});
const result = await response.json();
if (result.status === 'success') {
csharpLauncherManager.showNotification(result.message, 'success');
closeCSharpArgumentsEditor();
// Recargar la lista de ejecutables si está abierto el gestor
if (document.getElementById('csharp-executable-manager').style.display !== 'none') {
await loadCSharpExecutableManager();
}
// Recargar argumentos en las cards actuales
if (csharpLauncherManager.currentProject) {
csharpLauncherManager.loadExecutableArgumentsForCard(projectId, exeName);
}
} else {
csharpLauncherManager.showNotification(result.message, 'error');
}
} catch (error) {
csharpLauncherManager.showNotification('Error guardando argumentos', 'error');
console.error('Error saving arguments:', error);
}
}
function closeCSharpArgumentsEditor() {
const modal = document.getElementById('csharp-arguments-modal');
if (modal) modal.remove();
}
async function showCSharpExecutableArgs(projectId, exeName, displayName) {
try {
// Cargar argumentos predefinidos
const response = await fetch(`/api/csharp-executable-arguments/${projectId}/${exeName}`);
const data = response.ok ? await response.json() : { arguments: [] };
const predefinedArgs = data.arguments || [];
showCSharpExecutableArgsModal(projectId, exeName, displayName, predefinedArgs);
} catch (error) {
console.error('Error loading predefined arguments:', error);
showCSharpExecutableArgsModal(projectId, exeName, displayName, []);
}
}
function showCSharpExecutableArgsModal(projectId, exeName, displayName, predefinedArgs) {
const predefinedSection = predefinedArgs.length > 0 ? `
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">Argumentos predefinidos:</label>
<div class="space-y-2">
${predefinedArgs.map((arg, index) => `
<button type="button"
onclick="selectPredefinedCSharpArguments('${arg.arguments.replace(/'/g, "\\'")}', '${arg.description}')"
class="w-full text-left p-3 border border-gray-300 rounded-lg hover:bg-gray-50">
<div class="font-medium text-sm">${arg.description}</div>
<div class="text-xs text-gray-600 mt-1">${arg.arguments}</div>
</button>
`).join('')}
</div>
</div>
` : '';
const modalHtml = `
<div id="csharp-args-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white rounded-lg p-6 w-full max-w-md">
<h3 class="text-lg font-semibold mb-4">Ejecutar: ${displayName}</h3>
${predefinedSection}
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">Argumentos personalizados:</label>
<input type="text" id="csharp-custom-args" placeholder="--debug --verbose"
class="w-full border border-gray-300 rounded-md px-3 py-2">
</div>
<div class="flex justify-end space-x-3">
<button onclick="closeCSharpArgsModal()"
class="px-4 py-2 text-gray-600 border border-gray-300 rounded-md hover:bg-gray-50">
Cancelar
</button>
<button onclick="executeCSharpWithArgs('${projectId}', '${exeName}')"
class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600">
Ejecutar
</button>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
}
function selectPredefinedCSharpArguments(args, description) {
const input = document.getElementById('csharp-custom-args');
if (input) {
input.value = args;
}
}
async function executeCSharpWithArgs(projectId, exeName) {
const argsInput = document.getElementById('csharp-custom-args');
const argsString = argsInput ? argsInput.value.trim() : '';
const args = argsString ? argsString.split(' ').filter(arg => arg.length > 0) : [];
closeCSharpArgsModal();
await csharpLauncherManager.executeExecutable(projectId, exeName, args);
}
function closeCSharpArgsModal() {
const modal = document.getElementById('csharp-args-modal');
if (modal) modal.remove();
}
function closeCSharpExecutableManager() {
const modal = document.getElementById('csharp-executable-manager');
if (modal) modal.style.display = 'none';
}
function showCSharpExecutableArgs(projectId, exeName, displayName) {
// TODO: Implementar modal de argumentos para C#
const args = prompt(`Argumentos para ${displayName}:`, '');
if (args !== null) {
const argArray = args.trim() ? args.split(' ') : [];
window.csharpLauncherManager.executeExecutable(projectId, exeName, argArray);
}
}
// Asegurar inicialización cuando el DOM esté listo
(function () {
console.log('C# Launcher script loaded, preparing initialization...');
function initializeCSharpLauncher() {
try {
console.log('Initializing CSharpLauncherManager...');
if (!window.csharpLauncherManager) {
window.csharpLauncherManager = new CSharpLauncherManager();
console.log('CSharpLauncherManager created successfully');
}
} catch (error) {
console.error('Error creating CSharpLauncherManager:', error);
}
}
// Inicializar inmediatamente si el DOM ya está listo
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeCSharpLauncher);
} else {
initializeCSharpLauncher();
}
})();
// Función global para debug
window.debugCSharpLauncher = function () {
console.log('=== C# Launcher Debug Info ===');
console.log('Initialized:', window.csharpLauncherManager.initialized);
console.log('Projects:', window.csharpLauncherManager.projects);
console.log('Current Project:', window.csharpLauncherManager.currentProject);
console.log('Executables:', window.csharpLauncherManager.executables);
console.log('Favorites:', Array.from(window.csharpLauncherManager.favorites));
console.log('localStorage:', localStorage.getItem('csharp_last_selected_project'));
// Verificar elementos DOM
const select = document.getElementById('csharp-project-select');
console.log('Project Select Element:', select);
if (select) {
console.log('Select Options:', select.options.length);
console.log('Select Value:', select.value);
}
console.log('==============================');
};
// Función global para resetear
window.resetCSharpLauncher = function () {
console.log('Resetting C# Launcher...');
return window.csharpLauncherManager.reset();
};
// === FUNCIONES DE RESPALDO PARA EVITAR ERRORES DE REFERENCIA ===
// Asegurar que las funciones globales estén definidas antes de que se necesiten
document.addEventListener('DOMContentLoaded', function () {
console.log('DOM loaded, ensuring C# launcher functions are available...');
// Solo redefinir si no existe para evitar conflictos
if (typeof window.openCSharpProjectEditor === 'undefined') {
window.openCSharpProjectEditor = function () {
if (!window.csharpLauncherManager) {
console.error('csharpLauncherManager not initialized');
alert('Error: El launcher C# no está inicializado. Refresca la página.');
return;
}
window.csharpLauncherManager.openProjectEditor();
};
}
if (typeof window.loadCSharpExecutables === 'undefined') {
window.loadCSharpExecutables = function () {
if (!window.csharpLauncherManager) {
console.error('csharpLauncherManager not initialized');
return;
}
const select = document.getElementById('csharp-project-select');
if (select && window.csharpLauncherManager) {
window.csharpLauncherManager.onProjectChange({ target: select });
}
};
}
if (typeof window.filterCSharpByCategory === 'undefined') {
window.filterCSharpByCategory = function (category) {
if (!window.csharpLauncherManager) {
console.error('csharpLauncherManager not initialized');
return;
}
window.csharpLauncherManager.filterByCategory(category);
};
}
if (typeof window.refreshCSharpProcesses === 'undefined') {
window.refreshCSharpProcesses = function () {
if (!window.csharpLauncherManager) {
console.error('csharpLauncherManager not initialized');
return;
}
window.csharpLauncherManager.refreshProcesses();
};
}
if (typeof window.openCSharpExecutableManager === 'undefined') {
window.openCSharpExecutableManager = function () {
if (!window.csharpLauncherManager) {
console.error('csharpLauncherManager not initialized');
alert('Error: El launcher C# no está inicializado. Refresca la página.');
return;
}
if (!window.csharpLauncherManager.currentProject) {
alert('Por favor selecciona un proyecto primero');
return;
}
// Usar la función ya implementada
loadCSharpExecutableManager();
};
}
console.log('C# launcher backup functions initialized');
});