/** * Popup Script - UI Logic * * Responsibilities: * - Handle button clicks (Scan, Download) * - Communicate with content script to scan products * - Communicate with background service worker to start downloads * - Update UI with progress information * - Display logs */ // ============================================================================ // DOM ELEMENTS // ============================================================================ const scanBtn = document.getElementById('scanBtn'); const downloadBtn = document.getElementById('downloadBtn'); const concurrencyInput = document.getElementById('concurrency'); const clearLogBtn = document.getElementById('clearLogBtn'); const debugBtn = document.getElementById('debugBtn'); const totalProductsEl = document.getElementById('totalProducts'); const currentProductEl = document.getElementById('currentProduct'); const imagesDownloadedEl = document.getElementById('imagesDownloaded'); const errorCountEl = document.getElementById('errorCount'); const progressBarEl = document.getElementById('progressBar'); const logBoxEl = document.getElementById('logBox'); const statusMsgEl = document.getElementById('statusMsg'); // ============================================================================ // STATE // ============================================================================ let products = []; let currentState = { products: [], currentProductIndex: -1, totalImagesDownloaded: 0, errorCount: 0, isRunning: false, logs: [], }; // ============================================================================ // EVENT LISTENERS // ============================================================================ scanBtn.addEventListener('click', handleScanClick); downloadBtn.addEventListener('click', handleDownloadClick); clearLogBtn.addEventListener('click', handleClearLogClick); debugBtn.addEventListener('click', handleDebugClick); // ============================================================================ // SCANNING LOGIC // ============================================================================ async function handleDebugClick() { try { debugBtn.disabled = true; updateLog('Running debug diagnostics...', 'info'); // Get current active tab const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); if (!tabs.length) { throw new Error('No active tab found'); } const tabId = tabs[0].id; // Send debug message let response; try { response = await chrome.tabs.sendMessage(tabId, { action: 'debug', }); } catch (e) { updateLog('Content script not loaded, injecting...', 'warning'); await chrome.scripting.executeScript({ target: { tabId: tabId }, files: ['content.js'], }); await new Promise(resolve => setTimeout(resolve, 500)); response = await chrome.tabs.sendMessage(tabId, { action: 'debug' }); } if (response.success) { updateLog('=== DEBUG INFO ===', 'info'); updateLog(`Total links on page: ${response.allLinks}`, 'info'); updateLog(`a[href*="/item/"] = ${response.linkItem}`, 'info'); updateLog(`a[href*="/product"] = ${response.linkProduct}`, 'info'); updateLog(`div[onclick*="/item/"] = ${response.divOnclick}`, 'info'); updateLog(`div.item = ${response.divItem}`, 'info'); updateLog(`All images on page: ${response.allImages}`, 'info'); // Show sample links if (response.sampleLinks && response.sampleLinks.length > 0) { updateLog('--- Sample links on page (first 20) ---', 'info'); response.sampleLinks.forEach((link, i) => { // Show only the relevant part of href const shortLink = link.length > 80 ? link.substring(0, 80) + '...' : link; updateLog(` [${i + 1}] ${shortLink}`, 'info'); }); } updateLog('=== END DEBUG ===', 'info'); if (response.allLinks === 0) { updateLog('⚠️ No links found! Page might not be fully loaded.', 'warning'); } else if (response.allLinks > 0 && response.linkItem === 0 && response.linkProduct === 0) { updateLog('⚠️ Found links but none match /fs/, /f/, /item/ patterns.', 'warning'); updateLog('✅ Scanner will search for product containers instead.', 'info'); } } } catch (error) { updateLog(`Debug error: ${error.message}`, 'error'); } finally { debugBtn.disabled = false; } } async function handleScanClick() { try { scanBtn.disabled = true; setStatus('Scanning page...', 'working'); updateLog('Starting page scan...', 'info'); // Get current active tab const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); if (!tabs.length) { throw new Error('No active tab found'); } const tabId = tabs[0].id; const tabUrl = tabs[0].url; // Verify content script is injected let response; try { response = await chrome.tabs.sendMessage(tabId, { action: 'scanPage', }); } catch (e) { // Content script might not be loaded, try injecting it updateLog('Content script not found, injecting...', 'warning'); try { await chrome.scripting.executeScript({ target: { tabId: tabId }, files: ['content.js'], }); } catch (err) { throw new Error(`Cannot inject script on this page. URL: ${tabUrl}. Some pages (chrome://, extensions://, etc) don't allow scripts.`); } // Give it a moment to load await new Promise(resolve => setTimeout(resolve, 500)); // Try again response = await chrome.tabs.sendMessage(tabId, { action: 'scanPage', }); } if (!response.success) { throw new Error('Failed to scan page'); } products = response.products || []; const count = products.length; updateLog(`Found ${count} products`, 'success'); totalProductsEl.textContent = count; if (count > 0) { downloadBtn.disabled = false; setStatus(`Ready to download ${count} products`, 'idle'); // Send products to background await chrome.runtime.sendMessage({ action: 'updateProducts', products: products, }); } else { setStatus('No products found. Check page structure and selectors.', 'error'); } } catch (error) { console.error('Scan error:', error); updateLog(`Error: ${error.message}`, 'error'); setStatus(`Scan failed: ${error.message}`, 'error'); } finally { scanBtn.disabled = false; } } // ============================================================================ // DOWNLOAD LOGIC // ============================================================================ async function handleDownloadClick() { try { if (products.length === 0) { setStatus('No products to download. Scan the page first.', 'error'); return; } downloadBtn.disabled = true; const maxConcurrency = parseInt(concurrencyInput.value) || 2; updateLog(`Starting download of ${products.length} products...`, 'info'); updateLog(`Max concurrent: ${maxConcurrency}`, 'info'); await chrome.runtime.sendMessage({ action: 'startDownload', maxConcurrency: maxConcurrency, }); setStatus('Downloads in progress...', 'working'); // Start polling for updates pollForUpdates(); } catch (error) { console.error('Download error:', error); updateLog(`Error: ${error.message}`, 'error'); setStatus(`Download failed: ${error.message}`, 'error'); downloadBtn.disabled = false; } } // ============================================================================ // STATE UPDATES & POLLING // ============================================================================ async function pollForUpdates() { while (currentState.isRunning) { try { const response = await chrome.runtime.sendMessage({ action: 'getState', }); if (response && response.state) { currentState = response.state; updateUI(); } } catch (error) { // Extension context invalidated or popup closed break; } // Poll every 500ms await new Promise(resolve => setTimeout(resolve, 500)); } // Final update updateUI(); downloadBtn.disabled = false; setStatus('Download complete!', 'success'); } /** * Update UI with current state */ function updateUI() { totalProductsEl.textContent = currentState.products.length; imagesDownloadedEl.textContent = currentState.totalImagesDownloaded; errorCountEl.textContent = currentState.errorCount; // Update current product display if (currentState.currentProductIndex >= 0 && currentState.products[currentState.currentProductIndex]) { const product = currentState.products[currentState.currentProductIndex]; currentProductEl.textContent = product.productCode || `Product ${currentState.currentProductIndex + 1}`; } else { currentProductEl.textContent = '—'; } // Update progress bar if (currentState.products.length > 0) { const progress = ((currentState.currentProductIndex + 1) / currentState.products.length) * 100; progressBarEl.style.width = Math.max(0, Math.min(100, progress)) + '%'; } // Update logs const logEntries = currentState.logs || []; logBoxEl.innerHTML = ''; logEntries.forEach(entry => { const div = document.createElement('div'); div.className = `log-entry ${entry.type}`; div.textContent = entry.message; logBoxEl.appendChild(div); }); // Auto-scroll to bottom logBoxEl.scrollTop = logBoxEl.scrollHeight; } // ============================================================================ // UI HELPERS // ============================================================================ function updateLog(message, type = 'info') { const entry = document.createElement('div'); entry.className = `log-entry ${type}`; const timestamp = new Date().toLocaleTimeString(); entry.textContent = `[${timestamp}] ${message}`; logBoxEl.appendChild(entry); logBoxEl.scrollTop = logBoxEl.scrollHeight; } function setStatus(message, status = 'idle') { statusMsgEl.textContent = message; statusMsgEl.className = `status ${status}`; } function handleClearLogClick() { logBoxEl.innerHTML = ''; updateLog('Log cleared', 'info'); } // ============================================================================ // INITIALIZATION // ============================================================================ async function initializePopup() { try { // Get initial state from background const response = await chrome.runtime.sendMessage({ action: 'getState', }); if (response && response.state) { currentState = response.state; products = currentState.products; } updateUI(); // If download is in progress, start polling if (currentState.isRunning) { downloadBtn.disabled = true; setStatus('Download in progress...', 'working'); pollForUpdates(); } else { // Enable download button if we have products if (products.length > 0) { downloadBtn.disabled = false; } setStatus('Ready to scan', 'idle'); } } catch (error) { console.error('Initialization error:', error); updateLog('Failed to connect to background', 'error'); } } // Initialize when popup opens document.addEventListener('DOMContentLoaded', initializePopup); // Cleanup listeners on popup close window.addEventListener('beforeunload', () => { // Popup is closing }); console.log('[Popup] Script loaded');