pdftools / static /js /image-scraper.js
Shivakafle038's picture
PDF Tools Web App - compress, convert, watermark removal
32a841c
/**
* Image Scraper Module
*/
let currentJobId = null;
let currentImages = [];
let imageStepIndicator = null;
function initImageScraper() {
const elements = {
grid: document.getElementById('imgGrid'),
counter: document.getElementById('img_counter'),
toolbar: document.getElementById('imageToolbar'),
skeleton: document.getElementById('imageSkeleton'),
btnScrape: document.getElementById('btnScrape'),
btnSelectAll: document.getElementById('btnSelectAll'),
btnSelectNone: document.getElementById('btnSelectNone'),
btnZip: document.getElementById('btnZip'),
btnPdf: document.getElementById('btnPdf'),
zipName: document.getElementById('zip_name'),
pdfName: document.getElementById('pdf_name'),
pageUrl: document.getElementById('page_url'),
progress: document.getElementById('imgProgress')
};
// Initialize step indicator
imageStepIndicator = new StepIndicator('imageStepIndicator', [
'Enter URL',
'Fetch Images',
'Select',
'Download'
]);
// Show empty state initially
showImageGridEmpty(elements.grid);
// Event listeners
elements.btnScrape.addEventListener('click', () => handleScrapeImages(elements));
elements.btnSelectAll.addEventListener('click', () => selectAllImages(elements));
elements.btnSelectNone.addEventListener('click', () => clearImageSelection(elements));
elements.btnZip.addEventListener('click', () => {
let name = elements.zipName.value.trim() || 'images.zip';
if (!name.toLowerCase().endsWith('.zip')) name += '.zip';
handleDownload('/api/download-zip', 'zip_name', name, elements);
});
elements.btnPdf.addEventListener('click', () => {
let name = elements.pdfName.value.trim() || 'images.pdf';
if (!name.toLowerCase().endsWith('.pdf')) name += '.pdf';
handleDownload('/api/download-pdf', 'pdf_name', name, elements);
});
// URL input - update step
elements.pageUrl.addEventListener('input', () => {
if (elements.pageUrl.value.trim()) {
imageStepIndicator.setStep(1);
}
clearFieldError(elements.pageUrl.closest('.form-group'));
});
// URL validation
elements.pageUrl.addEventListener('blur', () => {
const value = elements.pageUrl.value.trim();
if (value) validateUrl(elements.pageUrl);
});
// Enter to scrape
elements.pageUrl.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
elements.btnScrape.click();
}
});
// Keyboard shortcuts
registerShortcut('ctrl+a', () => {
if (document.getElementById('page-images').classList.contains('active') && currentImages.length > 0) {
selectAllImages(elements);
}
});
registerShortcut('ctrl+d', () => {
if (document.getElementById('page-images').classList.contains('active') && currentImages.length > 0) {
clearImageSelection(elements);
}
});
}
function getSelectedIds(grid) {
const ids = [];
grid.querySelectorAll('input[type="checkbox"]').forEach(cb => {
if (cb.checked) ids.push(cb.dataset.id);
});
return ids;
}
function updateCounter(elements) {
const total = currentImages.length;
const selected = getSelectedIds(elements.grid).length;
elements.counter.textContent = `${total} images • ${selected} selected`;
// Update step indicator based on selection
if (selected > 0 && imageStepIndicator) {
imageStepIndicator.setStep(3); // Ready to download
}
}
function setImageButtonsEnabled(elements, enabled) {
elements.btnSelectAll.disabled = !enabled;
elements.btnSelectNone.disabled = !enabled;
elements.btnZip.disabled = !enabled;
elements.btnPdf.disabled = !enabled;
}
function renderImages(images, elements) {
elements.grid.innerHTML = '';
images.forEach(img => {
const card = createImageCard(img, elements);
elements.grid.appendChild(card);
});
updateCounter(elements);
}
function createImageCard(img, elements) {
const card = document.createElement('div');
card.className = 'image-card';
// Preview button
const previewBtn = document.createElement('button');
previewBtn.className = 'image-preview-btn';
previewBtn.innerHTML = `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7"/></svg>`;
previewBtn.addEventListener('click', (e) => {
e.stopPropagation();
const dims = (img.width && img.height) ? `${img.width}×${img.height}` : null;
const size = img.bytes ? formatFileSize(img.bytes) : null;
showImageModal(img.url, img.filename, dims, size);
});
// Thumbnail
const thumb = document.createElement('img');
thumb.className = 'image-thumb';
thumb.src = img.url;
thumb.loading = 'lazy';
thumb.referrerPolicy = 'no-referrer';
thumb.alt = img.filename;
// Meta
const meta = document.createElement('div');
meta.className = 'image-meta';
const filename = document.createElement('div');
filename.className = 'image-filename';
filename.title = img.filename;
filename.textContent = img.filename;
const info = document.createElement('div');
info.className = 'image-info';
const sizeBadge = document.createElement('span');
sizeBadge.className = 'image-badge';
sizeBadge.textContent = (img.width && img.height) ? `${img.width}×${img.height}` : '?';
const checkboxContainer = document.createElement('div');
checkboxContainer.className = 'image-checkbox';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.dataset.id = img.id;
checkbox.addEventListener('click', e => e.stopPropagation());
checkbox.addEventListener('change', () => {
card.classList.toggle('selected', checkbox.checked);
updateCounter(elements);
});
checkboxContainer.appendChild(checkbox);
checkboxContainer.appendChild(document.createTextNode('Select'));
info.appendChild(sizeBadge);
info.appendChild(checkboxContainer);
meta.appendChild(filename);
meta.appendChild(info);
card.appendChild(previewBtn);
card.appendChild(thumb);
card.appendChild(meta);
card.addEventListener('click', () => {
checkbox.checked = !checkbox.checked;
card.classList.toggle('selected', checkbox.checked);
updateCounter(elements);
});
return card;
}
async function handleScrapeImages(elements) {
const pageUrl = elements.pageUrl.value.trim();
if (!pageUrl) {
setFieldError(elements.pageUrl.closest('.form-group'), 'URL required');
showToast('URL Required', 'Enter a webpage URL', 'error');
return;
}
if (!validateUrl(elements.pageUrl)) {
showToast('Invalid URL', 'Enter a valid URL', 'error');
return;
}
setButtonLoading(elements.btnScrape, true, 'Scraping...');
setImageButtonsEnabled(elements, false);
elements.toolbar.style.display = 'none';
elements.skeleton.style.display = 'grid';
elements.grid.innerHTML = '';
currentJobId = null;
currentImages = [];
showProgress(elements.progress, true, true);
imageStepIndicator.setStep(1); // Fetching
try {
const fd = new FormData();
fd.append('page_url', pageUrl);
const res = await fetch('/api/scrape-images', { method: 'POST', body: fd });
if (!res.ok) {
let err = { detail: 'Request failed' };
try { err = await res.json(); } catch {}
throw new Error(err.detail || 'Request failed');
}
const data = await res.json();
currentJobId = data.job_id;
currentImages = data.images || [];
elements.skeleton.style.display = 'none';
if (currentImages.length > 0) {
renderImages(currentImages, elements);
elements.toolbar.style.display = 'flex';
setImageButtonsEnabled(elements, true);
showToast('Scrape Complete', `Found ${currentImages.length} images`, 'success');
imageStepIndicator.setStep(2); // Select step
} else {
showImageGridEmpty(elements.grid);
showToast('No Images', 'No suitable images found on this page', 'info');
imageStepIndicator.setStep(0);
}
} catch (e) {
elements.skeleton.style.display = 'none';
// Show error state with retry action
showErrorState(elements.grid,
'Scraping Failed',
e.message || 'Could not fetch images from this URL. The page might be blocking requests or require authentication.',
[
{ id: 'retry', label: 'Try Again', primary: true, handler: () => handleScrapeImages(elements) },
{ id: 'clear', label: 'Clear', handler: () => {
elements.pageUrl.value = '';
showImageGridEmpty(elements.grid);
imageStepIndicator.reset();
}}
]
);
imageStepIndicator.setStep(0);
} finally {
setButtonLoading(elements.btnScrape, false);
showProgress(elements.progress, false);
}
}
function selectAllImages(elements) {
elements.grid.querySelectorAll('input[type="checkbox"]').forEach(cb => {
cb.checked = true;
cb.closest('.image-card').classList.add('selected');
});
updateCounter(elements);
showToast('Selected All', `${currentImages.length} images`, 'success');
imageStepIndicator.setStep(3); // Ready to download
}
function clearImageSelection(elements) {
elements.grid.querySelectorAll('input[type="checkbox"]').forEach(cb => {
cb.checked = false;
cb.closest('.image-card').classList.remove('selected');
});
updateCounter(elements);
showToast('Cleared', 'Selection cleared', 'info');
imageStepIndicator.setStep(2); // Back to select
}
async function handleDownload(endpoint, nameField, nameValue, elements) {
if (!currentJobId) {
showToast('No Images', 'Fetch images first', 'error');
return;
}
const ids = getSelectedIds(elements.grid);
if (ids.length === 0) {
showToast('No Selection', 'Select at least one image', 'error');
return;
}
const isZip = nameField === 'zip_name';
const btn = isZip ? elements.btnZip : elements.btnPdf;
setButtonLoading(btn, true, 'Preparing...');
showProgress(elements.progress, true, true);
try {
const fd = new FormData();
fd.append('job_id', currentJobId);
fd.append('image_ids', ids.join(','));
fd.append(nameField, nameValue);
const res = await fetch(endpoint, { method: 'POST', body: fd });
if (!res.ok) {
let err = { detail: 'Request failed' };
try { err = await res.json(); } catch {}
throw new Error(err.detail || 'Request failed');
}
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = nameValue;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
showToast('Downloaded', `${ids.length} images as ${nameValue}`, 'success');
imageStepIndicator.complete();
// Show success animation
showSuccessAnimation('Download Complete!', `${ids.length} images saved as ${nameValue}`);
} catch (e) {
showToast('Download Failed', e.message, 'error');
imageStepIndicator.setStep(2);
} finally {
setButtonLoading(btn, false);
showProgress(elements.progress, false);
updateCounter(elements);
}
}