munchboka-edutools 0.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of munchboka-edutools might be problematic. Click here for more details.

Files changed (157) hide show
  1. munchboka_edutools/__init__.py +184 -0
  2. munchboka_edutools/_plotmath_shim.py +126 -0
  3. munchboka_edutools/_version.py +2 -0
  4. munchboka_edutools/directives/__init__.py +1 -0
  5. munchboka_edutools/directives/admonitions.py +389 -0
  6. munchboka_edutools/directives/cas_popup.py +428 -0
  7. munchboka_edutools/directives/clear.py +103 -0
  8. munchboka_edutools/directives/dialogue.py +137 -0
  9. munchboka_edutools/directives/escape_room.py +296 -0
  10. munchboka_edutools/directives/escape_room2.py +318 -0
  11. munchboka_edutools/directives/factor_tree.py +552 -0
  12. munchboka_edutools/directives/flashcards.py +233 -0
  13. munchboka_edutools/directives/ggb.py +209 -0
  14. munchboka_edutools/directives/ggb_icon.py +105 -0
  15. munchboka_edutools/directives/ggb_popup.py +308 -0
  16. munchboka_edutools/directives/horner.py +326 -0
  17. munchboka_edutools/directives/interactive_code.py +75 -0
  18. munchboka_edutools/directives/jeopardy.py +252 -0
  19. munchboka_edutools/directives/jeopardy2.py +636 -0
  20. munchboka_edutools/directives/multi_plot.py +2524 -0
  21. munchboka_edutools/directives/multi_plot2.py +252 -0
  22. munchboka_edutools/directives/pair_puzzle.py +191 -0
  23. munchboka_edutools/directives/parsons.py +109 -0
  24. munchboka_edutools/directives/plot.py +3758 -0
  25. munchboka_edutools/directives/poly_icon.py +111 -0
  26. munchboka_edutools/directives/polydiv.py +346 -0
  27. munchboka_edutools/directives/popup.py +245 -0
  28. munchboka_edutools/directives/quiz.py +291 -0
  29. munchboka_edutools/directives/quiz2.py +453 -0
  30. munchboka_edutools/directives/signchart.py +519 -0
  31. munchboka_edutools/directives/signchart2.py +1545 -0
  32. munchboka_edutools/directives/timed_quiz.py +436 -0
  33. munchboka_edutools/directives/turtle.py +157 -0
  34. munchboka_edutools/static/css/admonitions.css +2012 -0
  35. munchboka_edutools/static/css/cas_popup.css +242 -0
  36. munchboka_edutools/static/css/code_mirror_themes/github_dark_cm.css +112 -0
  37. munchboka_edutools/static/css/code_mirror_themes/github_dark_default_cm.css +40 -0
  38. munchboka_edutools/static/css/code_mirror_themes/github_dark_high_contrast_cm.css +141 -0
  39. munchboka_edutools/static/css/code_mirror_themes/github_light_cm.css +120 -0
  40. munchboka_edutools/static/css/code_mirror_themes/github_light_default_cm.css +108 -0
  41. munchboka_edutools/static/css/code_mirror_themes/one_dark_cm.css +121 -0
  42. munchboka_edutools/static/css/dialogue.css +92 -0
  43. munchboka_edutools/static/css/escapeRoom/escape-room.css +223 -0
  44. munchboka_edutools/static/css/figures.css +321 -0
  45. munchboka_edutools/static/css/flashcards.css +219 -0
  46. munchboka_edutools/static/css/general_style.css +74 -0
  47. munchboka_edutools/static/css/github-dark-high-contrast.css +141 -0
  48. munchboka_edutools/static/css/github-dark.css +147 -0
  49. munchboka_edutools/static/css/github-light.css +155 -0
  50. munchboka_edutools/static/css/interactive_code/style.css +575 -0
  51. munchboka_edutools/static/css/interactive_code.css +582 -0
  52. munchboka_edutools/static/css/jeopardy.css +553 -0
  53. munchboka_edutools/static/css/pairPuzzle/style.css +177 -0
  54. munchboka_edutools/static/css/parsons/parsonsPuzzle.css +331 -0
  55. munchboka_edutools/static/css/popup.css +115 -0
  56. munchboka_edutools/static/css/quiz.css +377 -0
  57. munchboka_edutools/static/css/timedQuiz.css +375 -0
  58. munchboka_edutools/static/icons/ggb/mode_evaluate.svg +1 -0
  59. munchboka_edutools/static/icons/ggb/mode_extremum.svg +1 -0
  60. munchboka_edutools/static/icons/ggb/mode_intersect.svg +1 -0
  61. munchboka_edutools/static/icons/ggb/mode_nsolve.svg +1 -0
  62. munchboka_edutools/static/icons/ggb/mode_numeric.svg +1 -0
  63. munchboka_edutools/static/icons/ggb/mode_point.svg +1 -0
  64. munchboka_edutools/static/icons/ggb/mode_solve.svg +1 -0
  65. munchboka_edutools/static/icons/misc/windows-logo.svg +1 -0
  66. munchboka_edutools/static/icons/outline/dark_mode/academic.svg +3 -0
  67. munchboka_edutools/static/icons/outline/dark_mode/backward.svg +3 -0
  68. munchboka_edutools/static/icons/outline/dark_mode/book.svg +3 -0
  69. munchboka_edutools/static/icons/outline/dark_mode/chat_bubble.svg +3 -0
  70. munchboka_edutools/static/icons/outline/dark_mode/check.svg +3 -0
  71. munchboka_edutools/static/icons/outline/dark_mode/cmd_line.svg +3 -0
  72. munchboka_edutools/static/icons/outline/dark_mode/file.svg +1 -0
  73. munchboka_edutools/static/icons/outline/dark_mode/fire.svg +4 -0
  74. munchboka_edutools/static/icons/outline/dark_mode/key.svg +3 -0
  75. munchboka_edutools/static/icons/outline/dark_mode/magnifying.svg +3 -0
  76. munchboka_edutools/static/icons/outline/dark_mode/pencil_square.svg +3 -0
  77. munchboka_edutools/static/icons/outline/dark_mode/play.svg +3 -0
  78. munchboka_edutools/static/icons/outline/dark_mode/question.svg +3 -0
  79. munchboka_edutools/static/icons/outline/dark_mode/square_check.svg +1 -0
  80. munchboka_edutools/static/icons/outline/dark_mode/stop.svg +3 -0
  81. munchboka_edutools/static/icons/outline/dark_mode/summary.svg +3 -0
  82. munchboka_edutools/static/icons/outline/dark_mode/undo.svg +3 -0
  83. munchboka_edutools/static/icons/outline/dark_mode/unlock.svg +3 -0
  84. munchboka_edutools/static/icons/outline/light_mode/academic.svg +3 -0
  85. munchboka_edutools/static/icons/outline/light_mode/backward.svg +3 -0
  86. munchboka_edutools/static/icons/outline/light_mode/book.svg +3 -0
  87. munchboka_edutools/static/icons/outline/light_mode/chat_bubble.svg +3 -0
  88. munchboka_edutools/static/icons/outline/light_mode/check.svg +3 -0
  89. munchboka_edutools/static/icons/outline/light_mode/cmd_line.svg +3 -0
  90. munchboka_edutools/static/icons/outline/light_mode/file.svg +1 -0
  91. munchboka_edutools/static/icons/outline/light_mode/fire.svg +4 -0
  92. munchboka_edutools/static/icons/outline/light_mode/key.svg +3 -0
  93. munchboka_edutools/static/icons/outline/light_mode/magnifying.svg +3 -0
  94. munchboka_edutools/static/icons/outline/light_mode/pencil_square.svg +3 -0
  95. munchboka_edutools/static/icons/outline/light_mode/play.svg +3 -0
  96. munchboka_edutools/static/icons/outline/light_mode/question.svg +3 -0
  97. munchboka_edutools/static/icons/outline/light_mode/square_check.svg +1 -0
  98. munchboka_edutools/static/icons/outline/light_mode/stop.svg +3 -0
  99. munchboka_edutools/static/icons/outline/light_mode/summary.svg +3 -0
  100. munchboka_edutools/static/icons/outline/light_mode/undo.svg +3 -0
  101. munchboka_edutools/static/icons/outline/light_mode/unlock.svg +3 -0
  102. munchboka_edutools/static/icons/polyicons/cubicdown.svg +3 -0
  103. munchboka_edutools/static/icons/polyicons/cubicup.svg +3 -0
  104. munchboka_edutools/static/icons/polyicons/frown.svg +3 -0
  105. munchboka_edutools/static/icons/polyicons/smile.svg +3 -0
  106. munchboka_edutools/static/icons/solid/dark_mode/academic.svg +5 -0
  107. munchboka_edutools/static/icons/solid/dark_mode/backward.svg +3 -0
  108. munchboka_edutools/static/icons/solid/dark_mode/book.svg +3 -0
  109. munchboka_edutools/static/icons/solid/dark_mode/brain.svg +1 -0
  110. munchboka_edutools/static/icons/solid/dark_mode/file.svg +1 -0
  111. munchboka_edutools/static/icons/solid/dark_mode/fire.svg +3 -0
  112. munchboka_edutools/static/icons/solid/dark_mode/key.svg +3 -0
  113. munchboka_edutools/static/icons/solid/dark_mode/pencil_square.svg +4 -0
  114. munchboka_edutools/static/icons/solid/dark_mode/play.svg +3 -0
  115. munchboka_edutools/static/icons/solid/dark_mode/python.svg +1 -0
  116. munchboka_edutools/static/icons/solid/dark_mode/scroll.svg +1 -0
  117. munchboka_edutools/static/icons/solid/dark_mode/stop.svg +3 -0
  118. munchboka_edutools/static/icons/solid/light_mode/academic.svg +5 -0
  119. munchboka_edutools/static/icons/solid/light_mode/backward.svg +3 -0
  120. munchboka_edutools/static/icons/solid/light_mode/book.svg +3 -0
  121. munchboka_edutools/static/icons/solid/light_mode/brain.svg +1 -0
  122. munchboka_edutools/static/icons/solid/light_mode/file.svg +1 -0
  123. munchboka_edutools/static/icons/solid/light_mode/fire.svg +3 -0
  124. munchboka_edutools/static/icons/solid/light_mode/key.svg +3 -0
  125. munchboka_edutools/static/icons/solid/light_mode/pencil_square.svg +4 -0
  126. munchboka_edutools/static/icons/solid/light_mode/play.svg +3 -0
  127. munchboka_edutools/static/icons/solid/light_mode/python.svg +1 -0
  128. munchboka_edutools/static/icons/solid/light_mode/scroll.svg +1 -0
  129. munchboka_edutools/static/icons/solid/light_mode/stop.svg +3 -0
  130. munchboka_edutools/static/icons/stickers/edit.svg +1 -0
  131. munchboka_edutools/static/icons/stickers/pencil_square.svg +3 -0
  132. munchboka_edutools/static/js/casThemeManager.js +99 -0
  133. munchboka_edutools/static/js/escapeRoom/escape-room.js +219 -0
  134. munchboka_edutools/static/js/flashcards.js +199 -0
  135. munchboka_edutools/static/js/geogebra-setup.js +6 -0
  136. munchboka_edutools/static/js/highlight-init.js +6 -0
  137. munchboka_edutools/static/js/interactiveCode/codeEditor.js +648 -0
  138. munchboka_edutools/static/js/interactiveCode/interactiveCodeSetup.js +441 -0
  139. munchboka_edutools/static/js/interactiveCode/pythonRunner.js +336 -0
  140. munchboka_edutools/static/js/interactiveCode/turtleCode.js +203 -0
  141. munchboka_edutools/static/js/interactiveCode/workerManager.js +353 -0
  142. munchboka_edutools/static/js/jeopardy.js +560 -0
  143. munchboka_edutools/static/js/pairPuzzle/draggableItem.js +64 -0
  144. munchboka_edutools/static/js/pairPuzzle/dropZone.js +119 -0
  145. munchboka_edutools/static/js/pairPuzzle/game.js +160 -0
  146. munchboka_edutools/static/js/parsons/parsonsPuzzle.js +641 -0
  147. munchboka_edutools/static/js/popup.js +85 -0
  148. munchboka_edutools/static/js/quiz.js +566 -0
  149. munchboka_edutools/static/js/skulpt/skulpt.js +35721 -0
  150. munchboka_edutools/static/js/timedQuiz/multipleChoiceQuestion.js +184 -0
  151. munchboka_edutools/static/js/timedQuiz/timedMultipleChoiceQuiz.js +244 -0
  152. munchboka_edutools/static/js/timedQuiz/utils.js +6 -0
  153. munchboka_edutools/static/js/utils.js +3 -0
  154. munchboka_edutools-0.2.3.dist-info/METADATA +109 -0
  155. munchboka_edutools-0.2.3.dist-info/RECORD +157 -0
  156. munchboka_edutools-0.2.3.dist-info/WHEEL +4 -0
  157. munchboka_edutools-0.2.3.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,184 @@
