q5 4.5.0 → 4.5.4
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/README.md +3 -1
- package/deno.json +2 -1
- package/package.json +28 -5
- package/q5.d.ts +3 -1
- package/q5.js +178 -82
- package/q5.min.js +2 -1
- package/q5.min.js.map +1 -0
- package/.prettierignore +0 -2
- package/teach/accessibility.html +0 -503
package/README.md
CHANGED
|
@@ -47,7 +47,9 @@ p5.js is licensed under the LGPLv2, small sections of p5' code directly copied i
|
|
|
47
47
|
|
|
48
48
|
q5 was inspired by the incredible work of [Ben Fry](https://benfry.com) and [Casey Reas](https://x.com/REAS) on Java [Processing](https://processingfoundation.org/) from 2001 to 2023, [Lauren McCarthy](http://lauren-mccarthy.com)'s work on [p5.js](https://p5js.org) from 2013 to 2019, and all contributors to these projects.
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Huge thanks to all the [q5 contributors](https://github.com/q5js/q5.js/graphs/contributors)!
|
|
51
|
+
|
|
52
|
+
@evanalulu, @Tezumie, @keturn, @ormaq, @bertubi, @RedWilly, @Dukemz, @LingDong-
|
|
51
53
|
|
|
52
54
|
WebGPU MSDF text rendering:
|
|
53
55
|
https://webgpu.github.io/webgpu-samples/?sample=textRenderingMsdf
|
package/deno.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@q5/q5",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.4",
|
|
4
4
|
"license": "LGPL-3.0-only",
|
|
5
5
|
"description": "Beginner friendly graphics powered by WebGPU, optimized for interactive art!",
|
|
6
6
|
"author": "quinton-ashley",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"home",
|
|
25
25
|
"lang",
|
|
26
26
|
"learn",
|
|
27
|
+
"teach",
|
|
27
28
|
"test",
|
|
28
29
|
".npmignore",
|
|
29
30
|
".prettierignore",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "q5",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.4",
|
|
4
4
|
"description": "Beginner friendly graphics powered by WebGPU, optimized for interactive art!",
|
|
5
5
|
"author": "quinton-ashley",
|
|
6
6
|
"contributors": [
|
|
@@ -12,8 +12,27 @@
|
|
|
12
12
|
],
|
|
13
13
|
"license": "LGPL-3.0-only",
|
|
14
14
|
"homepage": "https://q5js.org/home",
|
|
15
|
-
"main": "q5-server.js",
|
|
16
15
|
"types": "q5.d.ts",
|
|
16
|
+
"browser": {
|
|
17
|
+
".": "./q5.js",
|
|
18
|
+
"node:process": false
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./q5.d.ts",
|
|
23
|
+
"browser": "./q5.js",
|
|
24
|
+
"deno": "./q5-deno-server.js",
|
|
25
|
+
"node": "./q5-server.js",
|
|
26
|
+
"default": "./q5.js"
|
|
27
|
+
},
|
|
28
|
+
"./q5.min.js": "./q5.min.js",
|
|
29
|
+
"./q5.js": "./q5.js",
|
|
30
|
+
"./q5-server.js": {
|
|
31
|
+
"browser": null,
|
|
32
|
+
"deno": "./q5-deno-server.js",
|
|
33
|
+
"default": "./q5-server.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
17
36
|
"funding": [
|
|
18
37
|
{
|
|
19
38
|
"type": "patreon",
|
|
@@ -30,7 +49,7 @@
|
|
|
30
49
|
],
|
|
31
50
|
"scripts": {
|
|
32
51
|
"bundle": "cat src/q5-core.js src/q5-canvas.js src/q5-c2d-canvas.js src/q5-c2d-shapes.js src/q5-c2d-image.js src/q5-c2d-soft-filters.js src/q5-c2d-text.js src/q5-color.js src/q5-display.js src/q5-dom.js src/q5-fes.js src/q5-input.js src/q5-math.js src/q5-record.js src/q5-sound.js src/q5-util.js src/q5-vector.js src/q5-webgpu.js src/q5-lang.js src/q5-python.js > q5.js",
|
|
33
|
-
"min": "terser q5.js --compress ecma=
|
|
52
|
+
"min": "terser q5.js --compress ecma=2026 --mangle --output q5.min.js --source-map url=q5.min.js.map",
|
|
34
53
|
"dist": "bun bundle && bun min",
|
|
35
54
|
"tests": "jest test",
|
|
36
55
|
"bld": "node lang/build.js",
|
|
@@ -61,6 +80,10 @@
|
|
|
61
80
|
"jest-cli": "^29.7.0",
|
|
62
81
|
"jsdom": "^25.0.1",
|
|
63
82
|
"json2csv": "^6.0.0-alpha.2",
|
|
64
|
-
"skia-canvas": "^1.0.2"
|
|
65
|
-
|
|
83
|
+
"skia-canvas": "^1.0.2",
|
|
84
|
+
"terser": "^5.46.1"
|
|
85
|
+
},
|
|
86
|
+
"trustedDependencies": [
|
|
87
|
+
"skia-canvas"
|
|
88
|
+
]
|
|
66
89
|
}
|
package/q5.d.ts
CHANGED
|
@@ -2500,6 +2500,8 @@ declare global {
|
|
|
2500
2500
|
* - If one numerical input is provided, returns a number between 0 and the provided value.
|
|
2501
2501
|
* - If two numerical inputs are provided, returns a number between the two values.
|
|
2502
2502
|
* - If an array is provided, returns a random element from the array.
|
|
2503
|
+
*
|
|
2504
|
+
* Return value can be the lower bound but can never exactly be the upper bound.
|
|
2503
2505
|
* @param {number | any[]} [low] lower bound (inclusive) or an array
|
|
2504
2506
|
* @param {number} [high] upper bound (exclusive)
|
|
2505
2507
|
* @returns {number | any} a random number or element
|
|
@@ -4035,7 +4037,7 @@ declare global {
|
|
|
4035
4037
|
*
|
|
4036
4038
|
* q5.draw = function () {
|
|
4037
4039
|
* shader(stripes);
|
|
4038
|
-
*
|
|
4040
|
+
* plane(0, 0, width, height);
|
|
4039
4041
|
*
|
|
4040
4042
|
* resetShader();
|
|
4041
4043
|
* triangle(-50, -50, 0, 50, 50, -50);
|
package/q5.js
CHANGED
|
@@ -308,9 +308,9 @@ function Q5(scope, parent, renderer) {
|
|
|
308
308
|
|
|
309
309
|
function wrapWithFES(name) {
|
|
310
310
|
const fn = t[name] || $[name];
|
|
311
|
-
$[name] = (
|
|
311
|
+
$[name] = function (...args) {
|
|
312
312
|
try {
|
|
313
|
-
return fn(
|
|
313
|
+
return fn.apply(this, args);
|
|
314
314
|
} catch (e) {
|
|
315
315
|
if ($._fes) $._fes(e);
|
|
316
316
|
throw e;
|
|
@@ -322,6 +322,7 @@ function Q5(scope, parent, renderer) {
|
|
|
322
322
|
await runHooks('presetup');
|
|
323
323
|
|
|
324
324
|
readyResolve();
|
|
325
|
+
if ($._removed) return;
|
|
325
326
|
|
|
326
327
|
if (t.preload || $.preload) {
|
|
327
328
|
wrapWithFES('preload');
|
|
@@ -353,9 +354,9 @@ function Q5(scope, parent, renderer) {
|
|
|
353
354
|
})
|
|
354
355
|
]);
|
|
355
356
|
|
|
356
|
-
if (
|
|
357
|
-
|
|
358
|
-
|
|
357
|
+
if ($._removed) return;
|
|
358
|
+
if (!$._disablePreload) await $.loadAll();
|
|
359
|
+
if ($._removed) return;
|
|
359
360
|
|
|
360
361
|
$.setup ??= t.setup || (() => {});
|
|
361
362
|
wrapWithFES('setup');
|
|
@@ -367,10 +368,12 @@ function Q5(scope, parent, renderer) {
|
|
|
367
368
|
millisStart = performance.now();
|
|
368
369
|
await $.setup();
|
|
369
370
|
$._setupDone = true;
|
|
371
|
+
|
|
372
|
+
if ($._removed) return;
|
|
370
373
|
if ($.ctx === null) $.createCanvas(200, 200);
|
|
371
374
|
await runHooks('postsetup');
|
|
372
375
|
|
|
373
|
-
if ($.frameCount) return;
|
|
376
|
+
if ($.frameCount || $._removed) return;
|
|
374
377
|
|
|
375
378
|
$._lastFrameTime = performance.now() - 15;
|
|
376
379
|
raf(_draw);
|
|
@@ -390,6 +393,7 @@ Q5._esm = this === undefined;
|
|
|
390
393
|
|
|
391
394
|
Q5._instanceCount = 0;
|
|
392
395
|
Q5.instances = [];
|
|
396
|
+
Q5.errorTolerant = false;
|
|
393
397
|
Q5._friendlyError = (msg, func) => {
|
|
394
398
|
if (!Q5.disableFriendlyErrors) console.error(func + ': ' + msg);
|
|
395
399
|
};
|
|
@@ -450,7 +454,7 @@ Q5.preloadMethods = {};
|
|
|
450
454
|
Q5.prototype.registerPreloadMethod = (n, fn) => (Q5.preloadMethods[n] = fn[n]);
|
|
451
455
|
|
|
452
456
|
function Canvas(w, h, opt) {
|
|
453
|
-
if (Q5._hasGlobal) return;
|
|
457
|
+
if (Q5._hasGlobal) return Promise.resolve(Q5.instances[0].canvas);
|
|
454
458
|
|
|
455
459
|
let useC2D = w == 'c2d' || h == 'c2d' || opt == 'c2d' || opt?.renderer == 'c2d' || !Q5._esm;
|
|
456
460
|
|
|
@@ -584,7 +588,7 @@ Q5.modules.canvas = ($, q) => {
|
|
|
584
588
|
if (!el) {
|
|
585
589
|
// reattach canvas to the DOM
|
|
586
590
|
document.getElementById(c.id)?.remove();
|
|
587
|
-
|
|
591
|
+
$._addCanvas();
|
|
588
592
|
}
|
|
589
593
|
|
|
590
594
|
if (window.IntersectionObserver) {
|
|
@@ -715,7 +719,7 @@ Q5.modules.canvas = ($, q) => {
|
|
|
715
719
|
}
|
|
716
720
|
};
|
|
717
721
|
|
|
718
|
-
|
|
722
|
+
$._addCanvas = () => {
|
|
719
723
|
let el = $._parent;
|
|
720
724
|
el ??= document.getElementsByTagName('main')[0];
|
|
721
725
|
if (!el) {
|
|
@@ -730,8 +734,8 @@ Q5.modules.canvas = ($, q) => {
|
|
|
730
734
|
if (document.body) document.body.appendChild(el);
|
|
731
735
|
});
|
|
732
736
|
}
|
|
733
|
-
}
|
|
734
|
-
|
|
737
|
+
};
|
|
738
|
+
$._addCanvas();
|
|
735
739
|
}
|
|
736
740
|
|
|
737
741
|
$.resizeCanvas = (w, h) => {
|
|
@@ -3456,16 +3460,12 @@ Q5.modules.fes = ($) => {
|
|
|
3456
3460
|
try {
|
|
3457
3461
|
let res = await (await fetch(fileUrl)).text(),
|
|
3458
3462
|
lines = res.split('\n'),
|
|
3459
|
-
errLine = lines[lineNum - 1]?.trim()
|
|
3460
|
-
bug = ['🐛', '🐞', '🐜', '🦗', '🦋', '🪲'][Math.floor(Math.random() * 6)],
|
|
3461
|
-
inIframe = window.self !== window.top,
|
|
3462
|
-
prefix = `q5.js ${bug}`,
|
|
3463
|
-
errorMsg = ` Error in ${fileBase} on line ${lineNum}:\n\n${errLine}`;
|
|
3463
|
+
errLine = lines[lineNum - 1]?.trim();
|
|
3464
3464
|
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3465
|
+
let type = '';
|
|
3466
|
+
if (e instanceof SyntaxError || e.name === 'SyntaxError') type = 'syntax';
|
|
3467
|
+
|
|
3468
|
+
Q5.friendlyError(fileBase, lineNum, errLine, type);
|
|
3469
3469
|
} catch (err) {}
|
|
3470
3470
|
};
|
|
3471
3471
|
|
|
@@ -3478,7 +3478,7 @@ Q5.modules.fes = ($) => {
|
|
|
3478
3478
|
let match = line.match(/(https?:\/\/[^\s)]+\.js|\b\/[^\s)]+\.js)/);
|
|
3479
3479
|
if (match) {
|
|
3480
3480
|
let file = match[1];
|
|
3481
|
-
if (!/q5|p5play/i.test(file)) {
|
|
3481
|
+
if (!/q5|p5play|q5play|brython/i.test(file)) {
|
|
3482
3482
|
$._sketchFile = file;
|
|
3483
3483
|
break;
|
|
3484
3484
|
}
|
|
@@ -3517,6 +3517,19 @@ Q5.modules.fes = ($) => {
|
|
|
3517
3517
|
checkLatestVersion();
|
|
3518
3518
|
}
|
|
3519
3519
|
};
|
|
3520
|
+
|
|
3521
|
+
Q5.friendlyError = (file, lineNum, detail) => {
|
|
3522
|
+
let bug = ['🐛', '🐞', '🐜', '🦗', '🦋', '🪲'][Math.floor(Math.random() * 6)],
|
|
3523
|
+
inIframe = window.self !== window.top,
|
|
3524
|
+
prefix = `q5 ${bug}`,
|
|
3525
|
+
msg = `Error in ${file} on line ${lineNum}`;
|
|
3526
|
+
|
|
3527
|
+
if (detail) msg += ':\n\n' + detail;
|
|
3528
|
+
|
|
3529
|
+
if (inIframe) return console.log(prefix + msg);
|
|
3530
|
+
|
|
3531
|
+
console.log(`%c${prefix}%c ${msg}`, 'background: #b7ebff; color: #000;', '');
|
|
3532
|
+
};
|
|
3520
3533
|
Q5.modules.input = ($, q) => {
|
|
3521
3534
|
if ($._isGraphics) return;
|
|
3522
3535
|
|
|
@@ -9349,17 +9362,17 @@ const userLangs = `
|
|
|
9349
9362
|
update -> es:actualizar
|
|
9350
9363
|
draw -> es:dibujar
|
|
9351
9364
|
postProcess -> es:postProcesar
|
|
9352
|
-
mousePressed -> es:
|
|
9353
|
-
mouseReleased -> es:
|
|
9354
|
-
mouseMoved -> es:
|
|
9355
|
-
mouseDragged -> es:
|
|
9365
|
+
mousePressed -> es:alPresionarRaton
|
|
9366
|
+
mouseReleased -> es:alSoltarRaton
|
|
9367
|
+
mouseMoved -> es:alMoverRaton
|
|
9368
|
+
mouseDragged -> es:alArrastrarRaton
|
|
9356
9369
|
doubleClicked -> es:dobleClic
|
|
9357
9370
|
keyPressed -> es:alPresionarTecla
|
|
9358
9371
|
keyReleased -> es:alSoltarTecla
|
|
9359
9372
|
touchStarted -> es:alEmpezarToque
|
|
9360
9373
|
touchEnded -> es:alTerminarToque
|
|
9361
9374
|
touchMoved -> es:alMoverToque
|
|
9362
|
-
mouseWheel -> es:
|
|
9375
|
+
mouseWheel -> es:ruedaRaton
|
|
9363
9376
|
`;
|
|
9364
9377
|
|
|
9365
9378
|
const classLangs = {
|
|
@@ -9440,20 +9453,19 @@ Object.defineProperty(Q5, 'lang', {
|
|
|
9440
9453
|
|
|
9441
9454
|
for (let className in classLangs) {
|
|
9442
9455
|
let target = className == 'Q5' ? Q5 : Q5[className] ? Q5[className].prototype : null;
|
|
9443
|
-
if (target)
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
}
|
|
9456
|
+
if (!target) continue;
|
|
9457
|
+
let map = parseLangs(classLangs[className], val);
|
|
9458
|
+
for (let name in map) {
|
|
9459
|
+
let translatedName = map[name];
|
|
9460
|
+
if (target.hasOwnProperty(translatedName)) continue;
|
|
9461
|
+
Object.defineProperty(target, translatedName, {
|
|
9462
|
+
get: function () {
|
|
9463
|
+
return this[name];
|
|
9464
|
+
},
|
|
9465
|
+
set: function (v) {
|
|
9466
|
+
this[name] = v;
|
|
9467
|
+
}
|
|
9468
|
+
});
|
|
9457
9469
|
}
|
|
9458
9470
|
}
|
|
9459
9471
|
|
|
@@ -9512,6 +9524,13 @@ Q5.addHook('init', (q) => {
|
|
|
9512
9524
|
for (let name in m) {
|
|
9513
9525
|
let translatedName = m[name];
|
|
9514
9526
|
q[translatedName] = q[name];
|
|
9527
|
+
|
|
9528
|
+
if (Q5._lang == 'es') {
|
|
9529
|
+
let unaccentedName = translatedName.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
9530
|
+
if (unaccentedName != translatedName) {
|
|
9531
|
+
q[unaccentedName] = q[name];
|
|
9532
|
+
}
|
|
9533
|
+
}
|
|
9515
9534
|
}
|
|
9516
9535
|
});
|
|
9517
9536
|
|
|
@@ -9524,6 +9543,8 @@ Q5.addHook('predraw', (q) => {
|
|
|
9524
9543
|
'frameCount',
|
|
9525
9544
|
'mouseX',
|
|
9526
9545
|
'mouseY',
|
|
9546
|
+
'pmouseX',
|
|
9547
|
+
'pmouseY',
|
|
9527
9548
|
'movedX',
|
|
9528
9549
|
'movedY',
|
|
9529
9550
|
'mouseIsPressed',
|
|
@@ -9536,7 +9557,14 @@ Q5.addHook('predraw', (q) => {
|
|
|
9536
9557
|
|
|
9537
9558
|
// sync properties
|
|
9538
9559
|
for (let p of props) {
|
|
9539
|
-
if (m[p])
|
|
9560
|
+
if (!m[p]) continue;
|
|
9561
|
+
q[m[p]] = q[p];
|
|
9562
|
+
if (Q5._lang == 'es') {
|
|
9563
|
+
let unaccentedName = m[p].normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
9564
|
+
if (unaccentedName != m[p]) {
|
|
9565
|
+
q[unaccentedName] = q[p];
|
|
9566
|
+
}
|
|
9567
|
+
}
|
|
9540
9568
|
}
|
|
9541
9569
|
});
|
|
9542
9570
|
const runPython = async function () {
|
|
@@ -9554,67 +9582,135 @@ const runPython = async function () {
|
|
|
9554
9582
|
document.head.appendChild(script);
|
|
9555
9583
|
});
|
|
9556
9584
|
|
|
9557
|
-
await loadScript('https://cdn.jsdelivr.net/npm/brython@3.
|
|
9558
|
-
await loadScript('https://cdn.jsdelivr.net/npm/brython@3.
|
|
9585
|
+
await loadScript('https://cdn.jsdelivr.net/npm/brython@3.14.0/brython.min.js');
|
|
9586
|
+
await loadScript('https://cdn.jsdelivr.net/npm/brython@3.14.0/brython_stdlib.min.js');
|
|
9559
9587
|
}
|
|
9560
9588
|
|
|
9561
|
-
brython();
|
|
9562
|
-
|
|
9563
|
-
__BRYTHON__.runPythonSource(`
|
|
9564
|
-
from browser import window, aio
|
|
9565
|
-
|
|
9566
|
-
async def runQ5PY(code, q5py):
|
|
9567
|
-
ns = globals().copy()
|
|
9568
|
-
ns['ns'] = ns
|
|
9569
|
-
ns['q5py'] = q5py
|
|
9570
|
-
|
|
9571
|
-
for attr in dir(q5py):
|
|
9572
|
-
if not attr.startswith('_'):
|
|
9573
|
-
try:
|
|
9574
|
-
ns[attr] = getattr(q5py, attr)
|
|
9575
|
-
except Exception:
|
|
9576
|
-
pass
|
|
9577
|
-
|
|
9578
|
-
exec(code, ns)
|
|
9579
|
-
|
|
9580
|
-
if "__run_code" in ns:
|
|
9581
|
-
await ns["__run_code"]()
|
|
9582
|
-
|
|
9583
|
-
window._runQ5PY = runQ5PY
|
|
9584
|
-
`);
|
|
9585
|
-
|
|
9586
9589
|
let code = '';
|
|
9587
9590
|
for (const script of scripts) {
|
|
9588
9591
|
code += script.src ? await (await fetch(script.src)).text() : script.innerText;
|
|
9589
9592
|
}
|
|
9590
9593
|
|
|
9591
9594
|
const useWebGPU = !code.slice(0, code.indexOf('\n')).includes('C2D'),
|
|
9592
|
-
|
|
9595
|
+
q = useWebGPU ? await Q5.WebGPU() : new Q5();
|
|
9593
9596
|
|
|
9594
|
-
|
|
9597
|
+
// `window.Canvas` returns a promise that resolves when Q5 is ready
|
|
9598
|
+
// but `q5py.Canvas` returns the renderer synchronously
|
|
9599
|
+
// so to make Brython happy with `await Canvas()` we need to make it async
|
|
9600
|
+
const Canvas = q.Canvas;
|
|
9601
|
+
q.Canvas = async (...a) => Canvas(...a);
|
|
9595
9602
|
|
|
9596
|
-
code
|
|
9597
|
-
|
|
9598
|
-
|
|
9603
|
+
// add a tab before each line of code to nest it inside the __run function
|
|
9604
|
+
// but not within triple-quoted strings
|
|
9605
|
+
code = code
|
|
9606
|
+
.split(/(\"\"\"[\s\S]*?\"\"\"|\'\'\'[\s\S]*?\'\'\')/g)
|
|
9607
|
+
.map((part, i) => (i % 2 === 0 ? part.replaceAll('\n', '\n\t') : part))
|
|
9608
|
+
.join('');
|
|
9599
9609
|
|
|
9610
|
+
code = `
|
|
9611
|
+
async def __run(q):
|
|
9600
9612
|
${code}
|
|
9601
9613
|
|
|
9602
|
-
|
|
9614
|
+
_state_vars = ["mouseX", "mouseY", "pmouseX", "pmouseY", "width", "height", "frameCount", "deltaTime", "mouseIsPressed", "mouseButton", "keyIsPressed", "key", "keyCode", "touches", "movedX", "movedY"]
|
|
9615
|
+
|
|
9616
|
+
_usr_fns = ["update", "draw", "postProcess", "mousePressed", "mouseReleased", "mouseMoved", "mouseDragged", "mouseClicked", "doubleClicked", "mouseWheel", "keyPressed", "keyReleased", "keyTyped", "touchStarted", "touchMoved", "touchEnded", "windowResized"]
|
|
9603
9617
|
|
|
9604
9618
|
def _sync_and_call(fn):
|
|
9605
9619
|
def _wrapper(*args):
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9620
|
+
try:
|
|
9621
|
+
for var in _state_vars:
|
|
9622
|
+
if hasattr(q, var):
|
|
9623
|
+
ns[var] = getattr(q, var)
|
|
9624
|
+
return fn(*args)
|
|
9625
|
+
except Exception as e:
|
|
9626
|
+
window._pyErr(_err())
|
|
9627
|
+
if not window.Q5.errorTolerant: noLoop()
|
|
9610
9628
|
return _wrapper
|
|
9611
9629
|
|
|
9612
|
-
for
|
|
9613
|
-
if
|
|
9614
|
-
setattr(window,
|
|
9630
|
+
for fn_name in _usr_fns:
|
|
9631
|
+
if fn_name in locals():
|
|
9632
|
+
setattr(window, fn_name, _sync_and_call(locals()[fn_name]))
|
|
9615
9633
|
`;
|
|
9616
9634
|
|
|
9617
|
-
|
|
9635
|
+
window._pyErr = (err, lineNum) => {
|
|
9636
|
+
if (typeof err === 'string' && err.includes('Traceback')) {
|
|
9637
|
+
let lines = err.split('\n');
|
|
9638
|
+
for (let i = lines.length - 1; i > 0; i--) {
|
|
9639
|
+
const match = lines[i].match(/File "<string>", line (\d+)/);
|
|
9640
|
+
if (match) {
|
|
9641
|
+
lineNum = parseInt(match[1]);
|
|
9642
|
+
lines = lines.slice(i + 1);
|
|
9643
|
+
// de-indent the first two lines based on the first line's indentation
|
|
9644
|
+
const indentMatch = lines[0].match(/^\s+/);
|
|
9645
|
+
if (indentMatch) {
|
|
9646
|
+
const indent = indentMatch[0];
|
|
9647
|
+
for (let j = 0; j < Math.min(2, lines.length); j++) {
|
|
9648
|
+
lines[j] = lines[j].slice(indent.length);
|
|
9649
|
+
}
|
|
9650
|
+
} else {
|
|
9651
|
+
let line = code.split('\n')[lineNum - 1].trim();
|
|
9652
|
+
lines.unshift(line, '');
|
|
9653
|
+
}
|
|
9654
|
+
err = lines.join('\n');
|
|
9655
|
+
break;
|
|
9656
|
+
}
|
|
9657
|
+
}
|
|
9658
|
+
}
|
|
9659
|
+
|
|
9660
|
+
let file = scripts[0].src || scripts[0]['data-filename'] || 'sketch.py';
|
|
9661
|
+
file = file.split('/').at(-1);
|
|
9662
|
+
|
|
9663
|
+
lineNum -= 2; // adjust for the wrapper code lines
|
|
9664
|
+
if (Q5.friendlyError) Q5.friendlyError(file, lineNum, err);
|
|
9665
|
+
else console.error(`Error in ${file} on line ${lineNum}:\n\n${err}`);
|
|
9666
|
+
};
|
|
9667
|
+
|
|
9668
|
+
brython();
|
|
9669
|
+
|
|
9670
|
+
// hide brython's internal logs by temporarily overriding console.log
|
|
9671
|
+
let log = console.log;
|
|
9672
|
+
console.log = function () {};
|
|
9673
|
+
|
|
9674
|
+
__BRYTHON__.runPythonSource(`
|
|
9675
|
+
from browser import window, aio
|
|
9676
|
+
import traceback
|
|
9677
|
+
import io
|
|
9678
|
+
|
|
9679
|
+
def _err():
|
|
9680
|
+
f = io.StringIO()
|
|
9681
|
+
traceback.print_exc(file=f)
|
|
9682
|
+
return f.getvalue()
|
|
9683
|
+
|
|
9684
|
+
async def _run_py(q, code):
|
|
9685
|
+
ns = globals().copy()
|
|
9686
|
+
ns['ns'] = ns
|
|
9687
|
+
ns['Q5'] = window.Q5
|
|
9688
|
+
|
|
9689
|
+
for attr in dir(q):
|
|
9690
|
+
if not attr.startswith('_'):
|
|
9691
|
+
try:
|
|
9692
|
+
ns[attr] = getattr(q, attr)
|
|
9693
|
+
except Exception:
|
|
9694
|
+
pass
|
|
9695
|
+
|
|
9696
|
+
try:
|
|
9697
|
+
exec(code, ns)
|
|
9698
|
+
except SyntaxError as e:
|
|
9699
|
+
return window._pyErr(_err(), e.lineno)
|
|
9700
|
+
except Exception as e:
|
|
9701
|
+
return window._pyErr(_err())
|
|
9702
|
+
|
|
9703
|
+
try:
|
|
9704
|
+
await ns["__run"](q)
|
|
9705
|
+
except Exception as e:
|
|
9706
|
+
window._pyErr(_err())
|
|
9707
|
+
|
|
9708
|
+
window._runPy = _run_py
|
|
9709
|
+
`);
|
|
9710
|
+
|
|
9711
|
+
console.log = log;
|
|
9712
|
+
|
|
9713
|
+
await window._runPy(q, code);
|
|
9618
9714
|
};
|
|
9619
9715
|
|
|
9620
9716
|
if (typeof document == 'object') {
|