// --- Event Listener Setup and Handlers --- // Note: Relies on MANY globally accessible functions and variables: // - State: setSelectedProject, selectedProject, selectedProjectName, currentProjectData // - State helpers: isProcessing // - UI: uiUpdateStatusBar, uiShowProcessingState, uiClearProcessingState, uiClearProjectDisplay, // uiUpdateProjectNameDisplay, uiShowSection, uiShowStatusMessage (via specific wrappers like uiShowUploadStatus), // uiClearManageFilesStatusMessages // - API: apiAddProject, apiUploadPdfs, apiTriggerAnalysis, apiDeletePdf, apiDeleteProject, apiUploadManifest // - Charting: chartManagerDestroyAll // - Modals: modalManagerHide, modalManagerUpdateManageFilesTitle // - Other Core Logic: handleLoadAndDisplayPdfs, updateUIScadaCore, updateUIDrawingCore, updateUIConflictsCore // --- Core Handler Functions --- /** * Handles the change event for the project selector dropdown. * Updates the global state and triggers a full UI refresh for the selected project. */ function handleProjectChange() { // selectedProjectName should have been updated by the calling event listener console.log(`[Events] Project selection changed to: ${selectedProjectName}`); chartManagerDestroyAll(); // Clear charts immediately const projectData = currentProjectData[selectedProjectName]; // Access global state if (projectData) { console.log(`[Events] Data found for ${selectedProjectName}. Status: ${projectData.status}`); uiUpdateStatusBar(selectedProjectName, projectData.status, projectData.last_commit); if (isProcessing(projectData.status)) { console.log(`[Events] Project processing. Showing loading state.`); uiShowProcessingState(projectData.status); } else { console.log(`[Events] Triggering redraw for newly selected project: ${selectedProjectName}`); uiClearProcessingState(); // Clear any potential global loaders // Draw immediately instead of using setTimeout const latestData = currentProjectData[selectedProjectName]; if (latestData && !isProcessing(latestData.status)) { updateUIScadaCore(latestData); updateUIDrawingCore(latestData); updateUIConflictsCore(latestData); } else { // If status is *still* processing (e.g., changed between checks), show loading console.warn("[Events:ProjectChange] State indicates processing on immediate draw attempt."); uiShowProcessingState(latestData?.status || "Processing..."); } } } else { if (selectedProjectName) { console.log(`[Events] No data found yet for selected project: ${selectedProjectName}. Showing loading.`); uiUpdateStatusBar(selectedProjectName, 'Loading data...', 'N/A'); uiShowProcessingState('Loading data...'); } else { console.log(`[Events] No project selected.`); uiUpdateStatusBar('...', 'No project selected', 'N/A'); uiUpdateProjectNameDisplay('No Projects'); uiClearProjectDisplay(); } } // Manage Files button state is handled by the selector's listener directly } // --- Add Project Form Handler --- async function handleAddProjectSubmit(event) { event.preventDefault(); const form = event.target; const statusDiv = document.getElementById('addProjectStatus'); const submitButton = form.querySelector('button[type="submit"]'); const modalElement = form.closest('.modal'); // UI update: Show submitting status statusDiv.style.display = 'none'; // Use direct style manipulation or a UI function statusDiv.textContent = ''; statusDiv.className = 'mt-3 alert alert-info'; statusDiv.textContent = 'Submitting project...'; statusDiv.style.display = 'block'; submitButton.disabled = true; // Validation const projectNameInput = document.getElementById('projectName'); const projectName = projectNameInput.value.trim(); const manifestFile = document.getElementById('manifestFile').files[0]; const pdfFiles = document.getElementById('pdfFiles').files; let validationError = null; if (!/^[a-zA-Z0-9_-]+$/.test(projectName)) validationError = 'Invalid Project Name. Use only letters, numbers, underscores, or hyphens.'; else if (!manifestFile) validationError = 'Manifest CSV file is required.'; else if (pdfFiles.length === 0) validationError = 'At least one Drawing PDF file is required.'; if (validationError) { statusDiv.className = 'mt-3 alert alert-danger'; statusDiv.textContent = validationError; submitButton.disabled = false; return; } // Call API try { const formData = new FormData(form); const result = await apiAddProject(formData); // Use API module statusDiv.className = 'mt-3 alert alert-success'; statusDiv.textContent = result.message || 'Project added successfully.'; form.reset(); modalManagerHide(modalElement); // Use Modal module setTimeout(() => { submitButton.disabled = false; }, 500); } catch (error) { statusDiv.className = 'mt-3 alert alert-danger'; statusDiv.textContent = `Error: ${error.message || 'Could not add project.'}`; submitButton.disabled = false; } } // --- Manage Files Modal Action Handlers --- async function handlePdfDeleteAction(projectName, filename) { if (!confirm(`Are you sure you want to delete the file: ${filename}? This cannot be undone.`)) { return; } console.log(`[Events] Requesting deletion of ${filename} from project ${projectName}`); uiClearManageFilesStatusMessages(); uiShowManageFilesStatus(`Deleting ${filename}...`, 'info'); try { const data = await apiDeletePdf(projectName, filename); // Use API module uiShowManageFilesStatus(data.message || `Successfully deleted ${filename}.`, 'success'); await handleLoadAndDisplayPdfs(projectName); // Refresh list (function in script.js) uiShowAnalysisTriggerStatus('File deleted. Trigger analysis to update progress.', 'info'); } catch (error) { console.error('[Events] Error deleting PDF:', error); uiShowManageFilesStatus(`Error deleting file: ${error.message}`, 'danger'); } } async function handlePdfUploadAction(form) { // selectedProject accessed globally if (!selectedProject) return; const formData = new FormData(form); const fileInput = document.getElementById('newPdfFiles'); if (!fileInput || !fileInput.files || fileInput.files.length === 0) { uiShowUploadStatus('Please select at least one PDF file to upload.', 'warning'); return; } console.log(`[Events] Uploading ${fileInput.files.length} file(s) to project ${selectedProject}`); uiClearManageFilesStatusMessages(); uiShowUploadStatus('Uploading files...', 'info', false); try { const data = await apiUploadPdfs(selectedProject, formData); // Use API module uiShowUploadStatus(data.message || `Successfully uploaded files.`, 'success'); form.reset(); await handleLoadAndDisplayPdfs(selectedProject); // Refresh list (function in script.js) uiShowAnalysisTriggerStatus('Files uploaded. Trigger analysis to update progress.', 'info'); } catch (error) { console.error('[Events] Error uploading PDFs:', error); uiShowUploadStatus(`Upload failed: ${error.message}`, 'danger'); } // Can't easily clear the 'uploading' message here if error occurs before final state // Consider removing the direct DOM check from the original function } async function handleTriggerAnalysisAction() { // selectedProject accessed globally if (!selectedProject) return; console.log(`[Events] Requesting manual analysis trigger for project ${selectedProject}`); uiClearManageFilesStatusMessages(); // --- Set global flag to true --- isAnalysisGloballyActive = true; console.log(`[State] isAnalysisGloballyActive set to: ${isAnalysisGloballyActive}`); // Show status in modal immediately uiShowAnalysisTriggerStatus('Triggering analysis...', 'info', false); // Also update main UI immediately to show processing state const statusMsg = "Analysis triggered..."; const currentCommit = currentProjectData[selectedProject]?.last_commit || 'N/A'; // Get current commit if available uiUpdateStatusBar(selectedProject, statusMsg, currentCommit); uiShowProcessingState(statusMsg); try { const data = await apiTriggerAnalysis(selectedProject); // Use API module // Update modal status on success, main UI is already showing processing uiShowAnalysisTriggerStatus(data.message || 'Analysis triggered successfully. Monitor status bar for updates.', 'success', false); } catch (error) { console.error('[Events] Error triggering analysis:', error); uiShowAnalysisTriggerStatus(`Error: ${error.message}`, 'danger'); // If trigger fails, reset global flag and clear processing state? Maybe. isAnalysisGloballyActive = false; // Reset flag on error uiClearProcessingState(); // Restore status bar to previous state? Or show error? uiUpdateStatusBar(selectedProject, `Error triggering: ${error.message}`, currentCommit); } } async function handleDeleteProjectAction() { // selectedProject accessed globally if (!selectedProject) { uiShowDeleteProjectStatus('No project selected to delete.', 'warning'); return; } const confirmation = prompt( `This action is IRREVERSIBLE and will permanently delete the project '${selectedProject}' and all its data (manifest, PDFs, text files, cloned repository) from the server.\n\nType the project name '${selectedProject}' below to confirm deletion:` ); if (confirmation !== selectedProject) { uiShowDeleteProjectStatus('Project deletion cancelled or confirmation mismatch.', 'info'); return; } console.log(`[Events] Requesting deletion of project ${selectedProject}`); uiClearManageFilesStatusMessages(); uiShowDeleteProjectStatus(`Deleting project '${selectedProject}'...`, 'info', false); const deleteBtn = document.getElementById('deleteProjectBtn'); // Direct access ok for disabling? if (deleteBtn) deleteBtn.disabled = true; try { const data = await apiDeleteProject(selectedProject); // Use API module uiShowDeleteProjectStatus(`Project '${selectedProject}' deleted successfully.`, 'success', false); // Remove setTimeout for immediate UI update // setTimeout(() => { const manageModalElem = document.getElementById('manageFilesModal'); modalManagerHide(manageModalElem); // Use Modal module // Update project selector UI const selector = document.getElementById('projectSelector'); let newSelectedIndex = -1; for (let i = 0; i < selector.options.length; i++) { if (selector.options[i].value === selectedProject) { newSelectedIndex = (i > 0) ? i - 1 : 0; selector.remove(i); break; } } // Update state and trigger UI refresh if (selector.options.length > 0) { selector.selectedIndex = newSelectedIndex >= 0 ? newSelectedIndex : 0; setSelectedProject(selector.value); handleProjectChange(); // Trigger full refresh } else { selector.innerHTML = ''; setSelectedProject(null); uiUpdateProjectNameDisplay('No Projects'); uiUpdateStatusBar('...', 'No projects available', 'N/A'); uiClearProjectDisplay(); const manageFilesBtn = document.getElementById('manageFilesBtn'); if(manageFilesBtn) manageFilesBtn.disabled = true; } if (deleteBtn) deleteBtn.disabled = false; // Re-enable after operation // }, 1500); } catch (error) { console.error('[Events] Error deleting project:', error); uiShowDeleteProjectStatus(`Error: ${error.message}`, 'danger', false); if (deleteBtn) deleteBtn.disabled = false; // Re-enable on error } } async function handleManifestUploadAction(form) { // selectedProject accessed globally if (!selectedProject) return; const formData = new FormData(form); const fileInput = document.getElementById('newManifestFile'); // Validation if (!fileInput || !fileInput.files || fileInput.files.length === 0) { uiShowUploadManifestStatus('Please select a manifest CSV file to upload.', 'warning'); return; } const manifestFile = fileInput.files[0]; if (!manifestFile.name.toLowerCase().endsWith('.csv')) { uiShowUploadManifestStatus('Invalid file type. Please select a .csv file.', 'warning'); return; } console.log(`[Events] Uploading new manifest for project ${selectedProject}`); uiClearManageFilesStatusMessages(); uiShowUploadManifestStatus('Uploading manifest...', 'info', false); try { const data = await apiUploadManifest(selectedProject, formData); // Use API module uiShowUploadManifestStatus(data.message || 'Manifest updated successfully.', 'success'); form.reset(); uiShowAnalysisTriggerStatus('Manifest updated. Trigger analysis to see changes.', 'info'); } catch (error) { console.error('[Events] Error uploading manifest:', error); uiShowUploadManifestStatus(`Upload failed: ${error.message}`, 'danger'); } } // --- Initialization Function --- /** * Initializes the application state and attaches all necessary event listeners. */ function initializeEventListeners() { console.log("[Events] Initializing event listeners and initial UI state..."); // --- Get DOM element references --- const projectSelector = document.getElementById('projectSelector'); const manageFilesBtn = document.getElementById('manageFilesBtn'); const addProjectForm = document.getElementById('addProjectForm'); const manageFilesModalElement = document.getElementById('manageFilesModal'); const uploadPdfsForm = document.getElementById('uploadPdfsForm'); const triggerAnalysisBtn = document.getElementById('triggerAnalysisBtn'); const deleteProjectBtn = document.getElementById('deleteProjectBtn'); const uploadManifestForm = document.getElementById('uploadManifestForm'); const existingPdfListDiv = document.getElementById('existingPdfList'); // --- Setup Initial State & UI --- // initialServerData is expected to be globally available from the HTML template initialStatuses = initialServerData.status || {}; // Store initial statuses globally if (projectSelector && projectSelector.options.length > 0 && projectSelector.value) { setSelectedProject(projectSelector.value); console.log("[Events] Initial project selected:", selectedProject); if (manageFilesBtn) manageFilesBtn.disabled = !selectedProject; // Set initial status bar text using potentially embedded initial status const initialStatus = initialStatuses[selectedProject] || 'Initializing...'; uiUpdateStatusBar(selectedProject, initialStatus, 'N/A'); // Perform initial UI setup (will show loading if status is processing) handleProjectChange(); } else { console.log("[Events] No projects found initially or selector empty."); setSelectedProject(null); if (manageFilesBtn) manageFilesBtn.disabled = true; uiUpdateProjectNameDisplay('No Projects'); uiUpdateStatusBar('...', 'No projects discovered.', 'N/A'); uiClearProjectDisplay(); } // Show initial section (SCADA by default) after potentially setting initial state uiShowSection('scada'); // --- Attach Event Listeners --- // Project Selector if (projectSelector) { projectSelector.addEventListener('change', (event) => { setSelectedProject(event.target.value); handleProjectChange(); // Trigger full update if (manageFilesBtn) manageFilesBtn.disabled = !selectedProject; }); } // Navigation Tabs document.querySelectorAll('.nav-link[data-view]').forEach(link => { link.addEventListener('click', (event) => { event.preventDefault(); const targetSection = link.getAttribute('data-view'); uiShowSection(targetSection); }); }); // Add Project Form if (addProjectForm) { addProjectForm.addEventListener('submit', handleAddProjectSubmit); } // Manage Files Modal: Show if (manageFilesModalElement && manageFilesBtn) { manageFilesModalElement.addEventListener('show.bs.modal', async () => { // selectedProject accessed globally if (!selectedProject) return; modalManagerUpdateManageFilesTitle(selectedProject); uiClearManageFilesStatusMessages(); await handleLoadAndDisplayPdfs(selectedProject); // Calls function in script.js }); } // Manage Files Modal: PDF Upload Form if (uploadPdfsForm) { uploadPdfsForm.addEventListener('submit', (event) => { event.preventDefault(); handlePdfUploadAction(event.target); }); } // Manage Files Modal: Trigger Analysis Button if (triggerAnalysisBtn) { triggerAnalysisBtn.addEventListener('click', handleTriggerAnalysisAction); } // Manage Files Modal: Delete PDF Button (Event Delegation) if (existingPdfListDiv) { existingPdfListDiv.addEventListener('click', (event) => { if (event.target.classList.contains('pdf-delete-btn')) { const button = event.target; const filename = button.dataset.filename; const projectName = button.dataset.project; if (filename && projectName) { handlePdfDeleteAction(projectName, filename); } else { console.error("[Events] Missing filename or project name on delete button.", button); } } }); } // Manage Files Modal: Delete Project Button if (deleteProjectBtn) { deleteProjectBtn.addEventListener('click', handleDeleteProjectAction); } // Manage Files Modal: Manifest Upload Form if (uploadManifestForm) { uploadManifestForm.addEventListener('submit', (event) => { event.preventDefault(); handleManifestUploadAction(event.target); }); } console.log("[Events] Event listeners attached."); } // --- Export (if using modules) --- // export { initializeEventListeners };