| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531 |
- /**
- * 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}
- <span class="delete-btn" onclick="deleteCustomTemplate(${index}, event)" title="Delete template">×</span>
- `;
- 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 =>
- `<li><${config.name}/> - ${config.type} (${config.count} values)</li>`
- ).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 =>
- `<li><${config.name}/> - ${config.type} (${config.count} values)</li>`
- ).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 = '<option value="">Select a template...</option>';
-
- 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 = `
- <div class="modal-content" style="max-width: 600px;">
- <div class="modal-header">
- <h3>🚀 Run Simulator</h3>
- <span class="close" onclick="closeSimulatorOptionsModal()">×</span>
- </div>
- <div class="modal-body">
- <p style="margin-bottom: 20px;">选择您想要运行 BRAIN Alpha 模拟器的方式:</p>
-
- <div class="simulator-options">
- <div class="simulator-option" onclick="runTraditionalSimulator()">
- <div class="option-icon">⚙️</div>
- <div class="option-content">
- <h4>命令行界面</h4>
- <p>传统的交互式命令行界面,带有逐步提示。</p>
- <ul>
- <li>交互式参数输入</li>
- <li>逐步配置</li>
- <li>适合熟悉命令行的高级用户</li>
- </ul>
- </div>
- </div>
-
- <div class="simulator-option" onclick="runWebSimulator()">
- <div class="option-icon">🌐</div>
- <div class="option-content">
- <h4>Web 界面</h4>
- <p>用户友好的 Web 表单,所有参数集中在一个页面。</p>
- <ul>
- <li>所有参数在一个表单中</li>
- <li>实时日志监控</li>
- <li>可视化进度跟踪</li>
- <li>对初学者友好的界面</li>
- </ul>
- </div>
- </div>
- </div>
- </div>
- </div>
- `;
- 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(<data_field/>, <backfill_days/>)/<secondary_data_field/>, <returns_window/>
- ), <decay_window/>
- ), <rank_window/>
- ), <group/>
- ), <group/>
- ), <market/>
- ), value=<nan_value/>, reverse=<reverse_bool/>
- )`,
- 2: `ts_decay_exp_window(
- ts_max(
- vec_avg(<model_field/>), <max_window/>
- ), <decay_window/>
- )`,
- 3: `financial_data = ts_backfill(vec_func(<analyst_metric/>), <backfill_days/>);
- gp = group_cartesian_product(<group1/>, <group2/>);
- data = <ts_operator/>(
- <group_operator/>(financial_data, gp), <window/>
- )`,
- 4: `alpha = <cross_sectional_transform/>(
- <time_series_transform/>(<feature/>, <ts_window/>), <group/>
- );
- alpha_gpm = group_mean(alpha, <weight/>, <group/>);
- resid = <neutralization_func/>(alpha, alpha_gpm);
- final_signal = <time_series_transform2/>(
- group_neutralize(resid, <group2/>), <final_window/>
- )`,
- 5: `alpha = group_zscore(
- ts_zscore(
- ts_backfill(vec_avg(<analyst_field/>), <backfill_days/>), <zscore_window/>
- ), <exchange/>
- );
- alpha_gpm = group_mean(alpha, <cap_weight/>, <country/>);
- resid = subtract(alpha, alpha_gpm);
- ts_mean(group_neutralize(resid, <market/>), <mean_window/>)`,
- 6: `data = ts_backfill(
- winsorize(vec_avg(<analyst_field/>), std=<std_value/>), <backfill_days/>
- );
- t_data = normalize(data);
- gp = group_cartesian_product(<market/>, <country/>);
- signal = group_normalize(ts_zscore(t_data, <zscore_window/>), gp);
- gpm = group_mean(signal, 1, gp);
- gpm_signal = subtract(signal, gpm);
- opt = group_neutralize(
- arc_tan(ts_decay_exp_window(gpm_signal, <decay_window/>)), gp
- );
- ts_target_tvr_delta_limit(opt, ts_std_dev(opt, <std_window/>), target_tvr=<tvr_value/>)`,
- 7: `group = <industry/>;
- data = ts_min_max_cps(
- group_zscore(
- ts_backfill(vec_min(<model_field/>), <backfill_days/>), group
- ), <minmax_window/>
- );
- ts_data = ts_median(data, <median_window/>);
- ts_target_tvr_hump(
- group_neutralize(subtract(data, ts_data), group), target_tvr=<tvr_value/>
- )`
- };
-
- 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 = `
- <div class="info-message">
- No templates detected in your expression.
- <button class="btn btn-secondary btn-small" onclick="navigateToPage('editor')">Go back to editor</button>
- </div>
- `;
- } else if (configuredTemplates === 0) {
- statusDiv.innerHTML = `
- <div class="warning-message">
- <strong>⚠️ No templates configured yet!</strong><br>
- You have ${totalTemplates} variable${totalTemplates > 1 ? 's' : ''} in your expression, but none are configured.<br>
- <button class="btn btn-secondary btn-small" onclick="navigateToPage('editor')">Configure templates</button>
- </div>
- `;
- } else if (configuredTemplates < totalTemplates) {
- statusDiv.innerHTML = `
- <div class="warning-message">
- <strong>⚠️ Some templates not configured!</strong><br>
- ${configuredTemplates} out of ${totalTemplates} templates are configured.<br>
- <button class="btn btn-secondary btn-small" onclick="navigateToPage('editor')">Configure remaining templates</button>
- </div>
- `;
- } 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 <span class="search-space">(SearchSpace: ${searchSpaceStr} = ${totalCombinations.toLocaleString()})</span>`;
-
- let configDetails = '<ul style="margin: 10px 0; padding-left: 20px;">';
- templates.forEach((template, name) => {
- if (template.variables.length > 0) {
- configDetails += `<li><strong><${name}/></strong>: ${template.variables.length} ${template.configType || 'values'}</li>`;
- }
- });
- configDetails += '</ul>';
-
- statusDiv.innerHTML = `
- <div class="success-message">
- <strong>✓ All templates configured!</strong><br>
- ${configDetails}
- Ready to decode your expressions.
- </div>
- `;
- }
- }
- // 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, '"')
- .replace(/'/g, ''');
-
- // Highlight template tags
- escapedText = escapedText.replace(/(<)(\/?)(\w+)(\/>)/g, function(match, open, slash, tagName, close) {
- return `<span class="template-brackets">${open}</span>` +
- `<span class="template-brackets">${slash}</span>` +
- `<span class="template-tag">${tagName}</span>` +
- `<span class="template-brackets">${close}</span>`;
- });
-
- 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 = '<div class="info-message">Enter an expression to check grammar</div>';
- 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 = '<div class="success-message">✓ Grammar check passed! No errors found.</div>';
- } else {
- errors.forEach(error => {
- const errorDiv = document.createElement('div');
- errorDiv.className = 'error-item';
- errorDiv.innerHTML = `<strong>${error.type.toUpperCase()}:</strong> ${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 <variable_name/>
- 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 = '<p style="color: #999; font-style: italic;">No templates detected</p>';
- 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 = `<span class="template-brackets"><</span><span class="template-tag">${templateName}</span><span class="template-brackets">/></span><span class="template-count"></span>`;
- 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 = `
- <div class="config-info-item">
- <strong>Status:</strong> <span class="config-status-unconfigured">Not configured</span><br>
- <strong>Template:</strong> <${templateName}/><br><br>
- <em>Click one of the configuration buttons (Op, Data, Normal) to set up this template.</em>
- </div>
- `;
- } else {
- const configTypeDisplay = template.configType ?
- template.configType.charAt(0).toUpperCase() + template.configType.slice(1) :
- 'Unknown';
-
- content.innerHTML = `
- <div class="config-info-item">
- <strong>Status:</strong> <span class="config-status-configured">Configured</span><br>
- <strong>Template:</strong> <${templateName}/><br>
- <strong>Type:</strong> ${configTypeDisplay}<br>
- <strong>Count:</strong> ${template.variables.length} value${template.variables.length > 1 ? 's' : ''}<br>
- <div class="config-info-values">
- <strong>Values:</strong><br>
- ${template.variables.join(', ')}
- </div>
- </div>
- `;
- }
-
- 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 = `<div class="success-message">✓ Template <${currentTemplate}/> configured as ${currentConfigType} with ${variables.length} variable${variables.length > 1 ? 's' : ''}</div>`;
- }
- // 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 = '<option value="">All Categories</option>';
-
- // 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 = '<p style="text-align: center; color: #666;">No operators found</p>';
- 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) ?
- '<span class="description-indicator" title="Has description/definition">📖</span>' : '';
-
- item.innerHTML = `
- <input type="checkbox" class="operator-checkbox" ${selectedOperators.has(operator.name) ? 'checked' : ''}>
- <div class="operator-info">
- <span class="operator-name">${operator.name} ${descriptionIndicator}</span>
- <span class="operator-category">${operator.category}</span>
- </div>
- `;
-
- 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 = '<em style="color: #666;">No operators selected</em>';
- return;
- }
-
- selectedOperators.forEach(operatorName => {
- const item = document.createElement('span');
- item.className = 'selected-item';
- item.innerHTML = `
- ${operatorName}
- <button class="remove-btn" onclick="removeSelectedOperator('${operatorName}')">×</button>
- `;
- 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 = '<tr><td colspan="7" style="text-align: center; color: #666; padding: 40px;">No data fields found matching the filters</td></tr>';
- 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 = `
- <td>
- <input type="checkbox" class="data-field-checkbox" ${selectedDataFields.has(field.id) ? 'checked' : ''}>
- </td>
- <td><span class="data-field-id">${field.id}</span></td>
- <td><span class="data-field-description">${field.description}</span></td>
- <td><span class="data-field-type">${field.type}</span></td>
- <td><span class="data-field-coverage">${(field.coverage * 100).toFixed(1)}%</span></td>
- <td><span class="data-field-count">${field.userCount.toLocaleString()}</span></td>
- <td><span class="data-field-count">${field.alphaCount.toLocaleString()}</span></td>
- `;
-
- 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 = '<em style="color: #666;">No data fields selected</em>';
- return;
- }
-
- selectedDataFields.forEach(fieldId => {
- const item = document.createElement('span');
- item.className = 'selected-item';
- item.innerHTML = `
- ${fieldId}
- <button class="remove-btn" onclick="removeSelectedDataField('${fieldId}')">×</button>
- `;
- 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 = '<option value="">All Types</option>';
-
- // 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';
- }
|