No features defined. Add categories and options below.
';
generateDescription(); // Clear description
return;
}
cdtConfig.categories.forEach(category => {
const formGroup = document.createElement('div');
formGroup.className = 'cdt-form-group';
const label = document.createElement('label');
label.htmlFor = `select-${category.id}`;
label.textContent = category.name;
const select = document.createElement('select');
select.id = `select-${category.id}`;
select.className = 'cdt-select';
select.dataset.categoryId = category.id;
select.onchange = handleOptionSelect; // Attach event listener
// Add default option
const defaultOption = document.createElement('option');
defaultOption.value = '';
defaultOption.textContent = `-- Select ${category.name} --`;
select.appendChild(defaultOption);
// Add category options
category.options.forEach(option => {
const opt = document.createElement('option');
opt.value = option.id;
opt.textContent = option.name;
// Pre-select if it's in the state
if (cdtState.selectedOptions[category.id] === option.id) {
opt.selected = true;
}
select.appendChild(opt);
});
formGroup.appendChild(label);
formGroup.appendChild(select);
builderGrid.appendChild(formGroup);
});
generateDescription(); // Update description after rendering selects
}
function generateDescription() {
if (!descriptionOutput) return;
let description = "Character Profile:\n";
let hasSelection = false;
cdtConfig.categories.forEach(category => {
const selectedOptionId = cdtState.selectedOptions[category.id];
if (selectedOptionId) {
const option = findOption(category.id, selectedOptionId);
if (option) {
description += `- ${category.name}: ${option.name}\n`;
hasSelection = true;
}
}
});
descriptionOutput.value = hasSelection ? description : "Select features above to generate a description...";
}
function renderConfig() {
if (!categoryList || !optionList || !optionsTitle || !addOptionBtn) return;
// 1. Render Categories
categoryList.innerHTML = '';
if(cdtConfig.categories.length === 0) {
categoryList.innerHTML = '
No categories yet.
';
}
cdtConfig.categories.forEach(category => {
const item = document.createElement('div');
item.className = 'cdt-list-item';
if (category.id === cdtState.selectedCategoryId) {
item.classList.add('selected');
}
item.dataset.categoryId = category.id;
item.onclick = handleCategorySelect; // Select category on click
item.innerHTML = `
${category.name}
`;
categoryList.appendChild(item);
});
// 2. Render Options for selected category
optionList.innerHTML = '';
addOptionBtn.disabled = !cdtState.selectedCategoryId;
if (cdtState.selectedCategoryId) {
const category = findCategory(cdtState.selectedCategoryId);
if (category) {
optionsTitle.textContent = `Options for ${category.name}`;
if (category.options.length === 0) {
optionList.innerHTML = '
No options yet.
';
}
category.options.forEach(option => {
const item = document.createElement('div');
item.className = 'cdt-list-item';
item.dataset.optionId = option.id;
item.innerHTML = `
${option.name}
`;
optionList.appendChild(item);
});
} else {
// Should not happen if state is managed correctly
optionsTitle.textContent = 'Options for...';
optionList.innerHTML = '
Error: Selected category not found.
';
cdtState.selectedCategoryId = null; // Reset selection
}
} else {
optionsTitle.textContent = 'Options for...';
optionList.innerHTML = '
Select a category to view/edit options.
';
}
}
function fullRefreshUI() {
renderBuilder();
renderConfig();
}
// === EVENT HANDLERS (Must be global for onclick) ===
window.cdtToggleEditSection = () => {
if (!editContent || !toggleEditText) return;
cdtState.editSectionVisible = !cdtState.editSectionVisible;
editContent.style.display = cdtState.editSectionVisible ? 'block' : 'none';
toggleEditText.textContent = cdtState.editSectionVisible ? 'Hide' : 'Show';
const icon = editContent.previousElementSibling.querySelector('button i');
if (icon) {
icon.className = cdtState.editSectionVisible ? 'fas fa-chevron-up' : 'fas fa-chevron-down';
}
};
// --- Builder Handlers ---
function handleOptionSelect(event) {
const selectElement = event.target;
const categoryId = selectElement.dataset.categoryId;
const optionId = selectElement.value;
if (optionId) {
cdtState.selectedOptions[categoryId] = optionId;
} else {
delete cdtState.selectedOptions[categoryId]; // Remove if default is selected
}
generateDescription(); // Update description immediately
}
// --- Config Handlers ---
window.cdtAddCategory = () => {
if (!newCategoryNameInput) return;
const name = newCategoryNameInput.value.trim();
if (!name) { alert('Please enter a category name.'); return; }
// Check for duplicates
if (cdtConfig.categories.some(c => c.name.toLowerCase() === name.toLowerCase())) {
alert('A category with this name already exists.'); return;
}
cdtConfig.categories.push({ id: 'c' + Date.now(), name, options: [] });
newCategoryNameInput.value = '';
renderConfig();
renderBuilder(); // Update the builder dropdowns
};
function handleCategorySelect(event) {
const item = event.currentTarget; // The div that was clicked
cdtState.selectedCategoryId = item.dataset.categoryId;
renderConfig(); // Re-render to show selection highlight and options
}
// Make it available globally for inline onclick
window.handleCategorySelect = handleCategorySelect;
window.cdtDeleteCategory = (id) => {
if (!confirm('Are you sure you want to delete this category and all its options?')) return;
cdtConfig.categories = cdtConfig.categories.filter(c => c.id !== id);
// Deselect if the deleted category was selected
if (cdtState.selectedCategoryId === id) {
cdtState.selectedCategoryId = null;
}
// Remove selections for this category from the character
delete cdtState.selectedOptions[id];
renderConfig();
renderBuilder();
};
window.cdtAddOption = () => {
if (!newOptionNameInput || !cdtState.selectedCategoryId) return;
const name = newOptionNameInput.value.trim();
if (!name) { alert('Please enter an option name.'); return; }
const category = findCategory(cdtState.selectedCategoryId);
if (!category) { alert('Error: No category selected.'); return; }
// Check for duplicates within the category
if (category.options.some(o => o.name.toLowerCase() === name.toLowerCase())) {
alert('An option with this name already exists in this category.'); return;
}
category.options.push({ id: 'o' + Date.now(), name });
newOptionNameInput.value = '';
renderConfig(); // Re-render options list
renderBuilder(); // Update the corresponding dropdown
};
window.cdtDeleteOption = (categoryId, optionId) => {
// No confirmation needed for quick deletion
const category = findCategory(categoryId);
if (category) {
category.options = category.options.filter(o => o.id !== optionId);
// If this option was selected for the character, remove it
if (cdtState.selectedOptions[categoryId] === optionId) {
delete cdtState.selectedOptions[categoryId];
}
renderConfig();
renderBuilder();
}
};
// --- PDF Download ---
window.cdtDownloadPDF = () => {
if (!jsPDF || !jsPDF.autoTable) { alert('PDF library not loaded.'); return; }
try {
const doc = new jsPDF();
const pageMargin = 15;
const pageWidth = doc.internal.pageSize.getWidth();
const contentWidth = pageWidth - pageMargin * 2;
let y = 20;
// 1. Title
doc.setFontSize(20);
doc.text("Character Sheet", pageWidth / 2, y, { align: 'center' });
y += 15;
// 2. Features Table
doc.setFontSize(16);
doc.text("Selected Features", pageMargin, y);
y += 10;
const tableHead = [["Category", "Selection"]];
const tableBody = [];
let hasSelections = false;
cdtConfig.categories.forEach(category => {
const selectedOptionId = cdtState.selectedOptions[category.id];
const option = selectedOptionId ? findOption(category.id, selectedOptionId) : null;
if (option) {
tableBody.push([category.name, option.name]);
hasSelections = true;
} else {
tableBody.push([category.name, "(Not Selected)"]);
}
});
if (tableBody.length > 0) {
doc.autoTable({
startY: y,
head: tableHead,
body: tableBody,
theme: 'striped',
headStyles: { fillColor: [0, 95, 204] }, // Primary color
});
y = doc.autoTable.previous.finalY + 15;
} else {
doc.setFontSize(12);
doc.text("No categories defined yet.", pageMargin, y);
y += 10;
}
// 3. Description
if (hasSelections && descriptionOutput) {
doc.setFontSize(16);
doc.text("Character Description", pageMargin, y);
y += 10;
doc.setFontSize(11);
doc.setFont(undefined, 'normal');
const splitText = doc.splitTextToSize(
descriptionOutput.value,
contentWidth
);
doc.text(splitText, pageMargin, y);
} else if (!hasSelections) {
doc.setFontSize(12);
doc.text("No features selected to generate a description.", pageMargin, y);
}
doc.save("Character_Sheet.pdf");
} catch (e) {
console.error("Error generating PDF:", e);
alert("An error occurred while generating the PDF.");
}
};
// === INITIALIZATION ===
fullRefreshUI();
// Initially hide edit section
if (editContent) editContent.style.display = 'none';
if (toggleEditText) toggleEditText.textContent = 'Show';
});