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