ParamManagerScripts/static/js/launcher.js

925 lines
33 KiB
JavaScript

// 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 = [];
}
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}`));
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;
selector.innerHTML = '<option value="">-- Seleccionar Grupo --</option>';
this.groups.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);
});
}
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;
if (!groupId) {
this.scripts = [];
this.renderScripts();
this.updateManageScriptsButton(false);
return;
}
try {
const response = await fetch(`/api/launcher-scripts/${groupId}`);
this.scripts = await response.json();
this.currentGroup = this.groups.find(g => g.id === groupId);
this.updateGroupIcon();
this.renderScripts();
this.updateManageScriptsButton(true);
} catch (error) {
console.error('Error loading launcher scripts:', error);
this.scripts = [];
this.renderScripts();
this.updateManageScriptsButton(false);
}
}
updateManageScriptsButton(show) {
const button = document.getElementById('manage-scripts-btn');
if (button) {
button.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 = `
<div class="col-span-full empty-state">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<p class="text-lg font-medium">No hay scripts disponibles</p>
<p class="text-sm">Selecciona un grupo o verifica que el directorio contenga archivos .py</p>
</div>
`;
return;
}
let filteredScripts = this.scripts;
if (this.currentFilter !== 'all' && this.currentGroup) {
if (this.currentGroup.category !== 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 = `
<div class="flex justify-between items-start mb-2">
<h4 class="font-medium text-gray-900">${script.display_name}</h4>
<button class="favorite-star ${isFavorite ? 'active' : ''}"
onclick="launcherManager.toggleFavorite('${this.currentGroup.id}', '${script.name}')">
</button>
</div>
<p class="text-sm text-gray-600 mb-3 line-clamp-2">${script.description || 'Script: ' + script.name}</p>
<div class="flex justify-between items-center">
<span class="category-badge">${this.currentGroup.category}</span>
<div class="space-x-2">
<button class="text-blue-500 hover:underline text-sm"
onclick="launcherManager.showArgsModal('${script.name}', '${script.display_name}')">
Con Argumentos
</button>
<button class="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600"
onclick="launcherManager.executeScript('${script.name}')">
Ejecutar
</button>
</div>
</div>
`;
grid.appendChild(card);
});
}
// === 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 = `
<div class="text-center text-gray-500 py-8">
<p>No hay scripts Python en este directorio</p>
</div>
`;
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 = `
<div class="flex justify-between items-start">
<div class="flex-1">
<div class="flex items-center gap-2 mb-1">
<h4 class="font-medium ${script.hidden ? 'text-gray-500' : 'text-gray-900'}">${script.display_name}</h4>
${script.hidden ? '<span class="text-xs bg-gray-200 text-gray-600 px-2 py-1 rounded">Oculto</span>' : ''}
</div>
<p class="text-sm text-gray-600 mb-2">${script.description || 'Sin descripción'}</p>
<p class="text-xs text-gray-500">Archivo: ${script.name}</p>
</div>
<div class="flex gap-2">
<button onclick="launcherManager.editScriptMetadata('${script.name}')"
class="text-blue-500 hover:underline text-sm">
Editar
</button>
</div>
</div>
`;
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 = `
<div class="group-icon-small default">
${this.getDefaultIconForCategory(group.category)}
</div>
<div class="group-info">
<div class="group-name">${group.name}</div>
<div class="group-category">${group.category}${envDisplay}</div>
</div>
<button class="text-blue-500 hover:underline text-sm" onclick="launcherManager.editGroup('${group.id}')">
Editar
</button>
`;
container.appendChild(item);
});
}
renderHistory() {
const historyList = document.getElementById('history-list');
if (!historyList) return;
if (this.history.length === 0) {
historyList.innerHTML = `
<div class="text-center text-gray-500 py-4">
<p>No hay ejecuciones recientes</p>
</div>
`;
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';
const statusIcon = entry.status === 'success' ? '✅' :
entry.status === 'error' ? '❌' : '🔄';
// Información del entorno Python
const envInfo = entry.python_env ? `${entry.python_env}` : '';
const item = document.createElement('div');
item.className = `history-item ${statusClass}`;
item.innerHTML = `
<div class="flex justify-between items-start">
<div>
<span class="font-medium">${entry.script_name.replace('.py', '')}</span>
<span class="text-sm text-gray-500 ml-2">${groupName}${envInfo}</span>
</div>
<span class="text-xs text-gray-400">${timeAgo}</span>
</div>
<div class="text-sm text-gray-600 mt-1">
${statusIcon} ${entry.status.charAt(0).toUpperCase() + entry.status.slice(1)}
${entry.execution_time ? ` - ${entry.execution_time}s` : ''}
${entry.arguments && entry.arguments.length > 0 ? ` - Con argumentos` : ''}
</div>
`;
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);
}
}
async executeScript(scriptName, args = []) {
if (!this.currentGroup) return;
try {
const response = await fetch('/api/execute-gui-script', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
group_id: this.currentGroup.id,
script_name: scriptName,
args: args
})
});
const result = await response.json();
if (result.status === 'success') {
// Recargar historial
await this.loadHistory();
}
} catch (error) {
console.error('Error executing script:', 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');
if (modal && scriptDisplayElement && argsInput) {
scriptDisplayElement.textContent = displayName;
argsInput.value = '';
modal.classList.remove('hidden');
// Guardar datos para uso posterior
modal.dataset.scriptName = scriptName;
modal.dataset.groupId = this.currentGroup.id;
}
}
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 = '';
favorites.slice(0, 5).forEach(fav => {
const group = this.groups.find(g => g.id === fav.group_id);
if (!group) return;
const item = document.createElement('div');
item.className = 'flex items-center justify-between p-2 bg-white rounded border';
item.innerHTML = `
<div class="flex items-center">
<div class="group-icon-small default mr-2">
${this.getDefaultIconForCategory(group.category)}
</div>
<div>
<div class="font-medium text-sm">${fav.script_name.replace('.py', '')}</div>
<div class="text-xs text-gray-500">${group.name}</div>
</div>
</div>
<button class="text-blue-500 hover:underline text-sm"
onclick="launcherManager.executeFavoriteScript('${fav.group_id}', '${fav.script_name}')">
Ejecutar
</button>
`;
favoritesList.appendChild(item);
});
}
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;
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() {
if (!confirm('¿Estás seguro de que quieres limpiar el historial?')) return;
try {
const response = await fetch('/api/launcher-history', {
method: 'DELETE'
});
const result = await response.json();
if (result.status === 'success') {
this.history = [];
this.renderHistory();
}
} 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);
}
} 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);
});
}
}
// === 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();
}
}
// 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');
if (modal && argsInput && window.launcherManager) {
const scriptName = modal.dataset.scriptName;
const args = argsInput.value.trim().split(/\s+/).filter(arg => arg.length > 0);
window.launcherManager.executeScript(scriptName, args);
closeArgsModal();
}
}
// Inicialización cuando se carga la página
document.addEventListener('DOMContentLoaded', function () {
console.log('Launcher JS loaded');
});