code-playpad 0.3.1 → 0.5.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 +345 -45
- package/dist/cdn.js +2 -2
- package/dist/{element-BYcX5Z6z.js → element-yit-fyhg.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-BHbRkfsQ.js +1 -0
- package/dist/java-BuW8Eeg_.js +6 -0
- package/dist/java-library.generated-I0MB-4YJ.js +6 -0
- package/dist/{javascript-BhISU47e.js → javascript-EtrNZmfD.js} +1 -1
- package/dist/{javascript-hfFD1Fg2.js → javascript-krS-hdur.js} +1 -1
- package/dist/{pyodide-j1wg1W9_.js → pyodide-BeJ9RqWJ.js} +1 -1
- package/dist/{pyodide-BTGQ_X_F.js → pyodide-C9S4Me6g.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-B64HCt-B.js +4 -0
- package/dist/worker-runtime-CNPpMRo1.js +9 -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.5.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 ~105 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,133 @@ 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. Since engine 0.27, other `.java` files in the
|
|
148
|
+
widget are compiled as the JDK's launcher compiles them, when the program first
|
|
149
|
+
uses their class (see [Several files](#several-files)). Packages are not
|
|
150
|
+
supported.
|
|
151
|
+
|
|
152
|
+
**Speed.** In loop-heavy code the engine runs roughly 150–300 million
|
|
153
|
+
bytecode instructions a second on a laptop. A typical practice solution (10⁵–10⁶
|
|
154
|
+
elements, nested loops, maps, sorting with a comparator) finishes in well
|
|
155
|
+
under a second. The engine stops a runaway program after 200 million steps
|
|
156
|
+
(about 0.6–1.5 s of work) and explains why.
|
|
157
|
+
|
|
158
|
+
**Exactly what is supported**, feature by feature and method by method, with
|
|
159
|
+
the evidence behind it and every known difference from the JDK, is in
|
|
160
|
+
[JAVA-COMPATIBILITY.md](https://cdn.jsdelivr.net/npm/code-playpad/JAVA-COMPATIBILITY.md), which ships in the package.
|
|
93
161
|
|
|
94
162
|
## Download size
|
|
95
163
|
|
|
96
|
-
What a page downloads once a reader presses Run, with the CDN build
|
|
97
|
-
|
|
98
|
-
CDN
|
|
164
|
+
What a page downloads once a reader presses Run, with the CDN build, as
|
|
165
|
+
measured for 0.4.0. Each one-widget page was loaded in a browser, and each
|
|
166
|
+
code-playpad file it fetched was gzipped. The CDN column is what the CDN
|
|
167
|
+
actually sent.
|
|
99
168
|
|
|
100
|
-
| Page uses | code-playpad (gzip) | Also fetched from a CDN at run time | 0.
|
|
169
|
+
| Page uses | code-playpad (gzip) | Also fetched from a CDN at run time | 0.3.0 (gzip) |
|
|
101
170
|
| --- | --- | --- | --- |
|
|
102
|
-
| Python | **
|
|
103
|
-
| JavaScript | **
|
|
104
|
-
| TypeScript | **
|
|
105
|
-
| Java | **
|
|
106
|
-
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
171
|
+
| 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 |
|
|
172
|
+
| JavaScript | **180.4 KB** | nothing | 179.9 KB |
|
|
173
|
+
| TypeScript | **180.4 KB** | Sucrase, 72 KB (≈ 252 KB in total) | 179.9 KB |
|
|
174
|
+
| Java | **267.8 KB** | nothing | 216.4 KB |
|
|
175
|
+
| Java, with collections | **342.4 KB** | nothing | 233.2 KB |
|
|
176
|
+
| All four | 399.9 KB | Pyodide and Sucrase | 273.9 KB |
|
|
177
|
+
|
|
178
|
+
- **Every row includes a 144.5 KB shared base:** the editor (CodeMirror) and
|
|
179
|
+
the widget, the same as in 0.3.0.
|
|
180
|
+
- **Each language adds:**
|
|
181
|
+
- Python: 21.7 KB.
|
|
182
|
+
- JavaScript/TypeScript: 35.9 KB.
|
|
183
|
+
- Java: 123.2 KB (the 104.2 KB engine plus 19 KB of Java syntax
|
|
184
|
+
highlighting).
|
|
185
|
+
- The first Java program that uses collections, `StringBuilder` or another
|
|
186
|
+
library class also adds the 74.6 KB Java library, once per page.
|
|
187
|
+
- **Java grew** from 0.3.0 because the subset did, from basic Java to most of
|
|
188
|
+
what practice programs use (see the version history). The owner treats
|
|
189
|
+
download size as secondary to coverage. The package's size checks still
|
|
190
|
+
catch accidental growth: 128 KiB for the Java chunk and 96 KiB for its
|
|
191
|
+
library.
|
|
192
|
+
- **Languages load lazily.** A Java page never fetches the Python or
|
|
193
|
+
JavaScript runtimes, and Python and JavaScript pages never fetch Java. As a
|
|
194
|
+
result, Python-only and JavaScript-only pages are the same size as in 0.3.0.
|
|
195
|
+
- **Java needs no CDN:** it is self-contained, while Python's first run
|
|
196
|
+
downloads about 6 MB of Pyodide (cached after that).
|
|
197
|
+
- **With npm and a bundler,** you already ship CodeMirror yourself.
|
|
198
|
+
code-playpad's own code then costs about 9 KB, plus the runtime chunk for
|
|
199
|
+
each language used. Java's chunk is 104 KB, and 74 KB more for its library
|
|
200
|
+
when a program uses it, plus `@codemirror/lang-java` if you install it.
|
|
120
201
|
|
|
121
202
|
## Install
|
|
122
203
|
|
|
123
204
|
### From a CDN — nothing to install
|
|
124
205
|
|
|
125
206
|
```html
|
|
126
|
-
<script type="module" src="https://cdn.jsdelivr.net/npm/code-playpad@0.
|
|
207
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/code-playpad@0.5.0/dist/standalone.js"></script>
|
|
127
208
|
```
|
|
128
209
|
|
|
129
210
|
This is the `standalone` build: CodeMirror is bundled in, so a static blog post
|
|
@@ -185,6 +266,7 @@ Next.js, Astro and SvelteKit need no dynamic import or `client:only` guard.
|
|
|
185
266
|
| `stdin` | shows the standard-input box up front |
|
|
186
267
|
| `persist` | key under which edits are saved (see [Saving work](#saving-work)) |
|
|
187
268
|
| `entry` | which file Run executes, for multi-file widgets |
|
|
269
|
+
| `practice` | Java only: test cases for one method, as JSON (see [Practice mode](#practice-mode-java)) |
|
|
188
270
|
|
|
189
271
|
```html
|
|
190
272
|
<!-- a worked example nobody should edit -->
|
|
@@ -286,6 +368,39 @@ JavaScript and TypeScript work the same way, including `.json` imported as data:
|
|
|
286
368
|
</code-playpad>
|
|
287
369
|
```
|
|
288
370
|
|
|
371
|
+
Java compiles the other files as `java Main.java` does (JDK 25's source
|
|
372
|
+
launcher): `Main.java` runs, and `Session.java` is compiled when `Main` first
|
|
373
|
+
uses `Session`. Each file is named after its class and has its own imports, and
|
|
374
|
+
errors and stack traces name the file. Write `<` as `<` inside the markup
|
|
375
|
+
(`List<String>`), since it is HTML:
|
|
376
|
+
|
|
377
|
+
```html
|
|
378
|
+
<code-playpad language="java" entry="Main.java" height="auto">
|
|
379
|
+
<playpad-file name="Main.java">
|
|
380
|
+
public class Main {
|
|
381
|
+
public static void main(String[] args) {
|
|
382
|
+
Session s = new Session("loops", 30);
|
|
383
|
+
System.out.println(s.describe());
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
</playpad-file>
|
|
387
|
+
|
|
388
|
+
<playpad-file name="Session.java">
|
|
389
|
+
public class Session {
|
|
390
|
+
private final String topic;
|
|
391
|
+
private final int minutes;
|
|
392
|
+
|
|
393
|
+
Session(String topic, int minutes) {
|
|
394
|
+
this.topic = topic;
|
|
395
|
+
this.minutes = minutes;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
String describe() { return topic + ": " + minutes + " min"; }
|
|
399
|
+
}
|
|
400
|
+
</playpad-file>
|
|
401
|
+
</code-playpad>
|
|
402
|
+
```
|
|
403
|
+
|
|
289
404
|
A tab bar appears once there is more than one file, with `▸` marking the entry
|
|
290
405
|
point, and each file keeps its own undo history. Editing a module takes effect
|
|
291
406
|
on the next run — the import cache is cleared between runs.
|
|
@@ -335,7 +450,26 @@ document.querySelector('code-playpad').stdin = 'Ada\n36';
|
|
|
335
450
|
Echoed input is display-only: `stdout` stays exactly what the program printed,
|
|
336
451
|
so graders are unaffected.
|
|
337
452
|
|
|
338
|
-
Java
|
|
453
|
+
Java reads the box as its standard input. `new Scanner(System.in)` and
|
|
454
|
+
`IO.readln()` see all of it at once, as when input is piped to `java Main.java`:
|
|
455
|
+
the program never stops to wait, and the input is not echoed into the output.
|
|
456
|
+
A program that reads past the end fails with the JDK's
|
|
457
|
+
`NoSuchElementException`, and the widget opens the box with a hint, as it
|
|
458
|
+
does for Python's `input()`.
|
|
459
|
+
|
|
460
|
+
```html
|
|
461
|
+
<code-playpad id="times" language="java" height="auto" stdin>
|
|
462
|
+
import java.util.Scanner;
|
|
463
|
+
|
|
464
|
+
void main() {
|
|
465
|
+
Scanner in = new Scanner(System.in);
|
|
466
|
+
IO.println(in.nextInt() * in.nextInt());
|
|
467
|
+
}
|
|
468
|
+
</code-playpad>
|
|
469
|
+
<script>
|
|
470
|
+
document.getElementById('times').stdin = '3 4';
|
|
471
|
+
</script>
|
|
472
|
+
```
|
|
339
473
|
|
|
340
474
|
## Controlling it from JavaScript
|
|
341
475
|
|
|
@@ -369,14 +503,26 @@ they press Run rather than after.
|
|
|
369
503
|
## Events and grading
|
|
370
504
|
|
|
371
505
|
Four events bubble and cross the shadow boundary, so answer-checking lives
|
|
372
|
-
outside the widget rather than inside it:
|
|
506
|
+
outside the widget rather than inside it. They fire for every language:
|
|
373
507
|
|
|
374
508
|
| event | `event.detail` |
|
|
375
509
|
| --- | --- |
|
|
376
|
-
| `
|
|
377
|
-
| `
|
|
378
|
-
| `
|
|
379
|
-
| `
|
|
510
|
+
| `playpad-ready` | `{ language, label }` — runtime booted |
|
|
511
|
+
| `playpad-run` | `{ code, entry, files, language }` — a run started |
|
|
512
|
+
| `playpad-output` | `{ stdout, stderr?, value?, table?, durationMs, failed, language }` |
|
|
513
|
+
| `playpad-error` | `{ message, hint?, language }` |
|
|
514
|
+
|
|
515
|
+
Each one is also fired under its original name, `py-ready`, `py-run`,
|
|
516
|
+
`py-output` and `py-error` (first, with the same `detail`), from before the
|
|
517
|
+
widget ran anything but Python. Existing pages keep working; new code should
|
|
518
|
+
listen for the `playpad-*` names.
|
|
519
|
+
|
|
520
|
+
`failed` says whether the run failed, and `playpad-error` fires exactly when it
|
|
521
|
+
did. For Python and JavaScript, any stderr output counts as a failure. For Java,
|
|
522
|
+
the run's own status decides: an exception, a compile error or a non-zero
|
|
523
|
+
`System.exit` fail, while javac's `[unchecked]` warnings and `System.err` output
|
|
524
|
+
from a program that finished normally do not. That is how a terminal reads it;
|
|
525
|
+
the warnings are still printed, and still in `stderr`.
|
|
380
526
|
|
|
381
527
|
A complete exercise check:
|
|
382
528
|
|
|
@@ -390,11 +536,11 @@ A complete exercise check:
|
|
|
390
536
|
```js
|
|
391
537
|
const expected = '1\n2\n3\n4\n5\n';
|
|
392
538
|
|
|
393
|
-
document.addEventListener('
|
|
539
|
+
document.addEventListener('playpad-output', (event) => {
|
|
394
540
|
if (event.target.id !== 'ex1') return;
|
|
395
541
|
|
|
396
542
|
const el = document.getElementById('ex1-result');
|
|
397
|
-
if (event.detail.
|
|
543
|
+
if (event.detail.failed) {
|
|
398
544
|
el.textContent = 'Your code raised an error — read the message above.';
|
|
399
545
|
} else if (event.detail.stdout === expected) {
|
|
400
546
|
el.textContent = '✅ Correct';
|
|
@@ -404,10 +550,73 @@ document.addEventListener('py-output', (event) => {
|
|
|
404
550
|
});
|
|
405
551
|
```
|
|
406
552
|
|
|
407
|
-
`
|
|
553
|
+
`playpad-error` also carries a `hint` when the runtime can explain the failure in
|
|
408
554
|
plain language — for example when a program calls `input()` more times than the
|
|
409
555
|
standard-input box has lines.
|
|
410
556
|
|
|
557
|
+
## Practice mode (Java)
|
|
558
|
+
|
|
559
|
+
For LeetCode-style exercises, give a Java widget test cases for one method.
|
|
560
|
+
Run then calls that method once per case, instead of running `main` (no
|
|
561
|
+
`main` is needed), and reports each case. The output shows one line per
|
|
562
|
+
case and the tally; the status line says "2 of 3 passed".
|
|
563
|
+
|
|
564
|
+
```html
|
|
565
|
+
<code-playpad language="java" height="auto"
|
|
566
|
+
practice='{"method": "lowerBound", "cases": [
|
|
567
|
+
{"args": [[1, 3, 3, 8], 3], "expected": 1},
|
|
568
|
+
{"args": [[1, 3, 3, 8], 9], "expected": 4},
|
|
569
|
+
{"args": [[], 5], "expected": 0}]}'>
|
|
570
|
+
class Solution {
|
|
571
|
+
static int lowerBound(int[] sorted, int target) {
|
|
572
|
+
return 0; // your code here
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
</code-playpad>
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Or set it from JavaScript (`el.practice = { method, cases }`), or pass the
|
|
579
|
+
`practice` prop to the React component. Each case's result arrives in
|
|
580
|
+
`playpad-output` as `detail.cases`, with `detail.passed` and `detail.total`,
|
|
581
|
+
so grading code can read them instead of comparing text:
|
|
582
|
+
|
|
583
|
+
```js
|
|
584
|
+
document.addEventListener('playpad-output', (event) => {
|
|
585
|
+
const { cases, passed, total } = event.detail;
|
|
586
|
+
if (cases) console.log(`${passed} of ${total}`, cases.filter((c) => c.status !== 'passed'));
|
|
587
|
+
});
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
**The spec.** `method` (required); `cases`, each `{ args, expected?, id? }`;
|
|
591
|
+
`className` when several classes have a method of that name; `params`
|
|
592
|
+
(`["int[]", "int"]`) to choose among overloads; `compare: { kind: "argument",
|
|
593
|
+
index: 1 }` to check an argument after the call, for a `void` method that
|
|
594
|
+
fills one in; `tolerance` in `compare` for doubles; `nodes` to describe a
|
|
595
|
+
linked-list or tree class whose fields do not make its shape obvious.
|
|
596
|
+
|
|
597
|
+
**Values** are JSON, the same way for arguments, `expected` and the
|
|
598
|
+
reported `actual`:
|
|
599
|
+
|
|
600
|
+
| Java | JSON |
|
|
601
|
+
| --- | --- |
|
|
602
|
+
| `int`, `boolean`, `String`, wrapper types | number, boolean, string, `null` |
|
|
603
|
+
| `long` | a decimal string, `"4000000000"` |
|
|
604
|
+
| `char` | a one-character string |
|
|
605
|
+
| `double` | a number, or `"NaN"`, `"Infinity"`, `"-Infinity"` |
|
|
606
|
+
| arrays, `List` / `ArrayList` | arrays (nested for `int[][]`, `List<List<Integer>>`) |
|
|
607
|
+
| linked-list nodes (a class with one field of its own type) | the values in order, `[1, 2, 3]` |
|
|
608
|
+
| tree nodes (two fields of its own type) | level order with `null` gaps, `[3, 9, 20, null, null, 15, 7]` |
|
|
609
|
+
|
|
610
|
+
Maps, sets and `Object` cannot be passed or returned yet.
|
|
611
|
+
|
|
612
|
+
**Each case's `status`:** `passed`, `failed` (with `expected` and
|
|
613
|
+
`actual`), `completed` (no `expected` was given), `runtime-error` (with
|
|
614
|
+
`error`, the stack trace), `resource-limit`, `engine-limitation`,
|
|
615
|
+
`invalid-case` (the JSON does not fit the parameters: the exercise's
|
|
616
|
+
mistake, not the learner's) and `not-run` (with a `reason`, such as
|
|
617
|
+
`compile-error`). Each case also has what it printed (`stdout`, `stderr`).
|
|
618
|
+
Every case runs on fresh state: static fields start over.
|
|
619
|
+
|
|
411
620
|
## Saving work
|
|
412
621
|
|
|
413
622
|
`persist` stores the whole file map in IndexedDB, so a reader's edits survive
|
|
@@ -428,7 +637,7 @@ For a course you usually also want completion state, which the package exports:
|
|
|
428
637
|
import { setProgress, getProgress, allProgress, loadSnippet, clearSnippet } from 'code-playpad';
|
|
429
638
|
|
|
430
639
|
// mark a lesson done when its exercise passes
|
|
431
|
-
document.addEventListener('
|
|
640
|
+
document.addEventListener('playpad-output', async (event) => {
|
|
432
641
|
if (event.detail.stdout === expected) {
|
|
433
642
|
await setProgress('lesson-3', { completed: true, score: 100 });
|
|
434
643
|
}
|
|
@@ -622,7 +831,98 @@ warms every other page on the site.
|
|
|
622
831
|
## Version history
|
|
623
832
|
|
|
624
833
|
No breaking changes so far: markup and API written against 0.1.3 still work on
|
|
625
|
-
0.
|
|
834
|
+
0.5.0.
|
|
835
|
+
|
|
836
|
+
### 0.5.0
|
|
837
|
+
|
|
838
|
+
- **Java: programs in several files** (engine 0.27.0). `Main.java` can use
|
|
839
|
+
`Session.java` from another tab, as `java Main.java` does on JDK 25 (JEP 458).
|
|
840
|
+
A file is compiled when the program first uses its class, and each file has
|
|
841
|
+
its own imports. Errors and stack traces name the right file and line. See
|
|
842
|
+
[Several files](#several-files).
|
|
843
|
+
- **Java: `new Object()` and `String.intern()`.**
|
|
844
|
+
|
|
845
|
+
### 0.4.0
|
|
846
|
+
|
|
847
|
+
Java grows from a first subset into most of what practice programs and
|
|
848
|
+
coding-interview solutions use, with its behavior recorded against JDK 25
|
|
849
|
+
throughout. Nothing is removed or changed for Python, JavaScript or
|
|
850
|
+
TypeScript, and existing markup and code keep working.
|
|
851
|
+
|
|
852
|
+
**Java: the language** (engines 0.4.0–0.24.0)
|
|
853
|
+
- **Objects and classes:** several classes per file; nested, inner, local
|
|
854
|
+
and anonymous classes; constructors and `this(...)`; inheritance with
|
|
855
|
+
`super`; `abstract` classes; interfaces with default, static and private
|
|
856
|
+
methods; `Object`'s methods; `instanceof` and casts.
|
|
857
|
+
- **Enums and records:** enums with fields, methods, `switch` and constants
|
|
858
|
+
that have their own bodies. Records with compact constructors.
|
|
859
|
+
- **Exceptions:** `try`/`catch`/`finally`, multi-catch,
|
|
860
|
+
try-with-resources, custom exceptions, and checked exceptions checked as
|
|
861
|
+
javac checks them. Stack traces, `Caused by:` and NullPointerException
|
|
862
|
+
messages match the JDK's.
|
|
863
|
+
- **Numbers and control flow:** `long`, `double`, `float`, `byte` and
|
|
864
|
+
`short`, with Java's overflow, conversions and printing. `switch`
|
|
865
|
+
statements and expressions, labeled `break`/`continue`, and
|
|
866
|
+
multi-dimensional arrays.
|
|
867
|
+
- **Generics and lambdas:** generic classes and methods, raw types, `var`,
|
|
868
|
+
varargs, annotations, and javac's `[unchecked]` warnings. Lambdas and
|
|
869
|
+
method references for any functional interface, with captures.
|
|
870
|
+
- **Modern Java:** sealed types, pattern matching (record patterns, guards,
|
|
871
|
+
`case null`), text blocks, static imports, initializer blocks and
|
|
872
|
+
`Interface.super` calls.
|
|
873
|
+
|
|
874
|
+
**Java: the library**
|
|
875
|
+
- **Collections:** `ArrayList`, `HashMap` and `HashSet` (iterating in the
|
|
876
|
+
JDK's order), `LinkedHashMap`, `TreeMap`, `TreeSet`, `ArrayDeque`,
|
|
877
|
+
`PriorityQueue`, `LinkedList`, `Stack`, `EnumMap` and `EnumSet`, plus
|
|
878
|
+
`List.of`/`Map.of`, `subList` and `Collections`.
|
|
879
|
+
- **Streams:** `Stream`, `IntStream`, `Collectors` and `Optional`, run
|
|
880
|
+
element by element in the JDK's order.
|
|
881
|
+
- **Strings and formatting:** the rest of `String`, with Java's regex syntax;
|
|
882
|
+
`StringBuilder`; `String.format` and `printf` with Java's rounding.
|
|
883
|
+
- **Numbers and system:** `Math`, `Random` (seeded sequences match the
|
|
884
|
+
JDK's), `BigInteger` and `BigDecimal`, and `System.exit`, `arraycopy` and
|
|
885
|
+
`nanoTime`.
|
|
886
|
+
- **Input and output:** `Scanner` and `IO.readln` read the widget's input
|
|
887
|
+
box. `BufferedReader`, `StringTokenizer`, `StreamTokenizer`,
|
|
888
|
+
`PrintWriter` and `BufferedWriter` give fast I/O, and output you never
|
|
889
|
+
flush is lost exactly as on the JDK.
|
|
890
|
+
|
|
891
|
+
**Java: speed** (engines 0.25.0–0.26.0)
|
|
892
|
+
- Loop-heavy programs run 2.3–4.3× faster than in 0.24.0. Library callbacks
|
|
893
|
+
(comparators, `merge`, `computeIfAbsent`, `PriorityQueue` with a lambda)
|
|
894
|
+
run 1.5–4.8× faster than in 0.25.0.
|
|
895
|
+
- The 200-million-step limit now covers about 0.6–1.5 s of work, so typical
|
|
896
|
+
10⁵–10⁶-element solutions finish well inside it.
|
|
897
|
+
|
|
898
|
+
**New for every language**
|
|
899
|
+
- **`playpad-ready`, `playpad-run`, `playpad-output`, `playpad-error`:**
|
|
900
|
+
language-neutral names for the four events. Each fires right after its
|
|
901
|
+
`py-*` original, with the same `detail`, and the `py-*` names keep firing.
|
|
902
|
+
- **`playpad-output` carries `failed`,** and `playpad-error` fires exactly
|
|
903
|
+
when a run failed. For Java, the run's own status decides (an exception, a
|
|
904
|
+
compile error or a non-zero `System.exit`), so javac's warnings and
|
|
905
|
+
`System.err` output from a program that finished normally do not count as a
|
|
906
|
+
failure. See [Events and grading](#events-and-grading).
|
|
907
|
+
|
|
908
|
+
**Java practice mode**
|
|
909
|
+
- **`practice`:** an attribute, property or React prop that gives a Java
|
|
910
|
+
widget test cases for one method. Run calls the method once per case and
|
|
911
|
+
shows a line per case and the tally.
|
|
912
|
+
- **`playpad-output`** carries `cases`, `passed` and `total`.
|
|
913
|
+
`RunOptions.practice` and `Result.cases` do the same for code that drives a
|
|
914
|
+
runtime directly. See [Practice mode](#practice-mode-java).
|
|
915
|
+
|
|
916
|
+
**Docs and size**
|
|
917
|
+
- **`JAVA-COMPATIBILITY.md`** ships in the package. It is the engine's full
|
|
918
|
+
compatibility table, regenerated with each engine sync: every feature and
|
|
919
|
+
library method, the evidence behind it, and every known difference from
|
|
920
|
+
the JDK.
|
|
921
|
+
- **Download size:** Java pages download more than in 0.3.0, Python and
|
|
922
|
+
JavaScript pages the same; see [Download size](#download-size).
|
|
923
|
+
- **Size checks:** the budgets were raised to 128 KiB for the Java chunk and
|
|
924
|
+
96 KiB for its library, at the owner's direction that size is not a
|
|
925
|
+
constraint.
|
|
626
926
|
|
|
627
927
|
### 0.3.1
|
|
628
928
|
|
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.5.0: CDN entry, loads dist/standalone.js of this exact version */
|
|
2
|
+
export * from "https://cdn.jsdelivr.net/npm/code-playpad@0.5.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-C9S4Me6g.js`).then(e=>e.pythonRuntime)),a(`javascript`,()=>import(`./javascript-krS-hdur.js`).then(e=>e.javascriptRuntime)),a(`typescript`,()=>import(`./javascript-krS-hdur.js`).then(e=>e.typescriptRuntime)),a(`java`,()=>import(`./java-BHbRkfsQ.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);
|