Spaces:
Running
Running
t Claude (claude-opus-4-5-thinking) commited on
Commit ·
bce2021
1
Parent(s): 5525be4
feat: add revision notes display in quiz and question reference in notes modal
Browse filesQuiz (quiz_v2.html):
- Display revision notes in details panel when pressing space
- Show question thumbnail alongside details
- Improved grid layout for details panel
Notes Modal (question_entry_v2.html):
- Add question reference panel on left side while writing notes
- User can see the question while handwriting revision notes
- Responsive: panel hidden on mobile
Backend (neetprep.py):
- Include note_filename in quiz question data for classified questions
Co-Authored-By: Claude (claude-opus-4-5-thinking) <noreply@anthropic.com>
- neetprep.py +5 -4
- templates/question_entry_v2.html +29 -4
- templates/quiz_v2.html +63 -5
neetprep.py
CHANGED
|
@@ -358,12 +358,12 @@ def generate_neetprep_pdf():
|
|
| 358 |
WHERE q.chapter IN ({placeholders}) AND s.user_id = ?
|
| 359 |
""", (*topics, current_user.id)).fetchall()
|
| 360 |
for q in classified_questions_from_db:
|
| 361 |
-
image_info = conn.execute("SELECT processed_filename FROM images WHERE id = ?", (q['image_id'],)).fetchone()
|
| 362 |
if image_info and image_info['processed_filename']:
|
| 363 |
if pdf_type == 'quiz':
|
| 364 |
all_questions.append({
|
| 365 |
'image_path': f"/processed/{image_info['processed_filename']}",
|
| 366 |
-
'details': {'id': q['id'], 'options': [], 'correct_answer_index': q['actual_solution'], 'user_answer_index': q['marked_solution'], 'source': 'classified', 'topic': q['chapter'], 'subject': q['subject']}
|
| 367 |
})
|
| 368 |
else:
|
| 369 |
all_questions.append({"id": q['id'], "question_text": f"<img src=\"{os.path.join(current_app.config['PROCESSED_FOLDER'], image_info['processed_filename'])}\" />", "options": [], "correct_answer_index": q['actual_solution'], "user_answer_index": q['marked_solution'], "status": q['status'], "source": "classified", "custom_fields": {"subject": q['subject'], "chapter": q['chapter'], "question_number": q['question_number']}})
|
|
@@ -1145,7 +1145,7 @@ def collection_quiz(session_id):
|
|
| 1145 |
# Get classified bookmarked questions
|
| 1146 |
classified_questions = conn.execute("""
|
| 1147 |
SELECT q.id, q.actual_solution as correct_answer_index, q.marked_solution as user_answer_index,
|
| 1148 |
-
q.chapter as topic, q.subject, i.processed_filename
|
| 1149 |
FROM neetprep_bookmarks b
|
| 1150 |
JOIN questions q ON CAST(b.neetprep_question_id AS INTEGER) = q.id
|
| 1151 |
LEFT JOIN images i ON q.image_id = i.id
|
|
@@ -1164,7 +1164,8 @@ def collection_quiz(session_id):
|
|
| 1164 |
'user_answer_index': q['user_answer_index'],
|
| 1165 |
'source': 'classified',
|
| 1166 |
'topic': q['topic'],
|
| 1167 |
-
'subject': q['subject']
|
|
|
|
| 1168 |
}
|
| 1169 |
})
|
| 1170 |
|
|
|
|
| 358 |
WHERE q.chapter IN ({placeholders}) AND s.user_id = ?
|
| 359 |
""", (*topics, current_user.id)).fetchall()
|
| 360 |
for q in classified_questions_from_db:
|
| 361 |
+
image_info = conn.execute("SELECT processed_filename, note_filename FROM images WHERE id = ?", (q['image_id'],)).fetchone()
|
| 362 |
if image_info and image_info['processed_filename']:
|
| 363 |
if pdf_type == 'quiz':
|
| 364 |
all_questions.append({
|
| 365 |
'image_path': f"/processed/{image_info['processed_filename']}",
|
| 366 |
+
'details': {'id': q['id'], 'options': [], 'correct_answer_index': q['actual_solution'], 'user_answer_index': q['marked_solution'], 'source': 'classified', 'topic': q['chapter'], 'subject': q['subject'], 'note_filename': image_info['note_filename']}
|
| 367 |
})
|
| 368 |
else:
|
| 369 |
all_questions.append({"id": q['id'], "question_text": f"<img src=\"{os.path.join(current_app.config['PROCESSED_FOLDER'], image_info['processed_filename'])}\" />", "options": [], "correct_answer_index": q['actual_solution'], "user_answer_index": q['marked_solution'], "status": q['status'], "source": "classified", "custom_fields": {"subject": q['subject'], "chapter": q['chapter'], "question_number": q['question_number']}})
|
|
|
|
| 1145 |
# Get classified bookmarked questions
|
| 1146 |
classified_questions = conn.execute("""
|
| 1147 |
SELECT q.id, q.actual_solution as correct_answer_index, q.marked_solution as user_answer_index,
|
| 1148 |
+
q.chapter as topic, q.subject, i.processed_filename, i.note_filename
|
| 1149 |
FROM neetprep_bookmarks b
|
| 1150 |
JOIN questions q ON CAST(b.neetprep_question_id AS INTEGER) = q.id
|
| 1151 |
LEFT JOIN images i ON q.image_id = i.id
|
|
|
|
| 1164 |
'user_answer_index': q['user_answer_index'],
|
| 1165 |
'source': 'classified',
|
| 1166 |
'topic': q['topic'],
|
| 1167 |
+
'subject': q['subject'],
|
| 1168 |
+
'note_filename': q['note_filename']
|
| 1169 |
}
|
| 1170 |
})
|
| 1171 |
|
templates/question_entry_v2.html
CHANGED
|
@@ -58,9 +58,15 @@
|
|
| 58 |
}
|
| 59 |
|
| 60 |
/* Notes Modal Styles */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
#notes-canvas-container {
|
| 62 |
-
|
| 63 |
-
height: calc(100vh - 180px);
|
| 64 |
background: #f8f9fa;
|
| 65 |
border: 1px solid #495057;
|
| 66 |
overflow: hidden;
|
|
@@ -73,6 +79,11 @@
|
|
| 73 |
#notes-canvas-container canvas {
|
| 74 |
touch-action: none; /* Critical for stylus/touch drawing */
|
| 75 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
.notes-toolbar {
|
| 77 |
display: flex;
|
| 78 |
gap: 8px;
|
|
@@ -454,8 +465,16 @@
|
|
| 454 |
</div>
|
| 455 |
</div>
|
| 456 |
</div>
|
| 457 |
-
<div class="modal-body p-0" id="notes-canvas-body">
|
| 458 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
<canvas id="notes-canvas"></canvas>
|
| 460 |
</div>
|
| 461 |
</div>
|
|
@@ -706,11 +725,17 @@
|
|
| 706 |
}
|
| 707 |
|
| 708 |
// --- CANVAS FUNCTIONS ---
|
|
|
|
|
|
|
| 709 |
function openNotesModal(imageId, imageUrl, existingNoteUrl) {
|
| 710 |
currentNoteImageId = imageId;
|
|
|
|
| 711 |
canvasHistory = [];
|
| 712 |
historyIndex = -1;
|
| 713 |
|
|
|
|
|
|
|
|
|
|
| 714 |
const modal = new bootstrap.Modal(document.getElementById('notesModal'));
|
| 715 |
modal.show();
|
| 716 |
|
|
|
|
| 58 |
}
|
| 59 |
|
| 60 |
/* Notes Modal Styles */
|
| 61 |
+
#notes-canvas-body {
|
| 62 |
+
height: calc(100vh - 140px);
|
| 63 |
+
}
|
| 64 |
+
#question-reference-panel {
|
| 65 |
+
min-width: 200px;
|
| 66 |
+
max-width: 300px;
|
| 67 |
+
}
|
| 68 |
#notes-canvas-container {
|
| 69 |
+
height: 100%;
|
|
|
|
| 70 |
background: #f8f9fa;
|
| 71 |
border: 1px solid #495057;
|
| 72 |
overflow: hidden;
|
|
|
|
| 79 |
#notes-canvas-container canvas {
|
| 80 |
touch-action: none; /* Critical for stylus/touch drawing */
|
| 81 |
}
|
| 82 |
+
@media (max-width: 768px) {
|
| 83 |
+
#question-reference-panel {
|
| 84 |
+
display: none !important;
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
.notes-toolbar {
|
| 88 |
display: flex;
|
| 89 |
gap: 8px;
|
|
|
|
| 465 |
</div>
|
| 466 |
</div>
|
| 467 |
</div>
|
| 468 |
+
<div class="modal-body p-0 d-flex" id="notes-canvas-body">
|
| 469 |
+
<!-- Question Reference Panel -->
|
| 470 |
+
<div id="question-reference-panel" class="bg-secondary" style="width: 250px; flex-shrink: 0; overflow: auto; border-right: 2px solid #495057;">
|
| 471 |
+
<div class="p-2 text-center">
|
| 472 |
+
<small class="text-white-50">Question Reference</small>
|
| 473 |
+
<img id="notes-question-ref" src="" class="img-fluid rounded mt-2" style="max-height: 80vh; object-fit: contain;" alt="Question">
|
| 474 |
+
</div>
|
| 475 |
+
</div>
|
| 476 |
+
<!-- Canvas Area -->
|
| 477 |
+
<div id="notes-canvas-container" style="flex: 1;">
|
| 478 |
<canvas id="notes-canvas"></canvas>
|
| 479 |
</div>
|
| 480 |
</div>
|
|
|
|
| 725 |
}
|
| 726 |
|
| 727 |
// --- CANVAS FUNCTIONS ---
|
| 728 |
+
let currentQuestionImageUrl = null;
|
| 729 |
+
|
| 730 |
function openNotesModal(imageId, imageUrl, existingNoteUrl) {
|
| 731 |
currentNoteImageId = imageId;
|
| 732 |
+
currentQuestionImageUrl = imageUrl;
|
| 733 |
canvasHistory = [];
|
| 734 |
historyIndex = -1;
|
| 735 |
|
| 736 |
+
// Set the question reference image
|
| 737 |
+
document.getElementById('notes-question-ref').src = imageUrl;
|
| 738 |
+
|
| 739 |
const modal = new bootstrap.Modal(document.getElementById('notesModal'));
|
| 740 |
modal.show();
|
| 741 |
|
templates/quiz_v2.html
CHANGED
|
@@ -89,12 +89,45 @@
|
|
| 89 |
display: none;
|
| 90 |
background-color: #343a40;
|
| 91 |
border-top: 1px solid #6c757d;
|
| 92 |
-
max-height:
|
| 93 |
overflow-y: auto;
|
| 94 |
padding: 15px;
|
| 95 |
font-size: 0.9rem;
|
| 96 |
color: #e9ecef;
|
| 97 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
/* --- BUTTON STYLES --- */
|
| 100 |
.btn-pill { border-radius: 50px; }
|
|
@@ -162,8 +195,20 @@
|
|
| 162 |
|
| 163 |
<!-- 3. Details (Hidden by default) -->
|
| 164 |
<div class="details-spoiler" id="details-spoiler">
|
| 165 |
-
<
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
</div>
|
| 168 |
|
| 169 |
<!-- 4. Controls -->
|
|
@@ -320,10 +365,23 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 320 |
|
| 321 |
// Details
|
| 322 |
const d = q.details;
|
| 323 |
-
let h = `<strong>
|
| 324 |
-
if(d.options) { h += '<ol type="A" style="padding-left:15px; margin:5px 0;">'; d.options.forEach(o=>h+=`<li>${o}</li>`); h+='</ol>'; }
|
| 325 |
document.getElementById('details-content').innerHTML = h;
|
| 326 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
// Overlays
|
| 328 |
const fmt = v => typeof v==='number'?String.fromCharCode(65+v):v;
|
| 329 |
yourOv.textContent = `You: ${fmt(d.user_answer_index)}`;
|
|
|
|
| 89 |
display: none;
|
| 90 |
background-color: #343a40;
|
| 91 |
border-top: 1px solid #6c757d;
|
| 92 |
+
max-height: 50%;
|
| 93 |
overflow-y: auto;
|
| 94 |
padding: 15px;
|
| 95 |
font-size: 0.9rem;
|
| 96 |
color: #e9ecef;
|
| 97 |
}
|
| 98 |
+
.details-spoiler .details-grid {
|
| 99 |
+
display: grid;
|
| 100 |
+
grid-template-columns: 1fr 1fr;
|
| 101 |
+
gap: 15px;
|
| 102 |
+
}
|
| 103 |
+
.details-spoiler .question-thumb {
|
| 104 |
+
max-height: 200px;
|
| 105 |
+
object-fit: contain;
|
| 106 |
+
border-radius: 6px;
|
| 107 |
+
border: 1px solid #495057;
|
| 108 |
+
}
|
| 109 |
+
.details-spoiler .note-thumb {
|
| 110 |
+
max-height: 200px;
|
| 111 |
+
object-fit: contain;
|
| 112 |
+
border-radius: 6px;
|
| 113 |
+
border: 2px solid #0dcaf0;
|
| 114 |
+
background: #fff;
|
| 115 |
+
}
|
| 116 |
+
.note-section {
|
| 117 |
+
background: rgba(13, 202, 240, 0.1);
|
| 118 |
+
border-radius: 8px;
|
| 119 |
+
padding: 10px;
|
| 120 |
+
margin-top: 10px;
|
| 121 |
+
}
|
| 122 |
+
.note-section h6 {
|
| 123 |
+
color: #0dcaf0;
|
| 124 |
+
margin-bottom: 8px;
|
| 125 |
+
}
|
| 126 |
+
@media (max-width: 768px) {
|
| 127 |
+
.details-spoiler .details-grid {
|
| 128 |
+
grid-template-columns: 1fr;
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
|
| 132 |
/* --- BUTTON STYLES --- */
|
| 133 |
.btn-pill { border-radius: 50px; }
|
|
|
|
| 195 |
|
| 196 |
<!-- 3. Details (Hidden by default) -->
|
| 197 |
<div class="details-spoiler" id="details-spoiler">
|
| 198 |
+
<div class="details-grid">
|
| 199 |
+
<div>
|
| 200 |
+
<h6 class="text-white mb-2"><i class="bi bi-image me-1"></i>Question</h6>
|
| 201 |
+
<img id="details-question-img" src="" class="question-thumb" alt="Question">
|
| 202 |
+
</div>
|
| 203 |
+
<div id="details-info">
|
| 204 |
+
<h6 class="text-white mb-2"><i class="bi bi-info-circle me-1"></i>Details</h6>
|
| 205 |
+
<div id="details-content"></div>
|
| 206 |
+
</div>
|
| 207 |
+
</div>
|
| 208 |
+
<div id="note-section" class="note-section" style="display: none;">
|
| 209 |
+
<h6><i class="bi bi-journal-text me-1"></i>Revision Notes</h6>
|
| 210 |
+
<img id="details-note-img" src="" class="note-thumb" alt="Revision Note">
|
| 211 |
+
</div>
|
| 212 |
</div>
|
| 213 |
|
| 214 |
<!-- 4. Controls -->
|
|
|
|
| 365 |
|
| 366 |
// Details
|
| 367 |
const d = q.details;
|
| 368 |
+
let h = `<strong>Subject:</strong> ${d.subject || 'N/A'} <br> <strong>Topic:</strong> ${d.topic || 'N/A'}`;
|
| 369 |
+
if(d.options && d.options.length) { h += '<ol type="A" style="padding-left:15px; margin:5px 0;">'; d.options.forEach(o=>h+=`<li>${o}</li>`); h+='</ol>'; }
|
| 370 |
document.getElementById('details-content').innerHTML = h;
|
| 371 |
|
| 372 |
+
// Set question thumbnail in details panel
|
| 373 |
+
document.getElementById('details-question-img').src = path;
|
| 374 |
+
|
| 375 |
+
// Set revision note if available
|
| 376 |
+
const noteSection = document.getElementById('note-section');
|
| 377 |
+
const noteImg = document.getElementById('details-note-img');
|
| 378 |
+
if (d.note_filename) {
|
| 379 |
+
noteImg.src = `/neetprep/processed/${d.note_filename}`;
|
| 380 |
+
noteSection.style.display = 'block';
|
| 381 |
+
} else {
|
| 382 |
+
noteSection.style.display = 'none';
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
// Overlays
|
| 386 |
const fmt = v => typeof v==='number'?String.fromCharCode(65+v):v;
|
| 387 |
yourOv.textContent = `You: ${fmt(d.user_answer_index)}`;
|