368 lines
11 KiB
JavaScript
368 lines
11 KiB
JavaScript
/**
|
|
* 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');
|