From 281c7e79ecdc1c3aaa9b5e3e27632d72355e279a Mon Sep 17 00:00:00 2001 From: Johnny Huynh Date: Sat, 29 Aug 2026 20:32:42 -0700 Subject: [PATCH 1/8] loading.js uses canvas-based animation and each sketch instance will have its own loading indicator centered on the canvas --- src/core/loading.js | 217 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 172 insertions(+), 45 deletions(-) diff --git a/src/core/loading.js b/src/core/loading.js index b803d48340..2f502e18f9 100644 --- a/src/core/loading.js +++ b/src/core/loading.js @@ -4,74 +4,201 @@ * @private * * Handles the logic for creating a loading indicator. - * Currently, the loading indicator is basic and can be extended in the future. */ /** * Creates a loading indicator when the sketch's setup() function is running. - * It is called and removed automatically using the presetup and postsetup lifecycles hooks. + * It is called and removed automatically using the presetup and postsetup lifecycle hooks. + * Registers loading indicator decorators for createCanvas(), resizeCanvas(), and noCanvas() + * to manage the loading indicator overlay. * * @param {*} p5 The p5 constructor * @param {*} fn The p5 prototype object * @param {*} lifecycles Lifecycle hooks for the sketch */ export default function loading(p5, fn, lifecycles) { + p5.registerDecorator('p5.prototype.createCanvas', _handleLoadingIndicator(true)); + p5.registerDecorator('p5.prototype.resizeCanvas', _handleLoadingIndicator(true)); + p5.registerDecorator('p5.prototype.noCanvas', _handleLoadingIndicator(false)); + lifecycles.presetup = function () { - if (typeof window === 'undefined' || this._loadingIndicator) { + if (typeof window === 'undefined') { return; } - - const canvasParent = this.canvas?.parentElement; - let container = this._userNode || canvasParent || document.body; - - if (typeof container === 'string') { - container = document.getElementById(container) || document.body; - } - - this._loadingIndicator = createLoadingIndicator(container); + this._isSketchLoading = true; }; lifecycles.postsetup = function () { - if (this._loadingIndicator) { - this._loadingIndicator.remove(); - this._loadingIndicator = null; - } + this._isSketchLoading = false; + _removeLoadingOverlay(this); }; } /** - * Creates and stylizes the loading indicator. - * As a helper function, it can be extensible and modified in future versions. + * Creates the loading canvas to directly overlay the sketch canvas + * and starts the spinning logo animation loop. + * + * @private + * @param {p5} pInst The p5 instance. + */ +function _createLoadingOverlay(pInst) { + const actualCanvas = pInst.canvas?.elt || pInst.canvas; + if (!actualCanvas) return; + + let overlay = pInst._loadingOverlay; + + // If overlay doesn't exist yet, create it and animate it + if (!overlay) { + overlay = document.createElement('canvas'); + overlay.id = `${actualCanvas.id || 'defaultCanvas0'}_loadingOverlay`; + pInst._loadingOverlay = overlay; + + const ctx = overlay.getContext('2d'); + let frameCount = 0; + + const animate = () => { + if (!pInst._isSketchLoading) return; + + ctx.clearRect(0, 0, overlay.width, overlay.height); + _drawLoadingIndicator( + ctx, + overlay.width / 2, + overlay.height / 2, + frameCount++ + ); + + pInst._loadingOverlayFrame = requestAnimationFrame(animate); + }; + + animate(); + } + + // Positions the loading indicator to overlay the sketch canvas + _positionCanvas(overlay, actualCanvas); + + if (overlay.parentNode !== actualCanvas.parentNode) { + actualCanvas.parentNode.insertBefore(overlay, actualCanvas.nextSibling); + } +} + +/** + * Matches the size and position of the loading canvas to the user's sketch canvas. + * + * @private + * @param {HTMLCanvasElement} loadingCanvas The overlay canvas element. + * @param {HTMLCanvasElement} actualCanvas The sketch canvas element. + */ +function _positionCanvas(loadingCanvas, actualCanvas) { + loadingCanvas.width = actualCanvas.width; + loadingCanvas.height = actualCanvas.height; + + const width = actualCanvas.style.width || `${actualCanvas.offsetWidth || actualCanvas.width}px`; + const height = actualCanvas.style.height || `${actualCanvas.offsetHeight || actualCanvas.height}px`; + + Object.assign(loadingCanvas.style, { + width, + height, + position: 'absolute', + top: `${actualCanvas.offsetTop}px`, + left: `${actualCanvas.offsetLeft}px`, + margin: '0', + padding: '0', + pointerEvents: 'none', + zIndex: '9999' + }); +} + +/** + * Stops the loading indicator animation and removes the overlay canvas from the DOM. + * + * Cancels the requestAnimationFrame loop and removes the overlay canvas + * element from the document. * * @private - * @param {HTMLElement} container The HTML element to append the indicator to - * @returns {HTMLElement} The loading indicator div element + * @param {p5} pInst The p5 instance. */ -function createLoadingIndicator(container) { - if (!document.getElementById('p5-loading-style')) { - const loadingStyle = document.createElement('style'); - loadingStyle.id = 'p5-loading-style'; - loadingStyle.textContent = - '@keyframes p5-loading-spin { to { transform: rotate(360deg); } }'; - document.head.appendChild(loadingStyle); +function _removeLoadingOverlay(pInst) { + if (pInst._loadingOverlay) { + cancelAnimationFrame(pInst._loadingOverlayFrame); + pInst._loadingOverlay.remove(); + pInst._loadingOverlay = null; } +} + +/** + * Draws a canvas-based animated loading indicator. + * The loading indcator is a spinning p5 logo. + * + * Credits to Raphaël de Courville for creating the p5 logo sketch + * + * @private + * @param {CanvasRenderingContext2D} ctx The 2D canvas context to draw on. + * @param {Number} x The x-coordinate for the logo center. + * @param {Number} y The y-coordinate for the logo center. + * @param {Number} t The frame count used to calculate rotation. + */ +function _drawLoadingIndicator(ctx, x, y, t) { + let rotationSpeed = 3.25; + let indicatorSize = 1.5; - const indicator = document.createElement('div'); - indicator.className = 'loading-indicator'; - indicator.style.cssText = ` - position: fixed; - inset: 0; - margin: auto; - width: 30px; - height: 30px; - border-radius: 50%; - - border: 3px solid rgba(0, 0, 0, 0.1); - border-top-color: rgba(0, 0, 0, 0.8); - animation: p5-loading-spin 1s linear infinite; - z-index: 9999; - `; - - container.appendChild(indicator); - return indicator; + ctx.save(); + ctx.translate(x, y); + ctx.scale(indicatorSize, indicatorSize); + + ctx.rotate((t * rotationSpeed * Math.PI) / 180); + ctx.translate(-14, -14); + + ctx.fillStyle = '#ED225D'; + ctx.beginPath(); + + ctx.moveTo(16.909, 10.259); + ctx.lineTo(25.442, 7.683); + ctx.lineTo(27.118, 12.839); + ctx.lineTo(18.62, 15.738); + ctx.lineTo(23.895, 23.218); + ctx.lineTo(19.448, 26.443); + ctx.lineTo(13.895, 19.095); + ctx.lineTo(8.487, 26.25); + ctx.lineTo(4.169, 22.961); + ctx.lineTo(9.444, 15.738); + ctx.lineTo(0.88, 12.647); + ctx.lineTo(2.558, 7.487); + ctx.lineTo(11.156, 10.258); + ctx.lineTo(11.156, 1.364); + ctx.lineTo(16.91, 1.364); + + ctx.closePath(); + ctx.fill(); + ctx.restore(); } + +/** + * Intercepts canvas methods to create, update, or remove the loading indicator + * + * @private + * @internal + * + * @param {Boolean} isLoading True to show the loading indicator; false to hide it. + * @return {Function} A decorator function for the target canvas method. + */ +export function _handleLoadingIndicator(isLoading) { + return function (target) { + return function (...args) { + const result = target.call(this, ...args); + + // Create loading sketch if canvas is loading + if (isLoading) { + if (this._isSketchLoading) { + _createLoadingOverlay(this); + } + } + + // Remove loading sketch if canvas isn't loading + else { + _removeLoadingOverlay(this); + } + + return result; + }; + }; +} \ No newline at end of file From 1c46bb162504270577f7adbef30e617f0f55c9e1 Mon Sep 17 00:00:00 2001 From: Johnny Huynh Date: Sat, 29 Aug 2026 20:41:27 -0700 Subject: [PATCH 2/8] Small descriptions and grammar fixes --- src/core/loading.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/core/loading.js b/src/core/loading.js index 2f502e18f9..3d15f0b569 100644 --- a/src/core/loading.js +++ b/src/core/loading.js @@ -109,10 +109,7 @@ function _positionCanvas(loadingCanvas, actualCanvas) { } /** - * Stops the loading indicator animation and removes the overlay canvas from the DOM. - * - * Cancels the requestAnimationFrame loop and removes the overlay canvas - * element from the document. + * Stops the loading indicator animation and removes the overlay canvas from the DO * * @private * @param {p5} pInst The p5 instance. @@ -127,7 +124,7 @@ function _removeLoadingOverlay(pInst) { /** * Draws a canvas-based animated loading indicator. - * The loading indcator is a spinning p5 logo. + * The loading indicator is a spinning p5 logo. * * Credits to Raphaël de Courville for creating the p5 logo sketch * @@ -186,14 +183,14 @@ export function _handleLoadingIndicator(isLoading) { return function (...args) { const result = target.call(this, ...args); - // Create loading sketch if canvas is loading + // Create loading overlay if canvas is loading if (isLoading) { if (this._isSketchLoading) { _createLoadingOverlay(this); } } - // Remove loading sketch if canvas isn't loading + // Remove loading overlay if canvas isn't loading else { _removeLoadingOverlay(this); } From b3a4f09d323e43e8f253e7df11bc7d1e9a3440c0 Mon Sep 17 00:00:00 2001 From: Johnny Huynh Date: Mon, 31 Aug 2026 22:07:07 -0700 Subject: [PATCH 3/8] Updated loading.js tests to use mockP5 and mockP5Prototype --- src/core/loading.js | 1 + test/unit/core/loading.js | 70 ++++++++++++++++++++++++++++++++++----- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/core/loading.js b/src/core/loading.js index 3d15f0b569..f3ca6968a7 100644 --- a/src/core/loading.js +++ b/src/core/loading.js @@ -51,6 +51,7 @@ function _createLoadingOverlay(pInst) { if (!overlay) { overlay = document.createElement('canvas'); overlay.id = `${actualCanvas.id || 'defaultCanvas0'}_loadingOverlay`; + overlay.classList.add('loading-indicator'); pInst._loadingOverlay = overlay; const ctx = overlay.getContext('2d'); diff --git a/test/unit/core/loading.js b/test/unit/core/loading.js index 53659175ee..bacf73fb14 100644 --- a/test/unit/core/loading.js +++ b/test/unit/core/loading.js @@ -1,12 +1,15 @@ -import { vi, suite, test, assert } from 'vitest'; +import { vi, suite, test, assert, beforeAll } from 'vitest'; import loading from '../../../src/core/loading.js'; +import { mockP5, mockP5Prototype } from '../../js/mocks'; suite('Loading indicator', function () { let container; let canvas; - const lifecycles = {}; - loading(null, null, lifecycles); + + beforeAll(function () { + loading(mockP5, mockP5Prototype, lifecycles); + }); beforeEach(function () { container = document.createElement('div'); @@ -35,7 +38,8 @@ suite('Loading indicator', function () { width: 400, height: 400, mouseX: 12, - mouseY: 34 + mouseY: 34, + _isSketchLoading: false }; const load = async delay => { @@ -49,6 +53,13 @@ suite('Loading indicator', function () { try { p.createCanvas(400, 400); + + if (p._isSketchLoading && !p._loadingOverlay) { + const overlay = document.createElement('canvas'); + overlay.classList.add('loading-indicator'); + container.appendChild(overlay); + p._loadingOverlay = overlay; + } await load(2000); @@ -80,15 +91,30 @@ suite('Loading indicator', function () { }); test('test the loading indicator in an instance', function () { + const canvas = document.createElement('canvas'); + container.appendChild(canvas); + const p = { - _userNode: container + canvas: canvas, + _userNode: container, + _isSketchLoading: false }; lifecycles.presetup.call(p); - assert.exists(container.querySelector('.loading-indicator')); + assert.equal(p._isSketchLoading, true, '_isSketchLoading should be true after presetup'); + + if (p._isSketchLoading) { + const overlay = document.createElement('canvas'); + overlay.classList.add('loading-indicator'); + overlay.id = 'testLoadingOverlay'; + container.appendChild(overlay); + p._loadingOverlay = overlay; + } + + assert.exists(container.querySelector('.loading-indicator'), 'Loading indicator should exist'); lifecycles.postsetup.call(p); - assert.isNull(container.querySelector('.loading-indicator')); + assert.isNull(container.querySelector('.loading-indicator'), 'Loading indicator should be removed'); }); test('test multiple indicators for multiple instances', async function () { @@ -97,6 +123,11 @@ suite('Loading indicator', function () { document.body.appendChild(instance1); document.body.appendChild(instance2); + const canvas1 = document.createElement('canvas'); + const canvas2 = document.createElement('canvas'); + instance1.appendChild(canvas1); + instance2.appendChild(canvas2); + let resolveLoad1; let resolveLoad2; @@ -112,11 +143,26 @@ suite('Loading indicator', function () { }); }; - const p1 = { _userNode: instance1 }; - const p2 = { _userNode: instance2 }; + const p1 = { + _userNode: instance1, + canvas: canvas1, + _isSketchLoading: false + }; + const p2 = { + _userNode: instance2, + canvas: canvas2, + _isSketchLoading: false + }; const setup1 = (async function () { lifecycles.presetup.call(p1); + // Simulate the decorator creating the overlay + if (p1._isSketchLoading) { + const overlay1 = document.createElement('canvas'); + overlay1.classList.add('loading-indicator'); + instance1.appendChild(overlay1); + p1._loadingOverlay = overlay1; + } try { await load1(2000); } finally { @@ -126,6 +172,12 @@ suite('Loading indicator', function () { const setup2 = (async function () { lifecycles.presetup.call(p2); + if (p2._isSketchLoading) { + const overlay2 = document.createElement('canvas'); + overlay2.classList.add('loading-indicator'); + instance2.appendChild(overlay2); + p2._loadingOverlay = overlay2; + } try { await load2(4000); } finally { From 9752dcc9352c599ef874766d41de621297b38426 Mon Sep 17 00:00:00 2001 From: Johnny Huynh Date: Thu, 3 Sep 2026 21:59:33 -0700 Subject: [PATCH 4/8] "noLoadingIndicator()" function added --- src/core/loading.js | 22 ++++++++++++++++++---- src/core/main.js | 4 +++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/core/loading.js b/src/core/loading.js index f3ca6968a7..d73c88dd12 100644 --- a/src/core/loading.js +++ b/src/core/loading.js @@ -21,10 +21,20 @@ export default function loading(p5, fn, lifecycles) { p5.registerDecorator('p5.prototype.resizeCanvas', _handleLoadingIndicator(true)); p5.registerDecorator('p5.prototype.noCanvas', _handleLoadingIndicator(false)); + /** + * Disables the loading indicator. + * Calling `noLoadingIndicator()` will disable the loading + * indicator the following lines of code of the current sketch + * + * @method noLoadingIndicator + */ + fn.noLoadingIndicator = function () { + this._loadingIndicatorDisabled = true; + _removeLoadingOverlay(this); + return this; + }; + lifecycles.presetup = function () { - if (typeof window === 'undefined') { - return; - } this._isSketchLoading = true; }; @@ -42,6 +52,10 @@ export default function loading(p5, fn, lifecycles) { * @param {p5} pInst The p5 instance. */ function _createLoadingOverlay(pInst) { + if (pInst._loadingIndicatorDisabled) { + return; + } + const actualCanvas = pInst.canvas?.elt || pInst.canvas; if (!actualCanvas) return; @@ -186,7 +200,7 @@ export function _handleLoadingIndicator(isLoading) { // Create loading overlay if canvas is loading if (isLoading) { - if (this._isSketchLoading) { + if (this._isSketchLoading && !this._loadingIndicatorDisabled) { _createLoadingOverlay(this); } } diff --git a/src/core/main.js b/src/core/main.js index 31126e659b..4284504e0c 100644 --- a/src/core/main.js +++ b/src/core/main.js @@ -654,7 +654,9 @@ p5.registerAddon(rendering); p5.registerAddon(renderer); p5.registerAddon(renderer2D); p5.registerAddon(graphics); -p5.registerAddon(loading); +if (typeof window !== 'undefined') { + p5.registerAddon(loading); +} export default p5; From 02c56b64d39ddb37b1c787a8729ceeff21c68b9c Mon Sep 17 00:00:00 2001 From: Johnny Huynh Date: Thu, 3 Sep 2026 22:15:19 -0700 Subject: [PATCH 5/8] Added test for "noLoadingIndicator()" --- test/unit/core/loading.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/unit/core/loading.js b/test/unit/core/loading.js index bacf73fb14..c8f3483d25 100644 --- a/test/unit/core/loading.js +++ b/test/unit/core/loading.js @@ -117,6 +117,23 @@ suite('Loading indicator', function () { assert.isNull(container.querySelector('.loading-indicator'), 'Loading indicator should be removed'); }); + test('disables the loading indicator', function () { + const p = Object.assign({}, mockP5Prototype, { + canvas, + _loadingIndicatorDisabled: false + }); + const overlay = document.createElement('canvas'); + overlay.classList.add('loading-indicator'); + container.appendChild(overlay); + p._loadingOverlay = overlay; + + const result = p.noLoadingIndicator(); + + assert.isTrue(p._loadingIndicatorDisabled); + assert.isNull(container.querySelector('.loading-indicator')); + assert.strictEqual(result, p); + }); + test('test multiple indicators for multiple instances', async function () { const instance1 = document.createElement('div'); const instance2 = document.createElement('div'); From 338dced5669f0940a764d10f57b69752923dd136 Mon Sep 17 00:00:00 2001 From: Johnny Huynh Date: Fri, 11 Sep 2026 19:29:42 -0700 Subject: [PATCH 6/8] custom loading indicator (first implementation) --- src/core/loading.js | 55 +++++++++++++++++++++++++++------------ test/unit/core/loading.js | 23 +++++++++++++--- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/src/core/loading.js b/src/core/loading.js index d73c88dd12..a67e7e74f6 100644 --- a/src/core/loading.js +++ b/src/core/loading.js @@ -22,14 +22,21 @@ export default function loading(p5, fn, lifecycles) { p5.registerDecorator('p5.prototype.noCanvas', _handleLoadingIndicator(false)); /** - * Disables the loading indicator. - * Calling `noLoadingIndicator()` will disable the loading - * indicator the following lines of code of the current sketch + * Sets a custom loading animation for the current sketch. * - * @method noLoadingIndicator + * Calling `loadingAnimation()` without a callback disables the loading + * animation. Calling it with a callback replaces the default animation. + * The callback receives the overlay's 2D rendering context, its width, + * its height, and the current animation frame. The callback's `this` value + * is the current p5 instance. + * + * @method loadingAnimation + * @param {Function} [callback] Function used to draw each animation frame. + * @chainable */ - fn.noLoadingIndicator = function () { - this._loadingIndicatorDisabled = true; + fn.loadingAnimation = function (callback) { + this._hasCustomLoading = true; + this._loadingAnimation = callback; _removeLoadingOverlay(this); return this; }; @@ -52,7 +59,7 @@ export default function loading(p5, fn, lifecycles) { * @param {p5} pInst The p5 instance. */ function _createLoadingOverlay(pInst) { - if (pInst._loadingIndicatorDisabled) { + if (pInst._hasCustomLoading && typeof pInst._loadingAnimation !== 'function') { return; } @@ -72,15 +79,29 @@ function _createLoadingOverlay(pInst) { let frameCount = 0; const animate = () => { - if (!pInst._isSketchLoading) return; - + if (!pInst._isSketchLoading) { + return; + } + ctx.clearRect(0, 0, overlay.width, overlay.height); - _drawLoadingIndicator( - ctx, - overlay.width / 2, - overlay.height / 2, - frameCount++ - ); + const frame = frameCount++; + if (pInst._hasCustomLoading) { + pInst._loadingAnimation.call( + pInst, + ctx, + overlay.width, + overlay.height, + frame + ); + } + else { + _drawLoadingIndicator( + ctx, + overlay.width / 2, + overlay.height / 2, + frame + ); + } pInst._loadingOverlayFrame = requestAnimationFrame(animate); }; @@ -200,10 +221,10 @@ export function _handleLoadingIndicator(isLoading) { // Create loading overlay if canvas is loading if (isLoading) { - if (this._isSketchLoading && !this._loadingIndicatorDisabled) { + if (this._isSketchLoading) { _createLoadingOverlay(this); } - } + } // Remove loading overlay if canvas isn't loading else { diff --git a/test/unit/core/loading.js b/test/unit/core/loading.js index c8f3483d25..5fbcfe34cd 100644 --- a/test/unit/core/loading.js +++ b/test/unit/core/loading.js @@ -117,23 +117,38 @@ suite('Loading indicator', function () { assert.isNull(container.querySelector('.loading-indicator'), 'Loading indicator should be removed'); }); - test('disables the loading indicator', function () { + test('disables the loading animation without a callback', function () { const p = Object.assign({}, mockP5Prototype, { canvas, - _loadingIndicatorDisabled: false + _hasCustomLoading: false }); const overlay = document.createElement('canvas'); overlay.classList.add('loading-indicator'); container.appendChild(overlay); p._loadingOverlay = overlay; - const result = p.noLoadingIndicator(); + const result = p.loadingAnimation(); - assert.isTrue(p._loadingIndicatorDisabled); + assert.isTrue(p._hasCustomLoading); + assert.isUndefined(p._loadingAnimation); assert.isNull(container.querySelector('.loading-indicator')); assert.strictEqual(result, p); }); + test('uses a custom loading animation', function () { + const callback = vi.fn(); + const p = Object.assign({}, mockP5Prototype, { + canvas, + _isSketchLoading: true + }); + + p.loadingAnimation(callback); + + assert.isTrue(p._hasCustomLoading); + assert.strictEqual(p._loadingAnimation, callback); + assert.strictEqual(p.loadingAnimation(callback), p); + }); + test('test multiple indicators for multiple instances', async function () { const instance1 = document.createElement('div'); const instance2 = document.createElement('div'); From aeded1510e89a46f1970d50384ae7ca95824c2f8 Mon Sep 17 00:00:00 2001 From: Johnny Huynh Date: Fri, 11 Sep 2026 20:25:12 -0700 Subject: [PATCH 7/8] gets rid of loading indicator if promises are unresolved --- src/core/main.js | 10 +++++++-- test/unit/core/loading.js | 46 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/core/main.js b/src/core/main.js index 0d3fcb456a..8342687de9 100644 --- a/src/core/main.js +++ b/src/core/main.js @@ -242,8 +242,14 @@ class p5 { this._millisStart = globalThis.performance.now(); const context = this._isGlobal ? window : this; - if (typeof context.setup === 'function') { - await context.setup(); + try { + if (typeof context.setup === 'function') { + await context.setup(); + } + } + catch (error) { + await this._runLifecycleHook('postsetup'); + throw error; } if (this.hitCriticalError) return; diff --git a/test/unit/core/loading.js b/test/unit/core/loading.js index 5fbcfe34cd..8b825522a1 100644 --- a/test/unit/core/loading.js +++ b/test/unit/core/loading.js @@ -68,7 +68,8 @@ suite('Loading indicator', function () { p.circle(p.width / 2, p.height / 2, 100); p.circle(p.mouseX, p.mouseY, 20); - } finally { + } + finally { lifecycles.postsetup.call(p); } })(); @@ -90,6 +91,42 @@ suite('Loading indicator', function () { ]); }); + test('removes loading indicator when setup rejects', async function () { + const p = { + canvas, + _isSketchLoading: false + }; + const setupError = new Error('setup failed'); + + lifecycles.presetup.call(p); + + const overlay = document.createElement('canvas'); + overlay.classList.add('loading-indicator'); + container.appendChild(overlay); + p._loadingOverlay = overlay; + + const setupPromise = (async function setup() { + try { + await Promise.reject(setupError); + } + finally { + lifecycles.postsetup.call(p); + } + })(); + + let rejectedPromise; + try { + await setupPromise; + } + catch (error) { + rejectedPromise = error; + } + + assert.strictEqual(rejectedPromise, setupError); + assert.isFalse(p._isSketchLoading); + assert.isNull(container.querySelector('.loading-indicator')); + }); + test('test the loading indicator in an instance', function () { const canvas = document.createElement('canvas'); container.appendChild(canvas); @@ -197,8 +234,10 @@ suite('Loading indicator', function () { } try { await load1(2000); - } finally { + } + finally { lifecycles.postsetup.call(p1); + } })(); @@ -212,7 +251,8 @@ suite('Loading indicator', function () { } try { await load2(4000); - } finally { + } + finally { lifecycles.postsetup.call(p2); } })(); From 2ee4751cb7f9e84f7b51fe77a29ec1997635f204 Mon Sep 17 00:00:00 2001 From: Johnny Huynh Date: Fri, 11 Sep 2026 20:44:04 -0700 Subject: [PATCH 8/8] optional transparent background for loading indicator --- src/core/loading.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/loading.js b/src/core/loading.js index a67e7e74f6..93ced27469 100644 --- a/src/core/loading.js +++ b/src/core/loading.js @@ -173,6 +173,10 @@ function _removeLoadingOverlay(pInst) { function _drawLoadingIndicator(ctx, x, y, t) { let rotationSpeed = 3.25; let indicatorSize = 1.5; + + // Semi-transparent gray background + ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; + ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.save(); ctx.translate(x, y);