/** * Main Application Script * Handles editor functionality, grammar checking, and template management * The 'templates' global variable is used by decoder.js module */ // Global variables let currentTemplate = null; let currentConfigType = null; let templates = new Map(); // Used by decoder.js for template decoding // Initialize the application document.addEventListener('DOMContentLoaded', function() { const editor = document.getElementById('expressionEditor'); const decodeTemplatesBtn = document.getElementById('decodeTemplates'); const detectTemplatesBtn = document.getElementById('detectTemplates'); const clearEditorBtn = document.getElementById('clearEditor'); // Initialize navigation initializeNavigation(); // Debounce timer for automatic grammar checking let grammarCheckTimer; // Update line numbers when content changes editor.addEventListener('input', function(e) { updateLineNumbers(); updateSyntaxHighlight(); // Handle auto-completion handleAutoComplete(e); // Clear previous timer clearTimeout(grammarCheckTimer); // Set new timer for automatic grammar check (300ms delay) grammarCheckTimer = setTimeout(function() { checkGrammar(); detectTemplates(); }, 300); }); // Handle keydown events for Tab completion and other keys editor.addEventListener('keydown', function(e) { if (e.key === 'Tab') { e.preventDefault(); handleTabCompletion(); } else if (e.key === 'Escape') { // Allow users to dismiss the shadow suggestion hideShadowSuggestion(); autoCompleteActive = false; } }); editor.addEventListener('scroll', syncScroll); // Hide shadow suggestion when editor loses focus editor.addEventListener('blur', function() { hideShadowSuggestion(); autoCompleteActive = false; }); // Button event listeners decodeTemplatesBtn.addEventListener('click', decodeTemplates); clearEditorBtn.addEventListener('click', clearEditor); // Random iteration button const randomIterationBtn = document.getElementById('randomIterationBtn'); if (randomIterationBtn) { randomIterationBtn.addEventListener('click', randomIteration); } // BRAIN connection button const connectToBrainBtn = document.getElementById('connectToBrain'); connectToBrainBtn.addEventListener('click', openBrainLoginModal); // Simulator button const runSimulatorBtn = document.getElementById('runSimulator'); if (runSimulatorBtn) { runSimulatorBtn.addEventListener('click', runSimulator); } // Results button listeners const copyDisplayedBtn = document.getElementById('copyDisplayedResults'); const copyAllBtn = document.getElementById('copyAllResults'); const downloadBtn = document.getElementById('downloadResults'); const nextMoveBtn = document.getElementById('nextMoveBtn'); if (copyDisplayedBtn) copyDisplayedBtn.addEventListener('click', copyDisplayedResults); if (copyAllBtn) copyAllBtn.addEventListener('click', copyAllResults); if (downloadBtn) downloadBtn.addEventListener('click', downloadResults); if (nextMoveBtn) nextMoveBtn.addEventListener('click', openSettingsModal); // Initialize line numbers and syntax highlighting updateLineNumbers(); updateSyntaxHighlight(); // Auto-detect templates and check grammar on load detectTemplates(); checkGrammar(); // Handle Enter key in variable input const variableInput = document.getElementById('variableInput'); variableInput.addEventListener('keydown', function(event) { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); applyTemplate(); } }); // Update line numbers on window resize window.addEventListener('resize', function() { updateLineNumbers(); }); // Load custom templates on startup loadCustomTemplates(); }); // Custom Templates Management (Server-side storage) // Load custom templates from server async function loadCustomTemplates() { try { const response = await fetch('/api/templates'); const customTemplates = await response.json(); const buttonsContainer = document.getElementById('customTemplateButtons'); const noTemplatesInfo = document.getElementById('noCustomTemplates'); if (!buttonsContainer) { console.error('customTemplateButtons container not found!'); return; } buttonsContainer.innerHTML = ''; if (!Array.isArray(customTemplates) || customTemplates.length === 0) { // Only show "no templates" message if we're viewing custom or all templates if (noTemplatesInfo && (currentTemplateView === 'all' || currentTemplateView === 'custom')) { noTemplatesInfo.style.display = 'block'; } } else { if (noTemplatesInfo) { noTemplatesInfo.style.display = 'none'; } customTemplates.forEach((template, index) => { const button = document.createElement('button'); button.className = 'btn btn-template btn-template-custom'; button.setAttribute('data-template-type', 'custom'); button.innerHTML = ` ${template.name} × `; button.onclick = () => loadCustomTemplate(index); button.title = template.description || 'Click to load this template'; buttonsContainer.appendChild(button); }); } } catch (error) { console.error('Error loading templates:', error); showNotification('Error loading templates', 'error'); } } // Save current template function saveCurrentTemplate() { const editor = document.getElementById('expressionEditor'); const expression = editor.value.trim(); if (!expression) { showNotification('Please enter an expression before saving', 'error'); return; } // Show save modal const modal = document.getElementById('saveTemplateModal'); const preview = document.getElementById('templatePreview'); const nameInput = document.getElementById('templateName'); const descInput = document.getElementById('templateDescription'); const configurationsInfo = document.getElementById('templateConfigurationsInfo'); const configurationsList = document.getElementById('configurationsList'); preview.textContent = expression; nameInput.value = ''; descInput.value = ''; // Check for configured templates and show info const configuredTemplates = []; templates.forEach((template, templateName) => { if (template.variables && template.variables.length > 0 && template.configType) { configuredTemplates.push({ name: templateName, type: template.configType, count: template.variables.length }); } }); if (configuredTemplates.length > 0) { configurationsList.innerHTML = configuredTemplates.map(config => `
  • <${config.name}/> - ${config.type} (${config.count} values)
  • ` ).join(''); configurationsInfo.style.display = 'block'; } else { configurationsInfo.style.display = 'none'; } modal.style.display = 'block'; nameInput.focus(); // Add Enter key support const handleEnter = (event) => { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); confirmSaveTemplate(); } }; nameInput.addEventListener('keydown', handleEnter); descInput.addEventListener('keydown', handleEnter); // Clean up event listeners when modal closes modal.addEventListener('close', () => { nameInput.removeEventListener('keydown', handleEnter); descInput.removeEventListener('keydown', handleEnter); }); } // Close save template modal function closeSaveTemplateModal() { const modal = document.getElementById('saveTemplateModal'); modal.style.display = 'none'; } // Overwrite existing template async function overwriteExistingTemplate() { const editor = document.getElementById('expressionEditor'); const expression = editor.value.trim(); if (!expression) { showNotification('Please enter an expression before overwriting a template', 'error'); return; } // Check if there are any custom templates first try { const response = await fetch('/api/templates'); const customTemplates = await response.json(); if (!Array.isArray(customTemplates) || customTemplates.length === 0) { showNotification('No custom templates available to overwrite. Create a template first using "Save Current Template".', 'warning'); return; } } catch (error) { console.error('Error checking templates:', error); showNotification('Error checking existing templates', 'error'); return; } // Show overwrite modal const modal = document.getElementById('overwriteTemplateModal'); const preview = document.getElementById('overwriteTemplatePreview'); const templateSelect = document.getElementById('existingTemplateSelect'); const confirmBtn = document.getElementById('overwriteConfirmBtn'); const configurationsInfo = document.getElementById('overwriteConfigurationsInfo'); const configurationsList = document.getElementById('overwriteConfigurationsList'); preview.textContent = expression; // Reset UI templateSelect.value = ''; confirmBtn.disabled = true; document.getElementById('selectedTemplateInfo').style.display = 'none'; // Check for configured templates and show info const configuredTemplates = []; templates.forEach((template, templateName) => { if (template.variables && template.variables.length > 0 && template.configType) { configuredTemplates.push({ name: templateName, type: template.configType, count: template.variables.length }); } }); if (configuredTemplates.length > 0) { configurationsList.innerHTML = configuredTemplates.map(config => `
  • <${config.name}/> - ${config.type} (${config.count} values)
  • ` ).join(''); configurationsInfo.style.display = 'block'; } else { configurationsInfo.style.display = 'none'; } // Load existing templates for dropdown loadExistingTemplatesForOverwrite(); // Add event listener for template selection templateSelect.onchange = handleTemplateSelectionForOverwrite; modal.style.display = 'block'; } // Load existing templates for the overwrite dropdown async function loadExistingTemplatesForOverwrite() { try { const response = await fetch('/api/templates'); const customTemplates = await response.json(); const templateSelect = document.getElementById('existingTemplateSelect'); // Clear existing options except the first one templateSelect.innerHTML = ''; if (Array.isArray(customTemplates) && customTemplates.length > 0) { customTemplates.forEach((template, index) => { const option = document.createElement('option'); option.value = index; option.textContent = template.name; option.dataset.description = template.description || ''; templateSelect.appendChild(option); }); } else { const option = document.createElement('option'); option.textContent = 'No custom templates available'; option.disabled = true; templateSelect.appendChild(option); } } catch (error) { console.error('Error loading templates for overwrite:', error); showNotification('Error loading templates', 'error'); } } // Handle template selection for overwrite function handleTemplateSelectionForOverwrite() { const templateSelect = document.getElementById('existingTemplateSelect'); const selectedTemplateInfo = document.getElementById('selectedTemplateInfo'); const currentTemplateDescription = document.getElementById('currentTemplateDescription'); const confirmBtn = document.getElementById('overwriteConfirmBtn'); if (templateSelect.value === '') { selectedTemplateInfo.style.display = 'none'; confirmBtn.disabled = true; return; } // Show selected template info const selectedOption = templateSelect.options[templateSelect.selectedIndex]; const description = selectedOption.dataset.description || 'No description'; currentTemplateDescription.textContent = description; selectedTemplateInfo.style.display = 'block'; confirmBtn.disabled = false; } // Close overwrite template modal function closeOverwriteTemplateModal() { const modal = document.getElementById('overwriteTemplateModal'); modal.style.display = 'none'; } // Confirm and overwrite template async function confirmOverwriteTemplate() { const templateSelect = document.getElementById('existingTemplateSelect'); const editor = document.getElementById('expressionEditor'); if (templateSelect.value === '') { showNotification('Please select a template to overwrite', 'error'); return; } const selectedIndex = parseInt(templateSelect.value); const selectedTemplateName = templateSelect.options[templateSelect.selectedIndex].textContent; // Confirm the overwrite action if (!confirm(`Are you sure you want to overwrite the template "${selectedTemplateName}"? This action cannot be undone.`)) { return; } const expression = editor.value.trim(); // Capture current template configurations const templateConfigurations = {}; templates.forEach((template, templateName) => { if (template.variables && template.variables.length > 0 && template.configType) { templateConfigurations[templateName] = { variables: template.variables, configType: template.configType }; } }); try { // First get the existing template to preserve its name and original creation date const response = await fetch('/api/templates'); const customTemplates = await response.json(); if (!Array.isArray(customTemplates) || selectedIndex >= customTemplates.length) { showNotification('Selected template not found', 'error'); return; } const existingTemplate = customTemplates[selectedIndex]; // Update the template with new expression and configurations const updateResponse = await fetch('/api/templates', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: existingTemplate.name, // Keep the original name description: existingTemplate.description, // Keep the original description expression: expression, templateConfigurations: templateConfigurations }) }); const result = await updateResponse.json(); if (result.success) { // Close modal and reload templates closeOverwriteTemplateModal(); loadCustomTemplates(); showNotification(`Template "${existingTemplate.name}" overwritten successfully`, 'success'); } else { showNotification(result.error || 'Error overwriting template', 'error'); } } catch (error) { console.error('Error overwriting template:', error); showNotification('Error overwriting template', 'error'); } } // Confirm and save template async function confirmSaveTemplate() { const nameInput = document.getElementById('templateName'); const descInput = document.getElementById('templateDescription'); const editor = document.getElementById('expressionEditor'); const name = nameInput.value.trim(); const description = descInput.value.trim(); const expression = editor.value.trim(); if (!name) { showNotification('Please enter a name for the template', 'error'); nameInput.focus(); return; } // Capture current template configurations const templateConfigurations = {}; templates.forEach((template, templateName) => { if (template.variables && template.variables.length > 0 && template.configType) { templateConfigurations[templateName] = { variables: template.variables, configType: template.configType }; } }); try { const response = await fetch('/api/templates', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name, description: description, expression: expression, templateConfigurations: templateConfigurations }) }); const result = await response.json(); if (result.success) { // Close modal and reload templates closeSaveTemplateModal(); loadCustomTemplates(); showNotification(result.message, 'success'); } else { showNotification(result.error || 'Error saving template', 'error'); } } catch (error) { console.error('Error saving template:', error); showNotification('Error saving template', 'error'); } } // Load a custom template async function loadCustomTemplate(index) { try { const response = await fetch('/api/templates'); const customTemplates = await response.json(); if (Array.isArray(customTemplates) && index >= 0 && index < customTemplates.length) { const template = customTemplates[index]; const editor = document.getElementById('expressionEditor'); editor.value = template.expression; updateLineNumbers(); updateSyntaxHighlight(); checkGrammar(); detectTemplates(); // Restore template configurations if they exist if (template.templateConfigurations) { setTimeout(() => { Object.entries(template.templateConfigurations).forEach(([templateName, config]) => { if (templates.has(templateName) && config.variables && config.configType) { const templateObj = templates.get(templateName); templateObj.variables = config.variables; templateObj.configType = config.configType; // Update visual state if (templateObj.element) { templateObj.element.className = 'template-item configured'; } updateTemplateCount(templateName); } }); // Update the overall template status updateTemplateStatus(); }, 100); // Small delay to ensure templates are detected first } showNotification(`Loaded template: ${template.name}`, 'success'); } } catch (error) { console.error('Error loading template:', error); showNotification('Error loading template', 'error'); } } // Delete a custom template async function deleteCustomTemplate(index, event) { event.stopPropagation(); // Prevent button click from triggering try { const response = await fetch('/api/templates'); const customTemplates = await response.json(); if (Array.isArray(customTemplates) && index >= 0 && index < customTemplates.length) { const template = customTemplates[index]; if (confirm(`Are you sure you want to delete the template "${template.name}"?`)) { const deleteResponse = await fetch(`/api/templates/${index}`, { method: 'DELETE' }); const result = await deleteResponse.json(); if (result.success) { loadCustomTemplates(); showNotification(result.message, 'info'); } else { showNotification(result.error || 'Error deleting template', 'error'); } } } } catch (error) { console.error('Error deleting template:', error); showNotification('Error deleting template', 'error'); } } // Export custom templates to JSON file async function exportCustomTemplates() { try { const response = await fetch('/api/templates/export'); const customTemplates = await response.json(); if (!Array.isArray(customTemplates) || customTemplates.length === 0) { showNotification('No custom templates to export', 'warning'); return; } const dataStr = JSON.stringify(customTemplates, null, 2); const dataBlob = new Blob([dataStr], { type: 'application/json' }); const link = document.createElement('a'); link.href = URL.createObjectURL(dataBlob); link.download = `brain_custom_templates_${new Date().toISOString().slice(0, 10)}.json`; document.body.appendChild(link); link.click(); document.body.removeChild(link); showNotification(`Exported ${customTemplates.length} template${customTemplates.length > 1 ? 's' : ''}`, 'success'); } catch (error) { console.error('Error exporting templates:', error); showNotification('Error exporting templates', 'error'); } } // Import custom templates from JSON file function importCustomTemplates(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = async function(e) { try { const importedTemplates = JSON.parse(e.target.result); if (!Array.isArray(importedTemplates)) { throw new Error('Invalid template file format'); } // Validate template structure const validTemplates = importedTemplates.filter(t => t.name && typeof t.name === 'string' && t.expression && typeof t.expression === 'string' ); if (validTemplates.length === 0) { throw new Error('No valid templates found in file'); } // Get existing templates to check for duplicates const response = await fetch('/api/templates'); const existingTemplates = await response.json(); // Check for duplicates const duplicates = validTemplates.filter(imported => Array.isArray(existingTemplates) && existingTemplates.some(existing => existing.name === imported.name) ); let overwrite = false; if (duplicates.length > 0) { const duplicateNames = duplicates.map(t => t.name).join(', '); overwrite = confirm(`The following templates already exist: ${duplicateNames}\n\nDo you want to overwrite them?`); if (!overwrite) { // Filter out duplicates if user doesn't want to overwrite const nonDuplicates = validTemplates.filter(imported => !Array.isArray(existingTemplates) || !existingTemplates.some(existing => existing.name === imported.name) ); if (nonDuplicates.length === 0) { showNotification('Import cancelled - all templates already exist', 'info'); event.target.value = ''; // Reset file input return; } } } // Import templates const importResponse = await fetch('/api/templates/import', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ templates: validTemplates, overwrite: overwrite }) }); const result = await importResponse.json(); if (result.success) { loadCustomTemplates(); let message = `Imported ${result.imported} new template${result.imported !== 1 ? 's' : ''}`; if (result.overwritten > 0) { message += `, overwritten ${result.overwritten}`; } showNotification(message, 'success'); } else { showNotification(result.error || 'Import failed', 'error'); } } catch (error) { showNotification(`Import failed: ${error.message}`, 'error'); } event.target.value = ''; // Reset file input }; reader.readAsText(file); } // Run simulator script function runSimulator() { // Show modal with two options showSimulatorOptionsModal(); } function showSimulatorOptionsModal() { // Create modal HTML if it doesn't exist let modal = document.getElementById('simulatorOptionsModal'); if (!modal) { modal = document.createElement('div'); modal.id = 'simulatorOptionsModal'; modal.className = 'modal'; modal.innerHTML = ` `; document.body.appendChild(modal); } modal.style.display = 'block'; } function closeSimulatorOptionsModal() { const modal = document.getElementById('simulatorOptionsModal'); if (modal) { modal.style.display = 'none'; } } async function runTraditionalSimulator() { closeSimulatorOptionsModal(); try { const response = await fetch('/api/run-simulator', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const result = await response.json(); if (result.success) { showNotification(result.message, 'success'); } else { showNotification(result.error || 'Failed to run simulator', 'error'); } } catch (error) { console.error('Error running simulator:', error); showNotification('Error running simulator', 'error'); } } function runWebSimulator() { closeSimulatorOptionsModal(); // Navigate to the new simulator page window.location.href = '/simulator'; } // Make functions globally accessible window.saveCurrentTemplate = saveCurrentTemplate; window.closeSaveTemplateModal = closeSaveTemplateModal; window.confirmSaveTemplate = confirmSaveTemplate; window.overwriteExistingTemplate = overwriteExistingTemplate; window.closeOverwriteTemplateModal = closeOverwriteTemplateModal; window.confirmOverwriteTemplate = confirmOverwriteTemplate; window.loadCustomTemplate = loadCustomTemplate; window.deleteCustomTemplate = deleteCustomTemplate; window.exportCustomTemplates = exportCustomTemplates; window.importCustomTemplates = importCustomTemplates; window.runSimulator = runSimulator; window.showSimulatorOptionsModal = showSimulatorOptionsModal; window.closeSimulatorOptionsModal = closeSimulatorOptionsModal; window.runTraditionalSimulator = runTraditionalSimulator; window.runWebSimulator = runWebSimulator; // Template View Toggle Functionality let currentTemplateView = 'all'; // 'all', 'custom', 'example' function toggleTemplateView() { const toggleBtn = document.getElementById('toggleTemplateView'); const toggleText = document.getElementById('toggleTemplateViewText'); const exampleTemplates = document.getElementById('exampleTemplateButtons'); const customTemplates = document.getElementById('customTemplateButtons'); const noTemplatesInfo = document.getElementById('noCustomTemplates'); // Cycle through views: all -> custom -> example -> all if (currentTemplateView === 'all') { currentTemplateView = 'custom'; toggleText.textContent = 'Show Examples Only'; exampleTemplates.style.display = 'none'; customTemplates.style.display = 'block'; // Check if there are custom templates if (customTemplates.children.length === 0 && noTemplatesInfo) { noTemplatesInfo.style.display = 'block'; } } else if (currentTemplateView === 'custom') { currentTemplateView = 'example'; toggleText.textContent = 'Show All Templates'; exampleTemplates.style.display = 'block'; customTemplates.style.display = 'none'; if (noTemplatesInfo) { noTemplatesInfo.style.display = 'none'; } } else { currentTemplateView = 'all'; toggleText.textContent = 'Show Custom Only'; exampleTemplates.style.display = 'block'; customTemplates.style.display = 'block'; // Show no templates info only if in all view and no custom templates if (customTemplates.children.length === 0 && noTemplatesInfo) { noTemplatesInfo.style.display = 'block'; } else if (noTemplatesInfo) { noTemplatesInfo.style.display = 'none'; } } } // Make toggleTemplateView globally accessible window.toggleTemplateView = toggleTemplateView; // Load template examples function loadTemplateExample(exampleNumber) { const editor = document.getElementById('expressionEditor'); const examples = { 1: `to_nan( group_normalize( group_neutralize( group_rank( ts_rank( ts_decay_linear( ts_returns( ts_backfill(, )/, ), ), ), ), ), ), value=, reverse= )`, 2: `ts_decay_exp_window( ts_max( vec_avg(), ), )`, 3: `financial_data = ts_backfill(vec_func(), ); gp = group_cartesian_product(, ); data = ( (financial_data, gp), )`, 4: `alpha = ( (, ), ); alpha_gpm = group_mean(alpha, , ); resid = (alpha, alpha_gpm); final_signal = ( group_neutralize(resid, ), )`, 5: `alpha = group_zscore( ts_zscore( ts_backfill(vec_avg(), ), ), ); alpha_gpm = group_mean(alpha, , ); resid = subtract(alpha, alpha_gpm); ts_mean(group_neutralize(resid, ), )`, 6: `data = ts_backfill( winsorize(vec_avg(), std=), ); t_data = normalize(data); gp = group_cartesian_product(, ); signal = group_normalize(ts_zscore(t_data, ), gp); gpm = group_mean(signal, 1, gp); gpm_signal = subtract(signal, gpm); opt = group_neutralize( arc_tan(ts_decay_exp_window(gpm_signal, )), gp ); ts_target_tvr_delta_limit(opt, ts_std_dev(opt, ), target_tvr=)`, 7: `group = ; data = ts_min_max_cps( group_zscore( ts_backfill(vec_min(), ), group ), ); ts_data = ts_median(data, ); ts_target_tvr_hump( group_neutralize(subtract(data, ts_data), group), target_tvr= )` }; if (examples[exampleNumber]) { editor.value = examples[exampleNumber]; updateLineNumbers(); updateSyntaxHighlight(); checkGrammar(); detectTemplates(); // Show a notification showNotification(`Loaded template example ${exampleNumber}`, 'success'); } } // Show notification function showNotification(message, type = 'info') { // Create notification element const notification = document.createElement('div'); notification.className = `notification ${type}`; notification.textContent = message; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 12px 20px; background: ${type === 'success' ? '#48bb78' : '#667eea'}; color: white; border-radius: 6px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); z-index: 10000; animation: slideIn 0.3s ease; `; document.body.appendChild(notification); // Remove after 3 seconds setTimeout(() => { notification.style.animation = 'fadeOut 0.3s ease'; setTimeout(() => { document.body.removeChild(notification); }, 300); }, 3000); } // Make loadTemplateExample globally accessible window.loadTemplateExample = loadTemplateExample; // Initialize navigation system function initializeNavigation() { const navTabs = document.querySelectorAll('.nav-tab'); navTabs.forEach(tab => { tab.addEventListener('click', function() { const targetPage = this.getAttribute('data-page'); navigateToPage(targetPage); }); }); } // Navigate to a specific page function navigateToPage(pageName) { // Update nav tabs const navTabs = document.querySelectorAll('.nav-tab'); navTabs.forEach(tab => { if (tab.getAttribute('data-page') === pageName) { tab.classList.add('active'); } else { tab.classList.remove('active'); } }); // Update page content const pages = document.querySelectorAll('.page-content'); pages.forEach(page => { if (page.id === pageName + 'Page') { page.classList.add('active'); } else { page.classList.remove('active'); } }); // Update template status when navigating to decode page if (pageName === 'decode') { updateTemplateStatus(); } } // Update template status on decode page function updateTemplateStatus() { const statusDiv = document.getElementById('templateStatus'); const decodeHeading = document.querySelector('#decodePage h2'); const totalTemplates = templates.size; const configuredTemplates = Array.from(templates.values()).filter(t => t.variables.length > 0).length; // Reset heading to default decodeHeading.textContent = 'Template Decoding Options'; if (totalTemplates === 0) { statusDiv.innerHTML = `
    No templates detected in your expression.
    `; } else if (configuredTemplates === 0) { statusDiv.innerHTML = `
    ⚠️ No templates configured yet!
    You have ${totalTemplates} variable${totalTemplates > 1 ? 's' : ''} in your expression, but none are configured.
    `; } else if (configuredTemplates < totalTemplates) { statusDiv.innerHTML = `
    ⚠️ Some templates not configured!
    ${configuredTemplates} out of ${totalTemplates} templates are configured.
    `; } else { // All templates configured - calculate search space let searchSpace = []; let totalCombinations = 1; templates.forEach((template, name) => { if (template.variables.length > 0) { searchSpace.push(template.variables.length); totalCombinations *= template.variables.length; } }); // Update heading with search space const searchSpaceStr = searchSpace.join(' × '); decodeHeading.innerHTML = `Template Decoding Options (SearchSpace: ${searchSpaceStr} = ${totalCombinations.toLocaleString()})`; let configDetails = '
      '; templates.forEach((template, name) => { if (template.variables.length > 0) { configDetails += `
    • <${name}/>: ${template.variables.length} ${template.configType || 'values'}
    • `; } }); configDetails += '
    '; statusDiv.innerHTML = `
    ✓ All templates configured!
    ${configDetails} Ready to decode your expressions.
    `; } } // Make navigateToPage globally accessible window.navigateToPage = navigateToPage; // Update line numbers in the editor function updateLineNumbers() { const editor = document.getElementById('expressionEditor'); const lineNumbers = document.getElementById('lineNumbers'); const lines = editor.value.split('\n'); // Calculate how many lines we need based on editor height const editorHeight = editor.offsetHeight || 500; const lineHeight = 25.6; // 16px font-size * 1.6 line-height const visibleLines = Math.ceil(editorHeight / lineHeight); const totalLines = Math.max(lines.length, visibleLines); // Build line numbers text let lineNumbersText = ''; for (let i = 1; i <= totalLines; i++) { lineNumbersText += i + '\n'; } // Remove trailing newline for better alignment lineNumbers.textContent = lineNumbersText.trimEnd(); } // Sync scroll between editor and line numbers function syncScroll() { const editor = document.getElementById('expressionEditor'); const lineNumbers = document.getElementById('lineNumbers'); const highlightedText = document.getElementById('highlightedText'); lineNumbers.scrollTop = editor.scrollTop; highlightedText.scrollTop = editor.scrollTop; highlightedText.scrollLeft = editor.scrollLeft; // Hide shadow suggestion when scrolling hideShadowSuggestion(); } // Update syntax highlighting function updateSyntaxHighlight() { const editor = document.getElementById('expressionEditor'); const highlightedText = document.getElementById('highlightedText'); const text = editor.value; // Escape HTML special characters let escapedText = text .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); // Highlight template tags escapedText = escapedText.replace(/(<)(\/?)(\w+)(\/>)/g, function(match, open, slash, tagName, close) { return `${open}` + `${slash}` + `${tagName}` + `${close}`; }); highlightedText.innerHTML = escapedText; } // Grammar checking function function checkGrammar() { const editor = document.getElementById('expressionEditor'); const content = editor.value; const errorsDiv = document.getElementById('grammarErrors'); const errors = []; // Clear previous errors errorsDiv.innerHTML = ''; // Check for unclosed block comments const commentStart = content.match(/\/\*/g) || []; const commentEnd = content.match(/\*\//g) || []; if (commentStart.length !== commentEnd.length) { errors.push({ type: 'error', message: 'Unclosed block comment detected. Each /* must have a matching */' }); } // Remove comments for statement detection let contentWithoutComments = content.replace(/\/\*[\s\S]*?\*\//g, '').trim(); // Check if content is empty after removing comments if (!contentWithoutComments) { errorsDiv.innerHTML = '
    Enter an expression to check grammar
    '; return; } // Detect statements by looking for assignment patterns (variable = expression) // or by semicolons const lines = contentWithoutComments.split('\n'); let statements = []; let currentStatement = ''; let statementStartLine = 0; let openParens = 0; let openBrackets = 0; let inStatement = false; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const trimmedLine = line.trim(); // Skip empty lines if (trimmedLine === '') { if (currentStatement.trim()) { currentStatement += '\n'; } continue; } // Track parentheses and brackets to handle multi-line expressions for (let char of trimmedLine) { if (char === '(') openParens++; else if (char === ')') openParens--; else if (char === '[') openBrackets++; else if (char === ']') openBrackets--; } currentStatement += (currentStatement ? '\n' : '') + line; // Check if this line starts a new statement (has assignment operator) if (!inStatement && trimmedLine.match(/^\w+\s*=/)) { inStatement = true; statementStartLine = i; } // Check if statement is complete if (trimmedLine.endsWith(';') || (i === lines.length - 1) || // Last line (i < lines.length - 1 && lines[i + 1].trim().match(/^\w+\s*=/))) { // Next line starts new assignment // Statement is complete if (currentStatement.trim()) { statements.push({ text: currentStatement.trim(), startLine: statementStartLine, endLine: i, hasSemicolon: trimmedLine.endsWith(';'), isLastStatement: i === lines.length - 1 || (i < lines.length - 1 && !lines.slice(i + 1).some(l => l.trim())) }); } currentStatement = ''; inStatement = false; openParens = 0; openBrackets = 0; } } // Validate statements if (statements.length === 0) { // Single expression without assignment const hasSemicolon = contentWithoutComments.trim().endsWith(';'); if (hasSemicolon) { errors.push({ type: 'warning', message: 'Single expression (Alpha expression) should not end with a semicolon' }); } // Check if single expression is a variable assignment const assignmentPattern = /^\s*\w+\s*=\s*[\s\S]*$/; if (assignmentPattern.test(contentWithoutComments)) { errors.push({ type: 'error', message: 'The Alpha expression (final result) cannot be assigned to a variable. Remove the variable assignment.' }); } } else if (statements.length === 1) { // Single statement if (statements[0].hasSemicolon && statements[0].isLastStatement) { errors.push({ type: 'warning', message: 'The last statement (Alpha expression) should not end with a semicolon' }); } // Check if single statement is a variable assignment const assignmentPattern = /^\s*\w+\s*=\s*[\s\S]*$/; if (assignmentPattern.test(statements[0].text)) { errors.push({ type: 'error', message: 'The Alpha expression (final result) cannot be assigned to a variable. Remove the variable assignment.' }); } } else { // Multiple statements for (let i = 0; i < statements.length; i++) { const stmt = statements[i]; if (i < statements.length - 1 && !stmt.hasSemicolon) { // Not the last statement and missing semicolon errors.push({ type: 'error', line: stmt.endLine + 1, message: `Line ${stmt.endLine + 1}: Missing semicolon at the end of the statement` }); } else if (i === statements.length - 1 && stmt.hasSemicolon) { // Last statement with semicolon errors.push({ type: 'warning', line: stmt.endLine + 1, message: `Line ${stmt.endLine + 1}: The last statement (Alpha expression) should not end with a semicolon` }); } // Check if last statement is a variable assignment if (i === statements.length - 1) { const assignmentPattern = /^\s*\w+\s*=\s*[\s\S]*$/; if (assignmentPattern.test(stmt.text)) { errors.push({ type: 'error', line: stmt.endLine + 1, message: `Line ${stmt.endLine + 1}: The Alpha expression (final result) cannot be assigned to a variable. Remove the variable assignment.` }); } } } } // Check for forbidden constructs const forbiddenPatterns = [ { pattern: /\bclass\s+\w+/, message: 'Classes are not allowed in this expression language' }, { pattern: /\bfunction\s+\w+/, message: 'Functions are not allowed in this expression language' }, { pattern: /\w+\s*\*\s*\w+/, message: 'Pointers are not allowed in this expression language' }, { pattern: /\bnew\s+\w+/, message: 'Object creation (new) is not allowed in this expression language' } ]; forbiddenPatterns.forEach(({ pattern, message }) => { const matches = content.match(pattern); if (matches) { errors.push({ type: 'error', message: message }); } }); // Display errors or success message if (errors.length === 0) { errorsDiv.innerHTML = '
    ✓ Grammar check passed! No errors found.
    '; } else { errors.forEach(error => { const errorDiv = document.createElement('div'); errorDiv.className = 'error-item'; errorDiv.innerHTML = `${error.type.toUpperCase()}: ${error.message}`; errorsDiv.appendChild(errorDiv); }); } } // Update template count display function updateTemplateCount(templateName) { const template = templates.get(templateName); if (template && template.element) { const countSpan = template.element.querySelector('.template-count'); if (countSpan) { if (template.variables && template.variables.length > 0) { countSpan.textContent = ` (${template.variables.length})`; countSpan.style.color = '#48bb78'; countSpan.style.fontWeight = '600'; } else { countSpan.textContent = ''; } } } } // Detect templates in the expression function detectTemplates() { const editor = document.getElementById('expressionEditor'); const content = editor.value; const templateList = document.getElementById('templateList'); // Store existing template configurations const existingTemplates = new Map(templates); // Clear previous templates templateList.innerHTML = ''; templates.clear(); // Regular expression to match templates like const templateRegex = /<(\w+)\/>/g; const matches = [...content.matchAll(templateRegex)]; // Get unique templates const uniqueTemplates = [...new Set(matches.map(match => match[1]))]; if (uniqueTemplates.length === 0) { templateList.innerHTML = '

    No templates detected

    '; return; } // Display each template uniqueTemplates.forEach(templateName => { const templateDiv = document.createElement('div'); templateDiv.className = 'template-item not-configured'; // Add default not-configured class const nameSpan = document.createElement('span'); nameSpan.className = 'template-name'; nameSpan.innerHTML = `<${templateName}/>`; nameSpan.onclick = () => showTemplateConfig(templateName); nameSpan.title = 'Click to view current configuration'; // Create container for the three buttons const buttonContainer = document.createElement('div'); buttonContainer.className = 'template-buttons'; // Create Op button const opBtn = document.createElement('button'); opBtn.className = 'btn btn-primary btn-small'; opBtn.textContent = 'Op'; opBtn.onclick = () => openTemplateModal(templateName, 'operator'); // Create Data button const dataBtn = document.createElement('button'); dataBtn.className = 'btn btn-secondary btn-small'; dataBtn.textContent = 'DataField'; dataBtn.onclick = () => openTemplateModal(templateName, 'data'); // Create Normal button const normalBtn = document.createElement('button'); normalBtn.className = 'btn btn-outline btn-small'; normalBtn.textContent = 'Other'; normalBtn.onclick = () => openTemplateModal(templateName, 'normal'); buttonContainer.appendChild(opBtn); buttonContainer.appendChild(dataBtn); buttonContainer.appendChild(normalBtn); templateDiv.appendChild(nameSpan); templateDiv.appendChild(buttonContainer); templateList.appendChild(templateDiv); // Store template info - restore existing config if available const existingTemplate = existingTemplates.get(templateName); if (existingTemplate && existingTemplate.variables.length > 0) { templates.set(templateName, { name: templateName, variables: existingTemplate.variables, element: templateDiv, configType: existingTemplate.configType }); // Update visual state templateDiv.className = 'template-item configured'; updateTemplateCount(templateName); } else { templates.set(templateName, { name: templateName, variables: [], element: templateDiv, configType: null }); } }); } // Open modal for template configuration function openTemplateModal(templateName, configType) { currentTemplate = templateName; currentConfigType = configType; // Store the configuration type const modal = document.getElementById('templateModal'); const modalTitle = document.getElementById('modalTitle'); const modalDescription = document.getElementById('modalDescription'); const variableInput = document.getElementById('variableInput'); const brainChooseSection = document.getElementById('brainChooseSection'); // Update modal content based on configuration type let typeDescription = ''; switch(configType) { case 'operator': typeDescription = 'operators'; break; case 'data': typeDescription = 'data fields'; break; case 'normal': typeDescription = 'normal parameters (like dates, etc.)'; break; } modalTitle.textContent = `Configure Template: <${templateName}/> - ${configType.charAt(0).toUpperCase() + configType.slice(1)}`; modalDescription.textContent = `Enter a comma-separated list of ${typeDescription} for the ${templateName} template:`; // Show "Choose from BRAIN" button for operators and data fields if connected to BRAIN if ((configType === 'operator' || configType === 'data') && window.brainAPI && window.brainAPI.isConnectedToBrain()) { brainChooseSection.style.display = 'block'; const chooseBrainBtn = document.getElementById('chooseBrainBtn'); if (configType === 'operator') { chooseBrainBtn.textContent = 'Choose Operators from BRAIN'; chooseBrainBtn.onclick = openBrainOperatorsModal; } else if (configType === 'data') { chooseBrainBtn.textContent = 'Choose Data Fields from BRAIN'; chooseBrainBtn.onclick = openBrainDataFieldsModal; } } else { brainChooseSection.style.display = 'none'; } // Load existing variables if any const template = templates.get(templateName); if (template && template.variables.length > 0 && template.configType === configType) { variableInput.value = template.variables.join(', '); } else { variableInput.value = ''; } modal.style.display = 'block'; variableInput.focus(); } // Close modal function closeModal() { const modal = document.getElementById('templateModal'); modal.style.display = 'none'; currentTemplate = null; } // Show current template configuration function showTemplateConfig(templateName) { const template = templates.get(templateName); const modal = document.getElementById('configInfoModal'); const title = document.getElementById('configInfoTitle'); const content = document.getElementById('configInfoContent'); title.textContent = `Template: <${templateName}/>`; if (!template || !template.variables || template.variables.length === 0) { content.innerHTML = `
    Status: Not configured
    Template: <${templateName}/>

    Click one of the configuration buttons (Op, Data, Normal) to set up this template.
    `; } else { const configTypeDisplay = template.configType ? template.configType.charAt(0).toUpperCase() + template.configType.slice(1) : 'Unknown'; content.innerHTML = `
    Status: Configured
    Template: <${templateName}/>
    Type: ${configTypeDisplay}
    Count: ${template.variables.length} value${template.variables.length > 1 ? 's' : ''}
    Values:
    ${template.variables.join(', ')}
    `; } modal.style.display = 'block'; } // Close configuration info modal function closeConfigInfoModal() { const modal = document.getElementById('configInfoModal'); modal.style.display = 'none'; } // Close modal when clicking outside window.onclick = function(event) { const templateModal = document.getElementById('templateModal'); const configInfoModal = document.getElementById('configInfoModal'); const brainLoginModal = document.getElementById('brainLoginModal'); const brainOperatorsModal = document.getElementById('brainOperatorsModal'); const brainDataFieldsModal = document.getElementById('brainDataFieldsModal'); const settingsModal = document.getElementById('settingsModal'); const saveTemplateModal = document.getElementById('saveTemplateModal'); const overwriteTemplateModal = document.getElementById('overwriteTemplateModal'); if (event.target === templateModal) { closeModal(); } else if (event.target === configInfoModal) { closeConfigInfoModal(); } else if (event.target === brainLoginModal) { // Check if login is in progress const loginBtn = document.getElementById('loginBtn'); if (!loginBtn || !loginBtn.disabled) { closeBrainLoginModal(); } } else if (event.target === brainOperatorsModal) { closeBrainOperatorsModal(); } else if (event.target === brainDataFieldsModal) { closeBrainDataFieldsModal(); } else if (event.target === settingsModal) { closeSettingsModal(); } else if (event.target === saveTemplateModal) { closeSaveTemplateModal(); } else if (event.target === overwriteTemplateModal) { closeOverwriteTemplateModal(); } } // Apply template variables function applyTemplate() { const variableInput = document.getElementById('variableInput'); const variables = variableInput.value .split(',') .map(v => v.trim()) .filter(v => v !== ''); if (variables.length === 0) { alert('Please enter at least one variable'); return; } // Store variables for the template const template = templates.get(currentTemplate); if (template) { template.variables = variables; template.configType = currentConfigType; // Store the configuration type // Update the visual indicator if (template.element) { template.element.className = 'template-item configured'; } // Update the count display updateTemplateCount(currentTemplate); } // Close the modal closeModal(); // Show success message const errorsDiv = document.getElementById('grammarErrors'); errorsDiv.innerHTML = `
    ✓ Template <${currentTemplate}/> configured as ${currentConfigType} with ${variables.length} variable${variables.length > 1 ? 's' : ''}
    `; } // Clear editor function clearEditor() { const editor = document.getElementById('expressionEditor'); editor.value = ''; updateLineNumbers(); updateSyntaxHighlight(); document.getElementById('grammarErrors').innerHTML = ''; document.getElementById('decodedResults').style.display = 'none'; detectTemplates(); } // Auto-completion functionality let autoCompleteActive = false; let autoCompletePosition = null; let shadowSuggestion = null; function handleAutoComplete(event) { const editor = event.target; const cursorPos = editor.selectionStart; const text = editor.value; const lastChar = text[cursorPos - 1]; const prevChar = cursorPos > 1 ? text[cursorPos - 2] : ''; // If user typed '<', show shadow suggestion if (lastChar === '<' && event.inputType === 'insertText') { // Show shadow suggestion for template showShadowSuggestion(editor, cursorPos, 'variable_name/>'); autoCompleteActive = true; autoCompletePosition = cursorPos; } // If user typed '/', check if it's after '<' else if (lastChar === '/' && prevChar === '<') { // Auto-complete the closing '>' const before = text.substring(0, cursorPos); const after = text.substring(cursorPos); editor.value = before + '>' + after; editor.setSelectionRange(cursorPos, cursorPos); // Update shadow to show between < and /> hideShadowSuggestion(); autoCompleteActive = false; } // If user typed something after '<' that's not '/', hide suggestion else if (prevChar === '<' && lastChar !== '/' && autoCompleteActive) { // User is typing something else after '<', like a comparison hideShadowSuggestion(); autoCompleteActive = false; } else { // Check if we should hide suggestion for other cases if (!autoCompleteActive || (autoCompletePosition && cursorPos > autoCompletePosition + 1)) { hideShadowSuggestion(); autoCompleteActive = false; } } } function handleTabCompletion() { const editor = document.getElementById('expressionEditor'); const cursorPos = editor.selectionStart; const text = editor.value; if (autoCompleteActive && shadowSuggestion) { // Check if we're right after '<' if (cursorPos > 0 && text[cursorPos - 1] === '<') { // Complete the template const before = text.substring(0, cursorPos); const after = text.substring(cursorPos); editor.value = before + '/>' + after; editor.setSelectionRange(cursorPos, cursorPos); hideShadowSuggestion(); autoCompleteActive = false; // Trigger input event to update everything const inputEvent = new Event('input', { bubbles: true }); editor.dispatchEvent(inputEvent); // Update syntax highlighting immediately updateSyntaxHighlight(); } } } function showShadowSuggestion(editor, position, suggestion) { // Remove any existing shadow hideShadowSuggestion(); // Create shadow element shadowSuggestion = document.createElement('div'); shadowSuggestion.className = 'shadow-suggestion'; shadowSuggestion.textContent = suggestion; // Get editor wrapper for relative positioning const editorWrapper = editor.closest('.editor-wrapper'); const editorRect = editor.getBoundingClientRect(); // Calculate position based on character position const lineHeight = parseInt(window.getComputedStyle(editor).lineHeight); const lines = editor.value.substring(0, position).split('\n'); const currentLine = lines.length; const currentCol = lines[lines.length - 1].length; // Approximate character width (monospace font) const charWidth = 9.6; // Approximate width for 16px monospace font // Position shadow relative to editor shadowSuggestion.style.position = 'fixed'; shadowSuggestion.style.left = (editorRect.left + 15 + (currentCol * charWidth)) + 'px'; shadowSuggestion.style.top = (editorRect.top + 12 + ((currentLine - 1) * lineHeight) - editor.scrollTop) + 'px'; shadowSuggestion.style.pointerEvents = 'none'; shadowSuggestion.style.zIndex = '1000'; // Add hint text below const hintText = document.createElement('div'); hintText.className = 'shadow-hint'; hintText.textContent = 'Tab to complete template'; shadowSuggestion.appendChild(hintText); document.body.appendChild(shadowSuggestion); } function hideShadowSuggestion() { if (shadowSuggestion) { shadowSuggestion.remove(); shadowSuggestion = null; } } // BRAIN Operators Modal Functions let selectedOperators = new Set(); function openBrainOperatorsModal() { const modal = document.getElementById('brainOperatorsModal'); selectedOperators.clear(); // Populate categories populateOperatorCategories(); // Populate operators list populateOperatorsList(); // Set up event listeners setupOperatorsModalEventListeners(); modal.style.display = 'block'; } function closeBrainOperatorsModal() { const modal = document.getElementById('brainOperatorsModal'); modal.style.display = 'none'; selectedOperators.clear(); updateSelectedOperatorsDisplay(); } function populateOperatorCategories() { const categoryFilter = document.getElementById('categoryFilter'); const operators = window.brainAPI ? window.brainAPI.getLoadedOperators() : []; // Clear existing options except "All Categories" categoryFilter.innerHTML = ''; // Get unique categories const categories = [...new Set(operators.map(op => op.category))].sort(); categories.forEach(category => { const option = document.createElement('option'); option.value = category; option.textContent = category; categoryFilter.appendChild(option); }); } function populateOperatorsList(searchTerm = '', categoryFilter = '') { const operatorsList = document.getElementById('operatorsList'); const operators = window.brainAPI ? window.brainAPI.getLoadedOperators() : []; // Filter operators let filteredOperators = operators; if (searchTerm) { const term = searchTerm.toLowerCase(); filteredOperators = filteredOperators.filter(op => op.name.toLowerCase().includes(term) || op.category.toLowerCase().includes(term) ); } if (categoryFilter) { filteredOperators = filteredOperators.filter(op => op.category === categoryFilter); } // Clear list operatorsList.innerHTML = ''; if (filteredOperators.length === 0) { operatorsList.innerHTML = '

    No operators found

    '; return; } // Create operator items filteredOperators.forEach(operator => { const item = document.createElement('div'); item.className = 'operator-item'; item.dataset.operatorName = operator.name; // Build tooltip content if description or definition is available let tooltipContent = ''; if (operator.description) { tooltipContent += `Description: ${operator.description}`; } if (operator.definition) { tooltipContent += tooltipContent ? `\n\nDefinition: ${operator.definition}` : `Definition: ${operator.definition}`; } if (operator.example) { tooltipContent += tooltipContent ? `\n\nExample: ${operator.example}` : `Example: ${operator.example}`; } if (operator.usageCount !== undefined) { tooltipContent += tooltipContent ? `\n\nUsage Count: ${operator.usageCount}` : `Usage Count: ${operator.usageCount}`; } // Add custom tooltip if we have content if (tooltipContent) { item.dataset.tooltip = tooltipContent; item.style.cursor = 'help'; // Add mouse event listeners for custom tooltip item.addEventListener('mouseenter', showCustomTooltip); item.addEventListener('mouseleave', hideCustomTooltip); item.addEventListener('mousemove', moveCustomTooltip); } // Create description indicator if description or definition is available const descriptionIndicator = (operator.description || operator.definition) ? '📖' : ''; item.innerHTML = `
    ${operator.name} ${descriptionIndicator} ${operator.category}
    `; item.onclick = () => toggleOperatorSelection(operator.name, item); operatorsList.appendChild(item); }); } function toggleOperatorSelection(operatorName, item) { const checkbox = item.querySelector('.operator-checkbox'); if (selectedOperators.has(operatorName)) { selectedOperators.delete(operatorName); checkbox.checked = false; item.classList.remove('selected'); } else { selectedOperators.add(operatorName); checkbox.checked = true; item.classList.add('selected'); } updateSelectedOperatorsDisplay(); } function updateSelectedOperatorsDisplay() { const selectedContainer = document.getElementById('selectedOperators'); selectedContainer.innerHTML = ''; if (selectedOperators.size === 0) { selectedContainer.innerHTML = 'No operators selected'; return; } selectedOperators.forEach(operatorName => { const item = document.createElement('span'); item.className = 'selected-item'; item.innerHTML = ` ${operatorName} `; selectedContainer.appendChild(item); }); } function removeSelectedOperator(operatorName) { selectedOperators.delete(operatorName); updateSelectedOperatorsDisplay(); // Update the checkbox in the list const operatorItem = document.querySelector(`[data-operator-name="${operatorName}"]`); if (operatorItem) { const checkbox = operatorItem.querySelector('.operator-checkbox'); checkbox.checked = false; operatorItem.classList.remove('selected'); } } function setupOperatorsModalEventListeners() { const searchInput = document.getElementById('operatorSearch'); const categoryFilter = document.getElementById('categoryFilter'); const selectAllBtn = document.getElementById('selectAllFilteredOperators'); const clearAllBtn = document.getElementById('clearAllOperators'); searchInput.oninput = () => { populateOperatorsList(searchInput.value, categoryFilter.value); }; categoryFilter.onchange = () => { populateOperatorsList(searchInput.value, categoryFilter.value); }; selectAllBtn.onclick = selectAllFilteredOperators; clearAllBtn.onclick = clearAllOperators; } function selectAllFilteredOperators() { const operatorItems = document.querySelectorAll('.operator-item'); operatorItems.forEach(item => { const operatorName = item.dataset.operatorName; if (!selectedOperators.has(operatorName)) { selectedOperators.add(operatorName); const checkbox = item.querySelector('.operator-checkbox'); checkbox.checked = true; item.classList.add('selected'); } }); updateSelectedOperatorsDisplay(); } function clearAllOperators() { selectedOperators.clear(); // Update all checkboxes document.querySelectorAll('.operator-item').forEach(item => { const checkbox = item.querySelector('.operator-checkbox'); checkbox.checked = false; item.classList.remove('selected'); }); updateSelectedOperatorsDisplay(); } function applySelectedOperators() { if (selectedOperators.size === 0) { alert('Please select at least one operator'); return; } // Add selected operators to the variable input const variableInput = document.getElementById('variableInput'); const currentValues = variableInput.value.trim(); const newValues = Array.from(selectedOperators); if (currentValues) { variableInput.value = currentValues + ', ' + newValues.join(', '); } else { variableInput.value = newValues.join(', '); } closeBrainOperatorsModal(); } // BRAIN Data Fields Modal Functions let selectedDataFields = new Set(); let currentDataFields = []; let filteredDataFields = []; let columnFilters = { id: '', description: '', type: '', coverage: { min: null, max: null }, userCount: null, alphaCount: null }; let sortColumn = null; let sortOrder = 'asc'; function openBrainDataFieldsModal() { const modal = document.getElementById('brainDataFieldsModal'); selectedDataFields.clear(); currentDataFields = []; filteredDataFields = []; // Reset column filters columnFilters = { id: '', description: '', type: '', coverage: { min: null, max: null }, userCount: null, alphaCount: null }; sortColumn = null; sortOrder = 'asc'; // Reset UI state document.getElementById('dataFieldsContent').style.display = 'none'; document.getElementById('dataFieldsLoading').style.display = 'none'; // Clear column filter inputs document.querySelectorAll('.column-filter').forEach(filter => { filter.value = ''; }); document.querySelectorAll('.column-filter-min, .column-filter-max').forEach(filter => { filter.value = ''; }); // Reset sort buttons document.querySelectorAll('.sort-btn').forEach(btn => { btn.classList.remove('asc', 'desc'); btn.dataset.order = 'asc'; }); // Set up event listeners setupDataFieldsModalEventListeners(); modal.style.display = 'block'; } function closeBrainDataFieldsModal() { const modal = document.getElementById('brainDataFieldsModal'); modal.style.display = 'none'; selectedDataFields.clear(); updateSelectedDataFieldsDisplay(); } async function loadDataFields() { const region = document.getElementById('regionInput').value; const delay = document.getElementById('delayInput').value; const universe = document.getElementById('universeInput').value; const datasetId = document.getElementById('datasetInput').value; const loadingDiv = document.getElementById('dataFieldsLoading'); const contentDiv = document.getElementById('dataFieldsContent'); try { loadingDiv.style.display = 'block'; contentDiv.style.display = 'none'; // Fetch data fields using the brain API if (!window.brainAPI || !window.brainAPI.isConnectedToBrain()) { throw new Error('Not connected to BRAIN'); } const dataFields = await window.brainAPI.getDataFields(region, parseInt(delay), universe, datasetId); currentDataFields = dataFields; filteredDataFields = [...dataFields]; populateDataFieldsList(); updateDataFieldsStats(); populateTypeFilter(); loadingDiv.style.display = 'none'; contentDiv.style.display = 'block'; } catch (error) { loadingDiv.style.display = 'none'; alert(`Failed to load data fields: ${error.message}`); } } function populateDataFieldsList() { const tableBody = document.getElementById('dataFieldsTableBody'); const highCoverageFilter = document.getElementById('filterHighCoverage').checked; const popularFilter = document.getElementById('filterPopular').checked; const matrixOnlyFilter = document.getElementById('filterMatrixOnly').checked; // Apply filters filteredDataFields = currentDataFields.filter(field => { // Column-specific filters // ID filter if (columnFilters.id && !field.id.toLowerCase().includes(columnFilters.id.toLowerCase())) { return false; } // Description filter if (columnFilters.description && !field.description.toLowerCase().includes(columnFilters.description.toLowerCase())) { return false; } // Type filter if (columnFilters.type && field.type !== columnFilters.type) { return false; } // Coverage range filter if (columnFilters.coverage.min !== null && field.coverage * 100 < columnFilters.coverage.min) { return false; } if (columnFilters.coverage.max !== null && field.coverage * 100 > columnFilters.coverage.max) { return false; } // User count filter if (columnFilters.userCount !== null && field.userCount < columnFilters.userCount) { return false; } // Alpha count filter if (columnFilters.alphaCount !== null && field.alphaCount < columnFilters.alphaCount) { return false; } // High coverage filter if (highCoverageFilter && field.coverage < 0.9) { return false; } // Popular filter if (popularFilter && field.userCount < 1000) { return false; } // Matrix type filter if (matrixOnlyFilter && field.type !== 'MATRIX') { return false; } return true; }); // Sort filtered data fields if (sortColumn) { filteredDataFields.sort((a, b) => { let aVal = a[sortColumn]; let bVal = b[sortColumn]; // Handle numeric values if (sortColumn === 'coverage' || sortColumn === 'userCount' || sortColumn === 'alphaCount') { aVal = Number(aVal); bVal = Number(bVal); } else { // String comparison aVal = String(aVal).toLowerCase(); bVal = String(bVal).toLowerCase(); } if (aVal < bVal) return sortOrder === 'asc' ? -1 : 1; if (aVal > bVal) return sortOrder === 'asc' ? 1 : -1; return 0; }); } // Clear table tableBody.innerHTML = ''; if (filteredDataFields.length === 0) { tableBody.innerHTML = 'No data fields found matching the filters'; updateDataFieldsStats(); return; } // Create table rows filteredDataFields.forEach(field => { const row = document.createElement('tr'); row.dataset.fieldId = field.id; if (selectedDataFields.has(field.id)) { row.classList.add('selected'); } row.innerHTML = ` ${field.id} ${field.description} ${field.type} ${(field.coverage * 100).toFixed(1)}% ${field.userCount.toLocaleString()} ${field.alphaCount.toLocaleString()} `; row.onclick = (e) => { if (e.target.type !== 'checkbox') { toggleDataFieldSelection(field.id, row); } }; const checkbox = row.querySelector('.data-field-checkbox'); checkbox.onclick = (e) => { e.stopPropagation(); toggleDataFieldSelection(field.id, row); }; tableBody.appendChild(row); }); updateDataFieldsStats(); } function toggleDataFieldSelection(fieldId, row) { const checkbox = row.querySelector('.data-field-checkbox'); if (selectedDataFields.has(fieldId)) { selectedDataFields.delete(fieldId); checkbox.checked = false; row.classList.remove('selected'); } else { selectedDataFields.add(fieldId); checkbox.checked = true; row.classList.add('selected'); } updateSelectedDataFieldsDisplay(); updateDataFieldsStats(); updateSelectAllCheckbox(); } function updateSelectedDataFieldsDisplay() { const selectedContainer = document.getElementById('selectedDataFields'); selectedContainer.innerHTML = ''; if (selectedDataFields.size === 0) { selectedContainer.innerHTML = 'No data fields selected'; return; } selectedDataFields.forEach(fieldId => { const item = document.createElement('span'); item.className = 'selected-item'; item.innerHTML = ` ${fieldId} `; selectedContainer.appendChild(item); }); } function removeSelectedDataField(fieldId) { selectedDataFields.delete(fieldId); updateSelectedDataFieldsDisplay(); updateDataFieldsStats(); // Update the checkbox in the table const row = document.querySelector(`tr[data-field-id="${fieldId}"]`); if (row) { const checkbox = row.querySelector('.data-field-checkbox'); checkbox.checked = false; row.classList.remove('selected'); } updateSelectAllCheckbox(); } function updateDataFieldsStats() { document.getElementById('dataFieldsCount').textContent = `${currentDataFields.length} fields loaded`; document.getElementById('filteredCount').textContent = `${filteredDataFields.length} filtered`; document.getElementById('selectedCount').textContent = `${selectedDataFields.size} selected`; } function populateTypeFilter() { const typeFilter = document.getElementById('typeFilter'); if (!typeFilter) return; // Get unique types from current data fields const uniqueTypes = [...new Set(currentDataFields.map(field => field.type))].sort(); // Clear existing options except "All Types" typeFilter.innerHTML = ''; // Add unique types as options uniqueTypes.forEach(type => { const option = document.createElement('option'); option.value = type; option.textContent = type; typeFilter.appendChild(option); }); // Restore selected value if it exists if (columnFilters.type && uniqueTypes.includes(columnFilters.type)) { typeFilter.value = columnFilters.type; } } function selectAllFilteredDataFields() { filteredDataFields.forEach(field => { selectedDataFields.add(field.id); const row = document.querySelector(`tr[data-field-id="${field.id}"]`); if (row) { const checkbox = row.querySelector('.data-field-checkbox'); checkbox.checked = true; row.classList.add('selected'); } }); updateSelectedDataFieldsDisplay(); updateDataFieldsStats(); updateSelectAllCheckbox(); } function clearAllSelectedDataFields() { selectedDataFields.clear(); // Update all checkboxes document.querySelectorAll('.data-field-checkbox').forEach(checkbox => { checkbox.checked = false; checkbox.closest('tr').classList.remove('selected'); }); updateSelectedDataFieldsDisplay(); updateDataFieldsStats(); updateSelectAllCheckbox(); } function setupDataFieldsModalEventListeners() { const loadBtn = document.getElementById('loadDataFieldsBtn'); const selectAllBtn = document.getElementById('selectAllFiltered'); const clearAllBtn = document.getElementById('clearAllSelected'); const selectAllCheckbox = document.getElementById('selectAllCheckbox'); // Filter checkboxes const highCoverageFilter = document.getElementById('filterHighCoverage'); const popularFilter = document.getElementById('filterPopular'); const matrixOnlyFilter = document.getElementById('filterMatrixOnly'); loadBtn.onclick = loadDataFields; // Filter checkbox listeners highCoverageFilter.onchange = () => populateDataFieldsList(); popularFilter.onchange = () => populateDataFieldsList(); matrixOnlyFilter.onchange = () => populateDataFieldsList(); selectAllBtn.onclick = selectAllFilteredDataFields; clearAllBtn.onclick = clearAllSelectedDataFields; selectAllCheckbox.onclick = (e) => { e.stopPropagation(); if (selectAllCheckbox.checked) { selectAllFilteredDataFields(); } else { clearAllFilteredDataFields(); } }; // Column filter listeners document.querySelectorAll('.column-filter').forEach(filter => { filter.addEventListener('input', (e) => { const column = e.target.dataset.column; const value = e.target.value; if (column === 'userCount' || column === 'alphaCount') { columnFilters[column] = value ? parseInt(value) : null; } else { columnFilters[column] = value; } // Add/remove active class if (value) { e.target.classList.add('active'); } else { e.target.classList.remove('active'); } populateDataFieldsList(); }); }); // Coverage range filters document.querySelectorAll('.column-filter-min, .column-filter-max').forEach(filter => { filter.addEventListener('input', (e) => { const isMin = e.target.classList.contains('column-filter-min'); const value = e.target.value; if (isMin) { columnFilters.coverage.min = value ? parseFloat(value) : null; } else { columnFilters.coverage.max = value ? parseFloat(value) : null; } // Add/remove active class const minInput = e.target.parentElement.querySelector('.column-filter-min'); const maxInput = e.target.parentElement.querySelector('.column-filter-max'); if (minInput.value || maxInput.value) { minInput.classList.add('active'); maxInput.classList.add('active'); } else { minInput.classList.remove('active'); maxInput.classList.remove('active'); } populateDataFieldsList(); }); }); // Sort button listeners document.querySelectorAll('.sort-btn').forEach(btn => { btn.addEventListener('click', (e) => { const column = e.target.dataset.column; // Reset all other sort buttons document.querySelectorAll('.sort-btn').forEach(b => { if (b !== e.target) { b.classList.remove('asc', 'desc'); b.dataset.order = 'asc'; } }); // Toggle sort order if (sortColumn === column) { sortOrder = sortOrder === 'asc' ? 'desc' : 'asc'; } else { sortColumn = column; sortOrder = 'asc'; } e.target.dataset.order = sortOrder; e.target.classList.remove('asc', 'desc'); e.target.classList.add(sortOrder); populateDataFieldsList(); }); }); } function updateSelectAllCheckbox() { const selectAllCheckbox = document.getElementById('selectAllCheckbox'); if (!selectAllCheckbox) return; const allFilteredSelected = filteredDataFields.length > 0 && filteredDataFields.every(field => selectedDataFields.has(field.id)); selectAllCheckbox.checked = allFilteredSelected; selectAllCheckbox.indeterminate = !allFilteredSelected && filteredDataFields.some(field => selectedDataFields.has(field.id)); } function clearAllFilteredDataFields() { filteredDataFields.forEach(field => { selectedDataFields.delete(field.id); const row = document.querySelector(`tr[data-field-id="${field.id}"]`); if (row) { const checkbox = row.querySelector('.data-field-checkbox'); checkbox.checked = false; row.classList.remove('selected'); } }); updateSelectedDataFieldsDisplay(); updateDataFieldsStats(); updateSelectAllCheckbox(); } function applySelectedDataFields() { if (selectedDataFields.size === 0) { alert('Please select at least one data field'); return; } // Add selected data fields to the variable input const variableInput = document.getElementById('variableInput'); const currentValues = variableInput.value.trim(); const newValues = Array.from(selectedDataFields); if (currentValues) { variableInput.value = currentValues + ', ' + newValues.join(', '); } else { variableInput.value = newValues.join(', '); } closeBrainDataFieldsModal(); } // Custom tooltip functionality let tooltipElement = null; function createTooltipElement() { if (!tooltipElement) { tooltipElement = document.createElement('div'); tooltipElement.className = 'custom-tooltip'; document.body.appendChild(tooltipElement); } return tooltipElement; } function showCustomTooltip(event) { const tooltip = createTooltipElement(); const content = event.target.closest('[data-tooltip]')?.dataset.tooltip; if (content) { tooltip.textContent = content; tooltip.style.opacity = '1'; moveCustomTooltip(event); } } function hideCustomTooltip() { if (tooltipElement) { tooltipElement.style.opacity = '0'; } } function moveCustomTooltip(event) { if (!tooltipElement || tooltipElement.style.opacity === '0') return; const tooltip = tooltipElement; const mouseX = event.clientX; const mouseY = event.clientY; const offset = 10; // Get tooltip dimensions const tooltipRect = tooltip.getBoundingClientRect(); const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; // Calculate position let left = mouseX + offset; let top = mouseY + offset; // Adjust if tooltip would go off-screen to the right if (left + tooltipRect.width > windowWidth) { left = mouseX - tooltipRect.width - offset; } // Adjust if tooltip would go off-screen at the bottom if (top + tooltipRect.height > windowHeight) { top = mouseY - tooltipRect.height - offset; } // Ensure tooltip doesn't go off-screen to the left or top if (left < 0) left = offset; if (top < 0) top = offset; tooltip.style.left = left + 'px'; tooltip.style.top = top + 'px'; }