code-playpad 0.3.1 → 0.4.0
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.
- package/JAVA-COMPATIBILITY.md +272 -0
- package/README.md +300 -45
- package/dist/cdn.js +2 -2
- package/dist/{element-BYcX5Z6z.js → element-Bh4TdnGA.js} +12 -11
- package/dist/element.d.ts +10 -1
- package/dist/element.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/java-CDMXnFT7.js +6 -0
- package/dist/java-DrWI9a5Q.js +1 -0
- package/dist/java-library.generated-Bg_JSWb_.js +6 -0
- package/dist/{javascript-hfFD1Fg2.js → javascript-C1_thu8i.js} +1 -1
- package/dist/{javascript-BhISU47e.js → javascript-EtrNZmfD.js} +1 -1
- package/dist/{pyodide-j1wg1W9_.js → pyodide-BeJ9RqWJ.js} +1 -1
- package/dist/{pyodide-BTGQ_X_F.js → pyodide-CqcU3YGb.js} +1 -1
- package/dist/react.d.ts +4 -2
- package/dist/react.js +1 -1
- package/dist/runtime/java-library.generated.d.ts +8 -0
- package/dist/runtime/java-worker.generated.d.ts +3 -3
- package/dist/runtime/worker-runtime.d.ts +17 -1
- package/dist/standalone.js +12 -11
- package/dist/types.d.ts +76 -0
- package/dist/worker-runtime-CNPpMRo1.js +9 -0
- package/dist/worker-runtime-Kjkt5ChF.js +4 -0
- package/package.json +3 -2
- package/dist/java-1ISFU8hm.js +0 -1
- package/dist/java-DSEX5tzl.js +0 -6
- package/dist/worker-runtime-CmbgEzLA.js +0 -9
- package/dist/worker-runtime-mfgjcG5l.js +0 -4
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ entirely in the reader's browser. No backend, no iframe, no sign-up.
|
|
|
9
9
|
**[Live demo →](https://code-playpad.swengr.com)**
|
|
10
10
|
|
|
11
11
|
```html
|
|
12
|
-
<script type="module" src="https://cdn.jsdelivr.net/npm/code-playpad@0.
|
|
12
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/code-playpad@0.4.0/dist/standalone.js"></script>
|
|
13
13
|
|
|
14
14
|
<code-playpad height="auto">
|
|
15
15
|
print("hello from a static page")
|
|
@@ -30,7 +30,7 @@ entirely in the reader's browser. No backend, no iframe, no sign-up.
|
|
|
30
30
|
| `python` (default) | Python 3.14 via Pyodide — full standard library, `input()`, real tracebacks, `numpy` on demand | ~6 MB (13.5 MB unpacked), once, cached |
|
|
31
31
|
| `javascript` | The browser's own engine, in a Worker | nothing |
|
|
32
32
|
| `typescript` | Same engine, types stripped before running | ~70 KB, once, cached |
|
|
33
|
-
| `java` | A documented subset of Java 25, compiled and interpreted in a Worker by an engine built for this package — see [Java](#java) | nothing: the engine is a ~
|
|
33
|
+
| `java` | A documented subset of Java 25, compiled and interpreted in a Worker by an engine built for this package — see [Java](#java) | nothing: the engine is a ~104 KB (gzipped) chunk of this package, loaded with the first Java widget, and its library a ~75 KB chunk loaded the first time a program uses it |
|
|
34
34
|
|
|
35
35
|
```html
|
|
36
36
|
<code-playpad height="auto">print("python is the default")</code-playpad>
|
|
@@ -78,52 +78,130 @@ documented subset of Java 25 and checks its behaviour against a real JDK 25:
|
|
|
78
78
|
|
|
79
79
|
- Java 25 **compact source files** and instance `main`, or a classic
|
|
80
80
|
`public class Main` with `public static void main(String[] args)`
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
`
|
|
84
|
-
|
|
81
|
+
- **Objects:** several classes per file, nested and inner classes,
|
|
82
|
+
constructors and `this(...)`, `new`, fields and methods on any object,
|
|
83
|
+
`private`, and `toString()` with `@Override`
|
|
84
|
+
- **Classes and interfaces:** inheritance with `super`, overriding and
|
|
85
|
+
dispatch, `abstract` classes, interfaces with default and static methods,
|
|
86
|
+
`Comparable`, `equals`/`hashCode` overrides, `instanceof` with patterns,
|
|
87
|
+
casts; **enums** (with fields, methods and `switch`) and **records**
|
|
88
|
+
- **Exceptions:** `try`/`catch`/`finally`, multi-catch, try-with-resources,
|
|
89
|
+
`throw`, custom exception classes, checked exceptions checked as javac
|
|
90
|
+
checks them, `printStackTrace()`, and traces with `Caused by:`
|
|
91
|
+
- **Numbers:** `int`, `char`, `long`, `double` with Java's conversions,
|
|
92
|
+
overflow and `double` printing; `Math` (`pow`, `floorMod`, `addExact`,
|
|
93
|
+
the trigonometric functions, `random`, …) and `java.util.Random`, whose
|
|
94
|
+
seeded sequences are the JDK's
|
|
95
|
+
- `boolean`, `String`; arrays of any dimension (of objects too); static and
|
|
96
|
+
instance methods with overloading and recursion; fields
|
|
97
|
+
- **Control flow:** `if`, `while`, `do`, `for` and enhanced `for`, `switch`
|
|
98
|
+
statements and expressions, labeled `break`/`continue`, `return`, `?:`
|
|
99
|
+
- **Strings:** `substring`, `indexOf`, `split`, `replace`, `replaceAll`,
|
|
100
|
+
`matches`, `trim`/`strip`, case, `repeat`, `join`, `valueOf` and the rest,
|
|
101
|
+
with Java's regex syntax; `String.format` and `System.out.printf` with
|
|
102
|
+
Java's rounding (`%.2f` of 1.005 is `1.01`)
|
|
103
|
+
- `System.out`/`System.err`, `IO.print`/`IO.println`, `System.exit`,
|
|
104
|
+
`System.arraycopy`, `System.nanoTime`
|
|
105
|
+
- **Input:** `Scanner` over `System.in` (the widget's input box) or a
|
|
106
|
+
`String`, with `nextInt`, `nextLine`, `nextDouble` and the rest, and
|
|
107
|
+
`IO.readln`. The classic `nextInt()`-then-`nextLine()` pitfall behaves
|
|
108
|
+
exactly as in Java
|
|
109
|
+
- **Collections and library:** `import`, generics at use sites with the
|
|
110
|
+
diamond, boxing; `ArrayList`, `HashMap`, `HashSet`, `LinkedHashMap`,
|
|
111
|
+
`ArrayDeque`, `PriorityQueue`, `TreeMap`, `TreeSet`, `LinkedList`, `Stack`,
|
|
112
|
+
`EnumMap`, `EnumSet`, their iterators and enhanced `for`, `subList`;
|
|
113
|
+
`List.of`, `Map.of`, `Arrays.asList`, `toArray`; `StringBuilder`, the
|
|
114
|
+
wrapper classes, `Character`, `Arrays` and `Collections`. A `HashMap` or
|
|
115
|
+
`HashSet` iterates and prints in the JDK's order.
|
|
116
|
+
- **Big numbers:** `BigInteger` and `BigDecimal`, with the JDK's rounding
|
|
117
|
+
modes and `MathContext`
|
|
118
|
+
- **Fast input and output**, as competitive programmers write it:
|
|
119
|
+
`BufferedReader`, `InputStreamReader`, `StringTokenizer`,
|
|
120
|
+
`StreamTokenizer`, `PrintWriter` and `BufferedWriter`. Output you forget
|
|
121
|
+
to flush is lost exactly as on the JDK
|
|
122
|
+
- **Lambdas and method references** for any functional interface
|
|
123
|
+
(`Integer::compare`, `Comparator.comparing(...).reversed()`, `merge`,
|
|
124
|
+
`computeIfAbsent`), capturing effectively final locals, with stack traces
|
|
125
|
+
and sorting that match the JDK's (even a comparator that overflows)
|
|
126
|
+
- **Generics:** generic classes, interfaces, records and methods with
|
|
127
|
+
bounds and wildcards; raw types; `var`; varargs; `@Override`,
|
|
128
|
+
`@SuppressWarnings`, `@FunctionalInterface`; javac's `[unchecked]`
|
|
129
|
+
warnings (for `List<Integer>[] g = new ArrayList[n]`, say) printed before
|
|
130
|
+
the output, as the JDK's source launcher prints them
|
|
131
|
+
- **Anonymous and local classes** (`new Comparator<Integer>() { ... }`)
|
|
132
|
+
named and capturing as javac does, and **streams**: `Stream`, `IntStream`,
|
|
133
|
+
`Collectors` (`groupingBy`, `toMap`, `counting`, `joining`, …), `Optional`,
|
|
134
|
+
run element by element in the JDK's order
|
|
135
|
+
- **Modern Java:** `sealed` classes and interfaces, pattern matching in
|
|
136
|
+
`switch` and `instanceof` (record patterns, guards, `case null`), text
|
|
137
|
+
blocks, static imports, initializer blocks, enum constants with their own
|
|
138
|
+
bodies, and `Interface.super.method()`
|
|
139
|
+
- **More types:** `float`, `byte` and `short`, with Java's conversions and
|
|
140
|
+
printing
|
|
85
141
|
- **Errors read like the JDK's**: compile errors in javac's format, exceptions
|
|
86
142
|
with Java stack traces and the JDK's NullPointerException messages
|
|
87
143
|
|
|
88
|
-
Anything outside the subset
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
unless `entry` says otherwise.
|
|
92
|
-
|
|
144
|
+
Anything outside the subset (`java.time`, `Pattern` and `Matcher`, parallel
|
|
145
|
+
streams, `outer.new Inner()`) is reported as *not supported in this
|
|
146
|
+
version* rather than run with a different meaning. The file is `Main.java`
|
|
147
|
+
unless `entry` says otherwise.
|
|
148
|
+
|
|
149
|
+
**Speed.** In loop-heavy code the engine runs roughly 150–300 million
|
|
150
|
+
bytecode instructions a second on a laptop. A typical practice solution (10⁵–10⁶
|
|
151
|
+
elements, nested loops, maps, sorting with a comparator) finishes in well
|
|
152
|
+
under a second. The engine stops a runaway program after 200 million steps
|
|
153
|
+
(about 0.6–1.5 s of work) and explains why.
|
|
154
|
+
|
|
155
|
+
**Exactly what is supported**, feature by feature and method by method, with
|
|
156
|
+
the evidence behind it and every known difference from the JDK, is in
|
|
157
|
+
[JAVA-COMPATIBILITY.md](https://cdn.jsdelivr.net/npm/code-playpad/JAVA-COMPATIBILITY.md), which ships in the package.
|
|
93
158
|
|
|
94
159
|
## Download size
|
|
95
160
|
|
|
96
|
-
What a page downloads once a reader presses Run, with the CDN build
|
|
97
|
-
|
|
98
|
-
CDN
|
|
161
|
+
What a page downloads once a reader presses Run, with the CDN build, as
|
|
162
|
+
measured for 0.4.0. Each one-widget page was loaded in a browser, and each
|
|
163
|
+
code-playpad file it fetched was gzipped. The CDN column is what the CDN
|
|
164
|
+
actually sent.
|
|
99
165
|
|
|
100
|
-
| Page uses | code-playpad (gzip) | Also fetched from a CDN at run time | 0.
|
|
166
|
+
| Page uses | code-playpad (gzip) | Also fetched from a CDN at run time | 0.3.0 (gzip) |
|
|
101
167
|
| --- | --- | --- | --- |
|
|
102
|
-
| Python | **
|
|
103
|
-
| JavaScript | **
|
|
104
|
-
| TypeScript | **
|
|
105
|
-
| Java | **
|
|
106
|
-
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
168
|
+
| Python | **166.3 KB** | Pyodide, about **6.0 MB** (a 3.3 MB wasm file and a 2.4 MB standard library) | 165.8 KB |
|
|
169
|
+
| JavaScript | **180.4 KB** | nothing | 179.9 KB |
|
|
170
|
+
| TypeScript | **180.4 KB** | Sucrase, 72 KB (≈ 252 KB in total) | 179.9 KB |
|
|
171
|
+
| Java | **267.8 KB** | nothing | 216.4 KB |
|
|
172
|
+
| Java, with collections | **342.4 KB** | nothing | 233.2 KB |
|
|
173
|
+
| All four | 399.9 KB | Pyodide and Sucrase | 273.9 KB |
|
|
174
|
+
|
|
175
|
+
- **Every row includes a 144.5 KB shared base:** the editor (CodeMirror) and
|
|
176
|
+
the widget, the same as in 0.3.0.
|
|
177
|
+
- **Each language adds:**
|
|
178
|
+
- Python: 21.7 KB.
|
|
179
|
+
- JavaScript/TypeScript: 35.9 KB.
|
|
180
|
+
- Java: 123.2 KB (the 104.2 KB engine plus 19 KB of Java syntax
|
|
181
|
+
highlighting).
|
|
182
|
+
- The first Java program that uses collections, `StringBuilder` or another
|
|
183
|
+
library class also adds the 74.6 KB Java library, once per page.
|
|
184
|
+
- **Java grew** from 0.3.0 because the subset did, from basic Java to most of
|
|
185
|
+
what practice programs use (see the version history). The owner treats
|
|
186
|
+
download size as secondary to coverage. The package's size checks still
|
|
187
|
+
catch accidental growth: 128 KiB for the Java chunk and 96 KiB for its
|
|
188
|
+
library.
|
|
189
|
+
- **Languages load lazily.** A Java page never fetches the Python or
|
|
190
|
+
JavaScript runtimes, and Python and JavaScript pages never fetch Java. As a
|
|
191
|
+
result, Python-only and JavaScript-only pages are the same size as in 0.3.0.
|
|
192
|
+
- **Java needs no CDN:** it is self-contained, while Python's first run
|
|
193
|
+
downloads about 6 MB of Pyodide (cached after that).
|
|
194
|
+
- **With npm and a bundler,** you already ship CodeMirror yourself.
|
|
195
|
+
code-playpad's own code then costs about 9 KB, plus the runtime chunk for
|
|
196
|
+
each language used. Java's chunk is 104 KB, and 74 KB more for its library
|
|
197
|
+
when a program uses it, plus `@codemirror/lang-java` if you install it.
|
|
120
198
|
|
|
121
199
|
## Install
|
|
122
200
|
|
|
123
201
|
### From a CDN — nothing to install
|
|
124
202
|
|
|
125
203
|
```html
|
|
126
|
-
<script type="module" src="https://cdn.jsdelivr.net/npm/code-playpad@0.
|
|
204
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/code-playpad@0.4.0/dist/standalone.js"></script>
|
|
127
205
|
```
|
|
128
206
|
|
|
129
207
|
This is the `standalone` build: CodeMirror is bundled in, so a static blog post
|
|
@@ -185,6 +263,7 @@ Next.js, Astro and SvelteKit need no dynamic import or `client:only` guard.
|
|
|
185
263
|
| `stdin` | shows the standard-input box up front |
|
|
186
264
|
| `persist` | key under which edits are saved (see [Saving work](#saving-work)) |
|
|
187
265
|
| `entry` | which file Run executes, for multi-file widgets |
|
|
266
|
+
| `practice` | Java only: test cases for one method, as JSON (see [Practice mode](#practice-mode-java)) |
|
|
188
267
|
|
|
189
268
|
```html
|
|
190
269
|
<!-- a worked example nobody should edit -->
|
|
@@ -335,7 +414,26 @@ document.querySelector('code-playpad').stdin = 'Ada\n36';
|
|
|
335
414
|
Echoed input is display-only: `stdout` stays exactly what the program printed,
|
|
336
415
|
so graders are unaffected.
|
|
337
416
|
|
|
338
|
-
Java
|
|
417
|
+
Java reads the box as its standard input. `new Scanner(System.in)` and
|
|
418
|
+
`IO.readln()` see all of it at once, as when input is piped to `java Main.java`:
|
|
419
|
+
the program never stops to wait, and the input is not echoed into the output.
|
|
420
|
+
A program that reads past the end fails with the JDK's
|
|
421
|
+
`NoSuchElementException`, and the widget opens the box with a hint, as it
|
|
422
|
+
does for Python's `input()`.
|
|
423
|
+
|
|
424
|
+
```html
|
|
425
|
+
<code-playpad id="times" language="java" height="auto" stdin>
|
|
426
|
+
import java.util.Scanner;
|
|
427
|
+
|
|
428
|
+
void main() {
|
|
429
|
+
Scanner in = new Scanner(System.in);
|
|
430
|
+
IO.println(in.nextInt() * in.nextInt());
|
|
431
|
+
}
|
|
432
|
+
</code-playpad>
|
|
433
|
+
<script>
|
|
434
|
+
document.getElementById('times').stdin = '3 4';
|
|
435
|
+
</script>
|
|
436
|
+
```
|
|
339
437
|
|
|
340
438
|
## Controlling it from JavaScript
|
|
341
439
|
|
|
@@ -369,14 +467,26 @@ they press Run rather than after.
|
|
|
369
467
|
## Events and grading
|
|
370
468
|
|
|
371
469
|
Four events bubble and cross the shadow boundary, so answer-checking lives
|
|
372
|
-
outside the widget rather than inside it:
|
|
470
|
+
outside the widget rather than inside it. They fire for every language:
|
|
373
471
|
|
|
374
472
|
| event | `event.detail` |
|
|
375
473
|
| --- | --- |
|
|
376
|
-
| `
|
|
377
|
-
| `
|
|
378
|
-
| `
|
|
379
|
-
| `
|
|
474
|
+
| `playpad-ready` | `{ language, label }` — runtime booted |
|
|
475
|
+
| `playpad-run` | `{ code, entry, files, language }` — a run started |
|
|
476
|
+
| `playpad-output` | `{ stdout, stderr?, value?, table?, durationMs, failed, language }` |
|
|
477
|
+
| `playpad-error` | `{ message, hint?, language }` |
|
|
478
|
+
|
|
479
|
+
Each one is also fired under its original name, `py-ready`, `py-run`,
|
|
480
|
+
`py-output` and `py-error` (first, with the same `detail`), from before the
|
|
481
|
+
widget ran anything but Python. Existing pages keep working; new code should
|
|
482
|
+
listen for the `playpad-*` names.
|
|
483
|
+
|
|
484
|
+
`failed` says whether the run failed, and `playpad-error` fires exactly when it
|
|
485
|
+
did. For Python and JavaScript, any stderr output counts as a failure. For Java,
|
|
486
|
+
the run's own status decides: an exception, a compile error or a non-zero
|
|
487
|
+
`System.exit` fail, while javac's `[unchecked]` warnings and `System.err` output
|
|
488
|
+
from a program that finished normally do not. That is how a terminal reads it;
|
|
489
|
+
the warnings are still printed, and still in `stderr`.
|
|
380
490
|
|
|
381
491
|
A complete exercise check:
|
|
382
492
|
|
|
@@ -390,11 +500,11 @@ A complete exercise check:
|
|
|
390
500
|
```js
|
|
391
501
|
const expected = '1\n2\n3\n4\n5\n';
|
|
392
502
|
|
|
393
|
-
document.addEventListener('
|
|
503
|
+
document.addEventListener('playpad-output', (event) => {
|
|
394
504
|
if (event.target.id !== 'ex1') return;
|
|
395
505
|
|
|
396
506
|
const el = document.getElementById('ex1-result');
|
|
397
|
-
if (event.detail.
|
|
507
|
+
if (event.detail.failed) {
|
|
398
508
|
el.textContent = 'Your code raised an error — read the message above.';
|
|
399
509
|
} else if (event.detail.stdout === expected) {
|
|
400
510
|
el.textContent = '✅ Correct';
|
|
@@ -404,10 +514,73 @@ document.addEventListener('py-output', (event) => {
|
|
|
404
514
|
});
|
|
405
515
|
```
|
|
406
516
|
|
|
407
|
-
`
|
|
517
|
+
`playpad-error` also carries a `hint` when the runtime can explain the failure in
|
|
408
518
|
plain language — for example when a program calls `input()` more times than the
|
|
409
519
|
standard-input box has lines.
|
|
410
520
|
|
|
521
|
+
## Practice mode (Java)
|
|
522
|
+
|
|
523
|
+
For LeetCode-style exercises, give a Java widget test cases for one method.
|
|
524
|
+
Run then calls that method once per case, instead of running `main` (no
|
|
525
|
+
`main` is needed), and reports each case. The output shows one line per
|
|
526
|
+
case and the tally; the status line says "2 of 3 passed".
|
|
527
|
+
|
|
528
|
+
```html
|
|
529
|
+
<code-playpad language="java" height="auto"
|
|
530
|
+
practice='{"method": "lowerBound", "cases": [
|
|
531
|
+
{"args": [[1, 3, 3, 8], 3], "expected": 1},
|
|
532
|
+
{"args": [[1, 3, 3, 8], 9], "expected": 4},
|
|
533
|
+
{"args": [[], 5], "expected": 0}]}'>
|
|
534
|
+
class Solution {
|
|
535
|
+
static int lowerBound(int[] sorted, int target) {
|
|
536
|
+
return 0; // your code here
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
</code-playpad>
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
Or set it from JavaScript (`el.practice = { method, cases }`), or pass the
|
|
543
|
+
`practice` prop to the React component. Each case's result arrives in
|
|
544
|
+
`playpad-output` as `detail.cases`, with `detail.passed` and `detail.total`,
|
|
545
|
+
so grading code can read them instead of comparing text:
|
|
546
|
+
|
|
547
|
+
```js
|
|
548
|
+
document.addEventListener('playpad-output', (event) => {
|
|
549
|
+
const { cases, passed, total } = event.detail;
|
|
550
|
+
if (cases) console.log(`${passed} of ${total}`, cases.filter((c) => c.status !== 'passed'));
|
|
551
|
+
});
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
**The spec.** `method` (required); `cases`, each `{ args, expected?, id? }`;
|
|
555
|
+
`className` when several classes have a method of that name; `params`
|
|
556
|
+
(`["int[]", "int"]`) to choose among overloads; `compare: { kind: "argument",
|
|
557
|
+
index: 1 }` to check an argument after the call, for a `void` method that
|
|
558
|
+
fills one in; `tolerance` in `compare` for doubles; `nodes` to describe a
|
|
559
|
+
linked-list or tree class whose fields do not make its shape obvious.
|
|
560
|
+
|
|
561
|
+
**Values** are JSON, the same way for arguments, `expected` and the
|
|
562
|
+
reported `actual`:
|
|
563
|
+
|
|
564
|
+
| Java | JSON |
|
|
565
|
+
| --- | --- |
|
|
566
|
+
| `int`, `boolean`, `String`, wrapper types | number, boolean, string, `null` |
|
|
567
|
+
| `long` | a decimal string, `"4000000000"` |
|
|
568
|
+
| `char` | a one-character string |
|
|
569
|
+
| `double` | a number, or `"NaN"`, `"Infinity"`, `"-Infinity"` |
|
|
570
|
+
| arrays, `List` / `ArrayList` | arrays (nested for `int[][]`, `List<List<Integer>>`) |
|
|
571
|
+
| linked-list nodes (a class with one field of its own type) | the values in order, `[1, 2, 3]` |
|
|
572
|
+
| tree nodes (two fields of its own type) | level order with `null` gaps, `[3, 9, 20, null, null, 15, 7]` |
|
|
573
|
+
|
|
574
|
+
Maps, sets and `Object` cannot be passed or returned yet.
|
|
575
|
+
|
|
576
|
+
**Each case's `status`:** `passed`, `failed` (with `expected` and
|
|
577
|
+
`actual`), `completed` (no `expected` was given), `runtime-error` (with
|
|
578
|
+
`error`, the stack trace), `resource-limit`, `engine-limitation`,
|
|
579
|
+
`invalid-case` (the JSON does not fit the parameters: the exercise's
|
|
580
|
+
mistake, not the learner's) and `not-run` (with a `reason`, such as
|
|
581
|
+
`compile-error`). Each case also has what it printed (`stdout`, `stderr`).
|
|
582
|
+
Every case runs on fresh state: static fields start over.
|
|
583
|
+
|
|
411
584
|
## Saving work
|
|
412
585
|
|
|
413
586
|
`persist` stores the whole file map in IndexedDB, so a reader's edits survive
|
|
@@ -428,7 +601,7 @@ For a course you usually also want completion state, which the package exports:
|
|
|
428
601
|
import { setProgress, getProgress, allProgress, loadSnippet, clearSnippet } from 'code-playpad';
|
|
429
602
|
|
|
430
603
|
// mark a lesson done when its exercise passes
|
|
431
|
-
document.addEventListener('
|
|
604
|
+
document.addEventListener('playpad-output', async (event) => {
|
|
432
605
|
if (event.detail.stdout === expected) {
|
|
433
606
|
await setProgress('lesson-3', { completed: true, score: 100 });
|
|
434
607
|
}
|
|
@@ -622,7 +795,89 @@ warms every other page on the site.
|
|
|
622
795
|
## Version history
|
|
623
796
|
|
|
624
797
|
No breaking changes so far: markup and API written against 0.1.3 still work on
|
|
625
|
-
0.
|
|
798
|
+
0.4.0.
|
|
799
|
+
|
|
800
|
+
### 0.4.0
|
|
801
|
+
|
|
802
|
+
Java grows from a first subset into most of what practice programs and
|
|
803
|
+
coding-interview solutions use, with its behavior recorded against JDK 25
|
|
804
|
+
throughout. Nothing is removed or changed for Python, JavaScript or
|
|
805
|
+
TypeScript, and existing markup and code keep working.
|
|
806
|
+
|
|
807
|
+
**Java: the language** (engines 0.4.0–0.24.0)
|
|
808
|
+
- **Objects and classes:** several classes per file; nested, inner, local
|
|
809
|
+
and anonymous classes; constructors and `this(...)`; inheritance with
|
|
810
|
+
`super`; `abstract` classes; interfaces with default, static and private
|
|
811
|
+
methods; `Object`'s methods; `instanceof` and casts.
|
|
812
|
+
- **Enums and records:** enums with fields, methods, `switch` and constants
|
|
813
|
+
that have their own bodies. Records with compact constructors.
|
|
814
|
+
- **Exceptions:** `try`/`catch`/`finally`, multi-catch,
|
|
815
|
+
try-with-resources, custom exceptions, and checked exceptions checked as
|
|
816
|
+
javac checks them. Stack traces, `Caused by:` and NullPointerException
|
|
817
|
+
messages match the JDK's.
|
|
818
|
+
- **Numbers and control flow:** `long`, `double`, `float`, `byte` and
|
|
819
|
+
`short`, with Java's overflow, conversions and printing. `switch`
|
|
820
|
+
statements and expressions, labeled `break`/`continue`, and
|
|
821
|
+
multi-dimensional arrays.
|
|
822
|
+
- **Generics and lambdas:** generic classes and methods, raw types, `var`,
|
|
823
|
+
varargs, annotations, and javac's `[unchecked]` warnings. Lambdas and
|
|
824
|
+
method references for any functional interface, with captures.
|
|
825
|
+
- **Modern Java:** sealed types, pattern matching (record patterns, guards,
|
|
826
|
+
`case null`), text blocks, static imports, initializer blocks and
|
|
827
|
+
`Interface.super` calls.
|
|
828
|
+
|
|
829
|
+
**Java: the library**
|
|
830
|
+
- **Collections:** `ArrayList`, `HashMap` and `HashSet` (iterating in the
|
|
831
|
+
JDK's order), `LinkedHashMap`, `TreeMap`, `TreeSet`, `ArrayDeque`,
|
|
832
|
+
`PriorityQueue`, `LinkedList`, `Stack`, `EnumMap` and `EnumSet`, plus
|
|
833
|
+
`List.of`/`Map.of`, `subList` and `Collections`.
|
|
834
|
+
- **Streams:** `Stream`, `IntStream`, `Collectors` and `Optional`, run
|
|
835
|
+
element by element in the JDK's order.
|
|
836
|
+
- **Strings and formatting:** the rest of `String`, with Java's regex syntax;
|
|
837
|
+
`StringBuilder`; `String.format` and `printf` with Java's rounding.
|
|
838
|
+
- **Numbers and system:** `Math`, `Random` (seeded sequences match the
|
|
839
|
+
JDK's), `BigInteger` and `BigDecimal`, and `System.exit`, `arraycopy` and
|
|
840
|
+
`nanoTime`.
|
|
841
|
+
- **Input and output:** `Scanner` and `IO.readln` read the widget's input
|
|
842
|
+
box. `BufferedReader`, `StringTokenizer`, `StreamTokenizer`,
|
|
843
|
+
`PrintWriter` and `BufferedWriter` give fast I/O, and output you never
|
|
844
|
+
flush is lost exactly as on the JDK.
|
|
845
|
+
|
|
846
|
+
**Java: speed** (engines 0.25.0–0.26.0)
|
|
847
|
+
- Loop-heavy programs run 2.3–4.3× faster than in 0.24.0. Library callbacks
|
|
848
|
+
(comparators, `merge`, `computeIfAbsent`, `PriorityQueue` with a lambda)
|
|
849
|
+
run 1.5–4.8× faster than in 0.25.0.
|
|
850
|
+
- The 200-million-step limit now covers about 0.6–1.5 s of work, so typical
|
|
851
|
+
10⁵–10⁶-element solutions finish well inside it.
|
|
852
|
+
|
|
853
|
+
**New for every language**
|
|
854
|
+
- **`playpad-ready`, `playpad-run`, `playpad-output`, `playpad-error`:**
|
|
855
|
+
language-neutral names for the four events. Each fires right after its
|
|
856
|
+
`py-*` original, with the same `detail`, and the `py-*` names keep firing.
|
|
857
|
+
- **`playpad-output` carries `failed`,** and `playpad-error` fires exactly
|
|
858
|
+
when a run failed. For Java, the run's own status decides (an exception, a
|
|
859
|
+
compile error or a non-zero `System.exit`), so javac's warnings and
|
|
860
|
+
`System.err` output from a program that finished normally do not count as a
|
|
861
|
+
failure. See [Events and grading](#events-and-grading).
|
|
862
|
+
|
|
863
|
+
**Java practice mode**
|
|
864
|
+
- **`practice`:** an attribute, property or React prop that gives a Java
|
|
865
|
+
widget test cases for one method. Run calls the method once per case and
|
|
866
|
+
shows a line per case and the tally.
|
|
867
|
+
- **`playpad-output`** carries `cases`, `passed` and `total`.
|
|
868
|
+
`RunOptions.practice` and `Result.cases` do the same for code that drives a
|
|
869
|
+
runtime directly. See [Practice mode](#practice-mode-java).
|
|
870
|
+
|
|
871
|
+
**Docs and size**
|
|
872
|
+
- **`JAVA-COMPATIBILITY.md`** ships in the package. It is the engine's full
|
|
873
|
+
compatibility table, regenerated with each engine sync: every feature and
|
|
874
|
+
library method, the evidence behind it, and every known difference from
|
|
875
|
+
the JDK.
|
|
876
|
+
- **Download size:** Java pages download more than in 0.3.0, Python and
|
|
877
|
+
JavaScript pages the same; see [Download size](#download-size).
|
|
878
|
+
- **Size checks:** the budgets were raised to 128 KiB for the Java chunk and
|
|
879
|
+
96 KiB for its library, at the owner's direction that size is not a
|
|
880
|
+
constraint.
|
|
626
881
|
|
|
627
882
|
### 0.3.1
|
|
628
883
|
|
package/dist/cdn.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! code-playpad 0.
|
|
2
|
-
export * from "https://cdn.jsdelivr.net/npm/code-playpad@0.
|
|
1
|
+
/*! code-playpad 0.4.0: CDN entry, loads dist/standalone.js of this exact version */
|
|
2
|
+
export * from "https://cdn.jsdelivr.net/npm/code-playpad@0.4.0/dist/standalone.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
async function e(e){try{return await t(e)}catch(t){return console.warn(`[code-playpad] CodeMirror unavailable, using plain textarea:`,t),n(e)}}async function t(e){let[t,n,r,i,a,o]=await Promise.all([import(`@codemirror/view`),import(`@codemirror/state`),import(`@codemirror/commands`),import(`@codemirror/language`),import(`@codemirror/autocomplete`),import(`@codemirror/theme-one-dark`)]),{EditorView:s,keymap:c,lineNumbers:l,highlightActiveLine:u,highlightActiveLineGutter:d,highlightSpecialChars:f,drawSelection:p,rectangularSelection:m,crosshairCursor:h}=t,{EditorState:g,Compartment:_}=n,{defaultKeymap:v,history:y,historyKeymap:b,indentWithTab:x}=r,{syntaxHighlighting:S,defaultHighlightStyle:C,indentOnInput:w,bracketMatching:T,foldGutter:E,foldKeymap:D}=i,{autocompletion:O,closeBrackets:k,closeBracketsKeymap:A,completionKeymap:j}=a,M=new _,N=new _,P=new _,F=[s.theme({"&":{color:`#1f2328`,backgroundColor:`#ffffff`},".cm-gutters":{backgroundColor:`#f6f8fa`,color:`#8b949e`,border:`none`},".cm-activeLine":{backgroundColor:`#f6f8fa88`},".cm-activeLineGutter":{backgroundColor:`#eaeef2`},".cm-cursor":{borderLeftColor:`#1f2328`},"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection":{backgroundColor:`#cfe3ff`}}),S(C,{fallback:!0})],I=e=>e?o.oneDark:F,L=c.of([{key:`Mod-Enter`,preventDefault:!0,run:()=>(e.onRun?.(),!0)},{key:`Shift-Enter`,preventDefault:!0,run:()=>(e.onRun?.(),!0)}]),R=e.dark,z=e.readOnly,B=e.language??[],V=t=>g.create({doc:t,extensions:[l(),d(),f(),y(),E(),p(),g.allowMultipleSelections.of(!0),w(),T(),k(),O(),m(),h(),u(),L,c.of([...A,...v,...b,...D,...j,x]),s.lineWrapping,P.of(B),M.of(I(R)),N.of(g.readOnly.of(z)),s.updateListener.of(t=>{t.docChanged&&e.onChange?.(t.state.doc.toString())})]}),H=new s({parent:e.parent,state:V(e.doc)}),U=new Map,W=e.name??``;return{fallback:!1,getCode:()=>H.state.doc.toString(),setCode(e){H.dispatch({changes:{from:0,to:H.state.doc.length,insert:e}})},openFile(e,t){if(e===W)return;U.set(W,H.state);let n=U.get(e);H.setState(n??V(t)),W=e,n&&H.dispatch({effects:[M.reconfigure(I(R)),N.reconfigure(g.readOnly.of(z)),P.reconfigure(B)]})},fileCode(e,t){if(e===W)return H.state.doc.toString();let n=U.get(e);return n?n.doc.toString():t},forgetFiles(e){for(let t of[...U.keys()])e.includes(t)||U.delete(t)},setTheme(e){R=e,H.dispatch({effects:M.reconfigure(I(R))})},setReadOnly(e){z=e,H.dispatch({effects:N.reconfigure(g.readOnly.of(z))})},setLanguage(e){B=e,H.dispatch({effects:P.reconfigure(B)})},focus:()=>H.focus(),destroy:()=>H.destroy()}}function n(e){let t=document.createElement(`textarea`);t.className=`cp-fallback-editor`,t.spellcheck=!1,t.value=e.doc,t.readOnly=e.readOnly,t.addEventListener(`input`,()=>e.onChange?.(t.value)),t.addEventListener(`keydown`,t=>{(t.metaKey||t.ctrlKey)&&t.key===`Enter`&&(t.preventDefault(),e.onRun?.())}),e.parent.appendChild(t);let n=new Map,r=e.name??``;return{fallback:!0,getCode:()=>t.value,setCode:e=>{t.value=e},openFile(e,i){e!==r&&(n.set(r,t.value),t.value=n.get(e)??i,r=e)},fileCode(e,i){return e===r?t.value:n.get(e)??i},forgetFiles(e){for(let t of[...n.keys()])e.includes(t)||n.delete(t)},setTheme:()=>{},setReadOnly:e=>{t.readOnly=e},setLanguage:()=>{},focus:()=>t.focus(),destroy:()=>t.remove()}}var r=new Map,i=new Map;function a(e,t){r.set(e,t)}function o(){return[...r.keys()]}function s(e){let t=i.get(e);if(t)return t;let n=r.get(e);if(!n)return Promise.reject(Error(`Unknown runtime "${e}". Registered: ${o().join(`, `)||`none`}`));let a=n().catch(t=>{throw i.delete(e),t});return i.set(e,a),a}a(`python`,()=>import(`./pyodide-
|
|
1
|
+
async function e(e){try{return await t(e)}catch(t){return console.warn(`[code-playpad] CodeMirror unavailable, using plain textarea:`,t),n(e)}}async function t(e){let[t,n,r,i,a,o]=await Promise.all([import(`@codemirror/view`),import(`@codemirror/state`),import(`@codemirror/commands`),import(`@codemirror/language`),import(`@codemirror/autocomplete`),import(`@codemirror/theme-one-dark`)]),{EditorView:s,keymap:c,lineNumbers:l,highlightActiveLine:u,highlightActiveLineGutter:d,highlightSpecialChars:f,drawSelection:p,rectangularSelection:m,crosshairCursor:h}=t,{EditorState:g,Compartment:_}=n,{defaultKeymap:v,history:y,historyKeymap:b,indentWithTab:x}=r,{syntaxHighlighting:S,defaultHighlightStyle:C,indentOnInput:w,bracketMatching:T,foldGutter:E,foldKeymap:D}=i,{autocompletion:O,closeBrackets:k,closeBracketsKeymap:A,completionKeymap:j}=a,M=new _,N=new _,P=new _,F=[s.theme({"&":{color:`#1f2328`,backgroundColor:`#ffffff`},".cm-gutters":{backgroundColor:`#f6f8fa`,color:`#8b949e`,border:`none`},".cm-activeLine":{backgroundColor:`#f6f8fa88`},".cm-activeLineGutter":{backgroundColor:`#eaeef2`},".cm-cursor":{borderLeftColor:`#1f2328`},"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection":{backgroundColor:`#cfe3ff`}}),S(C,{fallback:!0})],I=e=>e?o.oneDark:F,L=c.of([{key:`Mod-Enter`,preventDefault:!0,run:()=>(e.onRun?.(),!0)},{key:`Shift-Enter`,preventDefault:!0,run:()=>(e.onRun?.(),!0)}]),R=e.dark,z=e.readOnly,B=e.language??[],V=t=>g.create({doc:t,extensions:[l(),d(),f(),y(),E(),p(),g.allowMultipleSelections.of(!0),w(),T(),k(),O(),m(),h(),u(),L,c.of([...A,...v,...b,...D,...j,x]),s.lineWrapping,P.of(B),M.of(I(R)),N.of(g.readOnly.of(z)),s.updateListener.of(t=>{t.docChanged&&e.onChange?.(t.state.doc.toString())})]}),H=new s({parent:e.parent,state:V(e.doc)}),U=new Map,W=e.name??``;return{fallback:!1,getCode:()=>H.state.doc.toString(),setCode(e){H.dispatch({changes:{from:0,to:H.state.doc.length,insert:e}})},openFile(e,t){if(e===W)return;U.set(W,H.state);let n=U.get(e);H.setState(n??V(t)),W=e,n&&H.dispatch({effects:[M.reconfigure(I(R)),N.reconfigure(g.readOnly.of(z)),P.reconfigure(B)]})},fileCode(e,t){if(e===W)return H.state.doc.toString();let n=U.get(e);return n?n.doc.toString():t},forgetFiles(e){for(let t of[...U.keys()])e.includes(t)||U.delete(t)},setTheme(e){R=e,H.dispatch({effects:M.reconfigure(I(R))})},setReadOnly(e){z=e,H.dispatch({effects:N.reconfigure(g.readOnly.of(z))})},setLanguage(e){B=e,H.dispatch({effects:P.reconfigure(B)})},focus:()=>H.focus(),destroy:()=>H.destroy()}}function n(e){let t=document.createElement(`textarea`);t.className=`cp-fallback-editor`,t.spellcheck=!1,t.value=e.doc,t.readOnly=e.readOnly,t.addEventListener(`input`,()=>e.onChange?.(t.value)),t.addEventListener(`keydown`,t=>{(t.metaKey||t.ctrlKey)&&t.key===`Enter`&&(t.preventDefault(),e.onRun?.())}),e.parent.appendChild(t);let n=new Map,r=e.name??``;return{fallback:!0,getCode:()=>t.value,setCode:e=>{t.value=e},openFile(e,i){e!==r&&(n.set(r,t.value),t.value=n.get(e)??i,r=e)},fileCode(e,i){return e===r?t.value:n.get(e)??i},forgetFiles(e){for(let t of[...n.keys()])e.includes(t)||n.delete(t)},setTheme:()=>{},setReadOnly:e=>{t.readOnly=e},setLanguage:()=>{},focus:()=>t.focus(),destroy:()=>t.remove()}}var r=new Map,i=new Map;function a(e,t){r.set(e,t)}function o(){return[...r.keys()]}function s(e){let t=i.get(e);if(t)return t;let n=r.get(e);if(!n)return Promise.reject(Error(`Unknown runtime "${e}". Registered: ${o().join(`, `)||`none`}`));let a=n().catch(t=>{throw i.delete(e),t});return i.set(e,a),a}a(`python`,()=>import(`./pyodide-CqcU3YGb.js`).then(e=>e.pythonRuntime)),a(`javascript`,()=>import(`./javascript-C1_thu8i.js`).then(e=>e.javascriptRuntime)),a(`typescript`,()=>import(`./javascript-C1_thu8i.js`).then(e=>e.typescriptRuntime)),a(`java`,()=>import(`./java-DrWI9a5Q.js`).then(e=>e.javaRuntime));var c=class extends Error{constructor(e=`Execution stopped`){super(e),this.name=`RunStopped`}},l=class extends Error{constructor(e=`Web Workers are unavailable in this page`){super(e),this.name=`WorkerUnavailable`}},u=new WeakSet,d=String.raw`
|
|
2
2
|
:host {
|
|
3
3
|
--cp-bg: #ffffff;
|
|
4
4
|
--cp-fg: #1f2328;
|
|
@@ -296,10 +296,11 @@ table.cp-table th { background: var(--cp-bg); }
|
|
|
296
296
|
}
|
|
297
297
|
`,f=e=>()=>import(`@codemirror/lang-javascript`).then(t=>t.javascript(e)),p={py:()=>import(`@codemirror/lang-python`).then(e=>e.python()),pyi:()=>import(`@codemirror/lang-python`).then(e=>e.python()),js:f({}),mjs:f({}),cjs:f({}),jsx:f({jsx:!0}),ts:f({typescript:!0}),mts:f({typescript:!0}),cts:f({typescript:!0}),tsx:f({typescript:!0,jsx:!0}),json:()=>import(`@codemirror/lang-json`).then(e=>e.json()),java:()=>import(`@codemirror/lang-java`).then(e=>e.java())};async function m(e){let t=p[e.includes(`.`)?e.slice(e.lastIndexOf(`.`)+1).toLowerCase():``];return t?t().catch(()=>null):null}var h=`code-playpad`,g=1,_=`snippets`,v=`progress`,y=null;function b(){return typeof indexedDB>`u`?Promise.reject(Error(`IndexedDB unavailable`)):(y||=new Promise((e,t)=>{let n=indexedDB.open(h,g);n.onupgradeneeded=()=>{let e=n.result;e.objectStoreNames.contains(_)||e.createObjectStore(_),e.objectStoreNames.contains(v)||e.createObjectStore(v,{keyPath:`id`})},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)}).catch(e=>{throw y=null,e}),y)}function x(e,t,n){return b().then(r=>new Promise((i,a)=>{let o=n(r.transaction(e,t).objectStore(e));o.onsuccess=()=>i(o.result),o.onerror=()=>a(o.error)}))}function S(e,t){return x(_,`readwrite`,n=>n.put(t,e)).then(()=>void 0)}function C(e){return x(_,`readonly`,t=>t.get(e)).then(e=>e??null)}function w(e){return x(_,`readwrite`,t=>t.delete(e)).then(()=>void 0)}function T(e,t={}){let n={completed:!0,...t,id:e,updatedAt:Date.now()};return x(v,`readwrite`,e=>e.put(n)).then(()=>n)}function E(e){return x(v,`readonly`,t=>t.get(e)).then(e=>e??null)}function D(){return x(v,`readonly`,e=>e.getAll())}var O=`code-playpad`;function k(e){let t=e.replace(/\t/g,` `).split(`
|
|
298
298
|
`);for(;t.length&&t[0].trim()===``;)t.shift();for(;t.length&&t[t.length-1].trim()===``;)t.pop();let n=t.filter(e=>e.trim()!==``).reduce((e,t)=>Math.min(e,t.match(/^ */)[0].length),1/0);return(n===1/0?t:t.map(e=>e.slice(n))).join(`
|
|
299
|
-
`)}var A=2e5,j=`playpad-file`,M={python:`main.py`,javascript:`main.js`,typescript:`main.ts`,java:`Main.java`};function N(e){return M[e]??`main.txt`}function P(e,t){let n={};for(let t of e.children){if(t.tagName.toLowerCase()!==j)continue;let e=t.getAttribute(`name`);e&&(n[e]=k(t.textContent??``))}if(Object.keys(n).length)return n;let r=[...e.childNodes].filter(e=>e.nodeType!==Node.ELEMENT_NODE||e.tagName.toLowerCase()!==j),i=r.some(e=>e.nodeType===Node.ELEMENT_NODE),a=k(r.filter(e=>!(i&&e.nodeType===Node.TEXT_NODE&&!(e.textContent??``).trim())).map(e=>e.textContent??``).join(``));return{[t]:a}}function F(e,t){try{let t=JSON.parse(e);if(t&&typeof t==`object`&&!Array.isArray(t)){let e=t;return Object.keys(e).length?e:null}}catch{}return e?{[t]:e}:null}var I=typeof HTMLElement>`u`?class{}:HTMLElement,L=class extends I{static get observedAttributes(){return[`language`,`height`,`min-height`,`max-height`,`theme`,`readonly`,`autorun`,`stdin`,`persist`,`entry`]}#e;#t;#n;#r;#i;#a;#o;#s;#c;#l;#u=null;#d=null;#f=null;#p=null;#m={};#h={};#g=`main.py`;#_=null;#v;#y=!1;#b=null;#x=!1;#S=!1;#C=null;#w=null;#T=null;#E=()=>this.#
|
|
299
|
+
`)}var A=2e5,j=`playpad-file`,M={python:`main.py`,javascript:`main.js`,typescript:`main.ts`,java:`Main.java`};function N(e){return M[e]??`main.txt`}function P(e,t){let n={};for(let t of e.children){if(t.tagName.toLowerCase()!==j)continue;let e=t.getAttribute(`name`);e&&(n[e]=k(t.textContent??``))}if(Object.keys(n).length)return n;let r=[...e.childNodes].filter(e=>e.nodeType!==Node.ELEMENT_NODE||e.tagName.toLowerCase()!==j),i=r.some(e=>e.nodeType===Node.ELEMENT_NODE),a=k(r.filter(e=>!(i&&e.nodeType===Node.TEXT_NODE&&!(e.textContent??``).trim())).map(e=>e.textContent??``).join(``));return{[t]:a}}function F(e,t){try{let t=JSON.parse(e);if(t&&typeof t==`object`&&!Array.isArray(t)){let e=t;return Object.keys(e).length?e:null}}catch{}return e?{[t]:e}:null}var I=typeof HTMLElement>`u`?class{}:HTMLElement,L=class extends I{static get observedAttributes(){return[`language`,`height`,`min-height`,`max-height`,`theme`,`readonly`,`autorun`,`stdin`,`persist`,`entry`,`practice`]}#e;#t;#n;#r;#i;#a;#o;#s;#c;#l;#u=null;#d=null;#f=null;#p=null;#m={};#h={};#g=`main.py`;#_=null;#v;#y=!1;#b=null;#x=!1;#S=!1;#C=null;#w=null;#T=null;#E=()=>this.#q();#D=null;#O=!0;#k=[];#A=null;#j=null;#M=null;#N=0;#P=!1;#F=null;constructor(){super(),this.#e=this.attachShadow({mode:`open`})}get code(){return this.#u?this.#u.getCode():this.#m[this.#g]??``}set code(e){this.#m[this.#g]=e,this.#V()}get practice(){if(this.#F)return this.#F;let e=this.getAttribute(`practice`);if(!e)return null;try{return JSON.parse(e)}catch{return null}}set practice(e){this.#F=e}get files(){return this.#U()}set files(e){let t=Object.keys(e);if(!t.length)return;let n=Object.keys(this.#h);n.length===1&&!this.#h[n[0]]&&(this.#h={...e}),this.#m={...e},t.includes(this.#g)||(this.#g=this.#H()),this.#u?.forgetFiles([]),this.#V(),this.#W(),this.#B()}get entry(){return this.#H()}set entry(e){this.setAttribute(`entry`,e)}openFile(e){e in this.#m&&(this.#u&&(this.#m[this.#g]=this.#u.getCode(),this.#u.openFile(e,this.#m[e]??``)),this.#g=e,this.#W(),this.#B())}get stdin(){return this.#l?this.#l.value:``}set stdin(e){this.#l&&(this.#l.value=e,this.#c.hidden=!1)}get language(){return this.getAttribute(`language`)||`python`}set language(e){this.setAttribute(`language`,e)}getCode(){return this.code}getFiles(){return this.#U()}reset(){this.#m={...this.#h},this.#g=this.#H(),this.#u?.forgetFiles([]),this.#V(),this.#W(),this.#X(),this.#s.hidden=!0,this.#Y(this.#f?.ready?`${this.#f.label} ready`:``,`idle`)}async run(){if(this.#y)return;await this.#d;let e=this.#U(),t=this.#H(),n=e[t]??``;this.#y=!0,this.#x=!1,this.#n.disabled=!0,this.#r.hidden=!1,this.#X(),this.#s.hidden=!1;let r=this.practice;this.hasAttribute(`practice`)&&!r&&this.#Z(`The practice attribute is not valid JSON.
|
|
300
|
+
`,`cp-err`),this.#re(`py-run`,{code:n,entry:t,files:e,language:this.language,...r?{practice:r}:{}});try{let i=await this.#z();if(this.#x)throw new c;if(r&&!i.supportsPractice)throw Error(`Practice mode is not available for ${i.label}.`);this.#Y(`Queued…`,`busy`),this.#o.hidden=!1;let a=i.run(n,{stdin:this.#l.value,files:e,entry:t,onStart:()=>this.#Y(`Running…`,`busy`),onStdout:e=>this.#Z(e),onStderr:e=>this.#Z(e,`cp-err`),onEcho:e=>this.#Z(e,`cp-in`),onStdinRequest:()=>this.#$(),...r?{practice:r}:{}});this.#b={runtime:i,run:a};let o=await a;if(o.table&&this.#ne(o.table),o.value!==void 0&&this.#Z(`${o.value}\n`,`cp-value`),o.hint&&this.#te(o.hint,u.has(o)),o.failed=i.statusDecidesFailure?o.failed===!0:!!o.stderr,o.failed)this.#Y(`Error · ${o.durationMs} ms`,`error`),this.#re(`py-error`,{message:o.stderr??``,hint:o.hint,language:this.language});else if(o.cases){let e=o.cases.filter(e=>e.expected!==void 0).length,t=o.passed===e;this.#Y(e?`${o.passed} of ${e} passed · ${o.durationMs} ms`:`Done in ${o.durationMs} ms`,t?`ok`:`error`)}else this.#Y(`Done in ${o.durationMs} ms`,`ok`);return this.#re(`py-output`,{...o,language:this.language}),o}catch(e){if(e instanceof c)this.#Z(`
|
|
300
301
|
[stopped]
|
|
301
|
-
`,`cp-err`),this.#
|
|
302
|
-
`,`cp-err`),this.#
|
|
302
|
+
`,`cp-err`),this.#Y(`Stopped`,`error`);else{let t=e instanceof Error?e.message:String(e);this.#Z(t+`
|
|
303
|
+
`,`cp-err`),this.#Y(`Failed to start`,`error`),this.#re(`py-error`,{message:t,language:this.language})}return}finally{this.#b=null,this.#ee(null),this.#Q(),this.#y=!1,this.#n.disabled=!1,this.#r.hidden=!0,this.#o.hidden=!0}}stop(){this.#b?this.#b.runtime.stop?.(this.#b.run):this.#y&&(this.#x=!0)}prewarm(){return this.#z()}connectedCallback(){if(this.#P){this.#R();return}if(!this.textContent?.trim()&&document.readyState===`loading`){document.addEventListener(`DOMContentLoaded`,()=>this.isConnected&&this.#I(),{once:!0});return}this.#I()}disconnectedCallback(){this.#C?.disconnect(),this.#C=null,this.#w?.removeEventListener(`change`,this.#E),this.#w=null,this.#T?.disconnect(),this.#T=null,this.#D&&clearTimeout(this.#D),this.#ee(null),setTimeout(()=>{this.isConnected||!this.#u||(this.#_=this.#U(),this.#u.destroy(),this.#u=null,this.#d=null,this.#P=!1)},0)}attributeChangedCallback(e,t,n){if(this.#P)switch(e){case`theme`:this.#q();break;case`height`:case`min-height`:case`max-height`:this.#J();break;case`readonly`:this.#u?.setReadOnly(n!==null);break;case`stdin`:this.#c.hidden=n===null&&!this.#l.value;break;case`entry`:this.#W();break;case`language`:this.#f=null,this.#p=null,this.#S=!1,this.#z().catch(()=>{})}}#I(){this.#P=!0,this.#h=P(this,N(this.language)),this.#m=this.#_??{...this.#h},this.#g=this.#H(),this.#L(),this.#q(),this.#J();let t=this.getAttribute(`persist`);this.#d=(async()=>{if(t&&this.#_==null){let e=await C(t).catch(()=>null),n=e==null?null:F(e,N(this.language));if(n){let e={};for(let t of Object.keys(this.#h))e[t]=n[t]??this.#h[t];this.#m=e,this.#g=this.#H()}}this.#W();let n=await s(this.language).catch(()=>null),r=n?await n.editorLanguage().catch(()=>null):null;this.#u=await e({parent:this.#t,doc:this.#m[this.#g]??``,name:this.#g,dark:this.#K(),readOnly:this.hasAttribute(`readonly`),language:r,onRun:()=>void this.run(),onChange:e=>this.#G(e)}),this.#_=null,this.#B()})(),this.#R()}#L(){let e=document.createElement(`style`);e.textContent=d;let t=document.createElement(`div`);t.className=`cp-root`,t.innerHTML=`
|
|
303
304
|
<div class="cp-toolbar">
|
|
304
305
|
<button class="cp-primary" part="run-button" data-action="run">Run</button>
|
|
305
306
|
<button class="cp-danger" part="stop-button" data-action="stop" hidden>Stop</button>
|
|
@@ -314,10 +315,10 @@ table.cp-table th { background: var(--cp-bg); }
|
|
|
314
315
|
<label>Standard input — one value per line</label>
|
|
315
316
|
<textarea rows="2" spellcheck="false"></textarea>
|
|
316
317
|
</div>
|
|
317
|
-
<pre class="cp-output" part="output" hidden></pre>`,this.#e.replaceChildren(e,t),this.#n=t.querySelector(`[data-action="run"]`),this.#r=t.querySelector(`[data-action="stop"]`),this.#i=t.querySelector(`[data-action="reset"]`),this.#a=t.querySelector(`.cp-status`),this.#o=t.querySelector(`.cp-progress`),this.#v=t.querySelector(`.cp-tabs`),this.#t=t.querySelector(`.cp-editor`),this.#c=t.querySelector(`.cp-stdin`),this.#l=t.querySelector(`.cp-stdin textarea`),this.#s=t.querySelector(`.cp-output`),this.#c.hidden=!this.hasAttribute(`stdin`),this.#l.addEventListener(`input`,()=>this.#c.classList.remove(`cp-attention`)),this.#v.addEventListener(`click`,e=>{let t=e.target.closest(`button[data-file]`);t&&this.openFile(t.getAttribute(`data-file`))}),this.#n.addEventListener(`click`,()=>void this.run()),this.#r.addEventListener(`click`,()=>this.stop()),this.#i.addEventListener(`click`,()=>this.reset());let n=()=>void this.#
|
|
318
|
-
`,`cp-err`),this.#s.hidden=!1,e}finally{this.#o.hidden=!0}return this.#
|
|
319
|
-
`),this.#k.push({text:n,className:t}),this.#M??=setTimeout(()=>this.#
|
|
320
|
-
… output truncated for display — the full text is in the
|
|
321
|
-
`),this.#N+=t.text.length,r!==t.className&&(i(),r=t.className),n+=e}i(),this.#s.hidden=!1;let a=this.#s.querySelector(`.cp-prompt`);a?this.#s.insertBefore(t,a):this.#s.appendChild(t),this.#s.scrollTop=this.#s.scrollHeight}
|
|
322
|
-
`,`cp-in`):this.#
|
|
323
|
-
`,`cp-in`),this.#
|
|
318
|
+
<pre class="cp-output" part="output" hidden></pre>`,this.#e.replaceChildren(e,t),this.#n=t.querySelector(`[data-action="run"]`),this.#r=t.querySelector(`[data-action="stop"]`),this.#i=t.querySelector(`[data-action="reset"]`),this.#a=t.querySelector(`.cp-status`),this.#o=t.querySelector(`.cp-progress`),this.#v=t.querySelector(`.cp-tabs`),this.#t=t.querySelector(`.cp-editor`),this.#c=t.querySelector(`.cp-stdin`),this.#l=t.querySelector(`.cp-stdin textarea`),this.#s=t.querySelector(`.cp-output`),this.#c.hidden=!this.hasAttribute(`stdin`),this.#l.addEventListener(`input`,()=>this.#c.classList.remove(`cp-attention`)),this.#v.addEventListener(`click`,e=>{let t=e.target.closest(`button[data-file]`);t&&this.openFile(t.getAttribute(`data-file`))}),this.#n.addEventListener(`click`,()=>void this.run()),this.#r.addEventListener(`click`,()=>this.stop()),this.#i.addEventListener(`click`,()=>this.reset());let n=()=>void this.#z().catch(()=>{});this.#t.addEventListener(`pointerdown`,n,{once:!0}),this.#t.addEventListener(`focusin`,n,{once:!0})}#R(){this.#C||typeof IntersectionObserver>`u`||(this.#C=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&(this.#C?.disconnect(),this.#C=null,this.#z().then(()=>{this.hasAttribute(`autorun`)&&this.run()}).catch(()=>{}))},{rootMargin:`300px`}),this.#C.observe(this))}#z(){return this.#p||=(async()=>{let e=await s(this.language);this.#f=e,e.ready||(this.#o.hidden=!1,this.#Y(`Loading ${e.label}…`,`busy`));try{await e.load(e=>{this.#y||this.#Y(e.message,e.phase===`ready`?`ok`:`busy`)})}catch(e){this.#p=null,this.#Y(`Runtime unavailable`,`error`);let t=e instanceof Error?e.message:String(e);throw this.#Z(t+`
|
|
319
|
+
`,`cp-err`),this.#s.hidden=!1,e}finally{this.#o.hidden=!0}return this.#B(),this.#S||(this.#S=!0,this.#Y(`${e.label} ready`,`ok`),this.#re(`py-ready`,{language:e.id,label:e.label})),e})(),this.#p}async#B(){if(!this.#u)return;let e=this.#g,t=await m(e);if(t){this.#g===e&&this.#u?.setLanguage(t);return}let n=e.includes(`.`)?null:await(this.#f??await s(this.language).catch(()=>null))?.editorLanguage().catch(()=>null);this.#g===e&&this.#u?.setLanguage(n??[])}#V(){if(this.#u){this.#u.setCode(this.#m[this.#g]??``);return}this.#d?.then(()=>{this.#u?.setCode(this.#m[this.#g]??``),this.#W()})}#H(){let e=Object.keys(this.#m),t=this.getAttribute(`entry`);if(t&&e.includes(t))return t;let n=N(this.language);return e.includes(n)?n:e[0]??n}#U(){let e={...this.#m};if(this.#u)for(let t of Object.keys(e))e[t]=this.#u.fileCode(t,e[t]);return e}#W(){let e=Object.keys(this.#m);if(this.#v.hidden=e.length<2,this.#v.hidden){this.#v.replaceChildren();return}let t=this.#H();this.#v.replaceChildren(...e.map(e=>{let n=document.createElement(`button`);return n.dataset.file=e,n.textContent=e,n.setAttribute(`aria-pressed`,String(e===this.#g)),e===t&&(n.classList.add(`cp-entry`),n.title=`entry point — this is the file Run executes`),n}))}#G(e){this.#m[this.#g]=e;let t=this.getAttribute(`persist`);if(!t)return;this.#D&&clearTimeout(this.#D);let n=JSON.stringify(this.#U());this.#D=setTimeout(()=>void S(t,n).catch(()=>{}),400)}#K(){let e=this.getAttribute(`theme`)??`auto`;if(e===`dark`)return!0;if(e===`light`)return!1;let t=typeof document>`u`?``:getComputedStyle(document.documentElement).colorScheme,n=t.includes(`dark`);return n===t.includes(`light`)?!!this.#w?.matches:n}#q(){!this.#w&&typeof matchMedia==`function`&&(this.#w=matchMedia(`(prefers-color-scheme: dark)`),this.#w.addEventListener(`change`,this.#E)),!this.#T&&typeof MutationObserver==`function`&&typeof document<`u`&&(this.#T=new MutationObserver(this.#E),this.#T.observe(document.documentElement,{attributes:!0,attributeFilter:[`class`,`style`,`data-theme`,`data-color-scheme`,`color-scheme`]}));let e=this.#K();this.setAttribute(`data-resolved-theme`,e?`dark`:`light`),this.#u?.setTheme(e)}#J(){for(let[e,t]of[[`height`,`--cp-height`],[`min-height`,`--cp-min-height`],[`max-height`,`--cp-max-height`]]){let n=this.getAttribute(e);n===null||n===``?this.style.removeProperty(t):this.style.setProperty(t,/^\d+$/.test(n)?`${n}px`:n)}}#Y(e,t){this.#a.textContent=e,this.#a.dataset.state=t}#X(){this.#M&&clearTimeout(this.#M),this.#M=null,this.#k=[],this.#N=0,this.#s.replaceChildren(),this.#O=!0}#Z(e,t){if(!e)return;let n=t===`cp-err`&&!this.#O?`\n${e}`:e;this.#O=n.endsWith(`
|
|
320
|
+
`),this.#k.push({text:n,className:t}),this.#M??=setTimeout(()=>this.#Q(),16)}#Q(){if(this.#M&&clearTimeout(this.#M),this.#M=null,!this.#k.length)return;let e=this.#k;this.#k=[];let t=document.createDocumentFragment(),n=``,r,i=()=>{if(!n)return;let e=document.createElement(`span`);r&&(e.className=r),e.textContent=n,t.appendChild(e),n=``};for(let t of e){if(this.#N>=A)break;let e=t.text;this.#N+e.length>A&&(e=e.slice(0,A-this.#N)+`
|
|
321
|
+
… output truncated for display — the full text is in the playpad-output event
|
|
322
|
+
`),this.#N+=t.text.length,r!==t.className&&(i(),r=t.className),n+=e}i(),this.#s.hidden=!1;let a=this.#s.querySelector(`.cp-prompt`);a?this.#s.insertBefore(t,a):this.#s.appendChild(t),this.#s.scrollTop=this.#s.scrollHeight}#$(){return this.#Q(),this.#ee(null),new Promise(e=>{let t=document.createElement(`form`);t.className=`cp-prompt`;let n=document.createElement(`input`);n.type=`text`,n.setAttribute(`aria-label`,`Program input — press Enter to send`),n.autocapitalize=`off`,n.autocomplete=`off`,n.spellcheck=!1;let r=document.createElement(`button`);r.type=`submit`,r.className=`cp-send`,r.textContent=`⏎`,r.title=`Send this line (Enter)`;let i=document.createElement(`button`);i.type=`button`,i.className=`cp-eof`,i.textContent=`EOF`,i.title=`End of input (Ctrl-D) — makes input() raise EOFError`;let a=n=>{this.#A===e&&(this.#A=null,this.#j=null,t.remove(),n===null?this.#Z(`
|
|
323
|
+
`,`cp-in`):this.#Z(n+`
|
|
324
|
+
`,`cp-in`),this.#Q(),e(n))};t.addEventListener(`submit`,e=>{e.preventDefault(),a(n.value)}),i.addEventListener(`click`,()=>a(null)),n.addEventListener(`keydown`,e=>{(e.key===`Escape`||e.key===`d`&&e.ctrlKey)&&(e.preventDefault(),a(null))}),t.append(n,r,i),this.#s.hidden=!1,this.#s.appendChild(t),this.#s.scrollTop=this.#s.scrollHeight,this.#A=e,this.#j=a,n.focus({preventScroll:!0})})}#ee(e){this.#j?.(e),this.#j=null,this.#A=null}#te(e,t){this.#Q();let n=document.createElement(`div`);n.className=`cp-hint`,n.textContent=e,this.#s.hidden=!1,this.#s.appendChild(n),this.#s.scrollTop=this.#s.scrollHeight,t&&(this.#c.hidden=!1,this.#c.classList.add(`cp-attention`))}#ne(e){this.#Q();let t=document.createElement(`table`);t.className=`cp-table`;let n=t.createTHead().insertRow();for(let t of e.columns){let e=document.createElement(`th`);e.textContent=t,n.appendChild(e)}let r=t.createTBody();for(let t of e.rows){let e=r.insertRow();for(let n of t)e.insertCell().textContent=n==null?``:String(n)}this.#s.hidden=!1,this.#s.appendChild(t)}#re(e,t){this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0})),this.dispatchEvent(new CustomEvent(`playpad-${e.slice(3)}`,{detail:t,bubbles:!0,composed:!0}))}};function R(e=O){typeof window>`u`||typeof customElements>`u`||customElements.get(e)||customElements.define(e,L)}R();export{E as a,T as c,l as d,s as f,e as h,w as i,u as l,a as m,R as n,C as o,o as p,D as r,S as s,L as t,c as u};
|
package/dist/element.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type { Files, Result, Runtime } from './types.js';
|
|
1
|
+
import type { Files, PracticeSpec, Result, Runtime } from './types.js';
|
|
2
2
|
export interface RunEventDetail {
|
|
3
3
|
code: string;
|
|
4
4
|
language: string;
|
|
5
5
|
/** Path of the file being run, e.g. "main.py". */
|
|
6
6
|
entry: string;
|
|
7
7
|
files: Files;
|
|
8
|
+
/** Practice mode, when the widget has a `practice` spec. */
|
|
9
|
+
practice?: PracticeSpec;
|
|
8
10
|
}
|
|
9
11
|
export interface OutputEventDetail extends Result {
|
|
10
12
|
language: string;
|
|
@@ -29,6 +31,13 @@ export declare class CodePlayPadElement extends ElementBase {
|
|
|
29
31
|
/** Contents of the file currently on screen. */
|
|
30
32
|
get code(): string;
|
|
31
33
|
set code(value: string);
|
|
34
|
+
/**
|
|
35
|
+
* Practice mode (Java): Run calls `practice.method` with each of
|
|
36
|
+
* `practice.cases` instead of running main, and `Result.cases` reports
|
|
37
|
+
* them. Set it as a property, or as the `practice` attribute (JSON).
|
|
38
|
+
*/
|
|
39
|
+
get practice(): PracticeSpec | null;
|
|
40
|
+
set practice(value: PracticeSpec | null);
|
|
32
41
|
/** Every file in this widget, entry point included. */
|
|
33
42
|
get files(): Files;
|
|
34
43
|
set files(value: Files);
|
package/dist/element.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,t}from"./element-
|
|
1
|
+
import{n as e,t}from"./element-Bh4TdnGA.js";export{t as CodePlayPadElement,e as defineCodePlayPad};
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,6 @@ export type { EditorHandle, EditorOptions } from './editor.js';
|
|
|
5
5
|
export { getRuntime, listRuntimes, registerRuntime } from './runtime/registry.js';
|
|
6
6
|
export { configurePython, configureTypeScript, pyodideAssetUrls, PYODIDE_VERSION } from './runtime/config.js';
|
|
7
7
|
export { RunStopped, WorkerUnavailable } from './types.js';
|
|
8
|
-
export type { Files, LoadProgress, Result, Runtime, RunOptions, Table } from './types.js';
|
|
8
|
+
export type { CaseResult, CaseStatus, Files, LoadProgress, PracticeCase, PracticeSpec, PracticeValue, Result, Runtime, RunOptions, Table } from './types.js';
|
|
9
9
|
export { allProgress, clearSnippet, getProgress, loadSnippet, saveSnippet, setProgress } from './progress.js';
|
|
10
10
|
export type { ProgressRecord } from './progress.js';
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,c as t,d as n,f as r,h as i,i as a,m as o,n as s,o as c,p as l,r as u,s as d,t as f,u as p}from"./element-
|
|
1
|
+
import{a as e,c as t,d as n,f as r,h as i,i as a,m as o,n as s,o as c,p as l,r as u,s as d,t as f,u as p}from"./element-Bh4TdnGA.js";import{n as m,o as h,r as g,t as _}from"./config-DZqcFPEP.js";export{f as CodePlayPadElement,_ as PYODIDE_VERSION,p as RunStopped,n as WorkerUnavailable,u as allProgress,a as clearSnippet,m as configurePython,g as configureTypeScript,i as createEditor,s as defineCodePlayPad,e as getProgress,r as getRuntime,l as listRuntimes,c as loadSnippet,h as pyodideAssetUrls,o as registerRuntime,d as saveSnippet,t as setProgress};
|