From 94d927f56887bb77c80b56b5ff8cbf5412e1a612 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Fri, 24 Jul 2026 14:50:25 +0000
Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=B9=20Refactor=20docs/index.html=20to?=
=?UTF-8?q?=20improve=20readability=20and=20maintainability?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This commit resolves a code health issue where the image processing and ZIP generation logic inside the 'convertBtn' click event listener was deeply nested and complex.
The changes involve extracting two helper functions:
1. `processImage`: Handles reading, processing, and outputting an individual image file.
2. `generateAndDownloadZip`: Handles the creation and browser download logic for batch ZIP files.
These extractions significantly simplify the main event listener loop without altering functionality. Additionally, a new playwright end-to-end test (`test.js`) is added and the `.gitignore` has been updated to ignore node modules.
Co-authored-by: paul0728 <44644609+paul0728@users.noreply.github.com>
---
.gitignore | 1 +
docs/index.html | 143 +++++++++++++++++++++++++---------------------
package-lock.json | 56 ++++++++++++++++++
package.json | 5 ++
test.js | 47 +++++++++++++++
5 files changed, 188 insertions(+), 64 deletions(-)
create mode 100644 package-lock.json
create mode 100644 package.json
create mode 100644 test.js
diff --git a/.gitignore b/.gitignore
index 7a60b85..9057ce2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
__pycache__/
*.pyc
+node_modules
diff --git a/docs/index.html b/docs/index.html
index 9d406c0..812b754 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -270,6 +270,79 @@
Online Converter
}
}
+ async function processImage(file, targetExt, isBatch, zip, downloadsContainer) {
+ try {
+ const arrayBuffer = await file.arrayBuffer();
+ const sourceBytes = new Uint8Array(arrayBuffer);
+
+ // Await a Promise that resolves when the setTimeout for this image finishes
+ return await new Promise((resolve) => {
+ setTimeout(() => {
+ try {
+ ImageMagick.read(sourceBytes, (image) => {
+ if (targetExt === 'jpg' || targetExt === 'jpeg') {
+ image.hasAlpha = false;
+ }
+
+ // Title Case for enum
+ const titleCaseFormat = targetExt.charAt(0).toUpperCase() + targetExt.slice(1).toLowerCase();
+ const formatEnum = MagickFormat[titleCaseFormat] || targetExt.toUpperCase();
+
+ image.write(formatEnum, (data) => {
+ let originalName = file.name.split('.').slice(0, -1).join('.');
+ if (!originalName) originalName = "image"; // fallback if no extension
+ const outFileName = `${originalName}.${targetExt}`;
+
+ if (isBatch) {
+ // Add to zip
+ // CRITICAL: Must clone the Uint8Array because 'data' is a view into
+ // WebAssembly memory which will be overwritten by the next image processing.
+ zip.file(outFileName, new Uint8Array(data));
+ } else {
+ // Single file, provide direct link
+ const blob = new Blob([data], { type: 'application/octet-stream' });
+ const url = URL.createObjectURL(blob);
+ const dlLink = document.createElement('a');
+ dlLink.href = url;
+ dlLink.download = outFileName;
+ dlLink.textContent = `⬇️ Download ${outFileName}`;
+ dlLink.style.fontWeight = 'bold';
+ dlLink.style.color = '#10b981';
+ downloadsContainer.appendChild(dlLink);
+ }
+ resolve(true);
+ });
+ });
+ } catch(err) {
+ console.error(`Error processing ${file.name}:`, err);
+ resolve(false);
+ }
+ }, 50);
+ });
+ } catch (err) {
+ console.error(`Error reading ${file.name}:`, err);
+ return false;
+ }
+ }
+
+ async function generateAndDownloadZip(zip, successCount, downloadsContainer) {
+ try {
+ const content = await zip.generateAsync({ type: "blob" });
+ const url = URL.createObjectURL(content);
+ const dlLink = document.createElement('a');
+ dlLink.href = url;
+ dlLink.download = `converted_images.zip`;
+ dlLink.textContent = `📦 Download All as ZIP (${successCount} files)`;
+ dlLink.style.fontWeight = 'bold';
+ dlLink.style.color = '#10b981';
+ downloadsContainer.appendChild(dlLink);
+ return true;
+ } catch (err) {
+ console.error("ZIP Generation Failed:", err);
+ return false;
+ }
+ }
+
// 3. Conversion Logic
convertBtn.addEventListener('click', async () => {
if (selectedFiles.length === 0) {
@@ -292,76 +365,18 @@ Online Converter
const file = files[i];
statusDiv.textContent = `Processing file ${i + 1} of ${files.length}: ${file.name}...`;
- try {
- const arrayBuffer = await file.arrayBuffer();
- const sourceBytes = new Uint8Array(arrayBuffer);
-
- // Await a Promise that resolves when the setTimeout for this image finishes
- await new Promise((resolve) => {
- setTimeout(() => {
- try {
- ImageMagick.read(sourceBytes, (image) => {
- if (targetExt === 'jpg' || targetExt === 'jpeg') {
- image.hasAlpha = false;
- }
-
- // Title Case for enum
- const titleCaseFormat = targetExt.charAt(0).toUpperCase() + targetExt.slice(1).toLowerCase();
- const formatEnum = MagickFormat[titleCaseFormat] || targetExt.toUpperCase();
-
- image.write(formatEnum, (data) => {
- let originalName = file.name.split('.').slice(0, -1).join('.');
- if (!originalName) originalName = "image"; // fallback if no extension
- const outFileName = `${originalName}.${targetExt}`;
-
- if (isBatch) {
- // Add to zip
- // CRITICAL: Must clone the Uint8Array because 'data' is a view into
- // WebAssembly memory which will be overwritten by the next image processing.
- zip.file(outFileName, new Uint8Array(data));
- } else {
- // Single file, provide direct link
- const blob = new Blob([data], { type: 'application/octet-stream' });
- const url = URL.createObjectURL(blob);
- const dlLink = document.createElement('a');
- dlLink.href = url;
- dlLink.download = outFileName;
- dlLink.textContent = `⬇️ Download ${outFileName}`;
- dlLink.style.fontWeight = 'bold';
- dlLink.style.color = '#10b981';
- downloadsContainer.appendChild(dlLink);
- }
- successCount++;
- });
- });
- } catch(err) {
- console.error(`Error processing ${file.name}:`, err);
- failCount++;
- }
- resolve();
- }, 50);
- });
- } catch (err) {
- console.error(`Error reading ${file.name}:`, err);
+ const success = await processImage(file, targetExt, isBatch, zip, downloadsContainer);
+ if (success) {
+ successCount++;
+ } else {
failCount++;
}
}
if (isBatch && successCount > 0) {
statusDiv.textContent = `Compressing ${successCount} files into ZIP...`;
-
- try {
- const content = await zip.generateAsync({ type: "blob" });
- const url = URL.createObjectURL(content);
- const dlLink = document.createElement('a');
- dlLink.href = url;
- dlLink.download = `converted_images.zip`;
- dlLink.textContent = `📦 Download All as ZIP (${successCount} files)`;
- dlLink.style.fontWeight = 'bold';
- dlLink.style.color = '#10b981';
- downloadsContainer.appendChild(dlLink);
- } catch (err) {
- console.error("ZIP Generation Failed:", err);
+ const zipSuccess = await generateAndDownloadZip(zip, successCount, downloadsContainer);
+ if (!zipSuccess) {
failCount += successCount; // Consider it a failure if we can't zip
}
}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..28fa456
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,56 @@
+{
+ "name": "app",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "playwright": "^1.61.1"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/playwright": {
+ "version": "1.61.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz",
+ "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.61.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.61.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz",
+ "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==",
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..2fe1cd5
--- /dev/null
+++ b/package.json
@@ -0,0 +1,5 @@
+{
+ "dependencies": {
+ "playwright": "^1.61.1"
+ }
+}
diff --git a/test.js b/test.js
new file mode 100644
index 0000000..c3287bc
--- /dev/null
+++ b/test.js
@@ -0,0 +1,47 @@
+const { chromium } = require('playwright');
+const path = require('path');
+const fs = require('fs');
+
+(async () => {
+ const browser = await chromium.launch({ headless: true });
+ const page = await browser.newPage();
+
+ try {
+ // Navigate to the local server
+ await page.goto('http://localhost:8000/docs/index.html');
+
+ // Wait for the WASM engine to initialize
+ await page.waitForFunction(() => document.getElementById('status').textContent.includes('Engine Ready'), { timeout: 10000 });
+
+ // Select a file
+ const filePath = path.resolve(__dirname, 'docs/dummy.png');
+ const fileChooserPromise = page.waitForEvent('filechooser');
+
+ // Click the file input label to trigger the file chooser
+ await page.click('.custom-file-btn');
+ const fileChooser = await fileChooserPromise;
+ await fileChooser.setFiles(filePath);
+
+ // Wait for preview to render
+ await page.waitForSelector('.thumbnail-wrapper');
+
+ // Click Convert
+ await page.click('#convertBtn');
+
+ // Wait for download link to appear
+ await page.waitForSelector('#downloads-container a');
+
+ // Get status text
+ const statusText = await page.textContent('#status');
+ if (!statusText.includes('Job complete! Successfully processed 1 files.')) {
+ throw new Error(`Unexpected status: ${statusText}`);
+ }
+
+ console.log("Test passed successfully.");
+ } catch (error) {
+ console.error("Test failed:", error);
+ process.exit(1);
+ } finally {
+ await browser.close();
+ }
+})();
\ No newline at end of file