import React, { useState, useEffect, useRef } from 'react'; import { __ } from '@wordpress/i18n'; import { useNavigate } from 'react-router-dom'; import { Image } from 'lucide-react'; import SettingsButton from '../components/ui/settings-button'; import ScanActionButton from '../components/ui/scan-action-button'; import { Checkbox } from '../components/ui/checkbox'; import { convertNow, convertBackground, getConvertProgress, cancelConvert, getSettings, saveSettings, } from '../api'; import { TotalImagesIcon2, ImageProcessedIcon, ConvertedIcon, RemainingIcon, SpaceSavedIcon2, } from '../components/icons'; import Header from '../components/layout/Header'; import PluginPage from '../components/layout/PluginPage'; import Card from '../components/ui/card'; import { Tooltip } from '../components/ui/tooltip'; interface FileFormat { label: string; value: string; checked: boolean; } const DEFAULT_FORMATS: FileFormat[] = [ { label: 'JPEG', value: 'jpeg', checked: false }, { label: 'PNG', value: 'png', checked: false }, { label: 'JPG', value: 'jpg', checked: true }, { label: 'GIF', value: 'gif', checked: false }, { label: 'BMP', value: 'bmp', checked: false }, { label: 'AVIF', value: 'avif', checked: false }, ]; export default function ConvertToWebP({ tooltip }: { tooltip?: string } = {}) { const navigate = useNavigate(); const [formats, setFormats] = useState(DEFAULT_FORMATS); const [chunkSize, setChunkSize] = useState('20'); const [loading, setLoading] = useState(true); const [converting, setConverting] = useState(false); const [hasStarted, setHasStarted] = useState(false); const [noImages, setNoImages] = useState(false); const [progress, setProgress] = useState(0); const [converted, setConverted] = useState(0); const [processed, setProcessed] = useState(0); const [remaining, setRemaining] = useState(0); const [total, setTotal] = useState(0); const [spaceSaved, setSpaceSaved] = useState(0); const [notFound, setNotFound] = useState(0); const pollRef = useRef | null>(null); const generationRef = useRef(0); const circumference = 2 * Math.PI * 80; const strokeDashoffset = circumference - (progress / 100) * circumference; const detectImageUrl = (window.THUMBPRESS?.assets_url || '') + 'admin/img/no-search-result.png'; useEffect(() => { // Always check live progress first — saved state may be stale if user reloaded before API responded. getConvertProgress().then((progressRes: any) => { if (progressRes?.data && !progressRes.data.is_complete && progressRes.data.total > 0) { setHasStarted(true); setConverting(true); setProgress(progressRes.data.progress); setConverted(progressRes.data.converted); setProcessed(progressRes.data.processed || 0); setRemaining(progressRes.data.remaining || 0); setTotal(progressRes.data.total); setSpaceSaved(progressRes.data.space_saved || 0); setNotFound(progressRes.data.not_found || 0); pollRef.current = setInterval(async () => { const pr = await getConvertProgress(); if (pr?.data) { setProgress(pr.data.progress); setConverted(pr.data.converted); setProcessed(pr.data.processed || 0); setRemaining(pr.data.remaining || 0); setTotal(pr.data.total); setSpaceSaved(pr.data.space_saved || 0); setNotFound(pr.data.not_found || 0); if (pr.data.is_complete) { if (pollRef.current) clearInterval(pollRef.current); setConverting(false); } } }, 3000); setLoading(false); } else { getSettings('thumbpress_webp_view_state').then((res: any) => { if (res?.value === 'progress') { setHasStarted(true); if (progressRes?.data) { setProgress(progressRes.data.progress); setConverted(progressRes.data.converted); setProcessed(progressRes.data.processed || 0); setRemaining(progressRes.data.remaining || 0); setTotal(progressRes.data.total); setSpaceSaved(progressRes.data.space_saved || 0); setNotFound(progressRes.data.not_found || 0); } } }).catch(() => { }).finally(() => setLoading(false)); } }).catch(() => { setLoading(false); }); return () => { if (pollRef.current) clearInterval(pollRef.current); }; }, []); const handleScanAgain = () => { generationRef.current++; setHasStarted(false); setConverting(false); setNoImages(false); setProgress(0); setConverted(0); setProcessed(0); setRemaining(0); setTotal(0); setSpaceSaved(0); setNotFound(0); if (pollRef.current) clearInterval(pollRef.current); cancelConvert(); saveSettings('thumbpress_webp_view_state', 'initial'); }; const toggleFormat = (index: number) => { setFormats((prev) => prev.map((f, i) => (i === index ? { ...f, checked: !f.checked } : f)) ); }; const handleConvertNow = () => { const myGen = ++generationRef.current; const limit = parseInt(chunkSize, 10) || 10; const fileFormats = formats.filter((f) => f.checked).map((f) => f.value); if (fileFormats.length === 0) return; setConverting(true); setHasStarted(true); setNoImages(false); setProgress(0); setConverted(0); setProcessed(0); setRemaining(0); setSpaceSaved(0); let lastId = 0; let currentProcessed = 0; let currentConverted = 0; let currentSpaceSaved = 0; let currentNotFound = 0; let firstChunk = true; const processChunk = async () => { if (generationRef.current !== myGen) return; try { const res = await convertNow(lastId, limit, fileFormats, currentSpaceSaved, currentNotFound, currentProcessed, currentConverted); if (generationRef.current !== myGen) return; if (!res?.success) { setConverting(false); if (firstChunk) { setNoImages(true); setHasStarted(true); } return; } if (res?.data && typeof res.data === 'object') { const data = res.data as any; if (firstChunk) { firstChunk = false; if ((data.total || 0) === 0) { setConverting(false); setNoImages(true); setHasStarted(true); return; } setHasStarted(true); } setProgress(Math.round(data.progress || 0)); setConverted(data.converted || 0); setProcessed(data.processed || 0); setRemaining(data.remaining || 0); setTotal(data.total || 0); setSpaceSaved(data.space_saved || 0); setNotFound(Number(data.not_found) || 0); lastId = data.last_id || 0; currentProcessed = data.processed || 0; currentConverted = data.converted || 0; currentSpaceSaved = data.space_saved || 0; currentNotFound = Number(data.not_found) || 0; if (data.is_complete || data.progress === undefined || data.progress >= 100) { setConverting(false); } else { setTimeout(processChunk, 500); } } else { setConverting(false); } } catch { setConverting(false); } }; processChunk(); }; const handleConvertBackground = async () => { const limit = parseInt(chunkSize, 10) || 10; const fileFormats = formats.filter((f) => f.checked).map((f) => f.value); if (fileFormats.length === 0) return; setConverting(true); setHasStarted(true); setNoImages(false); setProgress(0); setConverted(0); setProcessed(0); setRemaining(0); setSpaceSaved(0); try { const res = await convertBackground(limit, fileFormats); if (!res?.success || (res?.data && (res.data as any).total === 0)) { setConverting(false); setNoImages(true); setHasStarted(true); return; } if (res?.data) { setHasStarted(true); saveSettings('thumbpress_webp_view_state', 'progress'); pollRef.current = setInterval(async () => { const progressRes = await getConvertProgress(); if (progressRes?.data) { setProgress(progressRes.data.progress); setConverted(progressRes.data.converted); setProcessed(progressRes.data.processed || 0); setRemaining(progressRes.data.remaining || 0); setTotal(progressRes.data.total); setSpaceSaved(progressRes.data.space_saved || 0); setNotFound(progressRes.data.not_found || 0); if (progressRes.data.is_complete) { if (pollRef.current) clearInterval(pollRef.current); setConverting(false); } } }, 3000); } } catch { setConverting(false); } }; const StatCard = ({ icon, value, label }: { icon: React.ReactNode, value: number | string; label: string }) => (
{icon}

{value}

{label}

); return ( <>
= 100 ? (
navigate('/settings?tab=convert-to-webp')} />
) : ( navigate('/settings?tab=convert-to-webp')} /> )} > {loading ? (
{[1, 2, 3, 4, 5, 6].map(i =>
)}
) : !hasStarted || noImages ? (

{noImages ? __('No Images Found', 'image-sizes') : __('Ready to Convert to WebP?', 'image-sizes')}

{noImages ? __('There are no images to convert to WebP.', 'image-sizes') : __('Select file formats above and click "Convert Now" to start converting images to WebP for faster loading.', 'image-sizes')}

{formats.map((format, index) => ( ))}
setChunkSize(e.target.value)} placeholder="e.g. 50" className="flex w-full !rounded-lg !border !border-thumbpress-border bg-white !px-4 !h-[56px] text-sm placeholder:text-gray-400 focus:!outline-none focus:!shadow-none" />
) : (

{progress >= 100 ? __('WebP Conversion Complete', 'image-sizes') : __('WebP Conversion in Progress', 'image-sizes')}

{progress >= 100 ? __('All images have been converted to WebP.', 'image-sizes') : __('We\'re scanning your media library and converting eligible images to WebP.', 'image-sizes')} {progress < 100 && ( {__('Cancel', 'image-sizes')} )}

{progress}%
} value={total} label={__('Total Images', 'image-sizes')} /> } value={processed} label={__('Images Processed', 'image-sizes')} /> } value={notFound || 0} label={__('Images Not Found', 'image-sizes')} /> } value={converted} label={__('Images Converted', 'image-sizes')} /> } value={remaining} label={__('Images Remaining', 'image-sizes')} /> } value={spaceSaved > 1048576 ? `${(spaceSaved / 1048576).toFixed(1)} MB` : `${(spaceSaved / 1024).toFixed(1)} KB`} label={__('Space Saved', 'image-sizes')} />
)} ); }