From 50e15e78f35e8a7c24aee3de3088d4ba158cee78 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 14 Jun 2025 16:46:14 +0200 Subject: [PATCH 1/5] feat: Added asynchronous algorithm apply --- .../src/components/images/ImageConverter.tsx | 136 +++++++++++++++--- 1 file changed, 119 insertions(+), 17 deletions(-) diff --git a/client/src/components/images/ImageConverter.tsx b/client/src/components/images/ImageConverter.tsx index efc3fcd..9f6806e 100644 --- a/client/src/components/images/ImageConverter.tsx +++ b/client/src/components/images/ImageConverter.tsx @@ -1,4 +1,4 @@ -import { useState } from 'preact/hooks'; +import { useState, useRef, useEffect } from 'preact/hooks'; import { useWasm } from '@/hooks/useWasm'; import { to_grayscale, @@ -13,6 +13,7 @@ import AlgorithmsContainer from '@/components/algorithms/AlgorithmsContainer'; import { ConversionAlgorithm, ConversionAlgorithmType, + getAlgorithmName, } from '@/models/algorithms'; import { TargetedEvent } from 'preact/compat'; @@ -68,6 +69,29 @@ const ImageConverter = () => { const [previewsAspectRatios, setPreviewsAspectRatios] = useState(16 / 10); const [errorMessage, setErrorMessage] = useState(); + const [isProcessing, setIsProcessing] = useState(false); + const [processingProgress, setProcessingProgress] = useState(0); + const [currentAlgorithm, setCurrentAlgorithm] = useState(''); + + const prevSrcUrlRef = useRef(null); + const prevResultUrlRef = useRef(null); + + const cleanupBlobUrls = () => { + if (prevSrcUrlRef.current) { + URL.revokeObjectURL(prevSrcUrlRef.current); + prevSrcUrlRef.current = null; + } + if (prevResultUrlRef.current) { + URL.revokeObjectURL(prevResultUrlRef.current); + prevResultUrlRef.current = null; + } + }; + + useEffect(() => { + return () => { + cleanupBlobUrls(); + }; + }, []); const handleUpload = async (e: TargetedEvent) => { const file = e.currentTarget.files?.[0]; @@ -81,8 +105,21 @@ const ImageConverter = () => { try { const processedBytes = processBytes(file.type, bytes); setRawBytes(processedBytes); + + if (prevSrcUrlRef.current) { + URL.revokeObjectURL(prevSrcUrlRef.current); + } + const blob = new Blob([processedBytes]); - setImgSrc(URL.createObjectURL(blob)); + const newUrl = URL.createObjectURL(blob); + prevSrcUrlRef.current = newUrl; + setImgSrc(newUrl); + + if (prevResultUrlRef.current) { + URL.revokeObjectURL(prevResultUrlRef.current); + prevResultUrlRef.current = null; + setImgResult(null); + } } catch (err) { setErrorMessage(`Upload error: ${err}`); setImgSrc(null); @@ -90,28 +127,68 @@ const ImageConverter = () => { } }; - const handleRun = () => { + const handleRun = async () => { const enabledAlgorithms = algorithms.filter((a) => a.enabled); if (!rawBytes || !wasmReady) return; if (enabledAlgorithms.length === 0) { setErrorMessage('No algorithms selected'); return; } + setErrorMessage(undefined); + setIsProcessing(true); + setProcessingProgress(0); + setCurrentAlgorithm(''); - let processedBytes: Uint8Array | undefined = - Uint8Array.from(rawBytes); - for (const algorithm of enabledAlgorithms) { - processedBytes = convert(processedBytes, algorithm, setErrorMessage); - if (!processedBytes) { - console.error(`Conversion failed for algorithm: ${algorithm.type}`); - return; + try { + let processedBytes: Uint8Array | undefined = + Uint8Array.from(rawBytes); + + for (let i = 0; i < enabledAlgorithms.length; i++) { + const algorithm = enabledAlgorithms[i]; + + setCurrentAlgorithm(getAlgorithmName(algorithm.type)); + setProcessingProgress(Math.round((i / enabledAlgorithms.length) * 100)); + + processedBytes = convert(processedBytes, algorithm, setErrorMessage); + if (!processedBytes) { + console.error(`Conversion failed for algorithm: ${algorithm.type}`); + return; + } + + if (prevResultUrlRef.current) { + URL.revokeObjectURL(prevResultUrlRef.current); + } + + const intermediateBlob = new Blob([processedBytes], { type: 'image/png' }); + const newUrl = URL.createObjectURL(intermediateBlob); + prevResultUrlRef.current = newUrl; + setImgResult(newUrl); + + await new Promise(resolve => setTimeout(resolve, 10)); } - } - const blob = new Blob([processedBytes], { type: 'image/png' }); - setImgResult(URL.createObjectURL(blob)); - setErrorMessage(undefined); + setProcessingProgress(100); + setCurrentAlgorithm('Complete'); + + if (prevResultUrlRef.current) { + URL.revokeObjectURL(prevResultUrlRef.current); + } + + const finalBlob = new Blob([processedBytes], { type: 'image/png' }); + const finalUrl = URL.createObjectURL(finalBlob); + prevResultUrlRef.current = finalUrl; + setImgResult(finalUrl); + setErrorMessage(undefined); + + await new Promise(resolve => setTimeout(resolve, 500)); + } catch (error) { + setErrorMessage(`Processing error: ${error}`); + } finally { + setIsProcessing(false); + setProcessingProgress(0); + setCurrentAlgorithm(''); + } }; return ( @@ -131,7 +208,7 @@ const ImageConverter = () => { error={errorMessage} /> -
+
{ : 'No image selected' } /> + + {/* Progress Indicator */} + {isProcessing && ( +
+
+
+ {currentAlgorithm || 'Processing...'} +
+
+
+
+
+ {processingProgress}% +
+
+
+ )}
@@ -162,10 +259,15 @@ const ImageConverter = () => { />
From cc5c7ddc649e2eabc092d9b261e4eeec20f2eef3 Mon Sep 17 00:00:00 2001 From: Adam Mytnik <55660641+AdamMytnik@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:52:42 +0200 Subject: [PATCH 2/5] Update client/src/components/images/ImageConverter.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- client/src/components/images/ImageConverter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/images/ImageConverter.tsx b/client/src/components/images/ImageConverter.tsx index 9f6806e..520ff73 100644 --- a/client/src/components/images/ImageConverter.tsx +++ b/client/src/components/images/ImageConverter.tsx @@ -165,7 +165,7 @@ const ImageConverter = () => { prevResultUrlRef.current = newUrl; setImgResult(newUrl); - await new Promise(resolve => setTimeout(resolve, 10)); + await new Promise(resolve => setTimeout(resolve, YIELD_DELAY_MS)); } setProcessingProgress(100); From 9ff9fd0ef11076d15f3df2d521988e14e68a6ba6 Mon Sep 17 00:00:00 2001 From: Adam Mytnik <55660641+AdamMytnik@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:52:49 +0200 Subject: [PATCH 3/5] Update client/src/components/images/ImageConverter.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- client/src/components/images/ImageConverter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/images/ImageConverter.tsx b/client/src/components/images/ImageConverter.tsx index 520ff73..a7056b3 100644 --- a/client/src/components/images/ImageConverter.tsx +++ b/client/src/components/images/ImageConverter.tsx @@ -181,7 +181,7 @@ const ImageConverter = () => { setImgResult(finalUrl); setErrorMessage(undefined); - await new Promise(resolve => setTimeout(resolve, 500)); + await new Promise(resolve => setTimeout(resolve, FINAL_DISPLAY_DELAY_MS)); } catch (error) { setErrorMessage(`Processing error: ${error}`); } finally { From a36da07d459e803e4befe130b90699437e6549c1 Mon Sep 17 00:00:00 2001 From: Adam Mytnik <55660641+AdamMytnik@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:53:30 +0200 Subject: [PATCH 4/5] Update client/src/components/images/ImageConverter.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- client/src/components/images/ImageConverter.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/components/images/ImageConverter.tsx b/client/src/components/images/ImageConverter.tsx index a7056b3..d2ab3d0 100644 --- a/client/src/components/images/ImageConverter.tsx +++ b/client/src/components/images/ImageConverter.tsx @@ -183,7 +183,11 @@ const ImageConverter = () => { await new Promise(resolve => setTimeout(resolve, FINAL_DISPLAY_DELAY_MS)); } catch (error) { - setErrorMessage(`Processing error: ${error}`); + const errorMessage = + error instanceof Error + ? error.message + : JSON.stringify(error); + setErrorMessage(`Processing error: ${errorMessage}`); } finally { setIsProcessing(false); setProcessingProgress(0); From 42f82d734d9959386dfa855cdc9f589e6ff70a6f Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 14 Jun 2025 22:58:23 +0200 Subject: [PATCH 5/5] fix after copilot --- client/src/components/images/ImageConverter.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/components/images/ImageConverter.tsx b/client/src/components/images/ImageConverter.tsx index d2ab3d0..6b46d85 100644 --- a/client/src/components/images/ImageConverter.tsx +++ b/client/src/components/images/ImageConverter.tsx @@ -75,6 +75,8 @@ const ImageConverter = () => { const prevSrcUrlRef = useRef(null); const prevResultUrlRef = useRef(null); + const YIELD_DELAY_MS = 10; + const FINAL_DISPLAY_DELAY_MS = 500; const cleanupBlobUrls = () => { if (prevSrcUrlRef.current) {