munchboka-edutools 0.1.13__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.
- munchboka_edutools/__init__.py +184 -0
- munchboka_edutools/_plotmath_shim.py +126 -0
- munchboka_edutools/_version.py +2 -0
- munchboka_edutools/directives/__init__.py +1 -0
- munchboka_edutools/directives/admonitions.py +389 -0
- munchboka_edutools/directives/cas_popup.py +272 -0
- munchboka_edutools/directives/clear.py +103 -0
- munchboka_edutools/directives/dialogue.py +137 -0
- munchboka_edutools/directives/escape_room.py +296 -0
- munchboka_edutools/directives/factor_tree.py +549 -0
- munchboka_edutools/directives/ggb.py +209 -0
- munchboka_edutools/directives/ggb_icon.py +105 -0
- munchboka_edutools/directives/ggb_popup.py +165 -0
- munchboka_edutools/directives/horner.py +324 -0
- munchboka_edutools/directives/interactive_code.py +75 -0
- munchboka_edutools/directives/jeopardy.py +252 -0
- munchboka_edutools/directives/multi_plot.py +1126 -0
- munchboka_edutools/directives/pair_puzzle.py +191 -0
- munchboka_edutools/directives/parsons.py +109 -0
- munchboka_edutools/directives/plot.py +3105 -0
- munchboka_edutools/directives/poly_icon.py +111 -0
- munchboka_edutools/directives/polydiv.py +344 -0
- munchboka_edutools/directives/popup.py +245 -0
- munchboka_edutools/directives/quiz.py +291 -0
- munchboka_edutools/directives/signchart.py +516 -0
- munchboka_edutools/directives/timed_quiz.py +436 -0
- munchboka_edutools/directives/turtle.py +157 -0
- munchboka_edutools/static/css/admonitions.css +2012 -0
- munchboka_edutools/static/css/cas_popup.css +242 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_cm.css +112 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_default_cm.css +40 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_high_contrast_cm.css +141 -0
- munchboka_edutools/static/css/code_mirror_themes/github_light_cm.css +120 -0
- munchboka_edutools/static/css/code_mirror_themes/github_light_default_cm.css +108 -0
- munchboka_edutools/static/css/code_mirror_themes/one_dark_cm.css +121 -0
- munchboka_edutools/static/css/dialogue.css +92 -0
- munchboka_edutools/static/css/escapeRoom/escape-room.css +223 -0
- munchboka_edutools/static/css/figures.css +274 -0
- munchboka_edutools/static/css/general_style.css +74 -0
- munchboka_edutools/static/css/github-dark-high-contrast.css +141 -0
- munchboka_edutools/static/css/github-dark.css +112 -0
- munchboka_edutools/static/css/github-light.css +120 -0
- munchboka_edutools/static/css/interactive_code/style.css +575 -0
- munchboka_edutools/static/css/interactive_code.css +582 -0
- munchboka_edutools/static/css/jeopardy.css +529 -0
- munchboka_edutools/static/css/pairPuzzle/style.css +177 -0
- munchboka_edutools/static/css/parsons/parsonsPuzzle.css +331 -0
- munchboka_edutools/static/css/popup.css +115 -0
- munchboka_edutools/static/css/quiz.css +312 -0
- munchboka_edutools/static/css/timedQuiz.css +375 -0
- munchboka_edutools/static/icons/ggb/mode_evaluate.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_extremum.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_intersect.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_nsolve.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_numeric.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_point.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_solve.svg +1 -0
- munchboka_edutools/static/icons/misc/windows-logo.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/academic.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/book.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/chat_bubble.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/check.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/cmd_line.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/file.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/fire.svg +4 -0
- munchboka_edutools/static/icons/outline/dark_mode/key.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/magnifying.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/pencil_square.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/play.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/question.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/square_check.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/summary.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/undo.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/unlock.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/academic.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/book.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/chat_bubble.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/check.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/cmd_line.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/file.svg +1 -0
- munchboka_edutools/static/icons/outline/light_mode/fire.svg +4 -0
- munchboka_edutools/static/icons/outline/light_mode/key.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/magnifying.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/pencil_square.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/play.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/question.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/square_check.svg +1 -0
- munchboka_edutools/static/icons/outline/light_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/summary.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/undo.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/unlock.svg +3 -0
- munchboka_edutools/static/icons/polyicons/cubicdown.svg +3 -0
- munchboka_edutools/static/icons/polyicons/cubicup.svg +3 -0
- munchboka_edutools/static/icons/polyicons/frown.svg +3 -0
- munchboka_edutools/static/icons/polyicons/smile.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/academic.svg +5 -0
- munchboka_edutools/static/icons/solid/dark_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/book.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/brain.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/file.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/fire.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/key.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/pencil_square.svg +4 -0
- munchboka_edutools/static/icons/solid/dark_mode/play.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/python.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/scroll.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/academic.svg +5 -0
- munchboka_edutools/static/icons/solid/light_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/book.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/brain.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/file.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/fire.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/key.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/pencil_square.svg +4 -0
- munchboka_edutools/static/icons/solid/light_mode/play.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/python.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/scroll.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/stickers/edit.svg +1 -0
- munchboka_edutools/static/icons/stickers/pencil_square.svg +3 -0
- munchboka_edutools/static/js/casThemeManager.js +99 -0
- munchboka_edutools/static/js/escapeRoom/escape-room.js +219 -0
- munchboka_edutools/static/js/geogebra-setup.js +6 -0
- munchboka_edutools/static/js/highlight-init.js +6 -0
- munchboka_edutools/static/js/interactiveCode/codeEditor.js +632 -0
- munchboka_edutools/static/js/interactiveCode/interactiveCodeSetup.js +348 -0
- munchboka_edutools/static/js/interactiveCode/pythonRunner.js +336 -0
- munchboka_edutools/static/js/interactiveCode/turtleCode.js +203 -0
- munchboka_edutools/static/js/interactiveCode/workerManager.js +353 -0
- munchboka_edutools/static/js/jeopardy.js +523 -0
- munchboka_edutools/static/js/pairPuzzle/draggableItem.js +64 -0
- munchboka_edutools/static/js/pairPuzzle/dropZone.js +119 -0
- munchboka_edutools/static/js/pairPuzzle/game.js +160 -0
- munchboka_edutools/static/js/parsons/parsonsPuzzle.js +641 -0
- munchboka_edutools/static/js/popup.js +85 -0
- munchboka_edutools/static/js/quiz.js +422 -0
- munchboka_edutools/static/js/skulpt/skulpt.js +35721 -0
- munchboka_edutools/static/js/timedQuiz/multipleChoiceQuestion.js +184 -0
- munchboka_edutools/static/js/timedQuiz/timedMultipleChoiceQuiz.js +244 -0
- munchboka_edutools/static/js/timedQuiz/utils.js +6 -0
- munchboka_edutools/static/js/utils.js +3 -0
- munchboka_edutools-0.1.13.dist-info/METADATA +108 -0
- munchboka_edutools-0.1.13.dist-info/RECORD +149 -0
- munchboka_edutools-0.1.13.dist-info/WHEEL +4 -0
- munchboka_edutools-0.1.13.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
class ParsonsPuzzle {
|
|
2
|
+
constructor(puzzleContainerId, codeString, onSolvedCallback = null) {
|
|
3
|
+
this.puzzleContainerId = puzzleContainerId;
|
|
4
|
+
this.puzzleContainer = document.getElementById(puzzleContainerId);
|
|
5
|
+
this.codeString = codeString;
|
|
6
|
+
this.onSolvedCallback = onSolvedCallback;
|
|
7
|
+
|
|
8
|
+
this.generateHTML();
|
|
9
|
+
|
|
10
|
+
this.dropArea = document.getElementById(this.dropAreaId);
|
|
11
|
+
this.checkButton = document.getElementById(this.checkSolutionId);
|
|
12
|
+
this.resetButton = document.getElementById(this.resetButtonId);
|
|
13
|
+
this.draggableCodeContainer = document.getElementById(this.draggableId);
|
|
14
|
+
this.toast = document.getElementById(this.toastId);
|
|
15
|
+
|
|
16
|
+
this.solutionModal = this.createSolutionModal(puzzleContainerId);
|
|
17
|
+
this.fullCodeElement = this.solutionModal.querySelector(`#fullCode-${puzzleContainerId}`);
|
|
18
|
+
this.closeModalButton = this.solutionModal.querySelector('.close');
|
|
19
|
+
this.copyCodeButton = this.solutionModal.querySelector(`#copyCodeButton-${puzzleContainerId}`);
|
|
20
|
+
|
|
21
|
+
this.codeBlocks = this.preprocessCode(codeString);
|
|
22
|
+
this.shuffledCodeBlocks = this.shuffleArray(this.codeBlocks.slice());
|
|
23
|
+
|
|
24
|
+
this.renderDraggableCode(this.draggableCodeContainer, this.shuffledCodeBlocks);
|
|
25
|
+
this.createPlaceholder(this.dropArea);
|
|
26
|
+
this.enableDragAndDrop(this.draggableCodeContainer, this.dropArea);
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
this.isSolved = false;
|
|
30
|
+
|
|
31
|
+
this.addEventListeners();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
addEventListeners() {
|
|
36
|
+
this.checkButton.addEventListener('click', () => this.checkSolution());
|
|
37
|
+
this.resetButton.addEventListener('click', () => {
|
|
38
|
+
this.reset();
|
|
39
|
+
this.reshuffle();
|
|
40
|
+
});
|
|
41
|
+
this.closeModalButton.addEventListener('click', () => this.solutionModal.style.display = 'none');
|
|
42
|
+
this.copyCodeButton.addEventListener('click', () => {
|
|
43
|
+
navigator.clipboard.writeText(this.fullCodeElement.textContent).then(() => {
|
|
44
|
+
alert('Du har kopiert koden!');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
generateHTML() {
|
|
50
|
+
const container = document.getElementById(this.puzzleContainerId);
|
|
51
|
+
if (!container) {
|
|
52
|
+
console.error(`Container with ID ${this.puzzleContainerId} not found.`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const uniqueId = generateUUID();
|
|
57
|
+
this.dropAreaId = `drop-area-${uniqueId}`;
|
|
58
|
+
this.checkSolutionId = `check-solution-${uniqueId}`;
|
|
59
|
+
this.resetButtonId = `reset-button-${uniqueId}`;
|
|
60
|
+
this.draggableId = `draggable-code-${uniqueId}`;
|
|
61
|
+
this.toastSuccessId = `toast-success-${uniqueId}`;
|
|
62
|
+
this.toastErrorId = `toast-error-${uniqueId}`;
|
|
63
|
+
|
|
64
|
+
const checkSolutionIcon = `
|
|
65
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
|
66
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
|
|
67
|
+
</svg>
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
const resetIcon = `
|
|
71
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
|
72
|
+
<path fill-rule="evenodd" d="M9.53 2.47a.75.75 0 0 1 0 1.06L4.81 8.25H15a6.75 6.75 0 0 1 0 13.5h-3a.75.75 0 0 1 0-1.5h3a5.25 5.25 0 1 0 0-10.5H4.81l4.72 4.72a.75.75 0 1 1-1.06 1.06l-6-6a.75.75 0 0 1 0-1.06l6-6a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" />
|
|
73
|
+
</svg>
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
const html = `
|
|
77
|
+
<!-- Toast Notifications -->
|
|
78
|
+
<div id="${this.toastSuccessId}" class="toast toast-success" style="display: none;">
|
|
79
|
+
<p>Riktig! 🎉</p>
|
|
80
|
+
</div>
|
|
81
|
+
<div id="${this.toastErrorId}" class="toast toast-error" style="display: none;">
|
|
82
|
+
<p>Prøv igjen!</p>
|
|
83
|
+
</div>
|
|
84
|
+
<div id="${this.draggableId}" class="draggable-code"></div>
|
|
85
|
+
<div id="${this.dropAreaId}" class="drop-area"></div>
|
|
86
|
+
<div class="button-container">
|
|
87
|
+
<button id="${this.checkSolutionId}" class="button button-check-solution">Sjekk løsning ${checkSolutionIcon}</button>
|
|
88
|
+
<button id="${this.resetButtonId}" class="button button-reset-puzzle">Reset puslespill ${resetIcon}</button>
|
|
89
|
+
</div>
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
container.innerHTML = html;
|
|
93
|
+
|
|
94
|
+
// Get references to the toast elements
|
|
95
|
+
this.toastSuccess = document.getElementById(this.toastSuccessId);
|
|
96
|
+
this.toastError = document.getElementById(this.toastErrorId);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
preprocessCode(codeString) {
|
|
101
|
+
const lines = codeString.split('\n');
|
|
102
|
+
return lines.map((line, index) => {
|
|
103
|
+
let trimmedLine = line.trim();
|
|
104
|
+
if (line.includes(';')) {
|
|
105
|
+
const parts = line.split(';');
|
|
106
|
+
trimmedLine = parts.map(part => part.trim() === '' ? '' : part).join('\n');
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
block: line.includes(';') ? trimmedLine : line,
|
|
110
|
+
order: index,
|
|
111
|
+
isEmpty: line.trim() === ''
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
shuffleArray(array) {
|
|
117
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
118
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
119
|
+
[array[i], array[j]] = [array[j], array[i]];
|
|
120
|
+
}
|
|
121
|
+
return array;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
renderDraggableCode(container, codeBlockObjects) {
|
|
125
|
+
container.innerHTML = '';
|
|
126
|
+
codeBlockObjects.forEach((obj) => {
|
|
127
|
+
if (!obj.isEmpty) {
|
|
128
|
+
const lineElement = document.createElement('div');
|
|
129
|
+
lineElement.className = 'draggable';
|
|
130
|
+
lineElement.draggable = true;
|
|
131
|
+
lineElement.dataset.order = obj.order;
|
|
132
|
+
lineElement.innerHTML = `<pre class="highlight python"><code>${this.escapeHTML(obj.block)}</code></pre>`;
|
|
133
|
+
container.appendChild(lineElement);
|
|
134
|
+
hljs.highlightElement(lineElement.querySelector('code'));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
escapeHTML(str) {
|
|
140
|
+
return str.replace(/&/g, "&")
|
|
141
|
+
.replace(/</g, "<")
|
|
142
|
+
.replace(/>/g, ">")
|
|
143
|
+
.replace(/"/g, """)
|
|
144
|
+
.replace(/'/g, "'");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
checkSolution() {
|
|
148
|
+
const droppedItems = Array.from(this.dropArea.children).filter(item => !item.classList.contains('placeholder'));
|
|
149
|
+
const droppedOrder = droppedItems.map(item => parseInt(item.dataset.order));
|
|
150
|
+
const fullCode = this.codeBlocks.sort((a, b) => a.order - b.order).map(obj => obj.block).join('\n');
|
|
151
|
+
this.fullCodeElement.textContent = fullCode;
|
|
152
|
+
console.log("fullCode: \n", fullCode);
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
hljs.highlightElement(this.fullCodeElement);
|
|
156
|
+
const correctOrder = this.codeBlocks.filter(obj => !obj.isEmpty).sort((a, b) => a.order - b.order).map(obj => obj.order);
|
|
157
|
+
if (JSON.stringify(droppedOrder) === JSON.stringify(correctOrder)) {
|
|
158
|
+
this.showToast('success');
|
|
159
|
+
console.log("onSolvedCallback: ", this.onSolvedCallback);
|
|
160
|
+
if (this.onSolvedCallback) {
|
|
161
|
+
|
|
162
|
+
console.log("Calling callback function now!");
|
|
163
|
+
setTimeout(() => {
|
|
164
|
+
this.onSolvedCallback(fullCode);
|
|
165
|
+
}, 1500); // Display for 2.5 seconds
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
this.solutionModal.style.display = 'block';
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
} else {
|
|
172
|
+
this.showToast('error');
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
showToast(type) {
|
|
178
|
+
const toast = type === 'success' ? this.toastSuccess : this.toastError;
|
|
179
|
+
|
|
180
|
+
console.log("Toast: ", toast);
|
|
181
|
+
if (!toast) {
|
|
182
|
+
console.error(`Toast element not found for type ${type}.`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Ensure the puzzle container is positioned relatively
|
|
187
|
+
const containerStyle = getComputedStyle(this.puzzleContainer);
|
|
188
|
+
if (containerStyle.position === 'static') {
|
|
189
|
+
this.puzzleContainer.style.position = 'relative';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Position the toast in the center of the puzzle container
|
|
193
|
+
toast.style.position = 'absolute';
|
|
194
|
+
toast.style.top = '50%';
|
|
195
|
+
toast.style.left = '50%';
|
|
196
|
+
toast.style.transform = 'translate(-50%, -50%)';
|
|
197
|
+
toast.style.display = 'block';
|
|
198
|
+
|
|
199
|
+
// Hide the toast after a delay
|
|
200
|
+
setTimeout(() => {
|
|
201
|
+
toast.style.display = 'none';
|
|
202
|
+
}, 2500); // Display for 2.5 seconds
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
createSolutionModal(puzzleContainerId) {
|
|
207
|
+
const modal = document.createElement('div');
|
|
208
|
+
modal.id = `solutionModal-${puzzleContainerId}`;
|
|
209
|
+
modal.className = 'modal';
|
|
210
|
+
modal.innerHTML = `
|
|
211
|
+
<div class="modal-content">
|
|
212
|
+
<span class="close">×</span>
|
|
213
|
+
<pre><code id="fullCode-${puzzleContainerId}" class="highlight python"></code></pre>
|
|
214
|
+
<button id="copyCodeButton-${puzzleContainerId}" class="button button-check-solution">Riktig! 🔥 Kopier koden!</button>
|
|
215
|
+
</div>
|
|
216
|
+
`;
|
|
217
|
+
document.body.appendChild(modal);
|
|
218
|
+
return modal;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
createPlaceholder(dropArea) {
|
|
222
|
+
const placeholder = document.createElement('div');
|
|
223
|
+
placeholder.className = 'placeholder';
|
|
224
|
+
placeholder.textContent = 'Dra og dropp kode her!';
|
|
225
|
+
dropArea.appendChild(placeholder);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
enableDragAndDrop(draggableContainer, dropArea) {
|
|
230
|
+
const draggables = draggableContainer.querySelectorAll('.draggable');
|
|
231
|
+
|
|
232
|
+
// Event listeners for drag start and drag end
|
|
233
|
+
draggables.forEach(draggable => {
|
|
234
|
+
draggable.addEventListener('dragstart', (e) => this.dragStart(e));
|
|
235
|
+
draggable.addEventListener('dragend', (e) => this.dragEnd(e, dropArea)); // Always pass dropArea to manage the placeholder correctly
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Event listeners for drop area and draggable area
|
|
239
|
+
[dropArea, draggableContainer].forEach(container => {
|
|
240
|
+
container.addEventListener('dragover', (e) => this.dragOver(e, container));
|
|
241
|
+
container.addEventListener('drop', (e) => this.drop(e, container, this.dropArea.querySelector('.placeholder'))); // Always manage placeholder from dropArea
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// New event listeners to ensure no lingering active state after a drop
|
|
245
|
+
dropArea.addEventListener('drop', () => {
|
|
246
|
+
// Temporarily disable pointer events on all draggables to clear active state
|
|
247
|
+
const allDraggables = document.querySelectorAll('.draggable');
|
|
248
|
+
allDraggables.forEach(item => {
|
|
249
|
+
item.style.pointerEvents = 'none';
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Re-enable pointer events after a short delay
|
|
253
|
+
setTimeout(() => {
|
|
254
|
+
allDraggables.forEach(item => {
|
|
255
|
+
item.style.pointerEvents = '';
|
|
256
|
+
});
|
|
257
|
+
}, 100); // Short delay to allow the active state to clear
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
dragStart(e) {
|
|
263
|
+
e.dataTransfer.setData('text/plain', e.target.dataset.order);
|
|
264
|
+
e.target.classList.add('dragging');
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
setTimeout(() => {
|
|
268
|
+
e.target.style.display = 'none';
|
|
269
|
+
}, 0);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Ensure dragEnd method doesn't remove the placeholder incorrectly
|
|
273
|
+
dragEnd(e, dropArea) {
|
|
274
|
+
e.target.style.display = 'block';
|
|
275
|
+
e.target.classList.remove('dragging');
|
|
276
|
+
|
|
277
|
+
this.updatePlaceholderVisibility(this.dropArea, this.draggableCodeContainer); // Ensure placeholder visibility is updated based on both areas
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
dragOver(e, dropArea) {
|
|
281
|
+
e.preventDefault();
|
|
282
|
+
const draggable = document.querySelector('.dragging');
|
|
283
|
+
const afterElement = this.getDragAfterElement(e.clientY, dropArea);
|
|
284
|
+
if (afterElement == null) {
|
|
285
|
+
dropArea.insertBefore(draggable, dropArea.querySelector('.placeholder'));
|
|
286
|
+
} else {
|
|
287
|
+
const box = afterElement.getBoundingClientRect();
|
|
288
|
+
const offset = e.clientY - box.top;
|
|
289
|
+
if (offset < box.height / 2) {
|
|
290
|
+
dropArea.insertBefore(draggable, afterElement);
|
|
291
|
+
} else {
|
|
292
|
+
dropArea.insertBefore(draggable, afterElement.nextSibling);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
// Update the drop method to handle placeholder correctly
|
|
299
|
+
drop(e, container, placeholder) {
|
|
300
|
+
e.preventDefault();
|
|
301
|
+
const draggableElement = document.querySelector('.dragging');
|
|
302
|
+
const targetDropArea = e.target.closest('.drop-area');
|
|
303
|
+
const targetDraggableArea = e.target.closest('.draggable-code'); // Handle re-adding to the draggable area
|
|
304
|
+
const afterElement = this.getDragAfterElement(e.clientY, targetDropArea || targetDraggableArea);
|
|
305
|
+
|
|
306
|
+
if (afterElement == null) {
|
|
307
|
+
(targetDropArea || targetDraggableArea).insertBefore(draggableElement, placeholder);
|
|
308
|
+
} else {
|
|
309
|
+
const box = afterElement.getBoundingClientRect();
|
|
310
|
+
const offset = e.clientY - box.top;
|
|
311
|
+
if (offset < box.height / 2) {
|
|
312
|
+
(targetDropArea || targetDraggableArea).insertBefore(draggableElement, afterElement);
|
|
313
|
+
} else {
|
|
314
|
+
(targetDropArea || targetDraggableArea).insertBefore(draggableElement, afterElement.nextSibling);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
this.updatePlaceholderVisibility(this.dropArea, this.draggableCodeContainer); // Update placeholder visibility for both areas
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Updated updatePlaceholderVisibility to consider both areas
|
|
321
|
+
updatePlaceholderVisibility(dropArea, draggableCodeContainer) {
|
|
322
|
+
const dropAreaBlockCount = dropArea.querySelectorAll('.draggable').length;
|
|
323
|
+
const draggableBlockCount = draggableCodeContainer.querySelectorAll('.draggable').length;
|
|
324
|
+
|
|
325
|
+
console.log("dropAreaBlockCount: ", dropAreaBlockCount);
|
|
326
|
+
const placeholder = dropArea.querySelector('.placeholder');
|
|
327
|
+
if (draggableBlockCount === 0) {
|
|
328
|
+
if (placeholder) placeholder.style.display = 'none'; // Hide if no blocks in drop area
|
|
329
|
+
} else {
|
|
330
|
+
if (!placeholder) {
|
|
331
|
+
this.createPlaceholder(dropArea); // Create if missing
|
|
332
|
+
}
|
|
333
|
+
placeholder.style.display = ''; // Show if blocks exist
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Ensure the placeholder is always at the end of the drop area when visible
|
|
337
|
+
if (placeholder && dropAreaBlockCount > 0) {
|
|
338
|
+
dropArea.appendChild(placeholder);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
getDragAfterElement(y, dropArea) {
|
|
343
|
+
const draggableElements = [...dropArea.querySelectorAll('.draggable:not(.dragging)')];
|
|
344
|
+
return draggableElements.reduce((closest, child) => {
|
|
345
|
+
const box = child.getBoundingClientRect();
|
|
346
|
+
const offset = y - box.top - box.height / 2;
|
|
347
|
+
if (offset < 0 && offset > closest.offset) {
|
|
348
|
+
return { offset: offset, element: child };
|
|
349
|
+
} else {
|
|
350
|
+
return closest;
|
|
351
|
+
}
|
|
352
|
+
}, { offset: Number.NEGATIVE_INFINITY }).element;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
reset() {
|
|
356
|
+
this.feedback.textContent = '';
|
|
357
|
+
const draggableElements = this.dropArea.querySelectorAll('.draggable');
|
|
358
|
+
draggableElements.forEach(element => {
|
|
359
|
+
this.draggableCodeContainer.appendChild(element);
|
|
360
|
+
});
|
|
361
|
+
const originalShuffledOrder = Array.from(this.draggableCodeContainer.querySelectorAll('.draggable'));
|
|
362
|
+
this.shuffleArray(originalShuffledOrder).forEach(element => {
|
|
363
|
+
this.draggableCodeContainer.appendChild(element);
|
|
364
|
+
});
|
|
365
|
+
const placeholder = this.dropArea.querySelector('.placeholder');
|
|
366
|
+
if (placeholder) {
|
|
367
|
+
placeholder.style.display = '';
|
|
368
|
+
} else {
|
|
369
|
+
this.createPlaceholder(this.dropArea);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
reshuffle() {
|
|
374
|
+
const originalShuffledOrder = Array.from(this.draggableCodeContainer.querySelectorAll('.draggable'));
|
|
375
|
+
this.shuffleArray(originalShuffledOrder).forEach(element => {
|
|
376
|
+
this.draggableCodeContainer.appendChild(element);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
function makeParsonsPuzzle(puzzleContainerId, codeString) {
|
|
383
|
+
new ParsonsPuzzle(puzzleContainerId, codeString);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
function makeCallbackFunction(puzzleContainerId, editorId) {
|
|
388
|
+
function callbackFunction(fullCode) {
|
|
389
|
+
document.getElementById(puzzleContainerId).style.display = 'none';
|
|
390
|
+
let editorContainer = document.getElementById(editorId);
|
|
391
|
+
editorContainer.style.display = 'block';
|
|
392
|
+
|
|
393
|
+
makeInteractiveCode(editorId, fullCode);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return callbackFunction;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class IndentationParsonsPuzzle extends ParsonsPuzzle {
|
|
401
|
+
constructor(puzzleContainerId, codeString, onSolvedCallback = null) {
|
|
402
|
+
super(puzzleContainerId, codeString, onSolvedCallback);
|
|
403
|
+
|
|
404
|
+
// Override codeBlocks with preprocessed code
|
|
405
|
+
this.codeBlocks = this.preprocessCodeWithIndentation(codeString);
|
|
406
|
+
|
|
407
|
+
// Determine the maximum indentation level required by the code
|
|
408
|
+
this.maxIndentationLevel = Math.max(...this.codeBlocks.map(obj => obj.expectedIndentation));
|
|
409
|
+
|
|
410
|
+
// Initialize indentation settings
|
|
411
|
+
this.indentationWidth = 40; // Pixels per indentation level
|
|
412
|
+
|
|
413
|
+
// Shuffle the code blocks
|
|
414
|
+
this.shuffledCodeBlocks = this.shuffleArray(this.codeBlocks.slice());
|
|
415
|
+
|
|
416
|
+
// Render the draggable code lines
|
|
417
|
+
this.renderDraggableCodeLines(this.draggableCodeContainer, this.shuffledCodeBlocks);
|
|
418
|
+
|
|
419
|
+
// Add visual guides for indentation levels
|
|
420
|
+
this.addIndentationGuides(this.dropArea);
|
|
421
|
+
|
|
422
|
+
// Enable custom drag-and-drop functionality
|
|
423
|
+
this.enableCustomDragAndDrop();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Override the preprocessCode method to handle indentation
|
|
427
|
+
preprocessCodeWithIndentation(codeString) {
|
|
428
|
+
const lines = codeString.split('\n');
|
|
429
|
+
return lines.map((line, index) => {
|
|
430
|
+
const leadingSpaces = line.match(/^\s*/)[0].length;
|
|
431
|
+
const indentLevel = leadingSpaces / 4; // Assuming 4 spaces per indent level
|
|
432
|
+
const trimmedLine = line.trim();
|
|
433
|
+
return {
|
|
434
|
+
block: trimmedLine,
|
|
435
|
+
order: index,
|
|
436
|
+
expectedIndentation: indentLevel,
|
|
437
|
+
isEmpty: trimmedLine === ''
|
|
438
|
+
};
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Render draggable code lines
|
|
443
|
+
renderDraggableCodeLines(container, codeBlockObjects) {
|
|
444
|
+
container.innerHTML = '';
|
|
445
|
+
codeBlockObjects.forEach((obj) => {
|
|
446
|
+
if (!obj.isEmpty) {
|
|
447
|
+
const lineElement = document.createElement('div');
|
|
448
|
+
lineElement.className = 'code-line';
|
|
449
|
+
lineElement.dataset.order = obj.order;
|
|
450
|
+
lineElement.dataset.expectedIndentation = obj.expectedIndentation;
|
|
451
|
+
lineElement.dataset.currentIndentation = 0; // Initialize current indentation to 0
|
|
452
|
+
|
|
453
|
+
// Code content
|
|
454
|
+
const codeContent = document.createElement('pre');
|
|
455
|
+
codeContent.className = 'highlight python code-content';
|
|
456
|
+
codeContent.innerHTML = `<code>${this.escapeHTML(obj.block)}</code>`;
|
|
457
|
+
|
|
458
|
+
// Assemble line element
|
|
459
|
+
lineElement.appendChild(codeContent);
|
|
460
|
+
|
|
461
|
+
container.appendChild(lineElement);
|
|
462
|
+
hljs.highlightElement(codeContent.querySelector('code'));
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Add visual indentation guides to the drop area
|
|
468
|
+
addIndentationGuides(container) {
|
|
469
|
+
container.style.position = 'relative';
|
|
470
|
+
for (let i = 1; i <= this.maxIndentationLevel; i++) {
|
|
471
|
+
const guide = document.createElement('div');
|
|
472
|
+
guide.className = 'indentation-guide';
|
|
473
|
+
guide.style.left = `${i * this.indentationWidth}px`;
|
|
474
|
+
container.appendChild(guide);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Enable custom drag-and-drop functionality
|
|
479
|
+
enableCustomDragAndDrop() {
|
|
480
|
+
const codeLines = this.puzzleContainer.querySelectorAll('.code-line');
|
|
481
|
+
codeLines.forEach(codeLine => {
|
|
482
|
+
codeLine.addEventListener('mousedown', (e) => this.dragStart(e, codeLine));
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
dragStart(e, codeLine) {
|
|
487
|
+
e.preventDefault();
|
|
488
|
+
this.currentCodeLine = codeLine;
|
|
489
|
+
this.startX = e.clientX;
|
|
490
|
+
this.startY = e.clientY;
|
|
491
|
+
|
|
492
|
+
this.originalParent = codeLine.parentElement;
|
|
493
|
+
this.placeholder = document.createElement('div');
|
|
494
|
+
this.placeholder.className = 'placeholder-code-line';
|
|
495
|
+
this.placeholder.style.height = `${codeLine.offsetHeight}px`;
|
|
496
|
+
|
|
497
|
+
// Insert placeholder
|
|
498
|
+
codeLine.parentElement.insertBefore(this.placeholder, codeLine.nextSibling);
|
|
499
|
+
|
|
500
|
+
// Move code line to body for absolute positioning
|
|
501
|
+
document.body.appendChild(codeLine);
|
|
502
|
+
codeLine.style.position = 'absolute';
|
|
503
|
+
codeLine.style.zIndex = 1000;
|
|
504
|
+
codeLine.classList.add('dragging');
|
|
505
|
+
|
|
506
|
+
this.moveAt(e.pageX, e.pageY);
|
|
507
|
+
|
|
508
|
+
document.addEventListener('mousemove', this.dragMove.bind(this));
|
|
509
|
+
document.addEventListener('mouseup', this.dragEnd.bind(this));
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
moveAt(pageX, pageY) {
|
|
513
|
+
this.currentCodeLine.style.left = pageX - this.currentCodeLine.offsetWidth / 2 + 'px';
|
|
514
|
+
this.currentCodeLine.style.top = pageY - this.currentCodeLine.offsetHeight / 2 + 'px';
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
dragMove(e) {
|
|
518
|
+
e.preventDefault();
|
|
519
|
+
this.moveAt(e.pageX, e.pageY);
|
|
520
|
+
|
|
521
|
+
// Check for potential drop targets
|
|
522
|
+
const elementsBelow = document.elementsFromPoint(e.clientX, e.clientY);
|
|
523
|
+
const dropArea = this.dropArea;
|
|
524
|
+
const draggableArea = this.draggableCodeContainer;
|
|
525
|
+
|
|
526
|
+
let newParent = null;
|
|
527
|
+
if (elementsBelow.includes(dropArea)) {
|
|
528
|
+
newParent = dropArea;
|
|
529
|
+
} else if (elementsBelow.includes(draggableArea)) {
|
|
530
|
+
newParent = draggableArea;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (newParent && this.currentCodeLine.parentElement !== newParent) {
|
|
534
|
+
this.placeholder.remove();
|
|
535
|
+
newParent.appendChild(this.placeholder);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Adjust placeholder position within new parent
|
|
539
|
+
const codeLines = Array.from(newParent.querySelectorAll('.code-line:not(.dragging)'));
|
|
540
|
+
let insertBeforeElement = null;
|
|
541
|
+
for (let codeLine of codeLines) {
|
|
542
|
+
const rect = codeLine.getBoundingClientRect();
|
|
543
|
+
if (e.clientY < rect.top + rect.height / 2) {
|
|
544
|
+
insertBeforeElement = codeLine;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (insertBeforeElement) {
|
|
550
|
+
newParent.insertBefore(this.placeholder, insertBeforeElement);
|
|
551
|
+
} else {
|
|
552
|
+
newParent.appendChild(this.placeholder);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
dragEnd(e) {
|
|
557
|
+
e.preventDefault();
|
|
558
|
+
document.removeEventListener('mousemove', this.dragMove.bind(this));
|
|
559
|
+
document.removeEventListener('mouseup', this.dragEnd.bind(this));
|
|
560
|
+
|
|
561
|
+
// Snap to indentation level
|
|
562
|
+
const dropAreaRect = this.dropArea.getBoundingClientRect();
|
|
563
|
+
const relativeX = e.clientX - dropAreaRect.left;
|
|
564
|
+
let indentLevel = Math.round(relativeX / this.indentationWidth);
|
|
565
|
+
indentLevel = Math.max(0, Math.min(indentLevel, this.maxIndentationLevel));
|
|
566
|
+
|
|
567
|
+
this.currentCodeLine.dataset.currentIndentation = indentLevel;
|
|
568
|
+
this.currentCodeLine.style.paddingLeft = `${indentLevel * this.indentationWidth}px`;
|
|
569
|
+
|
|
570
|
+
// Remove styles added during dragging
|
|
571
|
+
this.currentCodeLine.style.position = '';
|
|
572
|
+
this.currentCodeLine.style.left = '';
|
|
573
|
+
this.currentCodeLine.style.top = '';
|
|
574
|
+
this.currentCodeLine.style.zIndex = '';
|
|
575
|
+
this.currentCodeLine.classList.remove('dragging');
|
|
576
|
+
|
|
577
|
+
// Insert the code line into the new parent
|
|
578
|
+
this.placeholder.parentElement.insertBefore(this.currentCodeLine, this.placeholder);
|
|
579
|
+
this.placeholder.remove();
|
|
580
|
+
|
|
581
|
+
this.currentCodeLine = null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Override the checkSolution method to include indentation
|
|
585
|
+
checkSolution() {
|
|
586
|
+
const droppedItems = Array.from(this.dropArea.querySelectorAll('.code-line'));
|
|
587
|
+
const droppedOrder = droppedItems.map(item => ({
|
|
588
|
+
order: parseInt(item.dataset.order),
|
|
589
|
+
indentation: parseInt(item.dataset.currentIndentation)
|
|
590
|
+
}));
|
|
591
|
+
|
|
592
|
+
// Get the expected order and indentation
|
|
593
|
+
const correctOrder = this.codeBlocks.filter(obj => !obj.isEmpty).map(obj => ({
|
|
594
|
+
order: obj.order,
|
|
595
|
+
indentation: obj.expectedIndentation
|
|
596
|
+
}));
|
|
597
|
+
|
|
598
|
+
// Compare the student's solution with the correct one
|
|
599
|
+
const isCorrect = this.compareSolutions(droppedOrder, correctOrder);
|
|
600
|
+
|
|
601
|
+
// Prepare the full code with student's indentation for display
|
|
602
|
+
const fullCode = droppedItems.map(item => {
|
|
603
|
+
const numSpaces = parseInt(item.dataset.currentIndentation) * 4; // Assuming 4 spaces per indent level
|
|
604
|
+
const indentation = ' '.repeat(numSpaces);
|
|
605
|
+
const codeLine = this.codeBlocks.find(obj => obj.order === parseInt(item.dataset.order)).block;
|
|
606
|
+
return indentation + codeLine;
|
|
607
|
+
}).join('\n');
|
|
608
|
+
|
|
609
|
+
this.fullCodeElement.textContent = fullCode;
|
|
610
|
+
hljs.highlightElement(this.fullCodeElement);
|
|
611
|
+
|
|
612
|
+
if (isCorrect) {
|
|
613
|
+
this.showToast('success');
|
|
614
|
+
if (this.onSolvedCallback) {
|
|
615
|
+
setTimeout(() => {
|
|
616
|
+
this.onSolvedCallback(fullCode);
|
|
617
|
+
}, 1500);
|
|
618
|
+
} else {
|
|
619
|
+
this.solutionModal.style.display = 'block';
|
|
620
|
+
}
|
|
621
|
+
return true;
|
|
622
|
+
} else {
|
|
623
|
+
this.showToast('error');
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Helper method to compare solutions
|
|
629
|
+
compareSolutions(droppedOrder, correctOrder) {
|
|
630
|
+
if (droppedOrder.length !== correctOrder.length) {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
for (let i = 0; i < droppedOrder.length; i++) {
|
|
634
|
+
if (droppedOrder[i].order !== correctOrder[i].order ||
|
|
635
|
+
droppedOrder[i].indentation !== correctOrder[i].indentation) {
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
}
|