Simplified data structure around template files

This commit is contained in:
Nathan Schneider
2025-05-07 21:30:06 -06:00
parent cb0130714a
commit 5a4af2bf79
27 changed files with 1540 additions and 3664 deletions

View File

@ -199,39 +199,279 @@ document.addEventListener('DOMContentLoaded', function() {
});
}
// Load module data
let allModules = {};
try {
// Check if moduleData is defined (from modules.js)
if (typeof moduleData !== 'undefined') {
allModules = moduleData;
console.log('Module data loaded from modules.js');
// Load template data
let templates = [];
// Function to fetch templates - simplified approach
function fetchTemplates() {
console.log('Fetching templates...');
// Load all templates at startup
try {
// First try loading from the standard path
fetch('/js/templates/index.js')
.then(response => {
if (!response.ok) {
throw new Error(`Failed to fetch templates: ${response.status} ${response.statusText}`);
}
return response.text();
})
.then(moduleText => {
// Add a script element to the document
const script = document.createElement('script');
script.type = 'module';
script.textContent = moduleText;
document.head.appendChild(script);
// Set a timeout to check for templates
setTimeout(() => {
// See if templates were loaded
if (window.allTemplates && Array.isArray(window.allTemplates)) {
console.log('Templates loaded successfully:', window.allTemplates.length, 'templates found');
templates = window.allTemplates;
// Populate the template selector
populateTemplateSelector(templates);
// Add template selection event handler
if (protocolTemplateSelect) {
protocolTemplateSelect.addEventListener('change', handleTemplateSelection);
console.log('Template selector event handler attached');
} else {
console.error('Template select element not found');
}
} else {
console.error('Templates not available after loading script');
}
}, 500);
})
.catch(error => {
console.error('Error fetching templates:', error);
// Try alternate loading method with ES modules import
console.log('Trying alternate loading method with ES modules...');
import('/js/templates/index.js')
.then(module => {
templates = module.default;
console.log('Templates loaded successfully using ES modules:', templates.length, 'templates found');
// Populate the template selector
populateTemplateSelector(templates);
// Add template selection event handler
if (protocolTemplateSelect) {
protocolTemplateSelect.addEventListener('change', handleTemplateSelection);
console.log('Template selector event handler attached');
} else {
console.error('Template select element not found');
}
})
.catch(importError => {
console.error('ES module import also failed:', importError);
console.error('Could not load templates through either method');
});
});
} catch (e) {
console.error('Error in template loading:', e);
}
} catch (e) {
console.error('Error loading module data:', e);
allModules = {};
}
// Load and process template data
let templates = [];
try {
// Check if raw templates are defined (from templates.js)
if (typeof rawProtocolTemplates !== 'undefined' && typeof templateMapper !== 'undefined') {
console.log('Raw protocol templates loaded from templates.js');
// Function to populate the template selector dropdown
function populateTemplateSelector(templatesList) {
if (!protocolTemplateSelect || !templatesList || templatesList.length === 0) {
console.error('Cannot populate template selector - missing element or templates');
return;
}
console.log('Populating template selector with', templatesList.length, 'templates');
// Clear all existing options
while (protocolTemplateSelect.options.length > 0) {
protocolTemplateSelect.remove(0);
}
// Add the default "Create Your Own" option
const defaultOption = document.createElement('option');
defaultOption.value = "";
defaultOption.textContent = "-- Create Your Own Protocol --";
protocolTemplateSelect.appendChild(defaultOption);
// Verify templates have required properties
let validTemplateCount = 0;
templatesList.forEach(template => {
if (!template.id || !template.title || !template.description) {
console.warn('Template missing required fields:', template);
} else {
validTemplateCount++;
}
});
console.log(`Found ${validTemplateCount} valid templates out of ${templatesList.length} total`);
// Add template options
templatesList.forEach(template => {
const option = document.createElement('option');
option.value = template.id;
option.textContent = template.title;
protocolTemplateSelect.appendChild(option);
console.log('Added template option:', template.title, 'with ID:', template.id);
// Process each template to use module references
rawProtocolTemplates.forEach(rawTemplate => {
const processedTemplate = templateMapper.convertTemplateToModules(rawTemplate, allModules);
templates.push(processedTemplate);
// Debugging template structure
console.log(' > Description:', template.description ? template.description.substring(0, 50) + '...' : 'MISSING');
console.log(' > Has data:', template.data ? 'Yes' : 'No');
console.log(' > Has stages:', template.data?.stages ? 'Yes - ' + Object.keys(template.data.stages).length + ' stages' : 'No');
});
}
// Handle template selection
function handleTemplateSelection() {
const selectedTemplateId = this.value;
console.log('Template selection changed to:', selectedTemplateId);
if (selectedTemplateId) {
// Find the selected template from our loaded templates
const selectedTemplate = templates.find(t => t.id === selectedTemplateId);
if (selectedTemplate) {
console.log('Found template:', selectedTemplate.title);
applyTemplate(selectedTemplate);
} else {
console.error('Template not found:', selectedTemplateId);
}
} else {
// Clear all fields if "Create Your Own" is selected
document.querySelectorAll('textarea').forEach(textarea => {
textarea.value = '';
});
console.log('Processed templates to use module references:', templates);
// Reset protocol data
protocol = {
metadata: {
communityName: "",
summary: ""
},
stages: {}
};
// Collapse all sections
stageContents.forEach(content => {
content.style.display = 'none';
const toggleBtn = content.parentElement.querySelector('.toggle-btn');
if (toggleBtn) {
toggleBtn.textContent = '+';
}
});
// Update preview mode if active
if (previewModeActive) {
markComponentsWithContent();
}
}
} catch (e) {
console.error('Error processing protocol templates:', e);
templates = [];
}
// Function to apply a template to the form
function applyTemplate(selectedTemplate) {
if (!selectedTemplate) {
console.error('Cannot apply template: template is null or undefined');
return;
}
console.log('Applying template:', selectedTemplate.title);
console.log('Template data structure:', selectedTemplate);
// Reset protocol data while preserving metadata
protocol = {
metadata: {
communityName: communityNameInput.value || "",
summary: protocolSummaryTextarea.value || ""
},
stages: {},
templateId: selectedTemplate.id,
templateTitle: selectedTemplate.title,
templateDescription: selectedTemplate.description
};
// Always use the template description for summary when applying a template
protocolSummaryTextarea.value = selectedTemplate.description;
protocol.metadata.summary = selectedTemplate.description;
console.log('Set summary to template description:', selectedTemplate.description);
// Check if template has data.stages
if (!selectedTemplate.data || !selectedTemplate.data.stages) {
console.error('Template has invalid structure - missing data.stages:', selectedTemplate);
return;
}
console.log('Template has these stages:', Object.keys(selectedTemplate.data.stages));
// Count fields that will be populated
let fieldsPopulated = 0;
// Apply the template content directly to the form
for (const stageId in selectedTemplate.data.stages) {
console.log(`Processing stage ${stageId}...`);
if (!protocol.stages[stageId]) {
protocol.stages[stageId] = {};
}
for (const componentId in selectedTemplate.data.stages[stageId]) {
console.log(` Processing component ${componentId}...`);
if (!protocol.stages[stageId][componentId]) {
protocol.stages[stageId][componentId] = {};
}
for (const fieldId in selectedTemplate.data.stages[stageId][componentId]) {
const content = selectedTemplate.data.stages[stageId][componentId][fieldId];
const textarea = document.getElementById(fieldId);
console.log(` Processing field ${fieldId}:`);
console.log(` Content exists: ${Boolean(content)}`);
console.log(` Field element exists: ${Boolean(textarea)}`);
if (textarea && content) {
// Apply the content to the textarea
textarea.value = content;
// Store in protocol data
protocol.stages[stageId][componentId][fieldId] = content;
fieldsPopulated++;
console.log(` ✓ Populated field ${fieldId} with content (${content.length} chars)`);
} else {
if (!textarea) {
console.warn(` ✗ Textarea element not found for field ID: ${fieldId}`);
}
if (!content) {
console.warn(` ✗ No content for field ID: ${fieldId}`);
}
}
}
}
}
console.log(`Template application complete: ${fieldsPopulated} fields populated`);
// Expand all sections to show the populated content
stageContents.forEach(content => {
content.style.display = 'block';
const toggleBtn = content.parentElement.querySelector('.toggle-btn');
if (toggleBtn) {
toggleBtn.textContent = '-';
}
});
// Update preview mode if active
if (previewModeActive) {
markComponentsWithContent();
}
}
// No helper functions needed anymore as we've simplified the approach
// Start loading templates
fetchTemplates();
// Protocol data structure
let protocol = {
metadata: {
@ -262,9 +502,19 @@ document.addEventListener('DOMContentLoaded', function() {
const importJsonInput = document.getElementById('import-json');
const importBtn = document.getElementById('import-btn');
// Populate protocol template select
if (protocolTemplateSelect && templates.length > 0) {
templates.forEach(template => {
// Function to initialize the template selector
function initializeTemplateSelector(templatesList) {
if (!protocolTemplateSelect || !templatesList || templatesList.length === 0) {
return;
}
// Clear existing options
while (protocolTemplateSelect.options.length > 1) {
protocolTemplateSelect.remove(1);
}
// Add template options
templatesList.forEach(template => {
const option = document.createElement('option');
option.value = template.id;
option.textContent = template.title;
@ -272,239 +522,14 @@ document.addEventListener('DOMContentLoaded', function() {
});
// Add template selection event handler
protocolTemplateSelect.addEventListener('change', function() {
const selectedTemplateId = this.value;
if (selectedTemplateId) {
// Find the selected template
const selectedTemplate = templates.find(t => t.id === selectedTemplateId);
if (selectedTemplate) {
console.log('Applying template:', selectedTemplate);
console.log('Selected template:', selectedTemplate.title);
// Reset protocol data while preserving metadata
protocol = {
metadata: {
communityName: communityNameInput.value || "",
summary: protocolSummaryTextarea.value || ""
},
stages: {},
templateId: selectedTemplate.id,
templateTitle: selectedTemplate.title,
templateDescription: selectedTemplate.description
};
// If summary is empty, use template description
if (!protocol.metadata.summary) {
protocolSummaryTextarea.value = selectedTemplate.description;
protocol.metadata.summary = selectedTemplate.description;
}
// Apply the template module references to the form
for (const stageId in selectedTemplate.moduleRefs) {
if (!protocol.stages[stageId]) {
protocol.stages[stageId] = {};
}
for (const componentId in selectedTemplate.moduleRefs[stageId]) {
if (!protocol.stages[stageId][componentId]) {
protocol.stages[stageId][componentId] = {};
}
for (const fieldId in selectedTemplate.moduleRefs[stageId][componentId]) {
const moduleId = selectedTemplate.moduleRefs[stageId][componentId][fieldId];
const textarea = document.getElementById(fieldId);
if (textarea) {
// Find the module with this ID
let moduleContent = '';
for (const category in allModules) {
const foundModule = allModules[category].find(m => m.id === moduleId);
if (foundModule) {
moduleContent = foundModule.content;
break;
}
}
// Apply the module content to the textarea
textarea.value = moduleContent;
// Store in protocol data
protocol.stages[stageId][componentId][fieldId] = moduleContent;
// If there's a template selector for this field, update it
const moduleSelector = document.querySelector(`select.module-select[data-field-id="${fieldId}"][data-component-id="${componentId}"]`);
if (moduleSelector) {
moduleSelector.value = moduleId;
}
}
}
}
}
// Expand all sections to show the populated content
stageContents.forEach(content => {
content.style.display = 'block';
const toggleBtn = content.parentElement.querySelector('.toggle-btn');
if (toggleBtn) {
toggleBtn.textContent = '-';
}
});
// Update preview mode if active
if (previewModeActive) {
markComponentsWithContent();
}
}
} else {
// Clear all fields if "Create Your Own" is selected
document.querySelectorAll('textarea').forEach(textarea => {
textarea.value = '';
});
// Reset protocol data
protocol = { stages: {} };
// Collapse all sections
stageContents.forEach(content => {
content.style.display = 'none';
const toggleBtn = content.parentElement.querySelector('.toggle-btn');
if (toggleBtn) {
toggleBtn.textContent = '+';
}
});
// Update preview mode if active
if (previewModeActive) {
markComponentsWithContent();
}
}
});
protocolTemplateSelect.addEventListener('change', handleTemplateSelection);
}
// Initialize all module selects
populateModuleSelects();
// Module selection handlers
moduleSelects.forEach(select => {
select.addEventListener('change', function() {
const fieldId = this.getAttribute('data-field-id');
const componentId = this.getAttribute('data-component-id');
const targetTextarea = document.getElementById(fieldId);
if (targetTextarea && this.value) {
// Find the selected module
for (const category in allModules) {
const selectedModule = allModules[category].find(m => m.id === this.value);
if (selectedModule) {
targetTextarea.value = selectedModule.content;
// Update protocol data
updateProtocolData();
// Update preview mode if active
if (previewModeActive) {
markComponentsWithContent();
}
break;
}
}
}
});
// Hide module selectors since we're using templates directly
document.querySelectorAll('.module-selector').forEach(selector => {
selector.style.display = 'none';
});
// Function to populate module select dropdowns
function populateModuleSelects() {
console.log('Populating module selects...');
console.log('Available module categories:', Object.keys(allModules));
// Debugging: Log all modules to check componentId and fieldId
console.log('All module mapping:');
for (const category in allModules) {
console.log(`Category: ${category}`);
allModules[category].forEach(module => {
console.log(` Module: ${module.id}, Component: ${module.componentId}, Field: ${module.fieldId}`);
});
}
moduleSelects.forEach(select => {
const fieldId = select.getAttribute('data-field-id');
const componentId = select.getAttribute('data-component-id');
console.log(`Processing module select for fieldId: ${fieldId}, componentId: ${componentId}`);
// Clear existing options except the first one
while (select.options.length > 1) {
select.remove(1);
}
// Find modules that match this field and component
let hasOptions = false;
// Always show select first - we'll hide it later if no options found
select.closest('.module-selector').style.display = 'flex';
// Check for case matching issues and missing references
for (const category in allModules) {
let exactMatches = allModules[category].filter(m =>
m.fieldId === fieldId && m.componentId === componentId
);
let caseInsensitiveMatches = allModules[category].filter(m =>
m.fieldId.toLowerCase() === fieldId.toLowerCase() &&
m.componentId.toLowerCase() === componentId.toLowerCase() &&
!exactMatches.includes(m)
);
if (caseInsensitiveMatches.length > 0) {
console.warn(`Found ${caseInsensitiveMatches.length} case-insensitive matches for ${componentId}/${fieldId}. Consider fixing these module references.`);
// Add case-insensitive matches to the collection
caseInsensitiveMatches.forEach(module => {
// Create a copy with corrected references
const correctedModule = {
...module,
componentId: componentId,
fieldId: fieldId
};
// Add to the exact matches
exactMatches.push(correctedModule);
});
}
if (exactMatches.length > 0) {
console.log(`Found ${exactMatches.length} modules in category ${category} for ${componentId}/${fieldId}`);
hasOptions = true;
// Don't use option groups - add options directly to the select
// This avoids showing category labels which can be confusing
exactMatches.forEach(module => {
const option = document.createElement('option');
option.value = module.id;
// Use the module title directly from the definition
// This relies on proper module titles being defined in modules.js
option.textContent = module.title;
// Add directly to select instead of to a group
select.appendChild(option);
});
}
}
// If no modules found, hide the selector
if (!hasOptions) {
console.log(`No modules found for ${componentId}/${fieldId}, hiding selector`);
select.closest('.module-selector').style.display = 'none';
}
});
}
// Update protocol data from form inputs
function updateProtocolData() {
// Update metadata
@ -862,7 +887,8 @@ document.addEventListener('DOMContentLoaded', function() {
});
// Import from JSON
importBtn.addEventListener('click', function() {
importBtn.addEventListener('click', function(e) {
e.preventDefault(); // Prevent the default anchor behavior
importJsonInput.click();
});