|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Exam Paper Creator</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet"> |
|
|
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> |
|
|
<script src="https://unpkg.com/feather-icons"></script> |
|
|
<script src="https://js.puter.com/v2/"></script> |
|
|
<script> |
|
|
tailwind.config = { |
|
|
theme: { |
|
|
extend: { |
|
|
colors: { |
|
|
primary: '#2563eb', |
|
|
secondary: '#64748b', |
|
|
accent: '#f59e0b', |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
</script> |
|
|
<style> |
|
|
|
|
|
#paperContent { |
|
|
transition: transform 0.2s ease; |
|
|
transform-origin: top center; |
|
|
} |
|
|
|
|
|
.paper-container { |
|
|
width: 210mm; |
|
|
min-height: 297mm; |
|
|
padding: 20mm; |
|
|
margin: 10mm auto; |
|
|
box-shadow: 0 0 10px rgba(0,0,0,0.1); |
|
|
background: white; |
|
|
} |
|
|
.question-item { |
|
|
position: relative; |
|
|
margin-bottom: 20px; |
|
|
padding: 15px; |
|
|
border-radius: 8px; |
|
|
border-left: 4px solid #2563eb; |
|
|
} |
|
|
.question-item:hover { |
|
|
background-color: #f8fafc; |
|
|
} |
|
|
.option-grid { |
|
|
display: grid; |
|
|
grid-template-columns: 1fr 1fr; |
|
|
gap: 15px; |
|
|
margin-top: 10px; |
|
|
} |
|
|
.subquestions-container { |
|
|
margin-left: 30px; |
|
|
border-left: 2px dashed #cbd5e1; |
|
|
padding-left: 15px; |
|
|
} |
|
|
.image-preview { |
|
|
max-width: 100%; |
|
|
max-height: 200px; |
|
|
border-radius: 6px; |
|
|
margin: 10px 0; |
|
|
} |
|
|
.popup-overlay { |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
bottom: 0; |
|
|
background-color: rgba(0,0,0,0.5); |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
} |
|
|
#questionModal.popup-overlay { |
|
|
z-index: 3000; |
|
|
} |
|
|
#subquestionTypeModal.popup-overlay { |
|
|
z-index: 5000; |
|
|
} |
|
|
.popup-content { |
|
|
background: white; |
|
|
border-radius: 12px; |
|
|
padding: 24px; |
|
|
max-width: 600px; |
|
|
width: 90%; |
|
|
max-height: 90vh; |
|
|
overflow-y: auto; |
|
|
} |
|
|
.question-type-btn { |
|
|
transition: all 0.2s ease; |
|
|
} |
|
|
.question-type-btn:hover { |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); |
|
|
} |
|
|
.text-size-control { |
|
|
position: fixed; |
|
|
bottom: 20px; |
|
|
right: 20px; |
|
|
background: white; |
|
|
border-radius: 50px; |
|
|
padding: 10px; |
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15); |
|
|
z-index: 100; |
|
|
} |
|
|
#questionsContainer .question-item { |
|
|
position: relative; |
|
|
z-index: 1; |
|
|
} |
|
|
.popup-overlay { |
|
|
z-index: 9999; |
|
|
} |
|
|
.question-type-btn.selected { |
|
|
background-color: #2563eb; |
|
|
color: white; |
|
|
border-color: #2563eb; |
|
|
} |
|
|
|
|
|
.paper-container { |
|
|
width: 210mm; |
|
|
min-height: 297mm; |
|
|
padding: 20mm; |
|
|
margin: 10mm auto; |
|
|
box-shadow: 0 0 10px rgba(0,0,0,0.1); |
|
|
background: white; |
|
|
box-sizing: border-box; |
|
|
} |
|
|
|
|
|
.question-item { |
|
|
break-inside: avoid; |
|
|
page-break-inside: avoid; |
|
|
} |
|
|
|
|
|
#questionsContainer { |
|
|
break-after: always; |
|
|
page-break-after: always; |
|
|
} |
|
|
|
|
|
.paper-container + .paper-container { |
|
|
margin-top: 20mm; |
|
|
box-shadow: 0 0 10px rgba(0,0,0,0.1); |
|
|
} |
|
|
|
|
|
.image-preview { |
|
|
max-width: 100%; |
|
|
max-height: 200px; |
|
|
border-radius: 6px; |
|
|
margin: 10px 0; |
|
|
} |
|
|
|
|
|
#paperContent img { |
|
|
width: auto; |
|
|
height: auto; |
|
|
max-width: 100%; |
|
|
max-height: 200px; |
|
|
} |
|
|
|
|
|
#logoContainer img { |
|
|
max-height: 64px; |
|
|
height: auto; |
|
|
width: auto; |
|
|
} |
|
|
|
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-100 min-h-screen"> |
|
|
|
|
|
<div id="setupModal" class="popup-overlay"> |
|
|
<div class="popup-content" data-aos="zoom-in"> |
|
|
<h2 class="text-2xl font-bold text-center mb-6">Create Your Exam Paper</h2> |
|
|
<div class="space-y-4"> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">School Name</label> |
|
|
<input type="text" id="schoolName" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" placeholder="Enter school name"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Exam Name</label> |
|
|
<input type="text" id="examName" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" placeholder="Enter exam name"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Language</label> |
|
|
<select id="language" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary"> |
|
|
<option value="english">English</option> |
|
|
<option value="hindi">Hindi</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Total Time (Hour)</label> |
|
|
<input type="number" id="totalTime" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" placeholder="Enter total time"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">School Logo (Optional)</label> |
|
|
<input type="file" id="schoolLogo" accept="image/*" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Important Notes (Optional)</label> |
|
|
<textarea id="importantNotes" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" rows="3" placeholder="e.g., Use of calculator not allowed, Show all steps, etc."></textarea> |
|
|
</div> |
|
|
<div class="flex justify-end pt-4"> |
|
|
<button onclick="saveSetup()" class="bg-primary text-white px-6 py-2 rounded-md hover:bg-blue-700 transition">Create Paper</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="addQuestionModal" class="popup-overlay hidden"> |
|
|
<div class="popup-content" data-aos="zoom-in"> |
|
|
<h2 class="text-2xl font-bold text-center mb-6">Add Question</h2> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Marks for this question</label> |
|
|
<input type="number" id="questionMarks" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" placeholder="Enter marks"> |
|
|
</div> |
|
|
|
|
|
<h3 class="text-lg font-semibold mb-4">Select Question Type</h3> |
|
|
<div class="grid grid-cols-2 gap-4"> |
|
|
<button onclick="selectQuestionType('mcq', this)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="check-square" class="mx-auto mb-2"></i> |
|
|
<p>Multiple Choice</p> |
|
|
</button> |
|
|
<button onclick="selectQuestionType('normal', this)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="edit" class="mx-auto mb-2"></i> |
|
|
<p>Normal Question</p> |
|
|
</button> |
|
|
<button onclick="selectQuestionType('truefalse', this)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="check-circle" class="mx-auto mb-2"></i> |
|
|
<p>True/False</p> |
|
|
</button> |
|
|
<button onclick="selectQuestionType('imagefirst', this)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="image" class="mx-auto mb-2"></i> |
|
|
<p>Image with Question</p> |
|
|
</button> |
|
|
<button onclick="selectQuestionType('questionfirst', this)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="type" class="mx-auto mb-2"></i> |
|
|
<p>Question with Image</p> |
|
|
</button> |
|
|
<button onclick="selectQuestionType('subquestion', this)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="layers" class="mx-auto mb-2"></i> |
|
|
<p>Question with Subquestions</p> |
|
|
</button> |
|
|
<button onclick="selectQuestionType('questionspace', this)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="align-left" class="mx-auto mb-2"></i> |
|
|
<p>Question with Space</p> |
|
|
</button> |
|
|
<button onclick="selectQuestionType('spacequestion', this)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="align-right" class="mx-auto mb-2"></i> |
|
|
<p>Space with Question</p> |
|
|
</button> |
|
|
<button onclick="selectQuestionType('blank', this)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="square" class="mx-auto mb-2"></i> |
|
|
<p>Blank Space</p> |
|
|
</button> |
|
|
<button onclick="selectQuestionType('orquestion', this)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="list" class="mx-auto mb-2"></i> |
|
|
<p>Or Question</p> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="flex justify-end space-x-3 mt-6 pt-4 border-t"> |
|
|
<button onclick="closeAddQuestionModal()" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50">Cancel</button> |
|
|
<button id="nextToDetailsBtn" onclick="showQuestionDetails()" class="bg-primary text-white px-4 py-2 rounded-md hover:bg-blue-700 hidden">Next</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="questionDetailsModal" class="popup-overlay hidden"> |
|
|
<div class="popup-content" data-aos="zoom-in"> |
|
|
<h2 id="detailsModalTitle" class="text-2xl font-bold text-center mb-6">Question Details</h2> |
|
|
|
|
|
|
|
|
<div id="questionDetailsContent"></div> |
|
|
|
|
|
<div class="flex justify-between mt-6 pt-4 border-t"> |
|
|
<button onclick="backToTypeSelection()" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50"> |
|
|
<i data-feather="arrow-left" class="inline mr-2"></i> Back |
|
|
</button> |
|
|
<div class="space-x-3"> |
|
|
<button onclick="showUpcomingAlert('scan')" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50"> |
|
|
<i data-feather="camera" class="inline mr-2"></i> Scan |
|
|
</button> |
|
|
<button onclick="showUpcomingAlert('ai')" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50"> |
|
|
<i data-feather="cpu" class="inline mr-2"></i> AI Rewrite |
|
|
</button> |
|
|
<button onclick="addQuestionToPaper()" class="bg-primary text-white px-4 py-2 rounded-md hover:bg-blue-700"> |
|
|
Add Question |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="subquestionModal" class="popup-overlay hidden"> |
|
|
<div class="popup-content" data-aos="zoom-in"> |
|
|
<h2 class="text-2xl font-bold text-center mb-6">Add Subquestion</h2> |
|
|
<div id="subquestionContainer" class="space-y-4 mb-4"></div> |
|
|
|
|
|
<div class="flex justify-between mt-6 pt-4 border-t"> |
|
|
<button onclick="addSubquestionType()" class="px-4 py-2 border border-primary text-primary rounded-md hover:bg-blue-50"> |
|
|
<i data-feather="plus" class="inline mr-2"></i> Add Another |
|
|
</button> |
|
|
<button onclick="finishSubquestions()" class="bg-primary text-white px-4 py-2 rounded-md hover:bg-blue-700">Done</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="container mx-auto py-8"> |
|
|
<div class="flex justify-between items-center mb-6 px-4"> |
|
|
<div class="flex items-center space-x-3"> |
|
|
<button onclick="showAddQuestionModal()" class="bg-primary text-white px-4 py-2 rounded-md hover:bg-blue-700 flex items-center"> |
|
|
<i data-feather="plus" class="mr-2"></i> Add Question |
|
|
</button> |
|
|
<button onclick="confirmNewPaper()" class="bg-gray-600 text-white px-4 py-2 rounded-md hover:bg-gray-700 flex items-center"> |
|
|
<i data-feather="file-plus" class="mr-2"></i> New Paper |
|
|
</button> |
|
|
<button id="downloadPdf" class="bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 flex items-center"> |
|
|
<i data-feather="download" class="mr-2"></i> Export PDF |
|
|
</button> |
|
|
<button onclick="useofAI()" id="useAI" class="bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 flex items-center"> |
|
|
<i data-feather="book" class="mr-2"></i> AI |
|
|
</button> |
|
|
<button onclick="editPaperDetails()" class="bg-amber-600 text-white px-4 py-2 rounded-md hover:bg-amber-700 flex items-center"> |
|
|
<i data-feather="edit" class="mr-2"></i> Edit Paper |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="paper-container" id="paperContent"> |
|
|
|
|
|
<div class="text-center mb-8"> |
|
|
<div class="flex items-start justify-between"> |
|
|
<div id="logoContainer" class="mr-4"></div> |
|
|
<div class="text-center flex-1"> |
|
|
<h2 id="schoolNameDisplay" class="text-xl font-bold uppercase"></h2> |
|
|
<h3 id="examNameDisplay" class="text-lg font-semibold mt-2"></h3> |
|
|
</div> |
|
|
</div> |
|
|
<div id="importantNotesSection" class="mt-4 text-sm italic text-gray-700 text-left"></div> |
|
|
<div class="flex justify-between items-center mt-6 text-sm"> |
|
|
<div>Time: <span id="paperTimeDisplay"></span></div> |
|
|
<div>Total Marks: <span id="paperTotalMarksDisplay">0</span></div> |
|
|
</div> |
|
|
<div class="border-b border-gray-300 mt-4"></div> |
|
|
</div> |
|
|
<div id="questionsContainer"></div> |
|
|
</div> |
|
|
|
|
|
<div class="text-center mt-4 text-gray-600"> |
|
|
Page <span id="currentPage">1</span> of <span id="totalPages">1</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="text-size-control flex items-center space-x-2"> |
|
|
<button onclick="decreaseTextSize()" class="w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center hover:bg-gray-200"> |
|
|
<i data-feather="minus"></i> |
|
|
</button> |
|
|
<span class="text-sm">Text Size</span> |
|
|
<button onclick="increaseTextSize()" class="w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center hover:bg-gray-200"> |
|
|
<i data-feather="plus"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
let currentQuestionType = ''; |
|
|
let questions = []; |
|
|
let textSize = 16; |
|
|
let paperScale = 1.0; |
|
|
let baseFontSize = 16; |
|
|
let currentScale = 1.0; |
|
|
let paperData = { |
|
|
schoolName: '', |
|
|
examName: '', |
|
|
language: 'english', |
|
|
totalTime: 0, |
|
|
importantNotes: '', // ← added |
|
|
date: new Date() |
|
|
}; |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
document.getElementById('questionDetailsModal').addEventListener('change', function(e) { |
|
|
if (e.target.id === 'imageUpload') { |
|
|
const file = e.target.files[0]; |
|
|
if (file) { |
|
|
const reader = new FileReader(); |
|
|
reader.onload = (event) => { |
|
|
document.getElementById('imageDataHidden').value = event.target.result; // store Base64 |
|
|
}; |
|
|
reader.readAsDataURL(file); |
|
|
} else { |
|
|
document.getElementById('imageDataHidden').value = ''; |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
AOS.init(); |
|
|
feather.replace(); |
|
|
const savedData = localStorage.getItem('examPaperData'); |
|
|
if (savedData) { |
|
|
try { |
|
|
paperData = JSON.parse(savedData); |
|
|
// Ensure questions is always an array |
|
|
if (Array.isArray(paperData.questions)) { |
|
|
questions = paperData.questions; |
|
|
} else { |
|
|
console.warn('Invalid questions format in localStorage. Resetting to empty array.'); |
|
|
questions = []; |
|
|
paperData.questions = questions; |
|
|
} |
|
|
} catch (e) { |
|
|
console.error('Failed to parse saved data', e); |
|
|
questions = []; |
|
|
paperData = { schoolName: '', examName: '', language: 'english', totalTime: 0, date: new Date(), questions: [] }; |
|
|
} |
|
|
updatePaperHeader(); |
|
|
renderLogo(); |
|
|
renderQuestions(); |
|
|
document.getElementById('setupModal').classList.add('hidden'); |
|
|
} |
|
|
updateDateTime(); |
|
|
setInterval(updateDateTime, 60000); |
|
|
}); |
|
|
|
|
|
function confirmNewPaper() { |
|
|
if (confirm('Are you sure you want to start a new question paper? All current data will be lost.')) { |
|
|
// Clear data |
|
|
questions = []; |
|
|
paperData = { |
|
|
schoolName: '', |
|
|
examName: '', |
|
|
language: 'english', |
|
|
totalTime: 0, |
|
|
date: new Date() |
|
|
}; |
|
|
// Clear localStorage |
|
|
localStorage.removeItem('examPaperData'); |
|
|
// Reset UI |
|
|
document.getElementById('questionsContainer').innerHTML = ''; |
|
|
document.getElementById('paperTotalMarksDisplay').textContent = '0'; |
|
|
document.getElementById('setupModal').classList.remove('hidden'); |
|
|
} |
|
|
} |
|
|
|
|
|
function renderLogo() { |
|
|
const logoContainer = document.getElementById('logoContainer'); |
|
|
if (paperData.logo) { |
|
|
logoContainer.innerHTML = `<img src="${paperData.logo}" alt="School Logo" class="h-16 object-contain">`; |
|
|
} else { |
|
|
logoContainer.innerHTML = ''; |
|
|
} |
|
|
} |
|
|
|
|
|
function getReadableQuestionTypeLabel(type) { |
|
|
const map = { |
|
|
'mcq': 'Multiple Choice', |
|
|
'normal': 'Normal Question', |
|
|
'truefalse': 'True/False', |
|
|
'imagefirst': 'Image with Question', |
|
|
'questionfirst': 'Question with Image', |
|
|
'questionspace': 'Question with Space', |
|
|
'spacequestion': 'Space with Question', |
|
|
'blank': 'Blank Space', |
|
|
'orquestion': 'Or Question', |
|
|
'subquestion': 'Subquestion' |
|
|
}; |
|
|
return map[type] || ''; |
|
|
} |
|
|
|
|
|
function removeSubquestion(btn) { |
|
|
const item = btn.closest('.subquestion-item'); |
|
|
if (!item) return; |
|
|
const id = item.getAttribute('data-id'); |
|
|
|
|
|
// remove from editor container |
|
|
const container = document.getElementById('subquestionContainer'); |
|
|
const modalItem = container ? container.querySelector(`.subquestion-item[data-id="${id}"]`) : null; |
|
|
if (modalItem) modalItem.remove(); |
|
|
|
|
|
// remove from preview/details container |
|
|
const detailsContainer = document.getElementById('subquestionsUI'); |
|
|
const detailsItem = detailsContainer ? detailsContainer.querySelector(`.subquestion-item[data-id="${id}"]`) : null; |
|
|
if (detailsItem) detailsItem.remove(); |
|
|
} |
|
|
|
|
|
function updateDateTime() { |
|
|
const now = new Date(); |
|
|
const timeStr = now.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); |
|
|
} |
|
|
|
|
|
function saveSetup() { |
|
|
const schoolName = document.getElementById('schoolName').value; |
|
|
const examName = document.getElementById('examName').value; |
|
|
const language = document.getElementById('language').value; |
|
|
const totalTime = document.getElementById('totalTime').value; |
|
|
const importantNotes = document.getElementById('importantNotes').value; |
|
|
const logoFile = document.getElementById('schoolLogo').files[0]; |
|
|
let logoData = null; |
|
|
if (logoFile) { |
|
|
const reader = new FileReader(); |
|
|
reader.onload = (e) => { |
|
|
paperData.logo = e.target.result; |
|
|
localStorage.setItem('examPaperData', JSON.stringify(paperData)); |
|
|
renderLogo(); |
|
|
}; |
|
|
reader.readAsDataURL(logoFile); |
|
|
} else { |
|
|
paperData.logo = null; |
|
|
} |
|
|
if (!schoolName || !examName || !totalTime) { |
|
|
alert('Please fill all required fields'); |
|
|
return; |
|
|
} |
|
|
paperData = { |
|
|
schoolName, |
|
|
examName, |
|
|
language, |
|
|
totalTime: parseInt(totalTime), |
|
|
importantNotes, |
|
|
logo: paperData.logo, // preserve existing logo if no new one uploaded |
|
|
date: new Date() |
|
|
}; |
|
|
localStorage.setItem('examPaperData', JSON.stringify(paperData)); |
|
|
updatePaperHeader(); |
|
|
document.getElementById('setupModal').classList.add('hidden'); |
|
|
} |
|
|
|
|
|
function updatePaperHeader() { |
|
|
document.getElementById('schoolNameDisplay').textContent = paperData.schoolName; |
|
|
document.getElementById('examNameDisplay').textContent = paperData.examName; |
|
|
document.getElementById('paperTimeDisplay').textContent = `${paperData.totalTime} Hours`; |
|
|
|
|
|
const notesSection = document.getElementById('importantNotesSection'); |
|
|
if (paperData.importantNotes && paperData.importantNotes.trim()) { |
|
|
const formattedNotes = paperData.importantNotes.replace(/\n/g, '<br>'); |
|
|
notesSection.innerHTML = `<strong>Notes:</strong> ${formattedNotes}`; |
|
|
notesSection.classList.remove('hidden'); |
|
|
} else { |
|
|
notesSection.innerHTML = ''; |
|
|
notesSection.classList.add('hidden'); |
|
|
} |
|
|
|
|
|
updateTotalMarks(); |
|
|
} |
|
|
|
|
|
function showAddQuestionModal() { |
|
|
document.getElementById('addQuestionModal').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function closeAddQuestionModal() { |
|
|
document.getElementById('addQuestionModal').classList.add('hidden'); |
|
|
document.getElementById('nextToDetailsBtn').classList.add('hidden'); |
|
|
currentQuestionType = ''; |
|
|
} |
|
|
|
|
|
function selectQuestionType(type, button) { |
|
|
currentQuestionType = type; |
|
|
document.querySelectorAll('.question-type-btn').forEach(btn => { |
|
|
btn.classList.remove('selected'); |
|
|
}); |
|
|
button.classList.add('selected'); |
|
|
document.getElementById('nextToDetailsBtn').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function showQuestionDetails() { |
|
|
if (!currentQuestionType) { |
|
|
alert('Please select a question type'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const marks = document.getElementById('questionMarks').value; |
|
|
if (!marks || marks <= 0) { |
|
|
alert('Please enter valid marks for this question'); |
|
|
return; |
|
|
} |
|
|
|
|
|
document.getElementById('addQuestionModal').classList.add('hidden'); |
|
|
|
|
|
// Set modal title based on type |
|
|
const typeTitles = { |
|
|
'mcq': 'Multiple Choice Question', |
|
|
'normal': 'Normal Question', |
|
|
'truefalse': 'True/False Question', |
|
|
'imagefirst': 'Image with Question', |
|
|
'questionfirst': 'Question with Image', |
|
|
'subquestion': 'Question with Subquestions', |
|
|
'questionspace': 'Question with Space', |
|
|
'spacequestion': 'Space with Question', |
|
|
'blank': 'Blank Space', |
|
|
'orquestion': 'Or Question' |
|
|
}; |
|
|
|
|
|
document.getElementById('detailsModalTitle').textContent = typeTitles[currentQuestionType]; |
|
|
|
|
|
// Generate content based on question type |
|
|
let contentHTML = ''; |
|
|
|
|
|
switch(currentQuestionType) { |
|
|
case 'subquestion': |
|
|
// Create parent object (only if not editing) |
|
|
const newQuestion = { |
|
|
type: 'subquestion', |
|
|
marks: parseInt(document.getElementById('questionMarks').value), |
|
|
id: Date.now(), |
|
|
mainTitle: '', |
|
|
subquestions: [] |
|
|
}; |
|
|
questions.push(newQuestion); |
|
|
|
|
|
// store the parent id on the details modal so we can find it later |
|
|
document.getElementById('questionDetailsModal').setAttribute('data-new-subquestion-id', newQuestion.id); |
|
|
|
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Main Question Title</label> |
|
|
<input type="text" id="mainQuestionTitle" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" placeholder="Enter main question title"> |
|
|
</div> |
|
|
<div id="subquestionsUI" class="space-y-3"></div> |
|
|
<div class="text-center py-4"> |
|
|
<button onclick="openSubquestionModal()" class="bg-primary text-white px-4 py-2 rounded-md hover:bg-blue-700"> |
|
|
<i data-feather="plus" class="inline mr-2"></i> Add Subquestions |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'mcq': |
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea id="mcqQuestion" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" rows="3" placeholder="Enter your question"></textarea> |
|
|
</div> |
|
|
<div class="option-grid"> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Option i)</label> |
|
|
<input type="text" id="option1" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" placeholder="Option 1"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Option ii)</label> |
|
|
<input type="text" id="option2" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" placeholder="Option 2"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Option iii)</label> |
|
|
<input type="text" id="option3" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" placeholder="Option 3"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Option iv)</label> |
|
|
<input type="text" id="option4" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" placeholder="Option 4"> |
|
|
</div> |
|
|
</div> |
|
|
<div class="mt-4 flex items-center"> |
|
|
<input type="checkbox" id="mcqAnswerOnPaper" class="mr-2"> |
|
|
<label for="mcqAnswerOnPaper" class="text-sm text-gray-700">Students answer on question paper</label> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'normal': |
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea id="normalQuestion" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" rows="3" placeholder="Enter your question"></textarea> |
|
|
</div> |
|
|
<div class="flex items-center"> |
|
|
<input type="checkbox" id="normalAnswerOnPaper" class="mr-2"> |
|
|
<label for="normalAnswerOnPaper" class="text-sm text-gray-700">Students answer on question paper</label> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'truefalse': |
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea id="tfQuestion" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" rows="3" placeholder="Enter your question"></textarea> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'imagefirst': |
|
|
case 'questionfirst': |
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">${currentQuestionType === 'imagefirst' ? 'Upload Image' : 'Question'}</label> |
|
|
${currentQuestionType === 'imagefirst' ? |
|
|
`<input type="file" id="imageUpload" accept="image/*" class="w-full px-4 py-2 border border-gray-300 rounded-md"><input type="hidden" id="imageDataHidden" value="">`: |
|
|
`<textarea id="questionText" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" rows="3" placeholder="Enter your question"></textarea>`} |
|
|
</div> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">${currentQuestionType === 'imagefirst' ? 'Question' : 'Upload Image'}</label> |
|
|
${currentQuestionType === 'imagefirst' ? |
|
|
`<textarea id="questionText" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" rows="3" placeholder="Enter your question"></textarea>` : |
|
|
`<input type="file" id="imageUpload" accept="image/*" class="w-full px-4 py-2 border border-gray-300 rounded-md"><input type="hidden" id="imageDataHidden" value="">`} |
|
|
</div> |
|
|
<div class="flex items-center"> |
|
|
<input type="checkbox" id="imageAnswerOnPaper" class="mr-2"> |
|
|
<label for="imageAnswerOnPaper" class="text-sm text-gray-700">Students answer on question paper</label> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'subquestion': |
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Main Question Title</label> |
|
|
<input type="text" id="mainQuestionTitle" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" placeholder="Enter main question title"> |
|
|
</div> |
|
|
<div class="text-center py-4"> |
|
|
<button onclick="openSubquestionModal()" class="bg-primary text-white px-4 py-2 rounded-md hover:bg-blue-700"> |
|
|
<i data-feather="plus" class="inline mr-2"></i> Add Subquestions |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'questionspace': |
|
|
case 'spacequestion': |
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea id="spaceQuestion" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" rows="3" placeholder="Enter your question"></textarea> |
|
|
</div> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Space Height (lines)</label> |
|
|
<input type="number" id="spaceHeight" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" value="5" min="1"> |
|
|
</div> |
|
|
<div class="flex items-center"> |
|
|
<input type="checkbox" id="spaceAnswerOnPaper" class="mr-2"> |
|
|
<label for="spaceAnswerOnPaper" class="text-sm text-gray-700">Students answer on question paper</label> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'blank': |
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Space Height (lines)</label> |
|
|
<input type="number" id="blankHeight" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" value="5" min="1"> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'orquestion': |
|
|
contentHTML = ` |
|
|
<div id="orQuestionsContainer" class="space-y-4"> |
|
|
<div class="or-question-item"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Option 1</label> |
|
|
<textarea class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" rows="2" placeholder="Enter question option"></textarea> |
|
|
</div> |
|
|
<div class="or-question-item"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Option 2</label> |
|
|
<textarea class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" rows="2" placeholder="Enter question option"></textarea> |
|
|
</div> |
|
|
</div> |
|
|
<div class="mt-4"> |
|
|
<button onclick="addOrOption()" class="text-primary hover:underline flex items-center"> |
|
|
<i data-feather="plus" class="inline mr-1 w-4 h-4"></i> Add another option |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
} |
|
|
|
|
|
document.getElementById('questionDetailsContent').innerHTML = contentHTML; |
|
|
feather.replace(); |
|
|
document.getElementById('questionDetailsModal').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function backToTypeSelection() { |
|
|
document.getElementById('questionDetailsModal').classList.add('hidden'); |
|
|
document.getElementById('addQuestionModal').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function addQuestionToPaper() { |
|
|
const marks = parseInt(document.getElementById('questionMarks').value); |
|
|
const editId = document.getElementById('questionDetailsModal').getAttribute('data-edit-id'); |
|
|
|
|
|
let questionData = { |
|
|
type: currentQuestionType, |
|
|
marks: marks, |
|
|
id: editId ? parseInt(editId) : Date.now() |
|
|
}; |
|
|
|
|
|
// Collect data based on question type |
|
|
switch(currentQuestionType) { |
|
|
case 'mcq': |
|
|
questionData.question = document.getElementById('mcqQuestion').value; |
|
|
questionData.options = [ |
|
|
document.getElementById('option1').value, |
|
|
document.getElementById('option2').value, |
|
|
document.getElementById('option3').value, |
|
|
document.getElementById('option4').value |
|
|
]; |
|
|
questionData.answerOnPaper = document.getElementById('mcqAnswerOnPaper').checked; |
|
|
break; |
|
|
|
|
|
case 'normal': |
|
|
questionData.question = document.getElementById('normalQuestion').value; |
|
|
questionData.answerOnPaper = document.getElementById('normalAnswerOnPaper').checked; |
|
|
break; |
|
|
|
|
|
case 'truefalse': |
|
|
questionData.question = document.getElementById('tfQuestion').value; |
|
|
break; |
|
|
|
|
|
case 'imagefirst': |
|
|
case 'questionfirst': |
|
|
questionData.question = document.getElementById('questionText').value; |
|
|
questionData.imageData = document.getElementById('imageDataHidden').value || null; |
|
|
questionData.answerOnPaper = document.getElementById('imageAnswerOnPaper').checked; |
|
|
break; |
|
|
|
|
|
case 'subquestion': |
|
|
const titleEl = document.getElementById('mainQuestionTitle'); |
|
|
// Prefer the id we stored earlier |
|
|
const newId = document.getElementById('questionDetailsModal').getAttribute('data-new-subquestion-id'); |
|
|
let mainQuestion = null; |
|
|
if (newId) mainQuestion = questions.find(q => q.id == newId); |
|
|
if (!mainQuestion) { |
|
|
// fallback |
|
|
mainQuestion = questions.find(q => q.type === 'subquestion'); |
|
|
} |
|
|
if (mainQuestion) { |
|
|
mainQuestion.mainTitle = titleEl ? titleEl.value : ''; |
|
|
questionData = mainQuestion; // reuse existing object |
|
|
} |
|
|
break; |
|
|
|
|
|
case 'questionspace': |
|
|
case 'spacequestion': |
|
|
questionData.question = document.getElementById('spaceQuestion').value; |
|
|
questionData.spaceHeight = parseInt(document.getElementById('spaceHeight').value); |
|
|
questionData.answerOnPaper = document.getElementById('spaceAnswerOnPaper').checked; |
|
|
break; |
|
|
|
|
|
case 'blank': |
|
|
questionData.spaceHeight = parseInt(document.getElementById('blankHeight').value); |
|
|
break; |
|
|
|
|
|
case 'orquestion': |
|
|
const orOptions = []; |
|
|
document.querySelectorAll('.or-question-item textarea').forEach((ta, index) => { |
|
|
orOptions.push({ |
|
|
id: index + 1, |
|
|
text: ta.value |
|
|
}); |
|
|
}); |
|
|
questionData.options = orOptions; |
|
|
break; |
|
|
} |
|
|
|
|
|
if (editId) { |
|
|
// Update existing question |
|
|
const index = questions.findIndex(q => q.id === parseInt(editId)); |
|
|
if (index !== -1) { |
|
|
questions[index] = { ...questions[index], ...questionData }; |
|
|
} |
|
|
} else { |
|
|
// Only push new questions if it's NOT subquestion |
|
|
if (currentQuestionType !== 'subquestion') { |
|
|
questions.push(questionData); |
|
|
} |
|
|
} |
|
|
|
|
|
renderQuestions(); |
|
|
updateTotalMarks(); |
|
|
|
|
|
// Close the modal |
|
|
document.getElementById('questionDetailsModal').classList.add('hidden'); |
|
|
document.getElementById('questionDetailsModal').removeAttribute('data-edit-id'); |
|
|
|
|
|
// Reset for next question |
|
|
currentQuestionType = ''; |
|
|
document.getElementById('questionMarks').value = ''; |
|
|
} |
|
|
|
|
|
function openSubquestionModal() { |
|
|
document.getElementById('questionDetailsModal').classList.add('hidden'); |
|
|
document.getElementById('subquestionModal').classList.remove('hidden'); |
|
|
|
|
|
const container = document.getElementById('subquestionContainer'); |
|
|
container.innerHTML = ''; |
|
|
|
|
|
// Get the parent question being edited |
|
|
const editId = document.getElementById('questionDetailsModal').getAttribute('data-edit-id'); |
|
|
const newId = document.getElementById('questionDetailsModal').getAttribute('data-new-subquestion-id'); |
|
|
const parentId = editId ? parseInt(editId) : (newId ? parseInt(newId) : null); |
|
|
|
|
|
let parentQuestion = null; |
|
|
if (parentId !== null) { |
|
|
parentQuestion = questions.find(q => q.id === parentId && q.type === 'subquestion'); |
|
|
} |
|
|
|
|
|
if (parentQuestion && parentQuestion.subquestions && parentQuestion.subquestions.length > 0) { |
|
|
// Rebuild editor UI from actual subquestion data |
|
|
parentQuestion.subquestions.forEach(sq => { |
|
|
const subquestionId = Date.now() + Math.floor(Math.random() * 1000); |
|
|
const item = document.createElement('div'); |
|
|
item.className = 'subquestion-item p-4 border border-gray-200 rounded-md mb-3'; |
|
|
item.setAttribute('data-id', subquestionId); |
|
|
item.setAttribute('data-type', sq.type); |
|
|
|
|
|
const readable = getReadableQuestionTypeLabel(sq.type); |
|
|
let html = ` |
|
|
<div class="flex justify-between items-start mb-3"> |
|
|
<h4 class="font-semibold">${readable}</h4> |
|
|
<button onclick="removeSubquestion(this)" class="text-red-600 hover:text-red-800"> |
|
|
<i data-feather="trash-2" class="w-4 h-4"></i> |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
// Reconstruct input fields with saved values |
|
|
switch (sq.type) { |
|
|
case 'mcq': |
|
|
html += ` |
|
|
<div class="mb-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea class="w-full px-3 py-2 border border-gray-300 rounded-md" rows="2">${sq.question || ''}</textarea> |
|
|
</div> |
|
|
<div class="grid grid-cols-2 gap-3 mb-3"> |
|
|
<div><label class="text-sm text-gray-600 block mb-1">i)</label><input type="text" class="w-full px-3 py-1 border border-gray-300 rounded-md" value="${sq.options?.[0] || ''}"></div> |
|
|
<div><label class="text-sm text-gray-600 block mb-1">ii)</label><input type="text" class="w-full px-3 py-1 border border-gray-300 rounded-md" value="${sq.options?.[1] || ''}"></div> |
|
|
<div><label class="text-sm text-gray-600 block mb-1">iii)</label><input type="text" class="w-full px-3 py-1 border border-gray-300 rounded-md" value="${sq.options?.[2] || ''}"></div> |
|
|
<div><label class="text-sm text-gray-600 block mb-1">iv)</label><input type="text" class="w-full px-3 py-1 border border-gray-300 rounded-md" value="${sq.options?.[3] || ''}"></div> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
case 'normal': |
|
|
case 'truefalse': |
|
|
html += ` |
|
|
<div class="mb-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea class="w-full px-3 py-2 border border-gray-300 rounded-md" rows="2">${sq.question || ''}</textarea> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
case 'imagefirst': |
|
|
case 'questionfirst': |
|
|
html += ` |
|
|
<div class="mb-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea class="w-full px-3 py-2 border border-gray-300 rounded-md" rows="2">${sq.question || ''}</textarea> |
|
|
</div> |
|
|
<div class="mb-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Image</label> |
|
|
<input type="file" class="w-full px-3 py-1 border border-gray-300 rounded-md"> |
|
|
${sq.imageData ? `<img src="${sq.imageData}" class="image-preview mt-2" style="max-height:100px;">` : ''} |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
case 'questionspace': |
|
|
case 'spacequestion': |
|
|
html += ` |
|
|
<div class="mb-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea class="w-full px-3 py-2 border border-gray-300 rounded-md" rows="2">${sq.question || ''}</textarea> |
|
|
</div> |
|
|
<div class="mb-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Space Height (lines)</label> |
|
|
<input type="number" class="w-full px-3 py-1 border border-gray-300 rounded-md" value="${sq.spaceHeight || 5}" min="1"> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
case 'blank': |
|
|
html += ` |
|
|
<div class="mb-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Space Height (lines)</label> |
|
|
<input type="number" class="w-full px-3 py-1 border border-gray-300 rounded-md" value="${sq.spaceHeight || 5}" min="1"> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
case 'orquestion': |
|
|
html += ` |
|
|
<div class="space-y-3 mb-3"> |
|
|
${(sq.options || []).map((opt, i) => ` |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Option ${i + 1}</label> |
|
|
<textarea class="w-full px-3 py-1 border border-gray-300 rounded-md" rows="1">${opt.text || ''}</textarea> |
|
|
</div> |
|
|
`).join('')} |
|
|
</div> |
|
|
<button onclick="addOrOptionToSubquestion(this)" class="text-primary text-sm hover:underline">+ Add another option</button> |
|
|
`; |
|
|
break; |
|
|
} |
|
|
|
|
|
html += ` |
|
|
<div class="mt-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Marks</label> |
|
|
<input type="number" class="w-20 px-3 py-1 border border-gray-300 rounded-md" value="${sq.marks || 1}" min="1"> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
item.innerHTML = html; |
|
|
container.appendChild(item); |
|
|
}); |
|
|
} else { |
|
|
container.innerHTML = ` |
|
|
<div class="text-center py-6 border-2 border-dashed border-gray-300 rounded-lg"> |
|
|
<p class="text-gray-500 mb-3">No subquestions added yet</p> |
|
|
<button onclick="addSubquestionType()" class="bg-primary text-white px-4 py-2 rounded-md hover:bg-blue-700"> |
|
|
<i data-feather="plus" class="inline mr-2"></i> Add Subquestion |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
feather.replace(); |
|
|
} |
|
|
|
|
|
function addSubquestionType() { |
|
|
// Create modal for selecting subquestion type |
|
|
const modal = document.createElement('div'); |
|
|
modal.className = 'popup-overlay'; |
|
|
modal.id = 'subquestionTypeModal'; |
|
|
modal.innerHTML = ` |
|
|
<div class="popup-content"> |
|
|
<h2 class="text-2xl font-bold text-center mb-6">Select Subquestion Type</h2> |
|
|
<div class="grid grid-cols-2 gap-4"> |
|
|
<button onclick="createSubquestion('mcq', true)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="check-square" class="mx-auto mb-2"></i> |
|
|
<p>Multiple Choice</p> |
|
|
</button> |
|
|
<button onclick="createSubquestion('normal', true)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="edit" class="mx-auto mb-2"></i> |
|
|
<p>Normal Question</p> |
|
|
</button> |
|
|
<button onclick="createSubquestion('truefalse', true)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="check-circle" class="mx-auto mb-2"></i> |
|
|
<p>True/False</p> |
|
|
</button> |
|
|
<button onclick="createSubquestion('imagefirst', true)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="image" class="mx-auto mb-2"></i> |
|
|
<p>Image with Question</p> |
|
|
</button> |
|
|
<button onclick="createSubquestion('questionfirst', true)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="type" class="mx-auto mb-2"></i> |
|
|
<p>Question with Image</p> |
|
|
</button> |
|
|
<button onclick="createSubquestion('questionspace', true)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="align-left" class="mx-auto mb-2"></i> |
|
|
<p>Question with Space</p> |
|
|
</button> |
|
|
<button onclick="createSubquestion('spacequestion', true)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="align-right" class="mx-auto mb-2"></i> |
|
|
<p>Space with Question</p> |
|
|
</button> |
|
|
<button onclick="createSubquestion('blank', true)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="square" class="mx-auto mb-2"></i> |
|
|
<p>Blank Space</p> |
|
|
</button> |
|
|
<button onclick="createSubquestion('orquestion', true)" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50"> |
|
|
<i data-feather="list" class="mx-auto mb-2"></i> |
|
|
<p>Or Question</p> |
|
|
</button> |
|
|
</div> |
|
|
<div class="flex justify-end mt-6 pt-4 border-t"> |
|
|
<button onclick="document.getElementById('subquestionTypeModal').remove()" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50">Cancel</button> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
document.body.appendChild(modal); |
|
|
modal.classList.remove('hidden'); |
|
|
document.getElementById('subquestionModal').style.display = 'none'; |
|
|
feather.replace(); |
|
|
} |
|
|
|
|
|
function createSubquestion(type, vie=false) { |
|
|
// remove the type selection modal |
|
|
if (vie) { |
|
|
document.getElementById('subquestionModal').style.removeProperty("display"); |
|
|
} |
|
|
const modal = document.getElementById('subquestionTypeModal'); |
|
|
if (modal) modal.remove(); |
|
|
|
|
|
const container = document.getElementById('subquestionContainer'); |
|
|
// Remove empty state if it exists |
|
|
if (container && container.innerHTML.includes('No subquestions')) { |
|
|
container.innerHTML = ''; |
|
|
} |
|
|
|
|
|
const subquestionId = Date.now(); |
|
|
const newSubquestion = document.createElement('div'); |
|
|
newSubquestion.className = 'subquestion-item p-4 border border-gray-200 rounded-md mb-3'; |
|
|
newSubquestion.setAttribute('data-id', subquestionId); |
|
|
newSubquestion.setAttribute('data-type', type); |
|
|
|
|
|
// Header + delete button (delete calls removeSubquestion to sync preview + editor) |
|
|
const readable = getReadableQuestionTypeLabel(type); |
|
|
let contentHTML = ` |
|
|
<div class="flex justify-between items-start mb-3"> |
|
|
<h4 class="font-semibold">${readable}</h4> |
|
|
<button onclick="removeSubquestion(this)" class="text-red-600 hover:text-red-800"> |
|
|
<i data-feather="trash-2" class="w-4 h-4"></i> |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
// Fill based on type |
|
|
switch(type) { |
|
|
case 'mcq': |
|
|
contentHTML += ` |
|
|
<div class="mb-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea class="w-full px-3 py-2 border border-gray-300 rounded-md" rows="2" placeholder="Enter question"></textarea> |
|
|
</div> |
|
|
<div class="grid grid-cols-2 gap-3 mb-3"> |
|
|
<div><label class="text-sm text-gray-600 block mb-1">i)</label><input type="text" class="w-full px-3 py-1 border border-gray-300 rounded-md" placeholder="Option 1"></div> |
|
|
<div><label class="text-sm text-gray-600 block mb-1">ii)</label><input type="text" class="w-full px-3 py-1 border border-gray-300 rounded-md" placeholder="Option 2"></div> |
|
|
<div><label class="text-sm text-gray-600 block mb-1">iii)</label><input type="text" class="w-full px-3 py-1 border border-gray-300 rounded-md" placeholder="Option 3"></div> |
|
|
<div><label class="text-sm text-gray-600 block mb-1">iv)</label><input type="text" class="w-full px-3 py-1 border border-gray-300 rounded-md" placeholder="Option 4"></div> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'normal': |
|
|
case 'truefalse': |
|
|
contentHTML += ` |
|
|
<div class="mb-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea class="w-full px-3 py-2 border border-gray-300 rounded-md" rows="2" placeholder="Enter question"></textarea> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'imagefirst': |
|
|
case 'questionfirst': |
|
|
contentHTML += ` |
|
|
<div class="mb-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea class="w-full px-3 py-2 border border-gray-300 rounded-md" rows="2" placeholder="Enter question"></textarea> |
|
|
</div> |
|
|
<div class="mb-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Image</label> |
|
|
<input type="file" class="w-full px-3 py-1 border border-gray-300 rounded-md"> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'questionspace': |
|
|
case 'spacequestion': |
|
|
// note: space height input comes BEFORE marks input so finishSubquestions can pick correctly |
|
|
contentHTML += ` |
|
|
<div class="mb-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea class="w-full px-3 py-2 border border-gray-300 rounded-md" rows="2" placeholder="Enter question"></textarea> |
|
|
</div> |
|
|
<div class="mb-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Space Height (lines)</label> |
|
|
<input type="number" class="w-full px-3 py-1 border border-gray-300 rounded-md" value="5" min="1"> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'blank': |
|
|
contentHTML += ` |
|
|
<div class="mb-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Space Height (lines)</label> |
|
|
<input type="number" class="w-full px-3 py-1 border border-gray-300 rounded-md" value="5" min="1"> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'orquestion': |
|
|
contentHTML += ` |
|
|
<div class="space-y-3 mb-3"> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Option 1</label> |
|
|
<textarea class="w-full px-3 py-1 border border-gray-300 rounded-md" rows="1" placeholder="Enter option"></textarea> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Option 2</label> |
|
|
<textarea class="w-full px-3 py-1 border border-gray-300 rounded-md" rows="1" placeholder="Enter option"></textarea> |
|
|
</div> |
|
|
</div> |
|
|
<button onclick="addOrOptionToSubquestion(this)" class="text-primary text-sm hover:underline">+ Add another option</button> |
|
|
`; |
|
|
break; |
|
|
} |
|
|
|
|
|
// Marks field - note: this should be the LAST number input to identify marks reliably |
|
|
contentHTML += ` |
|
|
<div class="mt-3"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Marks</label> |
|
|
<input type="number" class="w-20 px-3 py-1 border border-gray-300 rounded-md" value="1" min="1"> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
newSubquestion.innerHTML = contentHTML; |
|
|
|
|
|
// Append to modal subquestion container (editor) |
|
|
container.appendChild(newSubquestion); |
|
|
|
|
|
// Clone to preview area in details modal (so the user sees a live preview) |
|
|
const detailsContainer = document.getElementById("subquestionsUI"); |
|
|
if (detailsContainer) { |
|
|
detailsContainer.appendChild(newSubquestion.cloneNode(true)); |
|
|
} |
|
|
|
|
|
feather.replace(); |
|
|
} |
|
|
|
|
|
function addOrOptionToSubquestion(button) { |
|
|
const container = button.parentElement.querySelector('.space-y-3'); |
|
|
const optionCount = container.children.length + 1; |
|
|
|
|
|
const newOption = document.createElement('div'); |
|
|
newOption.innerHTML = ` |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Option ${optionCount}</label> |
|
|
<textarea class="w-full px-3 py-1 border border-gray-300 rounded-md" rows="1" placeholder="Enter option"></textarea> |
|
|
<button onclick="this.parentElement.remove()" class="text-red-600 text-xs hover:underline mt-1">Remove</button> |
|
|
</div> |
|
|
`; |
|
|
container.appendChild(newOption); |
|
|
} |
|
|
|
|
|
function finishSubquestions() { |
|
|
const subquestions = []; |
|
|
|
|
|
document.querySelectorAll('#subquestionContainer .subquestion-item').forEach((item) => { |
|
|
const type = item.getAttribute('data-type'); |
|
|
const numberInputs = item.querySelectorAll('input[type="number"]'); |
|
|
|
|
|
// last numeric input = marks, the earlier numeric input (if any) is space height |
|
|
let marks = 1; |
|
|
let spaceHeight; |
|
|
if (numberInputs.length) { |
|
|
marks = parseInt(numberInputs[numberInputs.length - 1].value) || 1; |
|
|
if (numberInputs.length > 1) { |
|
|
spaceHeight = parseInt(numberInputs[0].value) || 5; |
|
|
} |
|
|
} |
|
|
|
|
|
let questionData = { |
|
|
type: type, |
|
|
marks: marks |
|
|
}; |
|
|
|
|
|
switch (type) { |
|
|
case 'mcq': |
|
|
questionData.question = item.querySelector('textarea') ? item.querySelector('textarea').value : ''; |
|
|
questionData.options = Array.from(item.querySelectorAll('input[type="text"]')).map(opt => opt.value); |
|
|
break; |
|
|
|
|
|
case 'normal': |
|
|
case 'truefalse': |
|
|
questionData.question = item.querySelector('textarea') ? item.querySelector('textarea').value : ''; |
|
|
break; |
|
|
|
|
|
case 'imagefirst': |
|
|
case 'questionfirst': |
|
|
questionData.question = item.querySelector('textarea') ? item.querySelector('textarea').value : ''; |
|
|
const fileInput = item.querySelector('input[type="file"]'); |
|
|
if (fileInput && fileInput.files && fileInput.files[0]) { |
|
|
const file = fileInput.files[0]; |
|
|
const reader = new FileReader(); |
|
|
reader.onload = (e) => { |
|
|
questionData.imageData = e.target.result; // store base64 data |
|
|
}; |
|
|
reader.readAsDataURL(file); |
|
|
} else { |
|
|
questionData.imageData = null; // fallback if no file |
|
|
} |
|
|
break; |
|
|
|
|
|
case 'questionspace': |
|
|
case 'spacequestion': |
|
|
questionData.question = item.querySelector('textarea') ? item.querySelector('textarea').value : ''; |
|
|
questionData.spaceHeight = spaceHeight || 5; |
|
|
break; |
|
|
|
|
|
case 'blank': |
|
|
questionData.type = 'blank'; |
|
|
questionData.spaceHeight = spaceHeight || 5; |
|
|
break; |
|
|
|
|
|
case 'orquestion': |
|
|
questionData.options = Array.from(item.querySelectorAll('.space-y-3 textarea')).map((ta, idx) => ({ |
|
|
id: idx + 1, |
|
|
text: ta.value |
|
|
})); |
|
|
break; |
|
|
} |
|
|
|
|
|
subquestions.push(questionData); |
|
|
}); |
|
|
|
|
|
// Attach to the parent question using the id we stored on the details modal |
|
|
const mainId = document.getElementById('questionDetailsModal').getAttribute('data-new-subquestion-id'); |
|
|
let mainQuestion = null; |
|
|
if (mainId) { |
|
|
mainQuestion = questions.find(q => q.id == mainId); |
|
|
} |
|
|
// fallback: find the most recent subquestion parent |
|
|
if (!mainQuestion) { |
|
|
mainQuestion = [...questions].reverse().find(q => q.type === 'subquestion'); |
|
|
} |
|
|
|
|
|
if (mainQuestion) { |
|
|
mainQuestion.subquestions = subquestions; |
|
|
} |
|
|
|
|
|
// close the subquestion editor and return to details |
|
|
document.getElementById('subquestionModal').classList.add('hidden'); |
|
|
document.getElementById('questionDetailsModal').classList.remove('hidden'); |
|
|
|
|
|
// refresh preview in details (optional) |
|
|
const detailsContainer = document.getElementById('subquestionsUI'); |
|
|
if (detailsContainer) { |
|
|
detailsContainer.innerHTML = ''; |
|
|
subquestions.forEach((sq, idx) => { |
|
|
// make a simple preview node for each subquestion |
|
|
const preview = document.createElement('div'); |
|
|
preview.className = 'subquestion-item p-3 border border-gray-100 rounded mb-2'; |
|
|
preview.innerHTML = `<div class="font-medium">${getReadableQuestionTypeLabel(sq.type)}: ${sq.question || ''}</div>`; |
|
|
detailsContainer.appendChild(preview); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
function addOrOption() { |
|
|
const container = document.getElementById('orQuestionsContainer'); |
|
|
const count = container.children.length + 1; |
|
|
|
|
|
const newOption = document.createElement('div'); |
|
|
newOption.className = 'or-question-item'; |
|
|
newOption.innerHTML = ` |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Option ${count}</label> |
|
|
<textarea class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" rows="2" placeholder="Enter question option"></textarea> |
|
|
<button onclick="this.parentElement.remove()" class="mt-1 text-red-600 text-sm hover:underline flex items-center"> |
|
|
<i data-feather="trash-2" class="inline mr-1 w-4 h-4"></i> Remove |
|
|
</button> |
|
|
`; |
|
|
|
|
|
container.appendChild(newOption); |
|
|
feather.replace(); |
|
|
} |
|
|
|
|
|
function renderQuestions() { |
|
|
if (!Array.isArray(questions)) { |
|
|
console.error('questions is not an array in renderQuestions. Resetting.'); |
|
|
questions = []; |
|
|
} |
|
|
const container = document.getElementById('questionsContainer'); |
|
|
container.innerHTML = ''; |
|
|
|
|
|
// Create a temporary container to measure height |
|
|
const tempContainer = document.createElement('div'); |
|
|
tempContainer.style.width = '210mm'; |
|
|
tempContainer.style.padding = '20mm'; |
|
|
tempContainer.style.boxSizing = 'border-box'; |
|
|
tempContainer.style.position = 'absolute'; |
|
|
tempContainer.style.left = '-9999px'; |
|
|
tempContainer.style.top = '0'; |
|
|
document.body.appendChild(tempContainer); |
|
|
|
|
|
let currentPage = document.createElement('div'); |
|
|
currentPage.className = 'paper-page'; |
|
|
let currentPageHeight = 0; |
|
|
const pageHeight = 297 * 4; // ~297mm in pixels (approx 4px per mm) |
|
|
|
|
|
questions.forEach((q, index) => { |
|
|
const questionElement = document.createElement('div'); |
|
|
questionElement.className = 'question-item'; |
|
|
questionElement.setAttribute('data-id', q.id); |
|
|
|
|
|
// Build question HTML (same as before) |
|
|
let questionHTML = ` |
|
|
<div class="flex justify-between items-start mb-2"> |
|
|
<div class="font-semibold"> |
|
|
Q${index + 1}. ${ |
|
|
q.type === 'subquestion' |
|
|
? (q.mainTitle || '') |
|
|
: ['imagefirst','spacequestion','orquestion'].includes(q.type) |
|
|
? '' |
|
|
: q.question |
|
|
}${q.type === 'truefalse' ? ' (True / False)' : ''} |
|
|
</div> |
|
|
<div class="flex space-x-2"> |
|
|
<span class="text-sm text-blue-800 px-2 py-1 rounded">${q.marks || ''} marks</span> |
|
|
<button onclick="editQuestion(${q.id})" class="text-blue-600 hover:text-blue-800"> |
|
|
<i data-feather="edit" class="w-4 h-4"></i> |
|
|
</button> |
|
|
<button onclick="deleteQuestion(${q.id})" class="text-red-600 hover:text-red-800"> |
|
|
<i data-feather="trash-2" class="w-4 h-4"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
switch(q.type) { |
|
|
case 'mcq': |
|
|
questionHTML += ` |
|
|
<div class="option-grid mt-3"> |
|
|
<div>i) ${q.options[0]}</div> |
|
|
<div>ii) ${q.options[1]}</div> |
|
|
<div>iii) ${q.options[2]}</div> |
|
|
<div>iv) ${q.options[3]}</div> |
|
|
</div> |
|
|
`; |
|
|
if (q.answerOnPaper) { |
|
|
questionHTML += `<div class="mt-3 flex space-x-6"><div>i) □</div><div>ii) □</div><div>iii) □</div><div>iv) □</div></div>`; |
|
|
} |
|
|
break; |
|
|
case 'normal': |
|
|
if (q.answerOnPaper) { |
|
|
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`; |
|
|
} |
|
|
break; |
|
|
case 'truefalse': |
|
|
break; |
|
|
case 'imagefirst': |
|
|
questionHTML += ` |
|
|
<div class="mb-3"> |
|
|
${q.imageData ? `<img src="${q.imageData}" class="image-preview">` : '<div class="text-gray-500 italic">No image uploaded</div>'} |
|
|
</div> |
|
|
<div class="mb-2">${q.question}</div> |
|
|
`; |
|
|
if (q.answerOnPaper) { |
|
|
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`; |
|
|
} |
|
|
break; |
|
|
case 'questionfirst': |
|
|
questionHTML += ` |
|
|
<div class="mb-3"> |
|
|
${q.imageData ? `<img src="${q.imageData}" class="image-preview">` : '<div class="text-gray-500 italic">No image uploaded</div>'} |
|
|
</div> |
|
|
`; |
|
|
if (q.answerOnPaper) { |
|
|
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`; |
|
|
} |
|
|
break; |
|
|
case 'subquestion': |
|
|
if (q.subquestions && q.subquestions.length) { |
|
|
questionHTML += `<div class="subquestions-container ml-6">`; |
|
|
const romanNumerals = ['i','ii','iii','iv','v','vi','vii','viii','ix','x']; |
|
|
q.subquestions.forEach((sq, sqIndex) => { |
|
|
const label = romanNumerals[sqIndex] || (sqIndex + 1); |
|
|
questionHTML += `<div class="mb-3">`; |
|
|
questionHTML += `<div class="flex justify-between">`; |
|
|
questionHTML += `<div class="w-full">`; |
|
|
switch (sq.type) { |
|
|
case 'mcq': |
|
|
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`; |
|
|
questionHTML += `<div class="option-grid mt-2">`; |
|
|
questionHTML += `<div>i) ${sq.options && sq.options[0] ? sq.options[0] : ''}</div>`; |
|
|
questionHTML += `<div>ii) ${sq.options && sq.options[1] ? sq.options[1] : ''}</div>`; |
|
|
questionHTML += `<div>iii) ${sq.options && sq.options[2] ? sq.options[2] : ''}</div>`; |
|
|
questionHTML += `<div>iv) ${sq.options && sq.options[3] ? sq.options[3] : ''}</div>`; |
|
|
questionHTML += `</div>`; |
|
|
if (sq.answerOnPaper) { |
|
|
questionHTML += `<div class="mt-3 flex space-x-6"><div>i) □</div><div>ii) □</div><div>iii) □</div><div>iv) □</div></div>`; |
|
|
} |
|
|
break; |
|
|
case 'normal': |
|
|
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`; |
|
|
if (sq.answerOnPaper) questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`; |
|
|
break; |
|
|
case 'truefalse': |
|
|
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''} (True / False)</div>`; |
|
|
break; |
|
|
case 'imagefirst': |
|
|
questionHTML += `<div class="mb-3">${sq.imageData ? `<img src="${sq.imageData}" class="image-preview">` : '<span class="text-gray-500">No image</span>'}</div>`; |
|
|
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`; |
|
|
if (sq.answerOnPaper) questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`; |
|
|
break; |
|
|
case 'questionfirst': |
|
|
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`; |
|
|
questionHTML += `<div class="mb-3">${sq.imageData ? `<img src="${sq.imageData}" class="image-preview">` : '<span class="text-gray-500">No image</span>'}</div>`; |
|
|
if (sq.answerOnPaper) questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`; |
|
|
break; |
|
|
case 'questionspace': |
|
|
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`; |
|
|
questionHTML += `<div class="mt-2" style="height: ${(sq.spaceHeight || 5) * 24}px; border-bottom: 1px dashed #ccc;"></div>`; |
|
|
break; |
|
|
case 'spacequestion': |
|
|
questionHTML += `<div class="mb-2" style="height: ${(sq.spaceHeight || 5) * 24}px; border-bottom: 1px dashed #ccc;"></div>`; |
|
|
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`; |
|
|
break; |
|
|
case 'blank': |
|
|
questionHTML += `<div class="my-4" style="height: ${(sq.spaceHeight || 5) * 24}px; border-bottom: 1px dashed #ccc;"></div>`; |
|
|
break; |
|
|
case 'orquestion': |
|
|
questionHTML += `<div class="font-medium">${label})</div>`; |
|
|
questionHTML += `<div class="space-y-3">`; |
|
|
(sq.options || []).forEach((opt, optIndex) => { |
|
|
questionHTML += `<div class="flex items-start"><div class="mr-2 font-medium">${optIndex + 1}.</div><div>${opt.text || ''}</div></div>`; |
|
|
if (optIndex < (sq.options || []).length - 1) questionHTML += `<div class="text-center font-bold">OR</div>`; |
|
|
}); |
|
|
questionHTML += `</div>`; |
|
|
break; |
|
|
default: |
|
|
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`; |
|
|
} |
|
|
questionHTML += `</div>`; |
|
|
questionHTML += `<div class="ml-4"><span class="text-sm text-blue-800 px-2 py-1 rounded">${sq.marks || 1} marks</span></div>`; |
|
|
questionHTML += `</div>`; |
|
|
questionHTML += `</div>`; |
|
|
}); |
|
|
questionHTML += `</div>`; |
|
|
} |
|
|
break; |
|
|
case 'questionspace': |
|
|
questionHTML += `<div class="mt-4" style="height: ${q.spaceHeight * 24}px; border-bottom: 1px dashed #ccc;"></div>`; |
|
|
if (q.answerOnPaper) { |
|
|
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`; |
|
|
} |
|
|
break; |
|
|
case 'spacequestion': |
|
|
questionHTML += ` |
|
|
<div class="mb-4" style="height: ${q.spaceHeight * 24}px; border-bottom: 1px dashed #ccc;"></div> |
|
|
<div class="mb-2">${q.question}</div> |
|
|
`; |
|
|
if (q.answerOnPaper) { |
|
|
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`; |
|
|
} |
|
|
break; |
|
|
case 'blank': |
|
|
questionHTML += `<div class="my-4" style="height: ${q.spaceHeight * 24}px; border-bottom: 1px dashed #ccc;"></div>`; |
|
|
break; |
|
|
case 'orquestion': |
|
|
questionHTML += `<div class="space-y-3">`; |
|
|
q.options.forEach((opt, optIndex) => { |
|
|
questionHTML += `<div class="flex items-start"><div class="mr-2 font-medium">${optIndex + 1}.</div><div>${opt.text}</div></div>`; |
|
|
if (optIndex < q.options.length - 1) { |
|
|
questionHTML += `<div class="text-center font-bold">OR</div>`; |
|
|
} |
|
|
}); |
|
|
questionHTML += `</div>`; |
|
|
break; |
|
|
} |
|
|
|
|
|
questionElement.innerHTML = questionHTML; |
|
|
tempContainer.appendChild(questionElement.cloneNode(true)); |
|
|
const questionHeight = questionElement.offsetHeight + 30; // + margin |
|
|
|
|
|
if (currentPageHeight + questionHeight > pageHeight && currentPage.children.length > 0) { |
|
|
// Start new page |
|
|
container.appendChild(currentPage); |
|
|
currentPage = document.createElement('div'); |
|
|
currentPage.className = 'paper-page'; |
|
|
currentPageHeight = 0; |
|
|
} |
|
|
|
|
|
currentPage.appendChild(questionElement); |
|
|
currentPageHeight += questionHeight; |
|
|
}); |
|
|
|
|
|
if (currentPage.children.length > 0) { |
|
|
container.appendChild(currentPage); |
|
|
} |
|
|
|
|
|
document.body.removeChild(tempContainer); |
|
|
feather.replace(); |
|
|
|
|
|
// Auto-save |
|
|
if (paperData.schoolName) { |
|
|
paperData.questions = questions; |
|
|
localStorage.setItem('examPaperData', JSON.stringify(paperData)); |
|
|
} |
|
|
} |
|
|
|
|
|
function getQuestionTypeLabel(type) { |
|
|
const labels = { |
|
|
'mcq': '', |
|
|
'normal': '', |
|
|
'truefalse': '', |
|
|
'imagefirst': '', |
|
|
'questionfirst': '', |
|
|
'subquestion': '', |
|
|
'questionspace': '', |
|
|
'spacequestion': '', |
|
|
'blank': '', |
|
|
'orquestion': '' |
|
|
}; |
|
|
return labels[type] || ''; |
|
|
} |
|
|
|
|
|
function updateTotalMarks() { |
|
|
const total = questions.reduce((sum, q) => { |
|
|
if (q.type === 'subquestion' && q.subquestions) { |
|
|
return sum + q.subquestions.reduce((subSum, sq) => subSum + sq.marks, 0); |
|
|
} |
|
|
return sum + (q.marks || 0); |
|
|
}, 0); |
|
|
|
|
|
document.getElementById('paperTotalMarksDisplay').textContent = total; |
|
|
} |
|
|
|
|
|
function editQuestion(id) { |
|
|
const question = questions.find(q => q.id === Number(id)); |
|
|
if (!question) return; |
|
|
|
|
|
currentQuestionType = question.type; |
|
|
document.getElementById('questionMarks').value = question.marks; |
|
|
|
|
|
// Set modal title |
|
|
const typeTitles = { |
|
|
'mcq': 'Multiple Choice Question', |
|
|
'normal': 'Normal Question', |
|
|
'truefalse': 'True/False Question', |
|
|
'imagefirst': 'Image with Question', |
|
|
'questionfirst': 'Question with Image', |
|
|
'subquestion': 'Question with Subquestions', |
|
|
'questionspace': 'Question with Space', |
|
|
'spacequestion': 'Space with Question', |
|
|
'blank': 'Blank Space', |
|
|
'orquestion': 'Or Question' |
|
|
}; |
|
|
|
|
|
document.getElementById('detailsModalTitle').textContent = `Edit ${typeTitles[currentQuestionType]}`; |
|
|
|
|
|
// Generate content with pre-filled data |
|
|
let contentHTML = ''; |
|
|
|
|
|
switch(currentQuestionType) { |
|
|
case 'mcq': |
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea id="mcqQuestion" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" rows="3">${question.question || ''}</textarea> |
|
|
</div> |
|
|
<div class="option-grid"> |
|
|
<div><label class="block text-sm font-medium text-gray-700 mb-1">Option i)</label><input type="text" id="option1" class="w-full px-4 py-2 border border-gray-300 rounded-md" value="${question.options[0] || ''}"></div> |
|
|
<div><label class="block text-sm font-medium text-gray-700 mb-1">Option ii)</label><input type="text" id="option2" class="w-full px-4 py-2 border border-gray-300 rounded-md" value="${question.options[1] || ''}"></div> |
|
|
<div><label class="block text-sm font-medium text-gray-700 mb-1">Option iii)</label><input type="text" id="option3" class="w-full px-4 py-2 border border-gray-300 rounded-md" value="${question.options[2] || ''}"></div> |
|
|
<div><label class="block text-sm font-medium text-gray-700 mb-1">Option iv)</label><input type="text" id="option4" class="w-full px-4 py-2 border border-gray-300 rounded-md" value="${question.options[3] || ''}"></div> |
|
|
</div> |
|
|
<div class="mt-4 flex items-center"><input type="checkbox" id="mcqAnswerOnPaper" ${question.answerOnPaper ? 'checked' : ''} class="mr-2"><label class="text-sm text-gray-700">Students answer on question paper</label></div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'normal': |
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea id="normalQuestion" class="w-full px-4 py-2 border border-gray-300 rounded-md" rows="3">${question.question || ''}</textarea> |
|
|
</div> |
|
|
<div class="flex items-center"><input type="checkbox" id="normalAnswerOnPaper" ${question.answerOnPaper ? 'checked' : ''} class="mr-2"><label class="text-sm text-gray-700">Students answer on question paper</label></div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'truefalse': |
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea id="tfQuestion" class="w-full px-4 py-2 border border-gray-300 rounded-md" rows="3">${question.question || ''}</textarea> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'imagefirst': |
|
|
case 'questionfirst': |
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">${currentQuestionType === 'imagefirst' ? 'Upload Image' : 'Question'}</label> |
|
|
${currentQuestionType === 'imagefirst' ? |
|
|
`<input type="file" id="imageUpload" accept="image/*" class="w-full px-4 py-2 border border-gray-300 rounded-md"><input type="hidden" id="imageDataHidden" value="${question.imageData || ''}">` : |
|
|
`<textarea id="questionText" class="w-full px-4 py-2 border border-gray-300 rounded-md" rows="3">${question.question || ''}</textarea>`} |
|
|
</div> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">${currentQuestionType === 'imagefirst' ? 'Question' : 'Upload Image'}</label> |
|
|
${currentQuestionType === 'imagefirst' ? |
|
|
`<textarea id="questionText" class="w-full px-4 py-2 border border-gray-300 rounded-md" rows="3">${question.question || ''}</textarea>` : |
|
|
`<input type="file" id="imageUpload" accept="image/*" class="w-full px-4 py-2 border border-gray-300 rounded-md"><input type="hidden" id="imageDataHidden" value="${question.imageData || ''}">`} |
|
|
</div> |
|
|
<div class="flex items-center"><input type="checkbox" id="imageAnswerOnPaper" ${question.answerOnPaper ? 'checked' : ''} class="mr-2"><label class="text-sm text-gray-700">Students answer on question paper</label></div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'subquestion': |
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Main Question Title</label> |
|
|
<input type="text" id="mainQuestionTitle" class="w-full px-4 py-2 border border-gray-300 rounded-md" value="${question.mainTitle || ''}"> |
|
|
</div> |
|
|
<div class="text-center py-4"> |
|
|
<button onclick="openSubquestionModal()" class="bg-primary text-white px-4 py-2 rounded-md"> |
|
|
<i data-feather="edit" class="inline mr-2"></i> Edit Subquestions |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'questionspace': |
|
|
case 'spacequestion': |
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label> |
|
|
<textarea id="spaceQuestion" class="w-full px-4 py-2 border border-gray-300 rounded-md" rows="3">${question.question || ''}</textarea> |
|
|
</div> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Space Height (lines)</label> |
|
|
<input type="number" id="spaceHeight" class="w-full px-4 py-2 border border-gray-300 rounded-md" value="${question.spaceHeight || 5}"> |
|
|
</div> |
|
|
<div class="flex items-center"><input type="checkbox" id="spaceAnswerOnPaper" ${question.answerOnPaper ? 'checked' : ''} class="mr-2"><label class="text-sm text-gray-700">Students answer on question paper</label></div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'blank': |
|
|
contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Space Height (lines)</label> |
|
|
<input type="number" id="blankHeight" class="w-full px-4 py-2 border border-gray-300 rounded-md" value="${question.spaceHeight || 5}"> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
|
|
|
case 'orquestion': |
|
|
contentHTML = ` |
|
|
<div id="orQuestionsContainer" class="space-y-4"> |
|
|
${question.options.map((opt, index) => ` |
|
|
<div class="or-question-item"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Option ${index + 1}</label> |
|
|
<textarea class="w-full px-4 py-2 border border-gray-300 rounded-md" rows="2">${opt.text || ''}</textarea> |
|
|
</div> |
|
|
`).join('')} |
|
|
</div> |
|
|
<div class="mt-4"> |
|
|
<button onclick="addOrOption()" class="text-primary hover:underline flex items-center"> |
|
|
<i data-feather="plus" class="inline mr-1 w-4 h-4"></i> Add another option |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
break; |
|
|
} |
|
|
|
|
|
document.getElementById('questionDetailsContent').innerHTML = contentHTML; |
|
|
feather.replace(); |
|
|
|
|
|
// Store the question ID for updating |
|
|
document.getElementById('questionDetailsModal').setAttribute('data-edit-id', id); |
|
|
document.getElementById('questionDetailsModal').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function deleteQuestion(id) { |
|
|
if (confirm('Are you sure you want to delete this question?')) { |
|
|
if (!Array.isArray(questions)) { |
|
|
console.error('questions is not an array!', questions); |
|
|
questions = []; // reset |
|
|
} |
|
|
questions = questions.filter(q => q.id !== id); |
|
|
renderQuestions(); |
|
|
updateTotalMarks(); |
|
|
} |
|
|
} |
|
|
|
|
|
function increaseTextSize() { |
|
|
textSize = Math.min(24, textSize + 1); |
|
|
document.getElementById('paperContent').style.fontSize = `${textSize}px`; |
|
|
} |
|
|
|
|
|
function decreaseTextSize() { |
|
|
textSize = Math.max(12, textSize - 1); |
|
|
document.getElementById('paperContent').style.fontSize = `${textSize}px`; |
|
|
} |
|
|
|
|
|
function showUpcomingAlert(feature) { |
|
|
alert(`Feature "${feature}" is upcoming and will be available soon!`); |
|
|
} |
|
|
|
|
|
</script> |
|
|
<script>feather.replace();</script> |
|
|
<script>AOS.init();</script> |
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script> |
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> |
|
|
<script> |
|
|
document.getElementById("downloadPdf").addEventListener("click", async () => { |
|
|
document.querySelectorAll('#paperContent svg').forEach(el => el.style.display = 'none'); |
|
|
feather.replace(); |
|
|
const examName = paperData.examName || 'Exam Paper'; |
|
|
const now = new Date(); |
|
|
const dateStr = String(now.getDate()).padStart(2, '0'); |
|
|
const monthStr = String(now.getMonth() + 1).padStart(2, '0'); |
|
|
const year = now.getFullYear(); |
|
|
const hours = String(now.getHours()).padStart(2, '0'); |
|
|
const minutes = String(now.getMinutes()).padStart(2, '0'); |
|
|
const fileName = `${examName} - ${dateStr}-${monthStr}-${year} ${hours}-${minutes}.pdf`; |
|
|
const { jsPDF } = window.jspdf; |
|
|
|
|
|
const pdf = new jsPDF('p', 'pt', 'a4'); |
|
|
|
|
|
const element = document.getElementById("paperContent"); |
|
|
|
|
|
await pdf.html(element, { |
|
|
callback: function (pdf) { |
|
|
pdf.save(fileName); |
|
|
}, |
|
|
x: 5, |
|
|
y: 5, |
|
|
html2canvas: { scale: 0.7 } |
|
|
}); |
|
|
|
|
|
document.querySelectorAll('#paperContent svg').forEach(el => el.style.display = ''); |
|
|
}); |
|
|
</script> |
|
|
<script> |
|
|
|
|
|
function useofAI() { |
|
|
const existing = document.getElementById('aiModal'); |
|
|
if (existing) existing.remove(); |
|
|
const modal = document.createElement('div'); |
|
|
modal.id = 'aiModal'; |
|
|
modal.className = 'popup-overlay'; |
|
|
modal.style.zIndex = '6000'; |
|
|
modal.style.opacity = '1'; |
|
|
modal.style.transform = 'none !important'; |
|
|
modal.innerHTML = ` |
|
|
<div class="popup-content" data-aos="zoom-in" style="opacity: 1; z-index: 99999; font-size: 1rem;"> |
|
|
<h2 class="text-2xl font-bold text-center mb-6">AI Assistant</h2> |
|
|
<div class="grid grid-cols-2 gap-4"> |
|
|
<button onclick="aiOptionSelected('scan-paper')" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50 text-base"> |
|
|
<i data-feather="camera" class="mx-auto mb-2"></i> |
|
|
<p>Scan Question Paper</p> |
|
|
</button> |
|
|
<button onclick="aiOptionSelected('scan-paragraph')" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50 text-base"> |
|
|
<i data-feather="file-text" class="mx-auto mb-2"></i> |
|
|
<p>Scan Paragraph & Generate Questions</p> |
|
|
</button> |
|
|
<button onclick="aiOptionSelected('topic')" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50 text-base"> |
|
|
<i data-feather="hash" class="mx-auto mb-2"></i> |
|
|
<p>Create Questions on Topic</p> |
|
|
</button> |
|
|
<button onclick="aiOptionSelected('search')" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50 text-base"> |
|
|
<i data-feather="search" class="mx-auto mb-2"></i> |
|
|
<p>Search Question Papers Online</p> |
|
|
</button> |
|
|
<button onclick="aiOptionSelected('pdf')" class="question-type-btn p-4 border border-gray-200 rounded-md text-center hover:border-primary hover:bg-blue-50 text-base"> |
|
|
<i data-feather="file" class="mx-auto mb-2"></i> |
|
|
<p>Generate from PDF</p> |
|
|
</button> |
|
|
</div> |
|
|
<div class="flex justify-end mt-6 pt-4 border-t"> |
|
|
<button onclick="closeAiModal()" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 text-base">Close</button> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
document.body.appendChild(modal); |
|
|
feather.replace(); |
|
|
} |
|
|
|
|
|
function aiOptionSelected(option) { |
|
|
const modal = document.getElementById('aiModal'); |
|
|
const content = modal.querySelector('.popup-content'); |
|
|
|
|
|
if (option === 'scan-paper' || option === 'scan-paragraph') { |
|
|
|
|
|
navigator.mediaDevices.getUserMedia({ video: true }) |
|
|
.then(stream => { |
|
|
const video = document.createElement('video'); |
|
|
video.style.display = 'none'; |
|
|
video.autoplay = true; |
|
|
video.srcObject = stream; |
|
|
document.body.appendChild(video); |
|
|
|
|
|
video.onloadedmetadata = () => { |
|
|
const canvas = document.createElement('canvas'); |
|
|
canvas.width = video.videoWidth; |
|
|
canvas.height = video.videoHeight; |
|
|
const ctx = canvas.getContext('2d'); |
|
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height); |
|
|
const base64 = canvas.toDataURL('image/png'); |
|
|
|
|
|
|
|
|
stream.getTracks().forEach(track => track.stop()); |
|
|
document.body.removeChild(video); |
|
|
|
|
|
|
|
|
alert(option === 'scan-paper' |
|
|
? 'Received scanned question paper!' |
|
|
: 'Received paragraph image for question generation!'); |
|
|
closeAiModal(); |
|
|
}; |
|
|
}) |
|
|
.catch(err => { |
|
|
alert('Camera access denied or not supported.'); |
|
|
console.error(err); |
|
|
}); |
|
|
} else { |
|
|
|
|
|
let formHTML = ''; |
|
|
let placeholder = ''; |
|
|
let inputType = 'text'; |
|
|
let buttonText = 'Save'; |
|
|
|
|
|
if (option === 'topic') { |
|
|
formHTML = `<input type="text" id="aiInput" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" placeholder="Enter topic (e.g., Photosynthesis)">`; |
|
|
buttonText = 'Generate'; |
|
|
} else if (option === 'search') { |
|
|
formHTML = `<input type="text" id="aiInput" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-primary focus:border-primary" placeholder="Search term (e.g., Class 10 Math)">`; |
|
|
buttonText = 'Search'; |
|
|
} else if (option === 'pdf') { |
|
|
formHTML = `<input type="file" id="aiPdfInput" accept=".pdf" class="w-full px-4 py-2 border border-gray-300 rounded-md">`; |
|
|
inputType = 'file'; |
|
|
buttonText = 'Upload'; |
|
|
} |
|
|
|
|
|
content.innerHTML = ` |
|
|
<h2 class="text-2xl font-bold text-center mb-6"> |
|
|
${option === 'topic' ? 'Enter Topic' : option === 'search' ? 'Search Papers' : 'Upload PDF'} |
|
|
</h2> |
|
|
<div class="mb-6">${formHTML}</div> |
|
|
<div class="flex justify-between"> |
|
|
<button onclick="closeAiModal()" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50">Close</button> |
|
|
<button onclick="handleAiSave('${option}')" class="bg-primary text-white px-4 py-2 rounded-md hover:bg-blue-700">${buttonText}</button> |
|
|
</div> |
|
|
`; |
|
|
feather.replace(); |
|
|
} |
|
|
} |
|
|
|
|
|
function handleAiSave(option) { |
|
|
if (option === 'pdf') { |
|
|
const fileInput = document.getElementById('aiPdfInput'); |
|
|
if (fileInput.files.length === 0) { |
|
|
alert('Please select a PDF file.'); |
|
|
return; |
|
|
} |
|
|
alert('PDF received! Processing...'); |
|
|
} else { |
|
|
const input = document.getElementById('aiInput'); |
|
|
if (!input.value.trim()) { |
|
|
alert('Please enter a valid input.'); |
|
|
return; |
|
|
} |
|
|
alert(`"${input.value}" received! Processing...`); |
|
|
} |
|
|
closeAiModal(); |
|
|
} |
|
|
|
|
|
function closeAiModal() { |
|
|
const modal = document.getElementById('aiModal'); |
|
|
if (modal) modal.remove(); |
|
|
} |
|
|
|
|
|
function editPaperDetails() { |
|
|
document.getElementById('schoolName').value = paperData.schoolName || ''; |
|
|
document.getElementById('examName').value = paperData.examName || ''; |
|
|
document.getElementById('language').value = paperData.language || 'english'; |
|
|
document.getElementById('totalTime').value = paperData.totalTime || ''; |
|
|
document.getElementById('importantNotes').value = paperData.importantNotes || ''; |
|
|
document.getElementById('setupModal').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function multipleChoiceQuestion(question, options){ |
|
|
} |
|
|
|
|
|
function ocr(image){ |
|
|
return "Photosynthesis is the process by which green plants, algae, and some bacteria use sunlight, water, and carbon dioxide to create chemical energy in the form of glucose (sugar), releasing oxygen as a byproduct. This energy-producing process, which occurs within organelles called chloroplasts, is essential for most life on Earth, providing food and sustaining the atmospheric balance of oxygen and carbon dioxide" |
|
|
} |
|
|
|
|
|
function generateId() { |
|
|
return Date.now() + Math.floor(Math.random() * 1000); |
|
|
} |
|
|
|
|
|
function processPDF(pdfFile){ |
|
|
return "1. What is Photosynthesis?\nA) A process of respiration\nB) A process of digestion\nC) A process by which plants make food\nD) A process of transpiration\n\n2. Which organelle is responsible for Photosynthesis?\nA) Mitochondria\nB) Chloroplast\nC) Nucleus\nD) Ribosome" |
|
|
} |
|
|
|
|
|
function normalQuestion(question, marks = 1) { |
|
|
const q = { |
|
|
id: generateId(), |
|
|
type: 'normal', |
|
|
question: question, |
|
|
marks: marks, |
|
|
answerOnPaper: false |
|
|
}; |
|
|
questions.push(q); |
|
|
renderQuestions(); |
|
|
updateTotalMarks(); |
|
|
} |
|
|
|
|
|
function addTrueFalse(question, marks = 1) { |
|
|
const q = { |
|
|
id: generateId(), |
|
|
type: 'truefalse', |
|
|
question: question, |
|
|
marks: marks |
|
|
}; |
|
|
questions.push(q); |
|
|
renderQuestions(); |
|
|
updateTotalMarks(); |
|
|
} |
|
|
|
|
|
function ImageWithQuestion(image, question, marks = 1) { |
|
|
const q = { |
|
|
id: generateId(), |
|
|
type: 'imagefirst', |
|
|
question: question, |
|
|
marks: marks, |
|
|
imageData: image, |
|
|
answerOnPaper: false |
|
|
}; |
|
|
questions.push(q); |
|
|
renderQuestions(); |
|
|
updateTotalMarks(); |
|
|
} |
|
|
|
|
|
function questionWithImage(question, image, marks = 1) { |
|
|
const q = { |
|
|
id: generateId(), |
|
|
type: 'questionfirst', |
|
|
question: question, |
|
|
marks: marks, |
|
|
imageData: image, |
|
|
answerOnPaper: false |
|
|
}; |
|
|
questions.push(q); |
|
|
renderQuestions(); |
|
|
updateTotalMarks(); |
|
|
} |
|
|
|
|
|
function questionWithSubquestions(questionTitle, subQuestionList, marks = 1) { |
|
|
const subquestions = []; |
|
|
if (Array.isArray(subQuestionList)) { |
|
|
subQuestionList.forEach(item => { |
|
|
const entry = item; |
|
|
if (typeof entry === 'object' && entry !== null) { |
|
|
const key = Object.keys(entry)[0]; |
|
|
const value = entry[key]; |
|
|
if (typeof key === 'string') { |
|
|
const idNum = parseInt(key); |
|
|
if (!isNaN(idNum)) { |
|
|
let sq = { marks: 1 }; |
|
|
if (typeof value === 'string') { |
|
|
|
|
|
if (value.toLowerCase().includes('true') || value.toLowerCase().includes('false')) { |
|
|
sq = { type: 'truefalse', question: value, marks: 1 }; |
|
|
} else { |
|
|
sq = { type: 'normal', question: value, marks: 1 }; |
|
|
} |
|
|
} else if (Array.isArray(value)) { |
|
|
if (value.length === 2 && typeof value[0] === 'string' && typeof value[1] === 'string' && value[0].startsWith('data:image')) { |
|
|
|
|
|
sq = { type: 'imagefirst', imageData: value[0], question: value[1], marks: 1 }; |
|
|
} else if (value.length === 2 && typeof value[0] === 'string' && typeof value[1] === 'string' && value[1].startsWith('data:image')) { |
|
|
|
|
|
sq = { type: 'questionfirst', question: value[0], imageData: value[1], marks: 1 }; |
|
|
} else if (value.length === 2 && typeof value[0] === 'string' && typeof value[1] === 'number') { |
|
|
|
|
|
sq = { type: 'questionspace', question: value[0], spaceHeight: value[1], marks: 1 }; |
|
|
} else if (value.length === 2 && typeof value[0] === 'number' && typeof value[1] === 'string') { |
|
|
|
|
|
sq = { type: 'spacequestion', spaceHeight: value[0], question: value[1], marks: 1 }; |
|
|
} else if (Array.isArray(value) && value.every(v => typeof v === 'string')) { |
|
|
|
|
|
sq = { type: 'orquestion', options: value.map((text, i) => ({ id: i + 1, text })), marks: 1 }; |
|
|
} |
|
|
} else if (typeof value === 'number') { |
|
|
|
|
|
sq = { type: 'blank', spaceHeight: value, marks: 1 }; |
|
|
} |
|
|
subquestions.push(sq); |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
const q = { |
|
|
id: generateId(), |
|
|
type: 'subquestion', |
|
|
mainTitle: questionTitle, |
|
|
marks: marks, |
|
|
subquestions: subquestions |
|
|
}; |
|
|
questions.push(q); |
|
|
renderQuestions(); |
|
|
updateTotalMarks(); |
|
|
} |
|
|
|
|
|
function questionWithSpace(question, spaceSize, marks = 1) { |
|
|
const q = { |
|
|
id: generateId(), |
|
|
type: 'questionspace', |
|
|
question: question, |
|
|
spaceHeight: spaceSize, |
|
|
marks: marks, |
|
|
answerOnPaper: false |
|
|
}; |
|
|
questions.push(q); |
|
|
renderQuestions(); |
|
|
updateTotalMarks(); |
|
|
} |
|
|
|
|
|
function spaceWithQuestion(spaceSize, question, marks = 1) { |
|
|
const q = { |
|
|
id: generateId(), |
|
|
type: 'spacequestion', |
|
|
question: question, |
|
|
spaceHeight: spaceSize, |
|
|
marks: marks, |
|
|
answerOnPaper: false |
|
|
}; |
|
|
questions.push(q); |
|
|
renderQuestions(); |
|
|
updateTotalMarks(); |
|
|
} |
|
|
|
|
|
function addSpace(spaceSize, marks = 1) { |
|
|
const q = { |
|
|
id: generateId(), |
|
|
type: 'blank', |
|
|
spaceHeight: spaceSize, |
|
|
marks: marks |
|
|
}; |
|
|
questions.push(q); |
|
|
renderQuestions(); |
|
|
updateTotalMarks(); |
|
|
} |
|
|
|
|
|
function orQuestion(questionList, marks = 1) { |
|
|
if (!Array.isArray(questionList)) return; |
|
|
const options = questionList.map((text, i) => ({ id: i + 1, text })); |
|
|
const q = { |
|
|
id: generateId(), |
|
|
type: 'orquestion', |
|
|
options: options, |
|
|
marks: marks |
|
|
}; |
|
|
questions.push(q); |
|
|
renderQuestions(); |
|
|
updateTotalMarks(); |
|
|
} |
|
|
|
|
|
function linkToBase64(url) { |
|
|
return new Promise((resolve, reject) => { |
|
|
const img = new Image(); |
|
|
img.crossOrigin = 'anonymous'; |
|
|
img.onload = () => { |
|
|
const canvas = document.createElement('canvas'); |
|
|
canvas.width = img.width; |
|
|
canvas.height = img.height; |
|
|
const ctx = canvas.getContext('2d'); |
|
|
ctx.drawImage(img, 0, 0); |
|
|
const base64 = canvas.toDataURL('image/png'); |
|
|
resolve(base64); |
|
|
}; |
|
|
img.onerror = () => reject(new Error('Failed to load image')); |
|
|
img.src = url; |
|
|
}); |
|
|
} |
|
|
|
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|