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 files

Quiz (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 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
- width: 100%;
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
- <div id="notes-canvas-container">
 
 
 
 
 
 
 
 
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: 30%;
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
- <h6 class="text-white">Details</h6>
166
- <div id="details-content"></div>
 
 
 
 
 
 
 
 
 
 
 
 
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>Sub:</strong> ${d.subject} <br> <strong>Topic:</strong> ${d.topic}`;
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)}`;