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,336 @@
1
+ // pythonRunner.js
2
+
3
+ class PythonRunner {
4
+ constructor(outputId, errorBoxId, preloadPackages = ['casify']) {
5
+ this.outputId = outputId; // ID of the HTML element where output will be displayed
6
+ this.errorBoxId = errorBoxId; // ID of the HTML element for displaying errors
7
+ this.workerManager = WorkerManager.getInstance(preloadPackages);
8
+ this.preloadPackages = preloadPackages;
9
+ }
10
+
11
+ /**
12
+ * Prepares and runs the code provided in the editor.
13
+ * @param {Object} editor - The CodeMirror editor instance containing the code.
14
+ */
15
+ async run(editor, outputId = null) {
16
+ // Wait until worker is ready and preload packages are loaded
17
+ try {
18
+ await this.workerManager.workerReadyPromise;
19
+ } catch (error) {
20
+ console.error("Worker failed to initialize:", error);
21
+ this.handleErrorMessage("Failed to initialize Python environment.");
22
+ return;
23
+ }
24
+
25
+ this.editorInstance = editor;
26
+ this.editorInstance.clearLineHighlights();
27
+ let code = editor.getValue();
28
+ this.currentCode = code;
29
+
30
+ if (outputId) {
31
+ this.outputId = outputId;
32
+ }
33
+
34
+ // Handle input statements and modify the code accordingly
35
+ const inputStatements = this.findInputStatements(this.currentCode);
36
+ if (inputStatements.length > 0) {
37
+ const userValues = await this.getUserInputs(inputStatements);
38
+ this.currentCode = this.replaceInputStatements(this.currentCode, userValues);
39
+ console.log("Modified code:", this.currentCode);
40
+ }
41
+
42
+ // Extract and load necessary packages
43
+ const packages = this.extractPackageNames(this.currentCode);
44
+
45
+ if (!packages.includes('matplotlib')) {
46
+ packages.push('matplotlib');
47
+ }
48
+
49
+ console.log("Packages to load:", packages);
50
+ if (packages.length > 0) {
51
+ try {
52
+ await this.workerManager.loadPackages(packages);
53
+ } catch (error) {
54
+ // Handle package load error
55
+ console.error("Failed to load packages:", error);
56
+ this.handleErrorMessage(error.message);
57
+ return;
58
+ }
59
+ }
60
+
61
+
62
+ const callback = (data) => {
63
+ if (data.type === 'stdout') {
64
+ this.handleWorkerMessage(data);
65
+ } else if (data.type === 'stderr') {
66
+ this.handleErrorMessage(data.msg); // Displays the error
67
+ } else if (data.type === 'plot') {
68
+ const outputElement = document.getElementById(this.outputId);
69
+ if (!outputElement) {
70
+ console.error("Output element not found:", this.outputId);
71
+ return;
72
+ }
73
+
74
+ const img = document.createElement('img');
75
+ img.src = 'data:image/png;base64,' + data.data;
76
+ img.style.width = '100%'; // Or any other appropriate styling
77
+ img.style.height = 'auto'; // Or any other appropriate styling
78
+ img.style.maxHeight = '500px'; // Example: set a max width
79
+
80
+ outputElement.appendChild(img);
81
+ this.scrollToBottom(outputElement); // Scroll to show new plot
82
+ } else if (data.type === 'executionComplete') {
83
+ console.log("Code execution complete for messageId:", data.messageId);
84
+ }
85
+ };
86
+
87
+
88
+ this.workerManager.runCode(this.currentCode, callback);
89
+ }
90
+
91
+
92
+ /**
93
+ * Handles incoming messages from the WorkerManager.
94
+ * @param {Object} data - The parsed message data from the worker.
95
+ */
96
+ handleWorkerMessage(data) {
97
+ const { type, msg } = data;
98
+ console.log("Message from worker:", data);
99
+ const outputElement = document.getElementById(this.outputId);
100
+
101
+ if (!outputElement) {
102
+ console.error("Output element not found:", this.outputId);
103
+ return;
104
+ }
105
+
106
+ if (type === 'stdout') {
107
+ // Replace "&" with "∧", "oo" with "∞", and "|" with "∨"
108
+ let formattedMsg = msg
109
+ .replace(/And\(([^)]+)\)/g, (match, p1) => {
110
+ // Split conditions by comma, trim spaces, and wrap each in parentheses
111
+ const conditions = p1.split(',').map(cond => cond.trim());
112
+ return conditions.map(cond => `(${cond})`).join(' ∧ ');
113
+ })
114
+ // .replace(/&/g, '∧')
115
+ .replace(/oo/g, '∞')
116
+ .replace(/\|/g, '∨');
117
+ outputElement.innerHTML += this.formatErrorMessage(formattedMsg);
118
+ this.highlightLine(this.editorInstance, data.msg);
119
+ this.scrollToBottom(outputElement);
120
+
121
+ } else if (type === 'stderr') {
122
+ console.log("Error message:", msg);
123
+ this.handleErrorMessage(msg);
124
+ }
125
+ }
126
+
127
+ scrollToBottom(element) {
128
+ element.scrollTop = element.scrollHeight;
129
+ }
130
+
131
+ /**
132
+ * Handles error messages from the worker.
133
+ * @param {string} msg - The error message.
134
+ */
135
+ handleErrorMessage(msg) {
136
+ const errorElement = document.getElementById(this.errorBoxId);
137
+ if (errorElement) {
138
+ errorElement.innerHTML = this.formatErrorMessage(msg);
139
+ }
140
+ this.highlightLine(this.editorInstance, msg);
141
+
142
+ }
143
+
144
+ /**
145
+ * Formats output messages for display.
146
+ * @param {string} msg - The message to be formatted.
147
+ * @returns {string} - The formatted message.
148
+ */
149
+ formatOutput(msg) {
150
+ return msg;
151
+ }
152
+
153
+
154
+ highlightLine(editor, msg) {
155
+ const linePattern = /File "<exec>", line (\d+)/;
156
+ const match = linePattern.exec(msg);
157
+ if (match) {
158
+ const lineNumber = parseInt(match[1]) - 1;
159
+ console.log("Highlighting line:", lineNumber);
160
+ editor.highlightLine(lineNumber);
161
+ console.log("Highlighting error at line:", lineNumber);
162
+ }
163
+ }
164
+
165
+ formatErrorMessage(errorMsg) {
166
+ let formattedMessage = errorMsg;
167
+ let content = '';
168
+ let title = '';
169
+ let knownError = false;
170
+
171
+
172
+ // Highlight the line number in the pattern 'File "<exec>", line <number>'
173
+ const fileLinePattern = /File "<exec>", line (\d+)/g;
174
+ formattedMessage = formattedMessage.replace(fileLinePattern, (match, p1) => {
175
+ return match.replace(`line ${p1}`, `<span class="error-line">line ${p1}</span>`);
176
+ });
177
+
178
+
179
+ // Highlight the error type
180
+ const errorTypeMatch = errorMsg.match(/(\w+Error):/);
181
+ if (errorTypeMatch) {
182
+ console.log("Error type match:", errorTypeMatch[1]);
183
+ formattedMessage = formattedMessage.replace(errorTypeMatch[1], `<span class="error-type">${errorTypeMatch[1]}</span>`);
184
+
185
+ }
186
+
187
+
188
+ return formattedMessage;
189
+ }
190
+
191
+
192
+
193
+ /**
194
+ * Extracts package names from the code based on import statements.
195
+ * @param {string} code - The Python code to be analyzed.
196
+ * @returns {Array<string>} - An array of package names.
197
+ */
198
+ extractPackageNames(code) {
199
+ // Matches "import <package> as <alias>" and "import <package>"
200
+ const importRegex = /^\s*import\s+([^;\s]+)\s*/gm;
201
+
202
+ // Matches "from <package> import <something>"
203
+ const fromImportRegex = /^\s*from\s+([^;\s]+)\s+import/gm;
204
+
205
+ const packages = new Set();
206
+ const standardLibs = new Set([
207
+ 'abc', 'aifc', 'argparse', 'array', 'ast', 'asynchat', 'asyncio', 'asyncore', 'atexit', 'audioop', 'base64',
208
+ 'bdb', 'binascii', 'binhex', 'bisect', 'builtins', 'bz2', 'cProfile', 'calendar', 'cgi', 'cgitb', 'chunk',
209
+ 'cmath', 'cmd', 'code', 'codecs', 'codeop', 'collections', 'colorsys', 'compileall', 'concurrent', 'configparser',
210
+ 'contextlib', 'copy', 'copyreg', 'crypt', 'csv', 'ctypes', 'curses', 'dataclasses', 'datetime', 'dbm', 'decimal',
211
+ 'difflib', 'dis', 'distutils', 'doctest', 'email', 'encodings', 'ensurepip', 'enum', 'errno', 'faulthandler',
212
+ 'fcntl', 'filecmp', 'fileinput', 'fnmatch', 'formatter', 'fractions', 'ftplib', 'functools', 'gc', 'getopt',
213
+ 'getpass', 'gettext', 'glob', 'grp', 'gzip', 'hashlib', 'heapq', 'hmac', 'html', 'http', 'imaplib', 'imghdr',
214
+ 'imp', 'importlib', 'inspect', 'io', 'ipaddress', 'itertools', 'json', 'keyword', 'lib2to3', 'linecache', 'locale',
215
+ 'logging', 'lzma', 'mailbox', 'mailcap', 'marshal', 'math', 'mimetypes', 'mmap', 'modulefinder', 'msilib', 'msvcrt',
216
+ 'multiprocessing', 'netrc', 'nntplib', 'numbers', 'operator', 'optparse', 'os', 'ossaudiodev', 'parser', 'pathlib',
217
+ 'pdb', 'pickle', 'pickletools', 'pipes', 'pkgutil', 'platform', 'plistlib', 'poplib', 'posix', 'pprint', 'profile',
218
+ 'pstats', 'pty', 'pwd', 'py_compile', 'pyclbr', 'pydoc', 'queue', 'quopri', 'random', 're', 'readline', 'reprlib',
219
+ 'resource', 'rlcompleter', 'runpy', 'sched', 'secrets', 'select', 'selectors', 'shelve', 'shlex', 'shutil', 'signal',
220
+ 'site', 'smtpd', 'smtplib', 'sndhdr', 'socket', 'socketserver', 'spwd', 'sqlite3', 'ssl', 'stat', 'statistics',
221
+ 'string', 'stringprep', 'struct', 'subprocess', 'sunau', 'symbol', 'symtable', 'sys', 'sysconfig', 'tabnanny',
222
+ 'tarfile', 'telnetlib', 'tempfile', 'termios', 'test', 'textwrap', 'threading', 'time', 'timeit', 'tkinter', 'token',
223
+ 'tokenize', 'trace', 'traceback', 'tracemalloc', 'tty', 'turtle', 'turtledemo', 'types', 'typing', 'unicodedata',
224
+ 'unittest', 'urllib', 'uu', 'uuid', 'venv', 'warnings', 'wave', 'weakref', 'webbrowser', 'winreg', 'winsound',
225
+ 'wsgiref', 'xdrlib', 'xml', 'xmlrpc', 'zipapp', 'zipfile', 'zipimport', 'zlib'
226
+ ]);
227
+
228
+ let match;
229
+
230
+ // Process "import" statements
231
+ while ((match = importRegex.exec(code)) !== null) {
232
+ const packageName = match[1].split('.')[0];
233
+ if (!standardLibs.has(packageName)) {
234
+ packages.add(packageName);
235
+ }
236
+ }
237
+
238
+ // Process "from ... import" statements
239
+ while ((match = fromImportRegex.exec(code)) !== null) {
240
+ const packageName = match[1].split('.')[0];
241
+ if (!standardLibs.has(packageName)) {
242
+ packages.add(packageName);
243
+ }
244
+ }
245
+
246
+ console.log("Packages to load:", Array.from(packages));
247
+
248
+ return Array.from(packages);
249
+ }
250
+
251
+ /**
252
+ * Finds input statements in the code.
253
+ * @param {string} code - The Python code to be analyzed.
254
+ * @returns {Array<Object>} - An array of input statements found.
255
+ */
256
+ findInputStatements(code) {
257
+ const inputRegex = /(\w+)\s*=\s*(int|float|eval)?\(?input\(["'](.*?)["']\)\)?/g;
258
+ let match;
259
+ let inputs = [];
260
+
261
+ while ((match = inputRegex.exec(code)) !== null) {
262
+ inputs.push({
263
+ variable: match[1],
264
+ promptText: match[3]
265
+ });
266
+ }
267
+
268
+ return inputs;
269
+ }
270
+
271
+ /**
272
+ * Prompts the user for input when input statements are found in the code.
273
+ * @param {Array<Object>} inputs - An array of input statements.
274
+ * @returns {Object} - A dictionary of user inputs mapped to variable names.
275
+ */
276
+ async getUserInputs(inputs) {
277
+ let userValues = {};
278
+
279
+ for (let input of inputs) {
280
+ let promptText = input.promptText.replace(/['"]+/g, '');
281
+ let userValue = await this.promptUser(promptText);
282
+ userValues[input.variable] = userValue;
283
+ }
284
+
285
+ return userValues;
286
+ }
287
+
288
+ /**
289
+ * Prompts the user for input.
290
+ * @param {string} promptText - The text to display in the prompt.
291
+ * @returns {Promise<string>} - A promise that resolves with the user input.
292
+ */
293
+ promptUser(promptText) {
294
+ return new Promise((resolve) => {
295
+ let userInput = prompt(promptText);
296
+ resolve(userInput);
297
+ });
298
+ }
299
+
300
+ /**
301
+ * Replaces input statements in the code with user-provided values.
302
+ * @param {string} code - The Python code.
303
+ * @param {Object} userValues - A dictionary of user-provided values.
304
+ * @returns {string} - The modified code with input statements replaced.
305
+ */
306
+
307
+ replaceInputStatements(code, userValues) {
308
+ let codeLines = code.split('\n');
309
+
310
+ codeLines = codeLines.map(line => {
311
+ for (let variable in userValues) {
312
+ console.log("Variable: ", variable);
313
+ // Adjust the regex to handle Unicode characters in variable names
314
+ const inputRegex = new RegExp(`\\b${variable}\\b\\s*=\\s*(float|int|eval)?\\(?input\\(.*?\\)\\)?`, 'gu');
315
+
316
+ if (inputRegex.test(line)) {
317
+ let userValue = userValues[variable];
318
+
319
+ // Detect the type of the user input
320
+ if (!isNaN(userValue)) {
321
+ // Check if the input is a number (int or float)
322
+ //userValue = Number(userValue); // Convert the string to a number
323
+ } else if (typeof userValue === 'string') {
324
+ // Check if the input is a string and doesn't contain only numbers
325
+ userValue = `"${userValue}"`; // Add quotes around string inputs
326
+ }
327
+
328
+ line = line.replace(inputRegex, `${variable} = ${userValue}`);
329
+ }
330
+ }
331
+ return line;
332
+ });
333
+
334
+ return codeLines.join('\n');
335
+ }
336
+ }
@@ -0,0 +1,203 @@
1
+ function generateUUID() {
2
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
3
+ const r = (Math.random() * 16) | 0;
4
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
5
+ return v.toString(16);
6
+ });
7
+ }
8
+
9
+
10
+ class TurtleCode {
11
+ /**
12
+ * @param {string} containerId - ID of an existing <div> that will hold this environment.
13
+ * @param {string} [initialCode=""] - Initial Python code to display in the editor.
14
+ * @param {Object} [cmOptions={}] - Additional CodeMirror options to merge in.
15
+ */
16
+ constructor(containerId, initialCode = "", cmOptions = {}) {
17
+ this.containerId = containerId;
18
+ this.container = document.getElementById(containerId);
19
+ if (!this.container) {
20
+ throw new Error(`Container with id="${containerId}" not found.`);
21
+ }
22
+ this.initialCode = initialCode;
23
+ this.cmOptions = cmOptions;
24
+
25
+ this.uniqueSuffix = generateUUID(); // to avoid ID collisions
26
+
27
+ this.createUI();
28
+
29
+ // Initialize your existing CodeEditor on the created <textarea>
30
+ this.editor = new CodeEditor(this.textAreaEl.id);
31
+
32
+ // Add a small delay before setting the initial code
33
+ setTimeout(() => {
34
+ this.editor.setValue(this.initialCode);
35
+ }, 300); // 100ms delay should be enough
36
+ // this.editor.setValue(this.initialCode);
37
+
38
+ this.runButtonEl.addEventListener("click", () => this.runCode());
39
+ }
40
+
41
+ createUI() {
42
+ // Clear any existing content in the container
43
+ this.container.innerHTML = "";
44
+
45
+ // A flex container to hold the editor (left) and the turtle div (right)
46
+ const editorAndCanvasContainer = document.createElement("div");
47
+
48
+ editorAndCanvasContainer.classList.add("turtle-env"); // for styling
49
+
50
+ editorAndCanvasContainer.style.display = "flex";
51
+ editorAndCanvasContainer.style.flexWrap = "wrap"; // in case of narrow screens
52
+ editorAndCanvasContainer.style.width = "100%";
53
+
54
+ // --- LEFT COLUMN: Editor, Run Button, Output ---
55
+ const editorContainer = document.createElement("div");
56
+
57
+ editorContainer.classList.add("turtle-left"); // for styling
58
+
59
+ editorContainer.style.flex = "1"; // Take 50% of available width
60
+ editorContainer.style.minWidth = "100px"; // Reasonable min width
61
+ editorContainer.style.display = "flex";
62
+ editorContainer.style.flexDirection = "column";
63
+
64
+ // Textarea for CodeMirror
65
+ this.textAreaEl = document.createElement("textarea");
66
+ this.textAreaEl.id = `skulpt-editor-${this.uniqueSuffix}`;
67
+ // Hide the raw <textarea> so CodeMirror can replace it
68
+ this.textAreaEl.style.display = "none";
69
+ editorContainer.appendChild(this.textAreaEl);
70
+
71
+ // Run Button
72
+ this.runButtonEl = document.createElement("button");
73
+ this.runButtonEl.className = "button button-run";
74
+ this.runButtonEl.id = `skulpt-run-btn-${this.uniqueSuffix}`;
75
+ this.runButtonEl.textContent = "Kjør kode";
76
+ this.runButtonEl.style.margin = "1em 0";
77
+ editorContainer.appendChild(this.runButtonEl);
78
+
79
+ // Output <pre>
80
+ this.outputEl = document.createElement("pre");
81
+ this.outputEl.className = "pythonoutput";
82
+ this.outputEl.id = `skulpt-output-${this.uniqueSuffix}`;
83
+ this.outputEl.style.border = "1px solid #ccc";
84
+ this.outputEl.style.padding = "8px";
85
+ this.outputEl.style.minHeight = "2em";
86
+ this.outputEl.style.whiteSpace = "pre-wrap";
87
+ editorContainer.appendChild(this.outputEl);
88
+
89
+ editorAndCanvasContainer.appendChild(editorContainer);
90
+
91
+ // --- RIGHT COLUMN: Turtle Div ---
92
+ const canvasContainer = document.createElement("div");
93
+
94
+ // canvasContainer.classList.add("turtle-right"); // for styling
95
+
96
+ canvasContainer.style.flex = "1"; // Take the other 50%
97
+ canvasContainer.style.minWidth = "350px"; // Reasonable min width
98
+ canvasContainer.style.display = "flex";
99
+ canvasContainer.style.flexDirection = "column";
100
+ canvasContainer.style.alignItems = "center";
101
+ canvasContainer.style.justifyContent = "center";
102
+
103
+ // Turtle div
104
+ this.canvasEl = document.createElement("div");
105
+ this.canvasEl.id = `skulpt-canvas-${this.uniqueSuffix}`;
106
+ this.canvasEl.style.border = "1px solid #ccc";
107
+ this.canvasEl.style.width = "95%"; // Adjust as needed
108
+ this.canvasEl.style.height = "400px";
109
+ this.canvasEl.style.marginTop = "0em";
110
+ this.canvasEl.style.boxSizing = "border-box";
111
+ canvasContainer.appendChild(this.canvasEl);
112
+
113
+ editorAndCanvasContainer.appendChild(canvasContainer);
114
+
115
+ // Finally, append the flex container into the main container
116
+ this.container.appendChild(editorAndCanvasContainer);
117
+ }
118
+
119
+ runCode() {
120
+ let userCode = this.editor.getValue();
121
+ this.outputEl.innerHTML = ""; // clear output
122
+
123
+
124
+ const mode = document.documentElement.getAttribute("data-mode");
125
+ // default to black
126
+ let forcedColor = "black";
127
+ if (mode === "dark") {
128
+ forcedColor = "white";
129
+ }
130
+ else if (mode === "light") {
131
+ forcedColor = "black";
132
+ }
133
+ else if (mode === "auto") {
134
+ const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)").matches;
135
+ forcedColor = prefersDarkScheme ? "white" : "black";
136
+ }
137
+
138
+ console.log("Mode", mode);
139
+
140
+
141
+ // Safely prepend a snippet that ensures turtle is white
142
+ // We also do a basic check to ensure turtle is imported before coloring
143
+ // (some users might not have "import turtle" at all).
144
+ // "import turtle" will just re-import gracefully if the user already had it.
145
+ const snippet = `
146
+ try:
147
+ import turtle
148
+ turtle.color("${forcedColor}")
149
+ screen = turtle.Screen()
150
+
151
+ # screen.setup(width=300, height=300)
152
+
153
+ # Set a coordinate system so the center of the visible area is (0,0).
154
+ # For example, left = -200, right = 200, bottom = -150, top = 150
155
+ # screen.setworldcoordinates(-200, -150, 200, 150)
156
+ except:
157
+ pass
158
+
159
+ `;
160
+ userCode = snippet + userCode;
161
+
162
+ // Output function for Python's print
163
+ const outf = (text) => {
164
+ this.outputEl.innerHTML += text;
165
+ };
166
+
167
+ // Helper for Skulpt to read built-in libs
168
+ const builtinRead = (filename) => {
169
+ if (!Sk.builtinFiles || !Sk.builtinFiles["files"][filename]) {
170
+ throw new Error("File not found: " + filename);
171
+ }
172
+ return Sk.builtinFiles["files"][filename];
173
+ };
174
+
175
+ // Configure Skulpt
176
+ Sk.pre = this.outputEl.id;
177
+ Sk.configure({
178
+ output: outf,
179
+ read: builtinRead,
180
+ python3: true,
181
+ });
182
+
183
+ // Point turtle to our <div>
184
+ (Sk.TurtleGraphics || (Sk.TurtleGraphics = {})).target = this.canvasEl.id;
185
+
186
+ // Asynchronously run the code
187
+ Sk.misceval.asyncToPromise(() => {
188
+ return Sk.importMainWithBody("<stdin>", false, userCode, true);
189
+ }).then(
190
+ () => {
191
+ // success
192
+ },
193
+ (err) => {
194
+ // error
195
+ this.outputEl.innerHTML = err.toString();
196
+ }
197
+ );
198
+ }
199
+ }
200
+
201
+ function makeTurtleCode(containerId, initialCode = "", cmOptions = {}) {
202
+ return new TurtleCode(containerId, initialCode, cmOptions);
203
+ }