1
+ // Generate UUID function
2
+ function generateUUID() {
3
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
4
+ const r = Math.random() * 16 | 0,
5
+ v = c === 'x' ? r : (r & 0x3 | 0x8);
6
+ return v.toString(16);
7
+ });
8
+ }
9
+
10
+ function shuffleArray(array) {
11
+ for (let i = array.length - 1; i > 0; i--) {
12
+ const j = Math.floor(Math.random() * (i + 1));
13
+ [array[i], array[j]] = [array[j], array[i]];
14
+ }
15
+ }
16
+
17
+ class MultipleChoiceQuestion {
18
+ constructor({ id, content, answers }) {
19
+ this.id = id;
20
+ this.content = content;
21
+ this.answers = answers.map((answer) => {
22
+ if (!answer.hasOwnProperty('id')) {
23
+ answer.id = generateUUID();
24
+ }
25
+ return answer;
26
+ });
27
+ this.selectedAnswers = new Set();
28
+ this.elements = {}; // Store elements for easy access
29
+ this.correctlyAnswered = false; // Track if the question is correctly answered
30
+ }
31
+
32
+ shuffleAnswers() {
33
+ shuffleArray(this.answers);
34
+ }
35
+
36
+ render(containerId) {
37
+ const container = document.getElementById(containerId);
38
+
39
+ // Create question element
40
+ const questionCard = document.createElement('div');
41
+ questionCard.classList.add('question-card');
42
+ questionCard.innerHTML = this.content;
43
+
44
+ // Append the question card to the container
45
+ container.appendChild(questionCard);
46
+
47
+ // Render LaTeX in the question
48
+ this.renderMathInElement(questionCard);
49
+
50
+ // Apply syntax highlighting to the question card
51
+ this.applySyntaxHighlighting(questionCard);
52
+
53
+ // Create answers container
54
+ const answersGrid = document.createElement('div');
55
+ answersGrid.classList.add('answers-grid');
56
+
57
+ // Append the answers grid to the container
58
+ container.appendChild(answersGrid);
59
+
60
+ // Create answer elements
61
+ this.answers.forEach((answer) => {
62
+ const answerCard = document.createElement('div');
63
+ answerCard.classList.add('answer-card');
64
+ answerCard.innerHTML = answer.content;
65
+ answerCard.dataset.answerId = answer.id;
66
+
67
+ // Append the answer card to the answers grid
68
+ answersGrid.appendChild(answerCard);
69
+
70
+ // Render LaTeX in the answer
71
+ this.renderMathInElement(answerCard);
72
+
73
+ // Apply syntax highlighting to the answer card
74
+ this.applySyntaxHighlighting(answerCard);
75
+
76
+ // Mark as selected if previously selected
77
+ if (this.selectedAnswers.has(answer.id)) {
78
+ answerCard.classList.add('selected');
79
+ }
80
+
81
+ // Disable interaction if question is correctly answered
82
+ if (this.correctlyAnswered) {
83
+ answerCard.classList.add('disabled');
84
+ } else {
85
+ // Add click event to handle selection
86
+ answerCard.addEventListener('click', () => this.toggleSelection(answerCard));
87
+ }
88
+ });
89
+
90
+ // Store elements for later access
91
+ this.elements.container = container;
92
+ this.elements.questionCard = questionCard;
93
+ this.elements.answersGrid = answersGrid;
94
+
95
+ // Apply correct class if previously answered correctly
96
+ if (this.correctlyAnswered) {
97
+ this.elements.questionCard.classList.add('correct');
98
+ }
99
+ }
100
+
101
+ toggleSelection(answerCard) {
102
+ const answerId = answerCard.dataset.answerId;
103
+ if (this.selectedAnswers.has(answerId)) {
104
+ this.selectedAnswers.delete(answerId);
105
+ answerCard.classList.remove('selected');
106
+ } else {
107
+ // If single-choice, deselect other answers
108
+ if (this.isSingleChoice()) {
109
+ this.selectedAnswers.clear();
110
+ const allAnswerCards = this.elements.answersGrid.querySelectorAll('.answer-card');
111
+ allAnswerCards.forEach(card => card.classList.remove('selected'));
112
+ }
113
+ this.selectedAnswers.add(answerId);
114
+ answerCard.classList.add('selected');
115
+ }
116
+ }
117
+
118
+ checkAnswers(showAlert = true) {
119
+ // Get the correct answer IDs
120
+ const correctAnswerIds = this.answers
121
+ .filter(answer => answer.isCorrect)
122
+ .map(answer => answer.id);
123
+
124
+ // Compare selectedAnswers with correctAnswerIds
125
+ const isCorrect =
126
+ this.selectedAnswers.size === correctAnswerIds.length &&
127
+ [...this.selectedAnswers].every(id => correctAnswerIds.includes(id));
128
+
129
+ // Provide visual feedback
130
+ if (isCorrect) {
131
+ this.elements.questionCard.classList.remove('incorrect'); // ✅ Remove incorrect class
132
+ this.elements.questionCard.classList.add('correct');
133
+ this.correctlyAnswered = true; // Mark question as correctly answered
134
+ // Disable further interaction
135
+ const allAnswerCards = this.elements.answersGrid.querySelectorAll('.answer-card');
136
+ allAnswerCards.forEach(card => {
137
+ card.classList.add('disabled');
138
+
139
+ const newCard = card.cloneNode(true);
140
+ card.parentNode.replaceChild(newCard, card);
141
+ });
142
+ } else {
143
+ this.elements.questionCard.classList.add('incorrect');
144
+ }
145
+
146
+ // Optionally display feedback if showAlert is true
147
+ if (showAlert) {
148
+ alert(isCorrect ? 'Correct!' : 'Incorrect. Please try again.');
149
+ }
150
+
151
+ return isCorrect;
152
+ }
153
+
154
+ markAsCorrectlyAnswered() {
155
+ this.correctlyAnswered = true;
156
+ }
157
+
158
+ isSingleChoice() {
159
+ // Determine if the question is single-choice
160
+ const correctAnswersCount = this.answers.filter(answer => answer.isCorrect).length;
161
+ return correctAnswersCount === 1;
162
+ }
163
+
164
+ renderMathInElement(element) {
165
+ // Ensure KaTeX renders LaTeX inside the element
166
+ renderMathInElement(element, {
167
+ delimiters: [
168
+ { left: '$$', right: '$$', display: true },
169
+ { left: '$', right: '$', display: false },
170
+ { left: '\\[', right: '\\]', display: true },
171
+ { left: '\\(', right: '\\)', display: false }
172
+ // ... other delimiters if needed ...
173
+ ]
174
+ });
175
+ }
176
+
177
+ applySyntaxHighlighting(element) {
178
+ // Apply syntax highlighting to code blocks
179
+ const codeBlocks = element.querySelectorAll('code');
180
+ codeBlocks.forEach(block => {
181
+ hljs.highlightElement(block);
182
+ });
183
+ }
184
+ }
@@ -0,0 +1,244 @@
1
+ class TimedMultipleChoiceQuiz {
2
+ constructor(containerId, questionsData, timeLimit = 60) {
3
+ this.containerId = containerId;
4
+ this.container = document.getElementById(containerId);
5
+ if (!this.container) {
6
+ throw new Error('Container not found');
7
+ }
8
+ this.questionsData = questionsData;
9
+ this.timeLimit = timeLimit; // Time limit in seconds
10
+ this.remainingTime = timeLimit;
11
+ this.currentQuestionIndex = 0;
12
+ this.correctAnswers = 0;
13
+ this.questionsAttempted = 0;
14
+ this.uniqueId = generateUUID();
15
+ this.correctlyAnsweredQuestions = new Set(); // Track correctly answered questions
16
+ this.init();
17
+ }
18
+
19
+ init() {
20
+ // Shuffle the questions
21
+ this.shuffleQuestions();
22
+
23
+ // Generate the HTML structure
24
+ this.generateHTML();
25
+
26
+ // Wait for the user to start the quiz
27
+ }
28
+
29
+ shuffleQuestions() {
30
+ for (let i = this.questionsData.length - 1; i > 0; i--) {
31
+ const j = Math.floor(Math.random() * (i + 1));
32
+ [this.questionsData[i], this.questionsData[j]] = [this.questionsData[j], this.questionsData[i]];
33
+ }
34
+ }
35
+
36
+ generateHTML() {
37
+ // Set up the main structure
38
+ this.container.innerHTML = `
39
+ <div id="timer-container-${this.uniqueId}" class="timer-container" style="display: none;">
40
+ <div id="progress-bar-${this.uniqueId}" class="progress-bar"></div>
41
+ <span id="timer-${this.uniqueId}" class="timer-text">${this.formatTime(this.remainingTime)}</span>
42
+ </div>
43
+ <div id="question-container-${this.uniqueId}" class="mcq-container" style="display: none;"></div>
44
+ <div class="button-container">
45
+ <button id="start-quiz-${this.uniqueId}" class="button button-run">Start Quiz</button>
46
+ <button id="submit-answer-${this.uniqueId}" class="button button-run" style="display: none;">Sjekk svar</button>
47
+ </div>
48
+ <!-- Toast Notifications -->
49
+ <div id="toast-success-${this.uniqueId}" class="toast toast-success" style="display: none;">
50
+ <p>Riktig! 🎉</p>
51
+ </div>
52
+ <div id="toast-error-${this.uniqueId}" class="toast toast-error" style="display: none;">
53
+ <p>Feil svar!</p>
54
+ </div>
55
+ `;
56
+
57
+ // Add event listener for the start button
58
+ document.getElementById(`start-quiz-${this.uniqueId}`).addEventListener('click', () => this.startQuiz());
59
+ }
60
+
61
+ startQuiz() {
62
+ // Hide the start button
63
+ const startButton = document.getElementById(`start-quiz-${this.uniqueId}`);
64
+ startButton.style.display = 'none';
65
+
66
+ // Show the timer container
67
+ const timerContainer = document.getElementById(`timer-container-${this.uniqueId}`);
68
+ timerContainer.style.display = 'block';
69
+
70
+ // Show the question container
71
+ const questionContainer = document.getElementById(`question-container-${this.uniqueId}`);
72
+ questionContainer.style.display = 'block';
73
+
74
+ // Show the submit button
75
+ const submitButton = document.getElementById(`submit-answer-${this.uniqueId}`);
76
+ submitButton.style.display = 'inline-block';
77
+
78
+ // Add event listener for the submit button
79
+ submitButton.addEventListener('click', () => this.submitAnswer());
80
+
81
+ // Start the timer
82
+ this.startTimer();
83
+
84
+ // Show the first question
85
+ this.showQuestion();
86
+ }
87
+
88
+ formatTime(seconds) {
89
+ const mins = Math.floor(seconds / 60);
90
+ const secs = seconds % 60;
91
+ return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
92
+ }
93
+
94
+ startTimer() {
95
+ this.updateTimerUI();
96
+ this.timerInterval = setInterval(() => {
97
+ this.remainingTime--;
98
+ this.updateTimerUI();
99
+
100
+ if (this.remainingTime <= 0) {
101
+ this.endQuiz();
102
+ }
103
+ }, 1000);
104
+ }
105
+
106
+ updateTimerUI() {
107
+ // Update the timer text
108
+ const timerText = document.getElementById(`timer-${this.uniqueId}`);
109
+ timerText.textContent = this.formatTime(this.remainingTime);
110
+
111
+ // Update the progress bar
112
+ const progressBar = document.getElementById(`progress-bar-${this.uniqueId}`);
113
+ const percentage = (this.remainingTime / this.timeLimit) * 100;
114
+ progressBar.style.width = `${percentage}%`;
115
+
116
+ // Remove existing state classes
117
+ progressBar.classList.remove('progress-bar-warning', 'progress-bar-danger');
118
+
119
+ // Change the color of the progress bar if time is running out
120
+ if (percentage <= 20) {
121
+ progressBar.classList.add('progress-bar-danger'); // Red
122
+ } else if (percentage <= 50) {
123
+ progressBar.classList.add('progress-bar-warning'); // Yellow
124
+ } else {
125
+ // Default is green, no additional class needed
126
+ }
127
+ }
128
+
129
+ showQuestion() {
130
+ // Increment the number of questions attempted
131
+ if (this.currentQuestionIndex >= this.questionsData.length) {
132
+ this.endQuiz();
133
+ return;
134
+ }
135
+
136
+ this.questionsAttempted++;
137
+
138
+ if (this.currentQuestionIndex >= this.questionsData.length) {
139
+ // Restart from the first question if we've reached the end
140
+ this.currentQuestionIndex = 0;
141
+ // Optionally reshuffle the questions here
142
+ // this.shuffleQuestions();
143
+ }
144
+
145
+ const questionData = this.questionsData[this.currentQuestionIndex];
146
+
147
+ // Clear the question container before rendering the new question
148
+ const questionContainer = document.getElementById(`question-container-${this.uniqueId}`);
149
+ questionContainer.innerHTML = ''; // Clear previous question
150
+
151
+ // Render the new question
152
+ this.currentQuestion = new MultipleChoiceQuestion(questionData);
153
+ this.currentQuestion.shuffleAnswers();
154
+ this.currentQuestion.render(`question-container-${this.uniqueId}`);
155
+ }
156
+
157
+ submitAnswer() {
158
+ const isCorrect = this.currentQuestion.checkAnswers(false); // Pass 'false' to suppress alerts
159
+
160
+ if (isCorrect) {
161
+ this.correctAnswers++;
162
+ this.correctlyAnsweredQuestions.add(this.currentQuestionIndex); // Track correct answer
163
+ this.currentQuestion.markAsCorrectlyAnswered();
164
+
165
+ // this.showToast('success');
166
+ } else {
167
+ // this.showToast('error');
168
+ }
169
+
170
+ setTimeout(() => {
171
+ this.currentQuestionIndex++;
172
+ this.showQuestion();
173
+ }, 500);
174
+
175
+ // Move to the next question immediately
176
+ // this.currentQuestionIndex++;
177
+ // this.showQuestion();
178
+ }
179
+
180
+ showToast(type) {
181
+ const toastId = type === 'success' ? `toast-success-${this.uniqueId}` : `toast-error-${this.uniqueId}`;
182
+ const toast = document.getElementById(toastId);
183
+
184
+ if (!toast) {
185
+ console.error(`Toast element with ID ${toastId} not found.`);
186
+ return;
187
+ }
188
+
189
+ // Ensure the container is positioned relatively
190
+ if (getComputedStyle(this.container).position === 'static') {
191
+ this.container.style.position = 'relative';
192
+ }
193
+
194
+ // Display the toast in the center of the container
195
+ toast.style.position = 'absolute';
196
+ toast.style.top = '50%';
197
+ toast.style.left = '50%';
198
+ toast.style.transform = 'translate(-50%, -50%)';
199
+ toast.style.display = 'block';
200
+
201
+ // Hide the toast after a short delay
202
+ setTimeout(() => {
203
+ toast.style.display = 'none';
204
+ }, 500); // Display for 0.5 seconds
205
+ }
206
+
207
+ endQuiz() {
208
+ // Stop the timer
209
+ clearInterval(this.timerInterval);
210
+
211
+ // Determine the appropriate message
212
+ let completionMessage;
213
+ if (this.remainingTime <= 0) {
214
+ completionMessage = `Tiden er ute! Du svarte riktig på ${this.correctAnswers} av ${this.questionsAttempted} spørsmål. 🎉`;
215
+ } else {
216
+ completionMessage = `Du har fullført quizen! Du svarte riktig på ${this.correctAnswers} av ${this.questionsAttempted} spørsmål. 🎉`;
217
+ }
218
+
219
+ // Clear the container and display the final score
220
+ this.container.innerHTML = `
221
+ <div class="quiz-completion-message">
222
+ <p>${completionMessage}</p>
223
+ <button id="restart-quiz-${this.uniqueId}" class="button button-run">Start på nytt</button>
224
+ </div>
225
+ `;
226
+
227
+ // Add event listener to restart the quiz
228
+ document.getElementById(`restart-quiz-${this.uniqueId}`).addEventListener('click', () => this.restartQuiz());
229
+ }
230
+
231
+ restartQuiz() {
232
+ // Reset variables
233
+ this.remainingTime = this.timeLimit;
234
+ this.currentQuestionIndex = 0;
235
+ this.correctAnswers = 0;
236
+ this.questionsAttempted = 0;
237
+
238
+ // Shuffle questions again
239
+ this.shuffleQuestions();
240
+
241
+ // Reinitialize the quiz
242
+ this.init();
243
+ }
244
+ }
@@ -0,0 +1,6 @@
1
+ function shuffleArray(array) {
2
+ for (let i = array.length - 1; i > 0; i--) {
3
+ const j = Math.floor(Math.random() * (i + 1));
4
+ [array[i], array[j]] = [array[j], array[i]];
5
+ }
6
+ }
@@ -0,0 +1,3 @@
1
+ // Placeholder JS to verify asset registration
2
+ window.munchbokaEdutools = window.munchbokaEdutools || {};
3
+ console.debug("munchboka-edutools: utils.js loaded");
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: munchboka-edutools
3
+ Version: 0.2.3
4
+ Summary: Sphinx/Jupyter Book directives and assets for Munchboka and VGS books
5
+ Project-URL: Homepage, https://github.com/reneaas/munchboka-edutools
6
+ Project-URL: Issues, https://github.com/reneaas/munchboka-edutools/issues
7
+ Author: René Ask
8
+ License: MIT License
9
+
10
+ Copyright (c) [2024] [René Alexander Ask]
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: directives,education,jupyter-book,munchboka,sphinx
31
+ Classifier: Framework :: Sphinx :: Extension
32
+ Classifier: Framework :: Sphinx :: Theme
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Topic :: Documentation
36
+ Requires-Python: >=3.9
37
+ Requires-Dist: matplotlib>=3.7
38
+ Requires-Dist: numpy>=1.23
39
+ Requires-Dist: plotmath
40
+ Requires-Dist: scipy>=1.9
41
+ Requires-Dist: signchart
42
+ Requires-Dist: sphinx>=6.0
43
+ Requires-Dist: sympy>=1.11
44
+ Provides-Extra: dev
45
+ Requires-Dist: black; extra == 'dev'
46
+ Requires-Dist: build; extra == 'dev'
47
+ Requires-Dist: jupyter-book; extra == 'dev'
48
+ Requires-Dist: pytest; extra == 'dev'
49
+ Requires-Dist: ruff; extra == 'dev'
50
+ Requires-Dist: twine; extra == 'dev'
51
+ Description-Content-Type: text/markdown
52
+
53
+ # munchboka-edutools
54
+
55
+ Reusable Sphinx / Jupyter Book directives and assets for Norwegian educational content.
56
+
57
+ ## Install
58
+
59
+ ```bash
60
+ pip install munchboka-edutools
61
+ ```
62
+
63
+ ## Usage (Jupyter Book `_config.yml`)
64
+
65
+ ```yaml
66
+ sphinx:
67
+ extra_extensions:
68
+ - munchboka_edutools
69
+ ```
70
+
71
+ All packaged directives are auto-registered. Static assets are placed under `_static/munchboka/` during the build.
72
+
73
+ ## Development
74
+
75
+ Clone and install dev extras:
76
+
77
+ ```bash
78
+ pip install -e .[dev]
79
+ ```
80
+
81
+ ### Local demo book
82
+
83
+ Build the embedded demo book:
84
+
85
+ ```bash
86
+ jupyter-book build book/
87
+ ```
88
+
89
+ ### Adding a new directive
90
+
91
+ 1. Create `src/munchboka_edutools/directives/my_directive.py` exposing `setup(app)`.
92
+ 2. The package auto-loads modules under `munchboka_edutools.directives`.
93
+ 3. Add an example page in `book/examples/`.
94
+
95
+ ### Testing
96
+
97
+ Run tests:
98
+
99
+ ```bash
100
+ pytest -q
101
+ ```
102
+
103
+ ### Releasing
104
+
105
+ Tag a version (`v0.1.0`) and the GitHub Action will build and publish.
106
+
107
+ ## License
108
+
109
+ MIT