From b3b4048eabfbc451b71764c2a09ff5eb5db6d9d4 Mon Sep 17 00:00:00 2001 From: ShryukGrandhi <85529990+ShryukGrandhi@users.noreply.github.com> Date: Thu, 29 Oct 2026 19:33:58 -0700 Subject: [PATCH 1/2] fix(web): handle image loading failures in laptop screen generator Clear the previous image when a new file is picked, and report decode failures and non-image files in the status line. The PDF download stays disabled until a valid image loads. Fixes #95 Signed-off-by: ShryukGrandhi <85529990+ShryukGrandhi@users.noreply.github.com> --- web/js/screens.js | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/web/js/screens.js b/web/js/screens.js index ee6fbd7..58b54a7 100644 --- a/web/js/screens.js +++ b/web/js/screens.js @@ -20,7 +20,7 @@ const MARKUP = /\{([a-z][a-z0-9_-]*|\/)\}/g; const $ = (id) => document.getElementById(id); const state = { presets: [], designs: [], preset: null, source: 'text', design: null, - image: null, zoom: 1, panX: 0.5, panY: 0.5, print: 'colour', paperColour: '#fff1a8', mask: true, + image: null, imageError: '', zoom: 1, panX: 0.5, panY: 0.5, print: 'colour', paperColour: '#fff1a8', mask: true, }; // ------------------------------------------------------------ .screen format @@ -244,7 +244,10 @@ function renderPreview() { const status = $('scr-status'); const weak = state.source === 'text' && (state.print === 'colour' || state.print === 'mono') ? lowContrastColours(state.design) : []; - if (state.source === 'image' && !state.image) { + if (state.source === 'image' && state.imageError) { + status.textContent = state.imageError; + status.className = 'status-message is-err'; + } else if (state.source === 'image' && !state.image) { status.textContent = 'No image yet. Pick a file or drop one on the preview.'; status.className = 'status-message'; } else if (weak.length) { @@ -424,12 +427,19 @@ function syncColours() { } function loadImage(file) { - if (!file || !file.type.startsWith('image/')) return; - + if (!file) return; + // Drop the previous image so the preview and PDF never show a stale one. + state.image = null; if (state.imageUrl) { URL.revokeObjectURL(state.imageUrl); state.imageUrl = null; } + if (!file.type.startsWith('image/')) { + state.imageError = `${file.name} is not an image. Pick a PNG, JPEG or WebP file.`; + renderPreview(); + return; + } + state.imageError = ''; const url = URL.createObjectURL(file); state.imageUrl = url; @@ -444,13 +454,15 @@ function loadImage(file) { renderPreview(); }; img.onerror = () => { - if (state.imageUrl === url) { - URL.revokeObjectURL(url); - state.imageUrl = null; - } + if (state.imageUrl !== url) return; + URL.revokeObjectURL(url); + state.imageUrl = null; + + state.imageError = `Could not load ${file.name}. The file may be corrupted or in an unsupported format. Try another image.`; + renderPreview(); }; img.src = url; - + renderPreview(); } function setSource(source) { From f46910e739f6d84f66be984b4dc2d5c3ad4e0e88 Mon Sep 17 00:00:00 2001 From: ShryukGrandhi <85529990+ShryukGrandhi@users.noreply.github.com> Date: Thu, 29 Oct 2026 19:51:44 -0700 Subject: [PATCH 2/2] fix(web): allow re-picking the same image after a failed one Signed-off-by: ShryukGrandhi <85529990+ShryukGrandhi@users.noreply.github.com> --- web/js/screens.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/js/screens.js b/web/js/screens.js index 58b54a7..e555f78 100644 --- a/web/js/screens.js +++ b/web/js/screens.js @@ -497,7 +497,11 @@ function bind() { renderPreview(); }); document.querySelectorAll('input[name="scr-source"]').forEach((r) => r.addEventListener('change', () => setSource(r.value))); - $('scr-image').addEventListener('change', (e) => loadImage(e.target.files[0])); + $('scr-image').addEventListener('change', (e) => { + loadImage(e.target.files[0]); + // Let the same file be picked again, e.g. after a failed one. + e.target.value = ''; + }); const drop = $('scr-drop'); drop.addEventListener('dragover', (e) => { e.preventDefault(); drop.classList.add('is-over'); }); drop.addEventListener('dragleave', () => drop.classList.remove('is-over'));