Update index.html
Browse files- index.html +711 -115
index.html
CHANGED
|
@@ -9,6 +9,7 @@
|
|
| 9 |
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
|
| 10 |
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 11 |
<script src="https://unpkg.com/feather-icons"></script>
|
|
|
|
| 12 |
<script>
|
| 13 |
tailwind.config = {
|
| 14 |
theme: {
|
|
@@ -23,6 +24,12 @@
|
|
| 23 |
}
|
| 24 |
</script>
|
| 25 |
<style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
.paper-container {
|
| 27 |
width: 210mm;
|
| 28 |
min-height: 297mm;
|
|
@@ -113,10 +120,56 @@
|
|
| 113 |
color: white;
|
| 114 |
border-color: #2563eb;
|
| 115 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
</style>
|
| 117 |
</head>
|
| 118 |
<body class="bg-gray-100 min-h-screen">
|
| 119 |
-
|
| 120 |
<div id="setupModal" class="popup-overlay">
|
| 121 |
<div class="popup-content" data-aos="zoom-in">
|
| 122 |
<h2 class="text-2xl font-bold text-center mb-6">Create Your Exam Paper</h2>
|
|
@@ -140,6 +193,14 @@
|
|
| 140 |
<label class="block text-sm font-medium text-gray-700 mb-1">Total Time (Hour)</label>
|
| 141 |
<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">
|
| 142 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
<div class="flex justify-end pt-4">
|
| 144 |
<button onclick="saveSetup()" class="bg-primary text-white px-6 py-2 rounded-md hover:bg-blue-700 transition">Create Paper</button>
|
| 145 |
</div>
|
|
@@ -147,7 +208,6 @@
|
|
| 147 |
</div>
|
| 148 |
</div>
|
| 149 |
|
| 150 |
-
<!-- Add Question Modal -->
|
| 151 |
<div id="addQuestionModal" class="popup-overlay hidden">
|
| 152 |
<div class="popup-content" data-aos="zoom-in">
|
| 153 |
<h2 class="text-2xl font-bold text-center mb-6">Add Question</h2>
|
|
@@ -207,7 +267,6 @@
|
|
| 207 |
</div>
|
| 208 |
</div>
|
| 209 |
|
| 210 |
-
<!-- Question Details Modal -->
|
| 211 |
<div id="questionDetailsModal" class="popup-overlay hidden">
|
| 212 |
<div class="popup-content" data-aos="zoom-in">
|
| 213 |
<h2 id="detailsModalTitle" class="text-2xl font-bold text-center mb-6">Question Details</h2>
|
|
@@ -234,7 +293,6 @@
|
|
| 234 |
</div>
|
| 235 |
</div>
|
| 236 |
|
| 237 |
-
<!-- Subquestion Add Modal -->
|
| 238 |
<div id="subquestionModal" class="popup-overlay hidden">
|
| 239 |
<div class="popup-content" data-aos="zoom-in">
|
| 240 |
<h2 class="text-2xl font-bold text-center mb-6">Add Subquestion</h2>
|
|
@@ -249,50 +307,52 @@
|
|
| 249 |
</div>
|
| 250 |
</div>
|
| 251 |
|
| 252 |
-
<!-- Main Content -->
|
| 253 |
<div class="container mx-auto py-8">
|
| 254 |
-
<!-- Header with controls -->
|
| 255 |
<div class="flex justify-between items-center mb-6 px-4">
|
| 256 |
-
<div>
|
| 257 |
-
<div class="flex items-center space-x-4 mt-2 text-sm text-gray-600">
|
| 258 |
-
<span id="timeDisplay">Time: --:--</span>
|
| 259 |
-
<span id="totalMarksDisplay">Total Marks: 0</span>
|
| 260 |
-
</div>
|
| 261 |
-
</div>
|
| 262 |
<div class="flex items-center space-x-3">
|
| 263 |
<button onclick="showAddQuestionModal()" class="bg-primary text-white px-4 py-2 rounded-md hover:bg-blue-700 flex items-center">
|
| 264 |
<i data-feather="plus" class="mr-2"></i> Add Question
|
| 265 |
</button>
|
|
|
|
|
|
|
|
|
|
| 266 |
<button id="downloadPdf" class="bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 flex items-center">
|
| 267 |
<i data-feather="download" class="mr-2"></i> Export PDF
|
| 268 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
</div>
|
| 270 |
</div>
|
| 271 |
|
| 272 |
-
<!-- Paper Container -->
|
| 273 |
<div class="paper-container" id="paperContent">
|
| 274 |
<!-- School and exam details -->
|
| 275 |
<div class="text-center mb-8">
|
| 276 |
-
<
|
| 277 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
<div class="flex justify-between items-center mt-6 text-sm">
|
| 279 |
<div>Time: <span id="paperTimeDisplay"></span></div>
|
| 280 |
<div>Total Marks: <span id="paperTotalMarksDisplay">0</span></div>
|
| 281 |
</div>
|
| 282 |
<div class="border-b border-gray-300 mt-4"></div>
|
| 283 |
</div>
|
| 284 |
-
|
| 285 |
-
<!-- Questions will be added here -->
|
| 286 |
<div id="questionsContainer"></div>
|
| 287 |
</div>
|
| 288 |
|
| 289 |
-
<!-- Page counter -->
|
| 290 |
<div class="text-center mt-4 text-gray-600">
|
| 291 |
Page <span id="currentPage">1</span> of <span id="totalPages">1</span>
|
| 292 |
</div>
|
| 293 |
</div>
|
| 294 |
|
| 295 |
-
<!-- Text size controls -->
|
| 296 |
<div class="text-size-control flex items-center space-x-2">
|
| 297 |
<button onclick="decreaseTextSize()" class="w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center hover:bg-gray-200">
|
| 298 |
<i data-feather="minus"></i>
|
|
@@ -307,31 +367,93 @@
|
|
| 307 |
let currentQuestionType = '';
|
| 308 |
let questions = [];
|
| 309 |
let textSize = 16;
|
|
|
|
|
|
|
|
|
|
| 310 |
let paperData = {
|
| 311 |
schoolName: '',
|
| 312 |
examName: '',
|
| 313 |
language: 'english',
|
| 314 |
totalTime: 0,
|
|
|
|
| 315 |
date: new Date()
|
| 316 |
};
|
| 317 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
document.addEventListener('DOMContentLoaded', function() {
|
| 319 |
AOS.init();
|
| 320 |
feather.replace();
|
| 321 |
-
|
| 322 |
-
// Check if we have saved data
|
| 323 |
const savedData = localStorage.getItem('examPaperData');
|
| 324 |
if (savedData) {
|
| 325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
updatePaperHeader();
|
|
|
|
|
|
|
| 327 |
document.getElementById('setupModal').classList.add('hidden');
|
| 328 |
}
|
| 329 |
-
|
| 330 |
-
// Update date and time displays
|
| 331 |
updateDateTime();
|
| 332 |
-
setInterval(updateDateTime, 60000);
|
| 333 |
});
|
| 334 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
function getReadableQuestionTypeLabel(type) {
|
| 336 |
const map = {
|
| 337 |
'mcq': 'Multiple Choice',
|
|
@@ -367,8 +489,6 @@
|
|
| 367 |
function updateDateTime() {
|
| 368 |
const now = new Date();
|
| 369 |
const timeStr = now.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
|
| 370 |
-
|
| 371 |
-
document.getElementById('timeDisplay').textContent = `Time: ${timeStr}`;
|
| 372 |
}
|
| 373 |
|
| 374 |
function saveSetup() {
|
|
@@ -376,27 +496,35 @@
|
|
| 376 |
const examName = document.getElementById('examName').value;
|
| 377 |
const language = document.getElementById('language').value;
|
| 378 |
const totalTime = document.getElementById('totalTime').value;
|
| 379 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
if (!schoolName || !examName || !totalTime) {
|
| 381 |
alert('Please fill all required fields');
|
| 382 |
return;
|
| 383 |
}
|
| 384 |
-
|
| 385 |
paperData = {
|
| 386 |
schoolName,
|
| 387 |
examName,
|
| 388 |
language,
|
| 389 |
totalTime: parseInt(totalTime),
|
|
|
|
|
|
|
| 390 |
date: new Date()
|
| 391 |
};
|
| 392 |
-
|
| 393 |
-
// Save to localStorage
|
| 394 |
localStorage.setItem('examPaperData', JSON.stringify(paperData));
|
| 395 |
-
|
| 396 |
-
// Update the display
|
| 397 |
updatePaperHeader();
|
| 398 |
-
|
| 399 |
-
// Hide the setup modal
|
| 400 |
document.getElementById('setupModal').classList.add('hidden');
|
| 401 |
}
|
| 402 |
|
|
@@ -404,6 +532,17 @@
|
|
| 404 |
document.getElementById('schoolNameDisplay').textContent = paperData.schoolName;
|
| 405 |
document.getElementById('examNameDisplay').textContent = paperData.examName;
|
| 406 |
document.getElementById('paperTimeDisplay').textContent = `${paperData.totalTime} Hours`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
updateTotalMarks();
|
| 408 |
}
|
| 409 |
|
|
@@ -547,14 +686,14 @@
|
|
| 547 |
<div class="mb-4">
|
| 548 |
<label class="block text-sm font-medium text-gray-700 mb-1">${currentQuestionType === 'imagefirst' ? 'Upload Image' : 'Question'}</label>
|
| 549 |
${currentQuestionType === 'imagefirst' ?
|
| 550 |
-
`<input type="file" id="imageUpload" accept="image/*" class="w-full px-4 py-2 border border-gray-300 rounded-md"
|
| 551 |
`<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>`}
|
| 552 |
</div>
|
| 553 |
<div class="mb-4">
|
| 554 |
<label class="block text-sm font-medium text-gray-700 mb-1">${currentQuestionType === 'imagefirst' ? 'Question' : 'Upload Image'}</label>
|
| 555 |
${currentQuestionType === 'imagefirst' ?
|
| 556 |
`<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>` :
|
| 557 |
-
`<input type="file" id="imageUpload" accept="image/*" class="w-full px-4 py-2 border border-gray-300 rounded-md">`}
|
| 558 |
</div>
|
| 559 |
<div class="flex items-center">
|
| 560 |
<input type="checkbox" id="imageAnswerOnPaper" class="mr-2">
|
|
@@ -642,7 +781,7 @@
|
|
| 642 |
let questionData = {
|
| 643 |
type: currentQuestionType,
|
| 644 |
marks: marks,
|
| 645 |
-
id: editId
|
| 646 |
};
|
| 647 |
|
| 648 |
// Collect data based on question type
|
|
@@ -670,11 +809,10 @@
|
|
| 670 |
case 'imagefirst':
|
| 671 |
case 'questionfirst':
|
| 672 |
questionData.question = document.getElementById('questionText').value;
|
| 673 |
-
questionData.
|
| 674 |
-
questionData.imagePlaceholder = true;
|
| 675 |
questionData.answerOnPaper = document.getElementById('imageAnswerOnPaper').checked;
|
| 676 |
break;
|
| 677 |
-
|
| 678 |
case 'subquestion':
|
| 679 |
const titleEl = document.getElementById('mainQuestionTitle');
|
| 680 |
// Prefer the id we stored earlier
|
|
@@ -740,18 +878,125 @@
|
|
| 740 |
}
|
| 741 |
|
| 742 |
function openSubquestionModal() {
|
| 743 |
-
// show the subquestion editor modal
|
| 744 |
document.getElementById('questionDetailsModal').classList.add('hidden');
|
| 745 |
document.getElementById('subquestionModal').classList.remove('hidden');
|
| 746 |
|
| 747 |
-
// populate subquestionContainer from preview (#subquestionsUI) so user can edit
|
| 748 |
-
const details = document.getElementById('subquestionsUI');
|
| 749 |
const container = document.getElementById('subquestionContainer');
|
| 750 |
container.innerHTML = '';
|
| 751 |
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 755 |
});
|
| 756 |
} else {
|
| 757 |
container.innerHTML = `
|
|
@@ -1097,14 +1342,34 @@
|
|
| 1097 |
}
|
| 1098 |
|
| 1099 |
function renderQuestions() {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1100 |
const container = document.getElementById('questionsContainer');
|
| 1101 |
container.innerHTML = '';
|
| 1102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1103 |
questions.forEach((q, index) => {
|
| 1104 |
const questionElement = document.createElement('div');
|
| 1105 |
questionElement.className = 'question-item';
|
| 1106 |
questionElement.setAttribute('data-id', q.id);
|
| 1107 |
|
|
|
|
| 1108 |
let questionHTML = `
|
| 1109 |
<div class="flex justify-between items-start mb-2">
|
| 1110 |
<div class="font-semibold">
|
|
@@ -1139,30 +1404,20 @@
|
|
| 1139 |
</div>
|
| 1140 |
`;
|
| 1141 |
if (q.answerOnPaper) {
|
| 1142 |
-
questionHTML +=
|
| 1143 |
-
<div class="mt-3 flex space-x-6">
|
| 1144 |
-
<div>i) □</div>
|
| 1145 |
-
<div>ii) □</div>
|
| 1146 |
-
<div>iii) □</div>
|
| 1147 |
-
<div>iv) □</div>
|
| 1148 |
-
</div>
|
| 1149 |
-
`;
|
| 1150 |
}
|
| 1151 |
break;
|
| 1152 |
-
|
| 1153 |
case 'normal':
|
| 1154 |
if (q.answerOnPaper) {
|
| 1155 |
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1156 |
}
|
| 1157 |
break;
|
| 1158 |
-
|
| 1159 |
case 'truefalse':
|
| 1160 |
break;
|
| 1161 |
-
|
| 1162 |
case 'imagefirst':
|
| 1163 |
questionHTML += `
|
| 1164 |
<div class="mb-3">
|
| 1165 |
-
|
| 1166 |
</div>
|
| 1167 |
<div class="mb-2">${q.question}</div>
|
| 1168 |
`;
|
|
@@ -1170,35 +1425,25 @@
|
|
| 1170 |
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1171 |
}
|
| 1172 |
break;
|
| 1173 |
-
|
| 1174 |
case 'questionfirst':
|
| 1175 |
questionHTML += `
|
| 1176 |
<div class="mb-3">
|
| 1177 |
-
|
| 1178 |
</div>
|
| 1179 |
`;
|
| 1180 |
if (q.answerOnPaper) {
|
| 1181 |
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1182 |
}
|
| 1183 |
break;
|
| 1184 |
-
|
| 1185 |
case 'subquestion':
|
| 1186 |
-
// Main title with spacing
|
| 1187 |
if (q.subquestions && q.subquestions.length) {
|
| 1188 |
questionHTML += `<div class="subquestions-container ml-6">`;
|
| 1189 |
-
|
| 1190 |
-
// Roman numerals for numbering
|
| 1191 |
const romanNumerals = ['i','ii','iii','iv','v','vi','vii','viii','ix','x'];
|
| 1192 |
-
|
| 1193 |
q.subquestions.forEach((sq, sqIndex) => {
|
| 1194 |
const label = romanNumerals[sqIndex] || (sqIndex + 1);
|
| 1195 |
-
|
| 1196 |
questionHTML += `<div class="mb-3">`;
|
| 1197 |
-
// layout: left = question content, right = marks
|
| 1198 |
questionHTML += `<div class="flex justify-between">`;
|
| 1199 |
questionHTML += `<div class="w-full">`;
|
| 1200 |
-
|
| 1201 |
-
// render by subquestion type (mirror top-level rendering)
|
| 1202 |
switch (sq.type) {
|
| 1203 |
case 'mcq':
|
| 1204 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`;
|
|
@@ -1212,48 +1457,34 @@
|
|
| 1212 |
questionHTML += `<div class="mt-3 flex space-x-6"><div>i) □</div><div>ii) □</div><div>iii) □</div><div>iv) □</div></div>`;
|
| 1213 |
}
|
| 1214 |
break;
|
| 1215 |
-
|
| 1216 |
case 'normal':
|
| 1217 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`;
|
| 1218 |
if (sq.answerOnPaper) questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1219 |
break;
|
| 1220 |
-
|
| 1221 |
case 'truefalse':
|
| 1222 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''} (True / False)</div>`;
|
| 1223 |
break;
|
| 1224 |
-
|
| 1225 |
case 'imagefirst':
|
| 1226 |
-
|
| 1227 |
-
questionHTML += `<div class="mb-3">`;
|
| 1228 |
-
questionHTML += `<img src="${sq.imageData || `http://static.photos/education/640x360/${index + 10}-${sqIndex}`}" class="image-preview">`;
|
| 1229 |
-
questionHTML += `</div>`;
|
| 1230 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`;
|
| 1231 |
if (sq.answerOnPaper) questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1232 |
break;
|
| 1233 |
-
|
| 1234 |
case 'questionfirst':
|
| 1235 |
-
// question then image
|
| 1236 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`;
|
| 1237 |
-
questionHTML += `<div class="mb-3">`;
|
| 1238 |
-
questionHTML += `<img src="${sq.imageData || `http://static.photos/education/640x360/${index + 20}-${sqIndex}`}" class="image-preview">`;
|
| 1239 |
-
questionHTML += `</div>`;
|
| 1240 |
if (sq.answerOnPaper) questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1241 |
break;
|
| 1242 |
-
|
| 1243 |
case 'questionspace':
|
| 1244 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`;
|
| 1245 |
questionHTML += `<div class="mt-2" style="height: ${(sq.spaceHeight || 5) * 24}px; border-bottom: 1px dashed #ccc;"></div>`;
|
| 1246 |
break;
|
| 1247 |
-
|
| 1248 |
case 'spacequestion':
|
| 1249 |
questionHTML += `<div class="mb-2" style="height: ${(sq.spaceHeight || 5) * 24}px; border-bottom: 1px dashed #ccc;"></div>`;
|
| 1250 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`;
|
| 1251 |
break;
|
| 1252 |
-
|
| 1253 |
case 'blank':
|
| 1254 |
questionHTML += `<div class="my-4" style="height: ${(sq.spaceHeight || 5) * 24}px; border-bottom: 1px dashed #ccc;"></div>`;
|
| 1255 |
break;
|
| 1256 |
-
|
| 1257 |
case 'orquestion':
|
| 1258 |
questionHTML += `<div class="font-medium">${label})</div>`;
|
| 1259 |
questionHTML += `<div class="space-y-3">`;
|
|
@@ -1263,33 +1494,23 @@
|
|
| 1263 |
});
|
| 1264 |
questionHTML += `</div>`;
|
| 1265 |
break;
|
| 1266 |
-
|
| 1267 |
default:
|
| 1268 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`;
|
| 1269 |
}
|
| 1270 |
-
|
| 1271 |
-
questionHTML += `</div>`; // close left side (w-full)
|
| 1272 |
-
|
| 1273 |
-
// marks on the right
|
| 1274 |
questionHTML += `<div class="ml-4"><span class="text-sm text-blue-800 px-2 py-1 rounded">${sq.marks || 1} marks</span></div>`;
|
| 1275 |
-
|
| 1276 |
-
questionHTML += `</div>`;
|
| 1277 |
-
questionHTML += `</div>`; // close mb-3
|
| 1278 |
});
|
| 1279 |
-
|
| 1280 |
-
questionHTML += `</div>`; // close subquestions-container
|
| 1281 |
}
|
| 1282 |
break;
|
| 1283 |
-
|
| 1284 |
case 'questionspace':
|
| 1285 |
-
questionHTML +=
|
| 1286 |
-
<div class="mt-4" style="height: ${q.spaceHeight * 24}px; border-bottom: 1px dashed #ccc;"></div>
|
| 1287 |
-
`;
|
| 1288 |
if (q.answerOnPaper) {
|
| 1289 |
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1290 |
}
|
| 1291 |
break;
|
| 1292 |
-
|
| 1293 |
case 'spacequestion':
|
| 1294 |
questionHTML += `
|
| 1295 |
<div class="mb-4" style="height: ${q.spaceHeight * 24}px; border-bottom: 1px dashed #ccc;"></div>
|
|
@@ -1299,20 +1520,13 @@
|
|
| 1299 |
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1300 |
}
|
| 1301 |
break;
|
| 1302 |
-
|
| 1303 |
case 'blank':
|
| 1304 |
questionHTML += `<div class="my-4" style="height: ${q.spaceHeight * 24}px; border-bottom: 1px dashed #ccc;"></div>`;
|
| 1305 |
break;
|
| 1306 |
-
|
| 1307 |
case 'orquestion':
|
| 1308 |
questionHTML += `<div class="space-y-3">`;
|
| 1309 |
q.options.forEach((opt, optIndex) => {
|
| 1310 |
-
questionHTML +=
|
| 1311 |
-
<div class="flex items-start">
|
| 1312 |
-
<div class="mr-2 font-medium">${optIndex + 1}.</div>
|
| 1313 |
-
<div>${opt.text}</div>
|
| 1314 |
-
</div>
|
| 1315 |
-
`;
|
| 1316 |
if (optIndex < q.options.length - 1) {
|
| 1317 |
questionHTML += `<div class="text-center font-bold">OR</div>`;
|
| 1318 |
}
|
|
@@ -1322,10 +1536,33 @@
|
|
| 1322 |
}
|
| 1323 |
|
| 1324 |
questionElement.innerHTML = questionHTML;
|
| 1325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1326 |
});
|
| 1327 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1328 |
feather.replace();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1329 |
}
|
| 1330 |
|
| 1331 |
function getQuestionTypeLabel(type) {
|
|
@@ -1356,7 +1593,7 @@
|
|
| 1356 |
}
|
| 1357 |
|
| 1358 |
function editQuestion(id) {
|
| 1359 |
-
const question = questions.find(q => q.id === id);
|
| 1360 |
if (!question) return;
|
| 1361 |
|
| 1362 |
currentQuestionType = question.type;
|
|
@@ -1423,14 +1660,14 @@
|
|
| 1423 |
<div class="mb-4">
|
| 1424 |
<label class="block text-sm font-medium text-gray-700 mb-1">${currentQuestionType === 'imagefirst' ? 'Upload Image' : 'Question'}</label>
|
| 1425 |
${currentQuestionType === 'imagefirst' ?
|
| 1426 |
-
`<input type="file" id="imageUpload" accept="image/*" class="w-full px-4 py-2 border border-gray-300 rounded-md">` :
|
| 1427 |
`<textarea id="questionText" class="w-full px-4 py-2 border border-gray-300 rounded-md" rows="3">${question.question || ''}</textarea>`}
|
| 1428 |
</div>
|
| 1429 |
<div class="mb-4">
|
| 1430 |
<label class="block text-sm font-medium text-gray-700 mb-1">${currentQuestionType === 'imagefirst' ? 'Question' : 'Upload Image'}</label>
|
| 1431 |
${currentQuestionType === 'imagefirst' ?
|
| 1432 |
`<textarea id="questionText" class="w-full px-4 py-2 border border-gray-300 rounded-md" rows="3">${question.question || ''}</textarea>` :
|
| 1433 |
-
`<input type="file" id="imageUpload" accept="image/*" class="w-full px-4 py-2 border border-gray-300 rounded-md">`}
|
| 1434 |
</div>
|
| 1435 |
<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>
|
| 1436 |
`;
|
|
@@ -1503,6 +1740,10 @@
|
|
| 1503 |
|
| 1504 |
function deleteQuestion(id) {
|
| 1505 |
if (confirm('Are you sure you want to delete this question?')) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1506 |
questions = questions.filter(q => q.id !== id);
|
| 1507 |
renderQuestions();
|
| 1508 |
updateTotalMarks();
|
|
@@ -1532,6 +1773,14 @@
|
|
| 1532 |
document.getElementById("downloadPdf").addEventListener("click", async () => {
|
| 1533 |
document.querySelectorAll('#paperContent svg').forEach(el => el.style.display = 'none');
|
| 1534 |
feather.replace();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1535 |
const { jsPDF } = window.jspdf;
|
| 1536 |
|
| 1537 |
const pdf = new jsPDF('p', 'pt', 'a4'); // Use mm units for easier calculations
|
|
@@ -1540,7 +1789,7 @@
|
|
| 1540 |
|
| 1541 |
await pdf.html(element, {
|
| 1542 |
callback: function (pdf) {
|
| 1543 |
-
pdf.save(
|
| 1544 |
},
|
| 1545 |
x: 5,
|
| 1546 |
y: 5,
|
|
@@ -1550,5 +1799,352 @@
|
|
| 1550 |
document.querySelectorAll('#paperContent svg').forEach(el => el.style.display = '');
|
| 1551 |
});
|
| 1552 |
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1553 |
</body>
|
| 1554 |
</html>
|
|
|
|
| 9 |
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
|
| 10 |
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 11 |
<script src="https://unpkg.com/feather-icons"></script>
|
| 12 |
+
<script src="https://js.puter.com/v2/"></script>
|
| 13 |
<script>
|
| 14 |
tailwind.config = {
|
| 15 |
theme: {
|
|
|
|
| 24 |
}
|
| 25 |
</script>
|
| 26 |
<style>
|
| 27 |
+
|
| 28 |
+
#paperContent {
|
| 29 |
+
transition: transform 0.2s ease;
|
| 30 |
+
transform-origin: top center;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
.paper-container {
|
| 34 |
width: 210mm;
|
| 35 |
min-height: 297mm;
|
|
|
|
| 120 |
color: white;
|
| 121 |
border-color: #2563eb;
|
| 122 |
}
|
| 123 |
+
|
| 124 |
+
.paper-container {
|
| 125 |
+
width: 210mm;
|
| 126 |
+
min-height: 297mm;
|
| 127 |
+
padding: 20mm;
|
| 128 |
+
margin: 10mm auto;
|
| 129 |
+
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
| 130 |
+
background: white;
|
| 131 |
+
box-sizing: border-box;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
.question-item {
|
| 135 |
+
break-inside: avoid;
|
| 136 |
+
page-break-inside: avoid;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
#questionsContainer {
|
| 140 |
+
break-after: always;
|
| 141 |
+
page-break-after: always;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
.paper-container + .paper-container {
|
| 145 |
+
margin-top: 20mm;
|
| 146 |
+
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
.image-preview {
|
| 150 |
+
max-width: 100%;
|
| 151 |
+
max-height: 200px;
|
| 152 |
+
border-radius: 6px;
|
| 153 |
+
margin: 10px 0;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
#paperContent img {
|
| 157 |
+
width: auto;
|
| 158 |
+
height: auto;
|
| 159 |
+
max-width: 100%;
|
| 160 |
+
max-height: 200px;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
#logoContainer img {
|
| 164 |
+
max-height: 64px;
|
| 165 |
+
height: auto;
|
| 166 |
+
width: auto;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
</style>
|
| 170 |
</head>
|
| 171 |
<body class="bg-gray-100 min-h-screen">
|
| 172 |
+
|
| 173 |
<div id="setupModal" class="popup-overlay">
|
| 174 |
<div class="popup-content" data-aos="zoom-in">
|
| 175 |
<h2 class="text-2xl font-bold text-center mb-6">Create Your Exam Paper</h2>
|
|
|
|
| 193 |
<label class="block text-sm font-medium text-gray-700 mb-1">Total Time (Hour)</label>
|
| 194 |
<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">
|
| 195 |
</div>
|
| 196 |
+
<div>
|
| 197 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">School Logo (Optional)</label>
|
| 198 |
+
<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">
|
| 199 |
+
</div>
|
| 200 |
+
<div>
|
| 201 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Important Notes (Optional)</label>
|
| 202 |
+
<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>
|
| 203 |
+
</div>
|
| 204 |
<div class="flex justify-end pt-4">
|
| 205 |
<button onclick="saveSetup()" class="bg-primary text-white px-6 py-2 rounded-md hover:bg-blue-700 transition">Create Paper</button>
|
| 206 |
</div>
|
|
|
|
| 208 |
</div>
|
| 209 |
</div>
|
| 210 |
|
|
|
|
| 211 |
<div id="addQuestionModal" class="popup-overlay hidden">
|
| 212 |
<div class="popup-content" data-aos="zoom-in">
|
| 213 |
<h2 class="text-2xl font-bold text-center mb-6">Add Question</h2>
|
|
|
|
| 267 |
</div>
|
| 268 |
</div>
|
| 269 |
|
|
|
|
| 270 |
<div id="questionDetailsModal" class="popup-overlay hidden">
|
| 271 |
<div class="popup-content" data-aos="zoom-in">
|
| 272 |
<h2 id="detailsModalTitle" class="text-2xl font-bold text-center mb-6">Question Details</h2>
|
|
|
|
| 293 |
</div>
|
| 294 |
</div>
|
| 295 |
|
|
|
|
| 296 |
<div id="subquestionModal" class="popup-overlay hidden">
|
| 297 |
<div class="popup-content" data-aos="zoom-in">
|
| 298 |
<h2 class="text-2xl font-bold text-center mb-6">Add Subquestion</h2>
|
|
|
|
| 307 |
</div>
|
| 308 |
</div>
|
| 309 |
|
|
|
|
| 310 |
<div class="container mx-auto py-8">
|
|
|
|
| 311 |
<div class="flex justify-between items-center mb-6 px-4">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
<div class="flex items-center space-x-3">
|
| 313 |
<button onclick="showAddQuestionModal()" class="bg-primary text-white px-4 py-2 rounded-md hover:bg-blue-700 flex items-center">
|
| 314 |
<i data-feather="plus" class="mr-2"></i> Add Question
|
| 315 |
</button>
|
| 316 |
+
<button onclick="confirmNewPaper()" class="bg-gray-600 text-white px-4 py-2 rounded-md hover:bg-gray-700 flex items-center">
|
| 317 |
+
<i data-feather="file-plus" class="mr-2"></i> New Paper
|
| 318 |
+
</button>
|
| 319 |
<button id="downloadPdf" class="bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 flex items-center">
|
| 320 |
<i data-feather="download" class="mr-2"></i> Export PDF
|
| 321 |
</button>
|
| 322 |
+
<button onclick="useofAI()" id="useAI" class="bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 flex items-center">
|
| 323 |
+
<i data-feather="book" class="mr-2"></i> AI
|
| 324 |
+
</button>
|
| 325 |
+
<button onclick="editPaperDetails()" class="bg-amber-600 text-white px-4 py-2 rounded-md hover:bg-amber-700 flex items-center">
|
| 326 |
+
<i data-feather="edit" class="mr-2"></i> Edit Paper
|
| 327 |
+
</button>
|
| 328 |
</div>
|
| 329 |
</div>
|
| 330 |
|
|
|
|
| 331 |
<div class="paper-container" id="paperContent">
|
| 332 |
<!-- School and exam details -->
|
| 333 |
<div class="text-center mb-8">
|
| 334 |
+
<div class="flex items-start justify-between">
|
| 335 |
+
<div id="logoContainer" class="mr-4"></div>
|
| 336 |
+
<div class="text-center flex-1">
|
| 337 |
+
<h2 id="schoolNameDisplay" class="text-xl font-bold uppercase"></h2>
|
| 338 |
+
<h3 id="examNameDisplay" class="text-lg font-semibold mt-2"></h3>
|
| 339 |
+
</div>
|
| 340 |
+
</div>
|
| 341 |
+
<div id="importantNotesSection" class="mt-4 text-sm italic text-gray-700 text-left"></div>
|
| 342 |
<div class="flex justify-between items-center mt-6 text-sm">
|
| 343 |
<div>Time: <span id="paperTimeDisplay"></span></div>
|
| 344 |
<div>Total Marks: <span id="paperTotalMarksDisplay">0</span></div>
|
| 345 |
</div>
|
| 346 |
<div class="border-b border-gray-300 mt-4"></div>
|
| 347 |
</div>
|
|
|
|
|
|
|
| 348 |
<div id="questionsContainer"></div>
|
| 349 |
</div>
|
| 350 |
|
|
|
|
| 351 |
<div class="text-center mt-4 text-gray-600">
|
| 352 |
Page <span id="currentPage">1</span> of <span id="totalPages">1</span>
|
| 353 |
</div>
|
| 354 |
</div>
|
| 355 |
|
|
|
|
| 356 |
<div class="text-size-control flex items-center space-x-2">
|
| 357 |
<button onclick="decreaseTextSize()" class="w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center hover:bg-gray-200">
|
| 358 |
<i data-feather="minus"></i>
|
|
|
|
| 367 |
let currentQuestionType = '';
|
| 368 |
let questions = [];
|
| 369 |
let textSize = 16;
|
| 370 |
+
let paperScale = 1.0;
|
| 371 |
+
let baseFontSize = 16;
|
| 372 |
+
let currentScale = 1.0;
|
| 373 |
let paperData = {
|
| 374 |
schoolName: '',
|
| 375 |
examName: '',
|
| 376 |
language: 'english',
|
| 377 |
totalTime: 0,
|
| 378 |
+
importantNotes: '', // ← added
|
| 379 |
date: new Date()
|
| 380 |
};
|
| 381 |
|
| 382 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 383 |
+
document.getElementById('questionDetailsModal').addEventListener('change', function(e) {
|
| 384 |
+
if (e.target.id === 'imageUpload') {
|
| 385 |
+
const file = e.target.files[0];
|
| 386 |
+
if (file) {
|
| 387 |
+
const reader = new FileReader();
|
| 388 |
+
reader.onload = (event) => {
|
| 389 |
+
document.getElementById('imageDataHidden').value = event.target.result; // store Base64
|
| 390 |
+
};
|
| 391 |
+
reader.readAsDataURL(file);
|
| 392 |
+
} else {
|
| 393 |
+
document.getElementById('imageDataHidden').value = '';
|
| 394 |
+
}
|
| 395 |
+
}
|
| 396 |
+
});
|
| 397 |
+
});
|
| 398 |
+
|
| 399 |
document.addEventListener('DOMContentLoaded', function() {
|
| 400 |
AOS.init();
|
| 401 |
feather.replace();
|
|
|
|
|
|
|
| 402 |
const savedData = localStorage.getItem('examPaperData');
|
| 403 |
if (savedData) {
|
| 404 |
+
try {
|
| 405 |
+
paperData = JSON.parse(savedData);
|
| 406 |
+
// Ensure questions is always an array
|
| 407 |
+
if (Array.isArray(paperData.questions)) {
|
| 408 |
+
questions = paperData.questions;
|
| 409 |
+
} else {
|
| 410 |
+
console.warn('Invalid questions format in localStorage. Resetting to empty array.');
|
| 411 |
+
questions = [];
|
| 412 |
+
paperData.questions = questions;
|
| 413 |
+
}
|
| 414 |
+
} catch (e) {
|
| 415 |
+
console.error('Failed to parse saved data', e);
|
| 416 |
+
questions = [];
|
| 417 |
+
paperData = { schoolName: '', examName: '', language: 'english', totalTime: 0, date: new Date(), questions: [] };
|
| 418 |
+
}
|
| 419 |
updatePaperHeader();
|
| 420 |
+
renderLogo();
|
| 421 |
+
renderQuestions();
|
| 422 |
document.getElementById('setupModal').classList.add('hidden');
|
| 423 |
}
|
|
|
|
|
|
|
| 424 |
updateDateTime();
|
| 425 |
+
setInterval(updateDateTime, 60000);
|
| 426 |
});
|
| 427 |
|
| 428 |
+
function confirmNewPaper() {
|
| 429 |
+
if (confirm('Are you sure you want to start a new question paper? All current data will be lost.')) {
|
| 430 |
+
// Clear data
|
| 431 |
+
questions = [];
|
| 432 |
+
paperData = {
|
| 433 |
+
schoolName: '',
|
| 434 |
+
examName: '',
|
| 435 |
+
language: 'english',
|
| 436 |
+
totalTime: 0,
|
| 437 |
+
date: new Date()
|
| 438 |
+
};
|
| 439 |
+
// Clear localStorage
|
| 440 |
+
localStorage.removeItem('examPaperData');
|
| 441 |
+
// Reset UI
|
| 442 |
+
document.getElementById('questionsContainer').innerHTML = '';
|
| 443 |
+
document.getElementById('paperTotalMarksDisplay').textContent = '0';
|
| 444 |
+
document.getElementById('setupModal').classList.remove('hidden');
|
| 445 |
+
}
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
function renderLogo() {
|
| 449 |
+
const logoContainer = document.getElementById('logoContainer');
|
| 450 |
+
if (paperData.logo) {
|
| 451 |
+
logoContainer.innerHTML = `<img src="${paperData.logo}" alt="School Logo" class="h-16 object-contain">`;
|
| 452 |
+
} else {
|
| 453 |
+
logoContainer.innerHTML = '';
|
| 454 |
+
}
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
function getReadableQuestionTypeLabel(type) {
|
| 458 |
const map = {
|
| 459 |
'mcq': 'Multiple Choice',
|
|
|
|
| 489 |
function updateDateTime() {
|
| 490 |
const now = new Date();
|
| 491 |
const timeStr = now.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
|
|
|
|
|
|
|
| 492 |
}
|
| 493 |
|
| 494 |
function saveSetup() {
|
|
|
|
| 496 |
const examName = document.getElementById('examName').value;
|
| 497 |
const language = document.getElementById('language').value;
|
| 498 |
const totalTime = document.getElementById('totalTime').value;
|
| 499 |
+
const importantNotes = document.getElementById('importantNotes').value;
|
| 500 |
+
const logoFile = document.getElementById('schoolLogo').files[0];
|
| 501 |
+
let logoData = null;
|
| 502 |
+
if (logoFile) {
|
| 503 |
+
const reader = new FileReader();
|
| 504 |
+
reader.onload = (e) => {
|
| 505 |
+
paperData.logo = e.target.result;
|
| 506 |
+
localStorage.setItem('examPaperData', JSON.stringify(paperData));
|
| 507 |
+
renderLogo();
|
| 508 |
+
};
|
| 509 |
+
reader.readAsDataURL(logoFile);
|
| 510 |
+
} else {
|
| 511 |
+
paperData.logo = null;
|
| 512 |
+
}
|
| 513 |
if (!schoolName || !examName || !totalTime) {
|
| 514 |
alert('Please fill all required fields');
|
| 515 |
return;
|
| 516 |
}
|
|
|
|
| 517 |
paperData = {
|
| 518 |
schoolName,
|
| 519 |
examName,
|
| 520 |
language,
|
| 521 |
totalTime: parseInt(totalTime),
|
| 522 |
+
importantNotes,
|
| 523 |
+
logo: paperData.logo, // preserve existing logo if no new one uploaded
|
| 524 |
date: new Date()
|
| 525 |
};
|
|
|
|
|
|
|
| 526 |
localStorage.setItem('examPaperData', JSON.stringify(paperData));
|
|
|
|
|
|
|
| 527 |
updatePaperHeader();
|
|
|
|
|
|
|
| 528 |
document.getElementById('setupModal').classList.add('hidden');
|
| 529 |
}
|
| 530 |
|
|
|
|
| 532 |
document.getElementById('schoolNameDisplay').textContent = paperData.schoolName;
|
| 533 |
document.getElementById('examNameDisplay').textContent = paperData.examName;
|
| 534 |
document.getElementById('paperTimeDisplay').textContent = `${paperData.totalTime} Hours`;
|
| 535 |
+
|
| 536 |
+
const notesSection = document.getElementById('importantNotesSection');
|
| 537 |
+
if (paperData.importantNotes && paperData.importantNotes.trim()) {
|
| 538 |
+
const formattedNotes = paperData.importantNotes.replace(/\n/g, '<br>');
|
| 539 |
+
notesSection.innerHTML = `<strong>Notes:</strong> ${formattedNotes}`;
|
| 540 |
+
notesSection.classList.remove('hidden');
|
| 541 |
+
} else {
|
| 542 |
+
notesSection.innerHTML = '';
|
| 543 |
+
notesSection.classList.add('hidden');
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
updateTotalMarks();
|
| 547 |
}
|
| 548 |
|
|
|
|
| 686 |
<div class="mb-4">
|
| 687 |
<label class="block text-sm font-medium text-gray-700 mb-1">${currentQuestionType === 'imagefirst' ? 'Upload Image' : 'Question'}</label>
|
| 688 |
${currentQuestionType === 'imagefirst' ?
|
| 689 |
+
`<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="">`:
|
| 690 |
`<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>`}
|
| 691 |
</div>
|
| 692 |
<div class="mb-4">
|
| 693 |
<label class="block text-sm font-medium text-gray-700 mb-1">${currentQuestionType === 'imagefirst' ? 'Question' : 'Upload Image'}</label>
|
| 694 |
${currentQuestionType === 'imagefirst' ?
|
| 695 |
`<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>` :
|
| 696 |
+
`<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="">`}
|
| 697 |
</div>
|
| 698 |
<div class="flex items-center">
|
| 699 |
<input type="checkbox" id="imageAnswerOnPaper" class="mr-2">
|
|
|
|
| 781 |
let questionData = {
|
| 782 |
type: currentQuestionType,
|
| 783 |
marks: marks,
|
| 784 |
+
id: editId ? parseInt(editId) : Date.now()
|
| 785 |
};
|
| 786 |
|
| 787 |
// Collect data based on question type
|
|
|
|
| 809 |
case 'imagefirst':
|
| 810 |
case 'questionfirst':
|
| 811 |
questionData.question = document.getElementById('questionText').value;
|
| 812 |
+
questionData.imageData = document.getElementById('imageDataHidden').value || null;
|
|
|
|
| 813 |
questionData.answerOnPaper = document.getElementById('imageAnswerOnPaper').checked;
|
| 814 |
break;
|
| 815 |
+
|
| 816 |
case 'subquestion':
|
| 817 |
const titleEl = document.getElementById('mainQuestionTitle');
|
| 818 |
// Prefer the id we stored earlier
|
|
|
|
| 878 |
}
|
| 879 |
|
| 880 |
function openSubquestionModal() {
|
|
|
|
| 881 |
document.getElementById('questionDetailsModal').classList.add('hidden');
|
| 882 |
document.getElementById('subquestionModal').classList.remove('hidden');
|
| 883 |
|
|
|
|
|
|
|
| 884 |
const container = document.getElementById('subquestionContainer');
|
| 885 |
container.innerHTML = '';
|
| 886 |
|
| 887 |
+
// Get the parent question being edited
|
| 888 |
+
const editId = document.getElementById('questionDetailsModal').getAttribute('data-edit-id');
|
| 889 |
+
const newId = document.getElementById('questionDetailsModal').getAttribute('data-new-subquestion-id');
|
| 890 |
+
const parentId = editId ? parseInt(editId) : (newId ? parseInt(newId) : null);
|
| 891 |
+
|
| 892 |
+
let parentQuestion = null;
|
| 893 |
+
if (parentId !== null) {
|
| 894 |
+
parentQuestion = questions.find(q => q.id === parentId && q.type === 'subquestion');
|
| 895 |
+
}
|
| 896 |
+
|
| 897 |
+
if (parentQuestion && parentQuestion.subquestions && parentQuestion.subquestions.length > 0) {
|
| 898 |
+
// Rebuild editor UI from actual subquestion data
|
| 899 |
+
parentQuestion.subquestions.forEach(sq => {
|
| 900 |
+
const subquestionId = Date.now() + Math.floor(Math.random() * 1000);
|
| 901 |
+
const item = document.createElement('div');
|
| 902 |
+
item.className = 'subquestion-item p-4 border border-gray-200 rounded-md mb-3';
|
| 903 |
+
item.setAttribute('data-id', subquestionId);
|
| 904 |
+
item.setAttribute('data-type', sq.type);
|
| 905 |
+
|
| 906 |
+
const readable = getReadableQuestionTypeLabel(sq.type);
|
| 907 |
+
let html = `
|
| 908 |
+
<div class="flex justify-between items-start mb-3">
|
| 909 |
+
<h4 class="font-semibold">${readable}</h4>
|
| 910 |
+
<button onclick="removeSubquestion(this)" class="text-red-600 hover:text-red-800">
|
| 911 |
+
<i data-feather="trash-2" class="w-4 h-4"></i>
|
| 912 |
+
</button>
|
| 913 |
+
</div>
|
| 914 |
+
`;
|
| 915 |
+
|
| 916 |
+
// Reconstruct input fields with saved values
|
| 917 |
+
switch (sq.type) {
|
| 918 |
+
case 'mcq':
|
| 919 |
+
html += `
|
| 920 |
+
<div class="mb-3">
|
| 921 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label>
|
| 922 |
+
<textarea class="w-full px-3 py-2 border border-gray-300 rounded-md" rows="2">${sq.question || ''}</textarea>
|
| 923 |
+
</div>
|
| 924 |
+
<div class="grid grid-cols-2 gap-3 mb-3">
|
| 925 |
+
<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>
|
| 926 |
+
<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>
|
| 927 |
+
<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>
|
| 928 |
+
<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>
|
| 929 |
+
</div>
|
| 930 |
+
`;
|
| 931 |
+
break;
|
| 932 |
+
case 'normal':
|
| 933 |
+
case 'truefalse':
|
| 934 |
+
html += `
|
| 935 |
+
<div class="mb-3">
|
| 936 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label>
|
| 937 |
+
<textarea class="w-full px-3 py-2 border border-gray-300 rounded-md" rows="2">${sq.question || ''}</textarea>
|
| 938 |
+
</div>
|
| 939 |
+
`;
|
| 940 |
+
break;
|
| 941 |
+
case 'imagefirst':
|
| 942 |
+
case 'questionfirst':
|
| 943 |
+
html += `
|
| 944 |
+
<div class="mb-3">
|
| 945 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label>
|
| 946 |
+
<textarea class="w-full px-3 py-2 border border-gray-300 rounded-md" rows="2">${sq.question || ''}</textarea>
|
| 947 |
+
</div>
|
| 948 |
+
<div class="mb-3">
|
| 949 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Image</label>
|
| 950 |
+
<input type="file" class="w-full px-3 py-1 border border-gray-300 rounded-md">
|
| 951 |
+
${sq.imageData ? `<img src="${sq.imageData}" class="image-preview mt-2" style="max-height:100px;">` : ''}
|
| 952 |
+
</div>
|
| 953 |
+
`;
|
| 954 |
+
break;
|
| 955 |
+
case 'questionspace':
|
| 956 |
+
case 'spacequestion':
|
| 957 |
+
html += `
|
| 958 |
+
<div class="mb-3">
|
| 959 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Question</label>
|
| 960 |
+
<textarea class="w-full px-3 py-2 border border-gray-300 rounded-md" rows="2">${sq.question || ''}</textarea>
|
| 961 |
+
</div>
|
| 962 |
+
<div class="mb-3">
|
| 963 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Space Height (lines)</label>
|
| 964 |
+
<input type="number" class="w-full px-3 py-1 border border-gray-300 rounded-md" value="${sq.spaceHeight || 5}" min="1">
|
| 965 |
+
</div>
|
| 966 |
+
`;
|
| 967 |
+
break;
|
| 968 |
+
case 'blank':
|
| 969 |
+
html += `
|
| 970 |
+
<div class="mb-3">
|
| 971 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Space Height (lines)</label>
|
| 972 |
+
<input type="number" class="w-full px-3 py-1 border border-gray-300 rounded-md" value="${sq.spaceHeight || 5}" min="1">
|
| 973 |
+
</div>
|
| 974 |
+
`;
|
| 975 |
+
break;
|
| 976 |
+
case 'orquestion':
|
| 977 |
+
html += `
|
| 978 |
+
<div class="space-y-3 mb-3">
|
| 979 |
+
${(sq.options || []).map((opt, i) => `
|
| 980 |
+
<div>
|
| 981 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Option ${i + 1}</label>
|
| 982 |
+
<textarea class="w-full px-3 py-1 border border-gray-300 rounded-md" rows="1">${opt.text || ''}</textarea>
|
| 983 |
+
</div>
|
| 984 |
+
`).join('')}
|
| 985 |
+
</div>
|
| 986 |
+
<button onclick="addOrOptionToSubquestion(this)" class="text-primary text-sm hover:underline">+ Add another option</button>
|
| 987 |
+
`;
|
| 988 |
+
break;
|
| 989 |
+
}
|
| 990 |
+
|
| 991 |
+
html += `
|
| 992 |
+
<div class="mt-3">
|
| 993 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Marks</label>
|
| 994 |
+
<input type="number" class="w-20 px-3 py-1 border border-gray-300 rounded-md" value="${sq.marks || 1}" min="1">
|
| 995 |
+
</div>
|
| 996 |
+
`;
|
| 997 |
+
|
| 998 |
+
item.innerHTML = html;
|
| 999 |
+
container.appendChild(item);
|
| 1000 |
});
|
| 1001 |
} else {
|
| 1002 |
container.innerHTML = `
|
|
|
|
| 1342 |
}
|
| 1343 |
|
| 1344 |
function renderQuestions() {
|
| 1345 |
+
if (!Array.isArray(questions)) {
|
| 1346 |
+
console.error('questions is not an array in renderQuestions. Resetting.');
|
| 1347 |
+
questions = [];
|
| 1348 |
+
}
|
| 1349 |
const container = document.getElementById('questionsContainer');
|
| 1350 |
container.innerHTML = '';
|
| 1351 |
|
| 1352 |
+
// Create a temporary container to measure height
|
| 1353 |
+
const tempContainer = document.createElement('div');
|
| 1354 |
+
tempContainer.style.width = '210mm';
|
| 1355 |
+
tempContainer.style.padding = '20mm';
|
| 1356 |
+
tempContainer.style.boxSizing = 'border-box';
|
| 1357 |
+
tempContainer.style.position = 'absolute';
|
| 1358 |
+
tempContainer.style.left = '-9999px';
|
| 1359 |
+
tempContainer.style.top = '0';
|
| 1360 |
+
document.body.appendChild(tempContainer);
|
| 1361 |
+
|
| 1362 |
+
let currentPage = document.createElement('div');
|
| 1363 |
+
currentPage.className = 'paper-page';
|
| 1364 |
+
let currentPageHeight = 0;
|
| 1365 |
+
const pageHeight = 297 * 4; // ~297mm in pixels (approx 4px per mm)
|
| 1366 |
+
|
| 1367 |
questions.forEach((q, index) => {
|
| 1368 |
const questionElement = document.createElement('div');
|
| 1369 |
questionElement.className = 'question-item';
|
| 1370 |
questionElement.setAttribute('data-id', q.id);
|
| 1371 |
|
| 1372 |
+
// Build question HTML (same as before)
|
| 1373 |
let questionHTML = `
|
| 1374 |
<div class="flex justify-between items-start mb-2">
|
| 1375 |
<div class="font-semibold">
|
|
|
|
| 1404 |
</div>
|
| 1405 |
`;
|
| 1406 |
if (q.answerOnPaper) {
|
| 1407 |
+
questionHTML += `<div class="mt-3 flex space-x-6"><div>i) □</div><div>ii) □</div><div>iii) □</div><div>iv) □</div></div>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1408 |
}
|
| 1409 |
break;
|
|
|
|
| 1410 |
case 'normal':
|
| 1411 |
if (q.answerOnPaper) {
|
| 1412 |
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1413 |
}
|
| 1414 |
break;
|
|
|
|
| 1415 |
case 'truefalse':
|
| 1416 |
break;
|
|
|
|
| 1417 |
case 'imagefirst':
|
| 1418 |
questionHTML += `
|
| 1419 |
<div class="mb-3">
|
| 1420 |
+
${q.imageData ? `<img src="${q.imageData}" class="image-preview">` : '<div class="text-gray-500 italic">No image uploaded</div>'}
|
| 1421 |
</div>
|
| 1422 |
<div class="mb-2">${q.question}</div>
|
| 1423 |
`;
|
|
|
|
| 1425 |
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1426 |
}
|
| 1427 |
break;
|
|
|
|
| 1428 |
case 'questionfirst':
|
| 1429 |
questionHTML += `
|
| 1430 |
<div class="mb-3">
|
| 1431 |
+
${q.imageData ? `<img src="${q.imageData}" class="image-preview">` : '<div class="text-gray-500 italic">No image uploaded</div>'}
|
| 1432 |
</div>
|
| 1433 |
`;
|
| 1434 |
if (q.answerOnPaper) {
|
| 1435 |
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1436 |
}
|
| 1437 |
break;
|
|
|
|
| 1438 |
case 'subquestion':
|
|
|
|
| 1439 |
if (q.subquestions && q.subquestions.length) {
|
| 1440 |
questionHTML += `<div class="subquestions-container ml-6">`;
|
|
|
|
|
|
|
| 1441 |
const romanNumerals = ['i','ii','iii','iv','v','vi','vii','viii','ix','x'];
|
|
|
|
| 1442 |
q.subquestions.forEach((sq, sqIndex) => {
|
| 1443 |
const label = romanNumerals[sqIndex] || (sqIndex + 1);
|
|
|
|
| 1444 |
questionHTML += `<div class="mb-3">`;
|
|
|
|
| 1445 |
questionHTML += `<div class="flex justify-between">`;
|
| 1446 |
questionHTML += `<div class="w-full">`;
|
|
|
|
|
|
|
| 1447 |
switch (sq.type) {
|
| 1448 |
case 'mcq':
|
| 1449 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`;
|
|
|
|
| 1457 |
questionHTML += `<div class="mt-3 flex space-x-6"><div>i) □</div><div>ii) □</div><div>iii) □</div><div>iv) □</div></div>`;
|
| 1458 |
}
|
| 1459 |
break;
|
|
|
|
| 1460 |
case 'normal':
|
| 1461 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`;
|
| 1462 |
if (sq.answerOnPaper) questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1463 |
break;
|
|
|
|
| 1464 |
case 'truefalse':
|
| 1465 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''} (True / False)</div>`;
|
| 1466 |
break;
|
|
|
|
| 1467 |
case 'imagefirst':
|
| 1468 |
+
questionHTML += `<div class="mb-3">${sq.imageData ? `<img src="${sq.imageData}" class="image-preview">` : '<span class="text-gray-500">No image</span>'}</div>`;
|
|
|
|
|
|
|
|
|
|
| 1469 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`;
|
| 1470 |
if (sq.answerOnPaper) questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1471 |
break;
|
|
|
|
| 1472 |
case 'questionfirst':
|
|
|
|
| 1473 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`;
|
| 1474 |
+
questionHTML += `<div class="mb-3">${sq.imageData ? `<img src="${sq.imageData}" class="image-preview">` : '<span class="text-gray-500">No image</span>'}</div>`;
|
|
|
|
|
|
|
| 1475 |
if (sq.answerOnPaper) questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1476 |
break;
|
|
|
|
| 1477 |
case 'questionspace':
|
| 1478 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`;
|
| 1479 |
questionHTML += `<div class="mt-2" style="height: ${(sq.spaceHeight || 5) * 24}px; border-bottom: 1px dashed #ccc;"></div>`;
|
| 1480 |
break;
|
|
|
|
| 1481 |
case 'spacequestion':
|
| 1482 |
questionHTML += `<div class="mb-2" style="height: ${(sq.spaceHeight || 5) * 24}px; border-bottom: 1px dashed #ccc;"></div>`;
|
| 1483 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`;
|
| 1484 |
break;
|
|
|
|
| 1485 |
case 'blank':
|
| 1486 |
questionHTML += `<div class="my-4" style="height: ${(sq.spaceHeight || 5) * 24}px; border-bottom: 1px dashed #ccc;"></div>`;
|
| 1487 |
break;
|
|
|
|
| 1488 |
case 'orquestion':
|
| 1489 |
questionHTML += `<div class="font-medium">${label})</div>`;
|
| 1490 |
questionHTML += `<div class="space-y-3">`;
|
|
|
|
| 1494 |
});
|
| 1495 |
questionHTML += `</div>`;
|
| 1496 |
break;
|
|
|
|
| 1497 |
default:
|
| 1498 |
questionHTML += `<div class="font-medium">${label}) ${sq.question || ''}</div>`;
|
| 1499 |
}
|
| 1500 |
+
questionHTML += `</div>`;
|
|
|
|
|
|
|
|
|
|
| 1501 |
questionHTML += `<div class="ml-4"><span class="text-sm text-blue-800 px-2 py-1 rounded">${sq.marks || 1} marks</span></div>`;
|
| 1502 |
+
questionHTML += `</div>`;
|
| 1503 |
+
questionHTML += `</div>`;
|
|
|
|
| 1504 |
});
|
| 1505 |
+
questionHTML += `</div>`;
|
|
|
|
| 1506 |
}
|
| 1507 |
break;
|
|
|
|
| 1508 |
case 'questionspace':
|
| 1509 |
+
questionHTML += `<div class="mt-4" style="height: ${q.spaceHeight * 24}px; border-bottom: 1px dashed #ccc;"></div>`;
|
|
|
|
|
|
|
| 1510 |
if (q.answerOnPaper) {
|
| 1511 |
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1512 |
}
|
| 1513 |
break;
|
|
|
|
| 1514 |
case 'spacequestion':
|
| 1515 |
questionHTML += `
|
| 1516 |
<div class="mb-4" style="height: ${q.spaceHeight * 24}px; border-bottom: 1px dashed #ccc;"></div>
|
|
|
|
| 1520 |
questionHTML += `<div class="mt-2 border-t border-gray-300 pt-2">Answer: _________________________</div>`;
|
| 1521 |
}
|
| 1522 |
break;
|
|
|
|
| 1523 |
case 'blank':
|
| 1524 |
questionHTML += `<div class="my-4" style="height: ${q.spaceHeight * 24}px; border-bottom: 1px dashed #ccc;"></div>`;
|
| 1525 |
break;
|
|
|
|
| 1526 |
case 'orquestion':
|
| 1527 |
questionHTML += `<div class="space-y-3">`;
|
| 1528 |
q.options.forEach((opt, optIndex) => {
|
| 1529 |
+
questionHTML += `<div class="flex items-start"><div class="mr-2 font-medium">${optIndex + 1}.</div><div>${opt.text}</div></div>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1530 |
if (optIndex < q.options.length - 1) {
|
| 1531 |
questionHTML += `<div class="text-center font-bold">OR</div>`;
|
| 1532 |
}
|
|
|
|
| 1536 |
}
|
| 1537 |
|
| 1538 |
questionElement.innerHTML = questionHTML;
|
| 1539 |
+
tempContainer.appendChild(questionElement.cloneNode(true));
|
| 1540 |
+
const questionHeight = questionElement.offsetHeight + 30; // + margin
|
| 1541 |
+
|
| 1542 |
+
if (currentPageHeight + questionHeight > pageHeight && currentPage.children.length > 0) {
|
| 1543 |
+
// Start new page
|
| 1544 |
+
container.appendChild(currentPage);
|
| 1545 |
+
currentPage = document.createElement('div');
|
| 1546 |
+
currentPage.className = 'paper-page';
|
| 1547 |
+
currentPageHeight = 0;
|
| 1548 |
+
}
|
| 1549 |
+
|
| 1550 |
+
currentPage.appendChild(questionElement);
|
| 1551 |
+
currentPageHeight += questionHeight;
|
| 1552 |
});
|
| 1553 |
+
|
| 1554 |
+
if (currentPage.children.length > 0) {
|
| 1555 |
+
container.appendChild(currentPage);
|
| 1556 |
+
}
|
| 1557 |
+
|
| 1558 |
+
document.body.removeChild(tempContainer);
|
| 1559 |
feather.replace();
|
| 1560 |
+
|
| 1561 |
+
// Auto-save
|
| 1562 |
+
if (paperData.schoolName) {
|
| 1563 |
+
paperData.questions = questions;
|
| 1564 |
+
localStorage.setItem('examPaperData', JSON.stringify(paperData));
|
| 1565 |
+
}
|
| 1566 |
}
|
| 1567 |
|
| 1568 |
function getQuestionTypeLabel(type) {
|
|
|
|
| 1593 |
}
|
| 1594 |
|
| 1595 |
function editQuestion(id) {
|
| 1596 |
+
const question = questions.find(q => q.id === Number(id));
|
| 1597 |
if (!question) return;
|
| 1598 |
|
| 1599 |
currentQuestionType = question.type;
|
|
|
|
| 1660 |
<div class="mb-4">
|
| 1661 |
<label class="block text-sm font-medium text-gray-700 mb-1">${currentQuestionType === 'imagefirst' ? 'Upload Image' : 'Question'}</label>
|
| 1662 |
${currentQuestionType === 'imagefirst' ?
|
| 1663 |
+
`<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 || ''}">` :
|
| 1664 |
`<textarea id="questionText" class="w-full px-4 py-2 border border-gray-300 rounded-md" rows="3">${question.question || ''}</textarea>`}
|
| 1665 |
</div>
|
| 1666 |
<div class="mb-4">
|
| 1667 |
<label class="block text-sm font-medium text-gray-700 mb-1">${currentQuestionType === 'imagefirst' ? 'Question' : 'Upload Image'}</label>
|
| 1668 |
${currentQuestionType === 'imagefirst' ?
|
| 1669 |
`<textarea id="questionText" class="w-full px-4 py-2 border border-gray-300 rounded-md" rows="3">${question.question || ''}</textarea>` :
|
| 1670 |
+
`<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 || ''}">`}
|
| 1671 |
</div>
|
| 1672 |
<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>
|
| 1673 |
`;
|
|
|
|
| 1740 |
|
| 1741 |
function deleteQuestion(id) {
|
| 1742 |
if (confirm('Are you sure you want to delete this question?')) {
|
| 1743 |
+
if (!Array.isArray(questions)) {
|
| 1744 |
+
console.error('questions is not an array!', questions);
|
| 1745 |
+
questions = []; // reset
|
| 1746 |
+
}
|
| 1747 |
questions = questions.filter(q => q.id !== id);
|
| 1748 |
renderQuestions();
|
| 1749 |
updateTotalMarks();
|
|
|
|
| 1773 |
document.getElementById("downloadPdf").addEventListener("click", async () => {
|
| 1774 |
document.querySelectorAll('#paperContent svg').forEach(el => el.style.display = 'none');
|
| 1775 |
feather.replace();
|
| 1776 |
+
const examName = paperData.examName || 'Exam Paper';
|
| 1777 |
+
const now = new Date();
|
| 1778 |
+
const dateStr = String(now.getDate()).padStart(2, '0');
|
| 1779 |
+
const monthStr = String(now.getMonth() + 1).padStart(2, '0');
|
| 1780 |
+
const year = now.getFullYear();
|
| 1781 |
+
const hours = String(now.getHours()).padStart(2, '0');
|
| 1782 |
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
| 1783 |
+
const fileName = `${examName} - ${dateStr}-${monthStr}-${year} ${hours}-${minutes}.pdf`;
|
| 1784 |
const { jsPDF } = window.jspdf;
|
| 1785 |
|
| 1786 |
const pdf = new jsPDF('p', 'pt', 'a4'); // Use mm units for easier calculations
|
|
|
|
| 1789 |
|
| 1790 |
await pdf.html(element, {
|
| 1791 |
callback: function (pdf) {
|
| 1792 |
+
pdf.save(fileName);
|
| 1793 |
},
|
| 1794 |
x: 5,
|
| 1795 |
y: 5,
|
|
|
|
| 1799 |
document.querySelectorAll('#paperContent svg').forEach(el => el.style.display = '');
|
| 1800 |
});
|
| 1801 |
</script>
|
| 1802 |
+
<script>
|
| 1803 |
+
|
| 1804 |
+
function useofAI() {
|
| 1805 |
+
const existing = document.getElementById('aiModal');
|
| 1806 |
+
if (existing) existing.remove();
|
| 1807 |
+
const modal = document.createElement('div');
|
| 1808 |
+
modal.id = 'aiModal';
|
| 1809 |
+
modal.className = 'popup-overlay';
|
| 1810 |
+
modal.style.zIndex = '6000';
|
| 1811 |
+
modal.style.opacity = '1';
|
| 1812 |
+
modal.style.transform = 'none !important';
|
| 1813 |
+
modal.innerHTML = `
|
| 1814 |
+
<div class="popup-content" data-aos="zoom-in" style="opacity: 1; z-index: 99999; font-size: 1rem;">
|
| 1815 |
+
<h2 class="text-2xl font-bold text-center mb-6">AI Assistant</h2>
|
| 1816 |
+
<div class="grid grid-cols-2 gap-4">
|
| 1817 |
+
<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">
|
| 1818 |
+
<i data-feather="camera" class="mx-auto mb-2"></i>
|
| 1819 |
+
<p>Scan Question Paper</p>
|
| 1820 |
+
</button>
|
| 1821 |
+
<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">
|
| 1822 |
+
<i data-feather="file-text" class="mx-auto mb-2"></i>
|
| 1823 |
+
<p>Scan Paragraph & Generate Questions</p>
|
| 1824 |
+
</button>
|
| 1825 |
+
<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">
|
| 1826 |
+
<i data-feather="hash" class="mx-auto mb-2"></i>
|
| 1827 |
+
<p>Create Questions on Topic</p>
|
| 1828 |
+
</button>
|
| 1829 |
+
<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">
|
| 1830 |
+
<i data-feather="search" class="mx-auto mb-2"></i>
|
| 1831 |
+
<p>Search Question Papers Online</p>
|
| 1832 |
+
</button>
|
| 1833 |
+
<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">
|
| 1834 |
+
<i data-feather="file" class="mx-auto mb-2"></i>
|
| 1835 |
+
<p>Generate from PDF</p>
|
| 1836 |
+
</button>
|
| 1837 |
+
</div>
|
| 1838 |
+
<div class="flex justify-end mt-6 pt-4 border-t">
|
| 1839 |
+
<button onclick="closeAiModal()" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 text-base">Close</button>
|
| 1840 |
+
</div>
|
| 1841 |
+
</div>
|
| 1842 |
+
`;
|
| 1843 |
+
document.body.appendChild(modal);
|
| 1844 |
+
feather.replace();
|
| 1845 |
+
}
|
| 1846 |
+
|
| 1847 |
+
function aiOptionSelected(option) {
|
| 1848 |
+
const modal = document.getElementById('aiModal');
|
| 1849 |
+
const content = modal.querySelector('.popup-content');
|
| 1850 |
+
|
| 1851 |
+
if (option === 'scan-paper' || option === 'scan-paragraph') {
|
| 1852 |
+
// Trigger camera capture
|
| 1853 |
+
navigator.mediaDevices.getUserMedia({ video: true })
|
| 1854 |
+
.then(stream => {
|
| 1855 |
+
const video = document.createElement('video');
|
| 1856 |
+
video.style.display = 'none';
|
| 1857 |
+
video.autoplay = true;
|
| 1858 |
+
video.srcObject = stream;
|
| 1859 |
+
document.body.appendChild(video);
|
| 1860 |
+
|
| 1861 |
+
video.onloadedmetadata = () => {
|
| 1862 |
+
const canvas = document.createElement('canvas');
|
| 1863 |
+
canvas.width = video.videoWidth;
|
| 1864 |
+
canvas.height = video.videoHeight;
|
| 1865 |
+
const ctx = canvas.getContext('2d');
|
| 1866 |
+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 1867 |
+
const base64 = canvas.toDataURL('image/png');
|
| 1868 |
+
|
| 1869 |
+
// Stop camera
|
| 1870 |
+
stream.getTracks().forEach(track => track.stop());
|
| 1871 |
+
document.body.removeChild(video);
|
| 1872 |
+
|
| 1873 |
+
// Alert result
|
| 1874 |
+
alert(option === 'scan-paper'
|
| 1875 |
+
? 'Received scanned question paper!'
|
| 1876 |
+
: 'Received paragraph image for question generation!');
|
| 1877 |
+
closeAiModal();
|
| 1878 |
+
};
|
| 1879 |
+
})
|
| 1880 |
+
.catch(err => {
|
| 1881 |
+
alert('Camera access denied or not supported.');
|
| 1882 |
+
console.error(err);
|
| 1883 |
+
});
|
| 1884 |
+
} else {
|
| 1885 |
+
// Show input form for other options
|
| 1886 |
+
let formHTML = '';
|
| 1887 |
+
let placeholder = '';
|
| 1888 |
+
let inputType = 'text';
|
| 1889 |
+
let buttonText = 'Save';
|
| 1890 |
+
|
| 1891 |
+
if (option === 'topic') {
|
| 1892 |
+
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)">`;
|
| 1893 |
+
buttonText = 'Generate';
|
| 1894 |
+
} else if (option === 'search') {
|
| 1895 |
+
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)">`;
|
| 1896 |
+
buttonText = 'Search';
|
| 1897 |
+
} else if (option === 'pdf') {
|
| 1898 |
+
formHTML = `<input type="file" id="aiPdfInput" accept=".pdf" class="w-full px-4 py-2 border border-gray-300 rounded-md">`;
|
| 1899 |
+
inputType = 'file';
|
| 1900 |
+
buttonText = 'Upload';
|
| 1901 |
+
}
|
| 1902 |
+
|
| 1903 |
+
content.innerHTML = `
|
| 1904 |
+
<h2 class="text-2xl font-bold text-center mb-6">
|
| 1905 |
+
${option === 'topic' ? 'Enter Topic' : option === 'search' ? 'Search Papers' : 'Upload PDF'}
|
| 1906 |
+
</h2>
|
| 1907 |
+
<div class="mb-6">${formHTML}</div>
|
| 1908 |
+
<div class="flex justify-between">
|
| 1909 |
+
<button onclick="closeAiModal()" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50">Close</button>
|
| 1910 |
+
<button onclick="handleAiSave('${option}')" class="bg-primary text-white px-4 py-2 rounded-md hover:bg-blue-700">${buttonText}</button>
|
| 1911 |
+
</div>
|
| 1912 |
+
`;
|
| 1913 |
+
feather.replace();
|
| 1914 |
+
}
|
| 1915 |
+
}
|
| 1916 |
+
|
| 1917 |
+
function handleAiSave(option) {
|
| 1918 |
+
if (option === 'pdf') {
|
| 1919 |
+
const fileInput = document.getElementById('aiPdfInput');
|
| 1920 |
+
if (fileInput.files.length === 0) {
|
| 1921 |
+
alert('Please select a PDF file.');
|
| 1922 |
+
return;
|
| 1923 |
+
}
|
| 1924 |
+
alert('PDF received! Processing...');
|
| 1925 |
+
} else {
|
| 1926 |
+
const input = document.getElementById('aiInput');
|
| 1927 |
+
if (!input.value.trim()) {
|
| 1928 |
+
alert('Please enter a valid input.');
|
| 1929 |
+
return;
|
| 1930 |
+
}
|
| 1931 |
+
alert(`"${input.value}" received! Processing...`);
|
| 1932 |
+
}
|
| 1933 |
+
closeAiModal();
|
| 1934 |
+
}
|
| 1935 |
+
|
| 1936 |
+
function closeAiModal() {
|
| 1937 |
+
const modal = document.getElementById('aiModal');
|
| 1938 |
+
if (modal) modal.remove();
|
| 1939 |
+
}
|
| 1940 |
+
|
| 1941 |
+
function editPaperDetails() {
|
| 1942 |
+
document.getElementById('schoolName').value = paperData.schoolName || '';
|
| 1943 |
+
document.getElementById('examName').value = paperData.examName || '';
|
| 1944 |
+
document.getElementById('language').value = paperData.language || 'english';
|
| 1945 |
+
document.getElementById('totalTime').value = paperData.totalTime || '';
|
| 1946 |
+
document.getElementById('importantNotes').value = paperData.importantNotes || '';
|
| 1947 |
+
document.getElementById('setupModal').classList.remove('hidden');
|
| 1948 |
+
}
|
| 1949 |
+
|
| 1950 |
+
function multipleChoiceQuestion(question, options){
|
| 1951 |
+
}
|
| 1952 |
+
|
| 1953 |
+
function ocr(image){
|
| 1954 |
+
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"
|
| 1955 |
+
}
|
| 1956 |
+
|
| 1957 |
+
function generateId() {
|
| 1958 |
+
return Date.now() + Math.floor(Math.random() * 1000);
|
| 1959 |
+
}
|
| 1960 |
+
|
| 1961 |
+
function processPDF(pdfFile){
|
| 1962 |
+
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"
|
| 1963 |
+
}
|
| 1964 |
+
|
| 1965 |
+
function normalQuestion(question, marks = 1) {
|
| 1966 |
+
const q = {
|
| 1967 |
+
id: generateId(),
|
| 1968 |
+
type: 'normal',
|
| 1969 |
+
question: question,
|
| 1970 |
+
marks: marks,
|
| 1971 |
+
answerOnPaper: false
|
| 1972 |
+
};
|
| 1973 |
+
questions.push(q);
|
| 1974 |
+
renderQuestions();
|
| 1975 |
+
updateTotalMarks();
|
| 1976 |
+
}
|
| 1977 |
+
|
| 1978 |
+
function addTrueFalse(question, marks = 1) {
|
| 1979 |
+
const q = {
|
| 1980 |
+
id: generateId(),
|
| 1981 |
+
type: 'truefalse',
|
| 1982 |
+
question: question,
|
| 1983 |
+
marks: marks
|
| 1984 |
+
};
|
| 1985 |
+
questions.push(q);
|
| 1986 |
+
renderQuestions();
|
| 1987 |
+
updateTotalMarks();
|
| 1988 |
+
}
|
| 1989 |
+
|
| 1990 |
+
function ImageWithQuestion(image, question, marks = 1) {
|
| 1991 |
+
const q = {
|
| 1992 |
+
id: generateId(),
|
| 1993 |
+
type: 'imagefirst',
|
| 1994 |
+
question: question,
|
| 1995 |
+
marks: marks,
|
| 1996 |
+
imageData: image, // base64 string
|
| 1997 |
+
answerOnPaper: false
|
| 1998 |
+
};
|
| 1999 |
+
questions.push(q);
|
| 2000 |
+
renderQuestions();
|
| 2001 |
+
updateTotalMarks();
|
| 2002 |
+
}
|
| 2003 |
+
|
| 2004 |
+
function questionWithImage(question, image, marks = 1) {
|
| 2005 |
+
const q = {
|
| 2006 |
+
id: generateId(),
|
| 2007 |
+
type: 'questionfirst',
|
| 2008 |
+
question: question,
|
| 2009 |
+
marks: marks,
|
| 2010 |
+
imageData: image, // base64 string
|
| 2011 |
+
answerOnPaper: false
|
| 2012 |
+
};
|
| 2013 |
+
questions.push(q);
|
| 2014 |
+
renderQuestions();
|
| 2015 |
+
updateTotalMarks();
|
| 2016 |
+
}
|
| 2017 |
+
|
| 2018 |
+
function questionWithSubquestions(questionTitle, subQuestionList, marks = 1) {
|
| 2019 |
+
const subquestions = [];
|
| 2020 |
+
if (Array.isArray(subQuestionList)) {
|
| 2021 |
+
subQuestionList.forEach(item => {
|
| 2022 |
+
const entry = item;
|
| 2023 |
+
if (typeof entry === 'object' && entry !== null) {
|
| 2024 |
+
const key = Object.keys(entry)[0];
|
| 2025 |
+
const value = entry[key];
|
| 2026 |
+
if (typeof key === 'string') {
|
| 2027 |
+
const idNum = parseInt(key);
|
| 2028 |
+
if (!isNaN(idNum)) {
|
| 2029 |
+
let sq = { marks: 1 };
|
| 2030 |
+
if (typeof value === 'string') {
|
| 2031 |
+
// Normal or True/False
|
| 2032 |
+
if (value.toLowerCase().includes('true') || value.toLowerCase().includes('false')) {
|
| 2033 |
+
sq = { type: 'truefalse', question: value, marks: 1 };
|
| 2034 |
+
} else {
|
| 2035 |
+
sq = { type: 'normal', question: value, marks: 1 };
|
| 2036 |
+
}
|
| 2037 |
+
} else if (Array.isArray(value)) {
|
| 2038 |
+
if (value.length === 2 && typeof value[0] === 'string' && typeof value[1] === 'string' && value[0].startsWith('data:image')) {
|
| 2039 |
+
// [base64, "With Question"] → imagefirst
|
| 2040 |
+
sq = { type: 'imagefirst', imageData: value[0], question: value[1], marks: 1 };
|
| 2041 |
+
} else if (value.length === 2 && typeof value[0] === 'string' && typeof value[1] === 'string' && value[1].startsWith('data:image')) {
|
| 2042 |
+
// ["question", base64] → questionfirst
|
| 2043 |
+
sq = { type: 'questionfirst', question: value[0], imageData: value[1], marks: 1 };
|
| 2044 |
+
} else if (value.length === 2 && typeof value[0] === 'string' && typeof value[1] === 'number') {
|
| 2045 |
+
// ["Question with space", 10]
|
| 2046 |
+
sq = { type: 'questionspace', question: value[0], spaceHeight: value[1], marks: 1 };
|
| 2047 |
+
} else if (value.length === 2 && typeof value[0] === 'number' && typeof value[1] === 'string') {
|
| 2048 |
+
// [10, "Space with question"]
|
| 2049 |
+
sq = { type: 'spacequestion', spaceHeight: value[0], question: value[1], marks: 1 };
|
| 2050 |
+
} else if (Array.isArray(value) && value.every(v => typeof v === 'string')) {
|
| 2051 |
+
// Or question
|
| 2052 |
+
sq = { type: 'orquestion', options: value.map((text, i) => ({ id: i + 1, text })), marks: 1 };
|
| 2053 |
+
}
|
| 2054 |
+
} else if (typeof value === 'number') {
|
| 2055 |
+
// Blank space
|
| 2056 |
+
sq = { type: 'blank', spaceHeight: value, marks: 1 };
|
| 2057 |
+
}
|
| 2058 |
+
subquestions.push(sq);
|
| 2059 |
+
}
|
| 2060 |
+
}
|
| 2061 |
+
}
|
| 2062 |
+
});
|
| 2063 |
+
}
|
| 2064 |
+
const q = {
|
| 2065 |
+
id: generateId(),
|
| 2066 |
+
type: 'subquestion',
|
| 2067 |
+
mainTitle: questionTitle,
|
| 2068 |
+
marks: marks,
|
| 2069 |
+
subquestions: subquestions
|
| 2070 |
+
};
|
| 2071 |
+
questions.push(q);
|
| 2072 |
+
renderQuestions();
|
| 2073 |
+
updateTotalMarks();
|
| 2074 |
+
}
|
| 2075 |
+
|
| 2076 |
+
function questionWithSpace(question, spaceSize, marks = 1) {
|
| 2077 |
+
const q = {
|
| 2078 |
+
id: generateId(),
|
| 2079 |
+
type: 'questionspace',
|
| 2080 |
+
question: question,
|
| 2081 |
+
spaceHeight: spaceSize,
|
| 2082 |
+
marks: marks,
|
| 2083 |
+
answerOnPaper: false
|
| 2084 |
+
};
|
| 2085 |
+
questions.push(q);
|
| 2086 |
+
renderQuestions();
|
| 2087 |
+
updateTotalMarks();
|
| 2088 |
+
}
|
| 2089 |
+
|
| 2090 |
+
function spaceWithQuestion(spaceSize, question, marks = 1) {
|
| 2091 |
+
const q = {
|
| 2092 |
+
id: generateId(),
|
| 2093 |
+
type: 'spacequestion',
|
| 2094 |
+
question: question,
|
| 2095 |
+
spaceHeight: spaceSize,
|
| 2096 |
+
marks: marks,
|
| 2097 |
+
answerOnPaper: false
|
| 2098 |
+
};
|
| 2099 |
+
questions.push(q);
|
| 2100 |
+
renderQuestions();
|
| 2101 |
+
updateTotalMarks();
|
| 2102 |
+
}
|
| 2103 |
+
|
| 2104 |
+
function addSpace(spaceSize, marks = 1) {
|
| 2105 |
+
const q = {
|
| 2106 |
+
id: generateId(),
|
| 2107 |
+
type: 'blank',
|
| 2108 |
+
spaceHeight: spaceSize,
|
| 2109 |
+
marks: marks
|
| 2110 |
+
};
|
| 2111 |
+
questions.push(q);
|
| 2112 |
+
renderQuestions();
|
| 2113 |
+
updateTotalMarks();
|
| 2114 |
+
}
|
| 2115 |
+
|
| 2116 |
+
function orQuestion(questionList, marks = 1) {
|
| 2117 |
+
if (!Array.isArray(questionList)) return;
|
| 2118 |
+
const options = questionList.map((text, i) => ({ id: i + 1, text }));
|
| 2119 |
+
const q = {
|
| 2120 |
+
id: generateId(),
|
| 2121 |
+
type: 'orquestion',
|
| 2122 |
+
options: options,
|
| 2123 |
+
marks: marks
|
| 2124 |
+
};
|
| 2125 |
+
questions.push(q);
|
| 2126 |
+
renderQuestions();
|
| 2127 |
+
updateTotalMarks();
|
| 2128 |
+
}
|
| 2129 |
+
|
| 2130 |
+
function linkToBase64(url) {
|
| 2131 |
+
return new Promise((resolve, reject) => {
|
| 2132 |
+
const img = new Image();
|
| 2133 |
+
img.crossOrigin = 'anonymous'; // Handle CORS
|
| 2134 |
+
img.onload = () => {
|
| 2135 |
+
const canvas = document.createElement('canvas');
|
| 2136 |
+
canvas.width = img.width;
|
| 2137 |
+
canvas.height = img.height;
|
| 2138 |
+
const ctx = canvas.getContext('2d');
|
| 2139 |
+
ctx.drawImage(img, 0, 0);
|
| 2140 |
+
const base64 = canvas.toDataURL('image/png');
|
| 2141 |
+
resolve(base64);
|
| 2142 |
+
};
|
| 2143 |
+
img.onerror = () => reject(new Error('Failed to load image'));
|
| 2144 |
+
img.src = url;
|
| 2145 |
+
});
|
| 2146 |
+
}
|
| 2147 |
+
|
| 2148 |
+
</script>
|
| 2149 |
</body>
|
| 2150 |
</html>
|